| name | database-manager |
| description | Database operations and management covering schema design, migrations, query optimization, indexing, and backup strategies for PostgreSQL, MySQL, and MongoDB. This skill should be used when designing database schemas, writing migrations, optimizing queries, managing indexes, or performing database administration tasks. |
Database Manager
This skill provides structured guidance for database operations across PostgreSQL, MySQL, and MongoDB, covering schema design, migrations, query optimization, and administration patterns.
Prerequisites
- Database server access (PostgreSQL, MySQL, or MongoDB)
- Basic SQL or query language knowledge
- Migration tool installed (Alembic, Prisma, Django, Flyway, etc.)
When to Use This Skill
- Designing database schemas for new features
- Writing and managing database migrations
- Optimizing slow queries and adding indexes
- Setting up backup and recovery strategies
- Debugging database connectivity or performance issues
- Choosing between SQL and NoSQL for a use case
Schema Design
Relational (PostgreSQL / MySQL)
Normalization Guidelines
| Level | Rule | Practical Impact |
|---|
| 1NF | Atomic values, no repeating groups | One value per cell, no comma-separated lists |
| 2NF | All non-key attributes depend on the whole key | No partial dependencies in composite keys |
| 3NF | No transitive dependencies | Non-key fields don't depend on other non-key fields |
When to denormalize: Read-heavy analytics, reporting tables, caching computed values. Denormalize intentionally, not accidentally.
Table Design Pattern
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) NOT NULL,
name VARCHAR(100) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'member'
CHECK (role IN ('admin', 'editor', 'member')),
status VARCHAR(20) NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'suspended', 'deleted')),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_users_email UNIQUE (email)
);
CREATE INDEX idx_users_email ON users (email);
CREATE INDEX idx_users_status ON users (status) WHERE status = 'active';
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_users_updated_at
BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
Relationship Patterns
CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
author_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
content TEXT,
published BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_posts_author ON posts (author_id);
CREATE TABLE post_tags (
post_id UUID NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (post_id, tag_id)
);
CREATE TABLE comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
author_id UUID NOT NULL REFERENCES users(id),
body TEXT NOT NULL,
target_type VARCHAR(20) NOT NULL CHECK (target_type IN ('post', 'page')),
target_id UUID NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Document (MongoDB)
Collection Design Patterns
{
_id: ObjectId("..."),
name: "Jane Doe",
email: "jane@example.com",
addresses: [
{ type: "home", street: "123 Main St", city: "Austin", state: "TX" },
{ type: "work", street: "456 Tech Blvd", city: "Austin", state: "TX" }
]
}
{ _id: ObjectId("usr1"), name: "Jane" }
{ _id: ObjectId("pst1"), author_id: ObjectId("usr1"), title: "Hello" }
{
_id: ObjectId("prod1"),
name: "Widget",
category: { _id: ObjectId("cat1"), name: "Electronics" },
}
MongoDB Schema Rules
| Pattern | When to Use | Avoid When |
|---|
| Embed | Few items, always read together, <16MB doc | Unbounded arrays, frequent updates |
| Reference | Many items, accessed independently | Always joining (consider embedding) |
| Hybrid | Frequent display of summary data | Summary data changes often |
| Bucket | Time-series, IoT data (group by hour/day) | Sparse, irregular time intervals |
Migrations
Migration Best Practices
- Forward-only in production — Never edit a shipped migration
- Reversible in development — Write
down migrations for local dev
- Small and focused — One migration per logical change
- Data migrations separate — Don't mix schema and data changes
- Test on production-like data — Migrations on empty DBs hide problems
Migration Workflow
alembic revision --autogenerate -m "add_users_table"
alembic upgrade head
alembic downgrade -1
npx prisma migrate dev --name add_users_table
npx prisma migrate deploy
flyway migrate
flyway undo
Safe Migration Patterns
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active';
ALTER TABLE users ADD COLUMN preferred_name VARCHAR(100);
UPDATE users SET preferred_name = name WHERE preferred_name IS NULL LIMIT 1000;
ALTER TABLE users ALTER COLUMN preferred_name SET NOT NULL;
CREATE INDEX CONCURRENTLY idx_users_email ON users (email);
ALTER TABLE users ADD COLUMN full_name VARCHAR(200);
ALTER TABLE users DROP COLUMN name;
MongoDB Migrations
module.exports = {
async up(db) {
await db.collection('users').updateMany(
{ status: { $exists: false } },
{ $set: { status: 'active', updatedAt: new Date() } }
);
await db.collection('users').createIndex({ status: 1 });
},
async down(db) {
await db.collection('users').dropIndex('status_1');
await db.collection('users').updateMany(
{},
{ $unset: { status: '' } }
);
}
};
Query Optimization
Identifying Slow Queries
SELECT query, mean_exec_time, calls, total_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;
SELECT pid, now() - pg_stat_activity.query_start AS duration, query
FROM pg_stat_activity
WHERE state = 'active' AND query NOT LIKE '%pg_stat_activity%'
ORDER BY duration DESC;
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SHOW VARIABLES LIKE 'slow_query_log_file';
db.setProfilingLevel(1, { slowms: 100 }); // Log queries over 100ms
db.system.profile.find().sort({ ts: -1 }).limit(10);
EXPLAIN Analysis
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT u.name, COUNT(p.id) as post_count
FROM users u
JOIN posts p ON p.author_id = u.id
WHERE u.status = 'active'
GROUP BY u.id, u.name
ORDER BY post_count DESC
LIMIT 10;
Index Strategy
CREATE INDEX idx_posts_published ON posts (published) WHERE published = TRUE;
CREATE INDEX idx_posts_author_published ON posts (author_id, published);
CREATE INDEX idx_users_active_email ON users (email) WHERE status = 'active';
CREATE INDEX idx_posts_covering ON posts (author_id) INCLUDE (title, published);
CREATE INDEX idx_posts_content_search ON posts USING GIN (to_tsvector('english', content));
CREATE UNIQUE INDEX idx_users_email_unique ON users (email);
Query Rewrite Patterns
SELECT * FROM users WHERE status = 'active';
SELECT id, name, email FROM users WHERE status = 'active';
SELECT * FROM posts WHERE author_id = 'abc' OR status = 'published';
SELECT * FROM posts WHERE author_id = 'abc'
UNION ALL
SELECT * FROM posts WHERE status = 'published' AND author_id != 'abc';
SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 10000;
SELECT * FROM posts WHERE created_at < '2025-01-01' ORDER BY created_at DESC LIMIT 20;
Backup & Recovery
PostgreSQL
pg_dump -Fc dbname > dbname_$(date +%Y%m%d).dump
pg_restore -d dbname dbname_20250115.dump
MongoDB
mongodump --uri="mongodb://localhost:27017/mydb" --out=/backup/$(date +%Y%m%d)
mongorestore --uri="mongodb://localhost:27017/mydb" /backup/20250115/mydb
mongodump --oplog --uri="mongodb://localhost:27017"
Error Handling
Common Issues
| Problem | Cause | Solution |
|---|
| Connection refused | Server not running or wrong port | Check server status, verify port |
| Too many connections | Connection leak | Use connection pooling, close connections |
| Deadlock | Circular lock dependency | Retry with backoff, reduce transaction scope |
| Slow query | Missing index or bad plan | Run EXPLAIN, add index, rewrite query |
| Migration failed | Mid-state schema | Fix migration, run manually, mark as applied |
| Lock wait timeout | Long-running transaction | Identify blocker: pg_locks / SHOW PROCESSLIST |
Connection Pooling
from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool
engine = create_engine(
"postgresql://user:pass@localhost/db",
poolclass=QueuePool,
pool_size=10,
max_overflow=20,
pool_timeout=30,
pool_recycle=3600,
pool_pre_ping=True,
)
Tips
- Use UUIDs over auto-increment IDs for distributed systems; auto-increment for simple apps
- Always add indexes for foreign keys — most databases don't do this automatically
- Use
CONCURRENTLY for index creation on production PostgreSQL (non-blocking)
- Test migrations on a production-sized dataset before running in production
- Enable query logging/profiling in development; review slow queries before deploy
- Set up automated backups and test restoration regularly — an untested backup isn't a backup
- For MongoDB, prefer embedding for read-heavy access patterns and referencing for write-heavy ones
Cline Workflow Notes
- Install location: Copy this skill directory to
.cline/skills/database-manager/ (project-level) or ~/.cline/skills/database-manager/ (global)
- Activation: Cline will suggest this skill when you work with database schemas, migrations, queries, or indexing
- Progressive loading: Only metadata loads initially; full database patterns activate via
use_skill when database work begins