database
Overview
Relational database design, query optimization, migration safety, connection pooling in serverless environments, and ORM usage across PostgreSQL, Prisma, and Drizzle.
When to Use
Activate for tasks involving database schema design, migrations, indexing, relational models, ORM queries, transactions, or query performance tuning.
Rules & Patterns
Negative Constraints (What NOT to Do)
- NEVER do
SELECT * in production: Always select explicit columns required by the caller to minimize memory bandwidth and lock footprint.
- NEVER run destructive migrations without backward compatibility: Always follow expand-and-contract (Phase 1: add new column as nullable; Phase 2: backfill; Phase 3: make non-nullable & remove old column).
- NEVER execute queries in loops (The N+1 Anti-Pattern): Always use batch loading (
inArray, DataLoader, or relational include / JOIN).
- NEVER leave foreign keys without indexes: In PostgreSQL/MySQL, child foreign key columns must always have an index to prevent table-level locking on cascade deletes.
- NEVER perform multi-entity writes without a database transaction: Any operation touching multiple records must use
prisma.$transaction or db.transaction.
- NEVER open unpooled database connections in Serverless / Edge functions: Serverless scale-outs will instantly exhaust PostgreSQL's
max_connections.
Zero-Downtime Migrations (Expand-and-Contract)
When modifying schemas with zero downtime:
- Phase 1 (Expand): Add the new column as
NULLABLE (or with a default value). Deploy the application code that reads from old column and writes to both old and new.
- Phase 2 (Backfill): Run an asynchronous batch migration job in chunks (e.g. 1000 rows at a time) to populate data from old column to new column.
- Phase 3 (Contract): Update application code to read and write exclusively from the new column.
- Phase 4 (Cleanup): Once traffic is fully shifted, remove the old column and mark the new column as
NOT NULL in a separate migration.
Serverless & Edge Connection Pooling
In serverless environments (AWS Lambda, Vercel Functions):
- Always connect via a connection pooler:
- Prisma: Use Prisma Accelerate or configure transaction mode connection URLs.
- Drizzle / Node-Postgres: Use
@neondatabase/serverless or connect to PgBouncer pooler port (6543) with max: 1 per serverless container.
- Set strict statement timeouts (e.g.
statement_timeout = '5000') to prevent hanging queries from exhausting pool capacity.
Indexing & Performance Rules
- B-Tree Indexes: For high-cardinality filters (
status, user_id, created_at).
- Composite Indexes: When querying multiple columns together (
WHERE organization_id = ? AND status = ?), order columns in index by equality first, range second.
- Partial Indexes: For sparse boolean flags (
WHERE is_processed = false).
- Covering Indexes: Include frequently selected columns (
INCLUDE (title, created_at)) to enable index-only scans without table heap access.
Code Examples
Zero-Downtime Column Rename (Drizzle ORM)
// Step 1 (Expand): Keep old column, add new column
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
fullName: varchar('full_name', { length: 255 }), // new column
name: varchar('name', { length: 255 }), // old column kept during transition
});
// App write logic during transition:
await db.insert(users).values({
name: input.name,
fullName: input.name
});
Validation Checklist
Common Mistakes
- Missing pagination limits: Unbounded
findMany() calls leading to Out-Of-Memory crashes under production volume.
- Locking entire tables: Adding
NOT NULL columns with heavy compute defaults in PostgreSQL without concurrent index creation.
Integration Notes
- Interacts with
system-design, ddd, and security (multi-tenant tenantId scoping).
1---2name: database-23description: Database architecture, schema design, Prisma, Drizzle ORM, indexing strategies, migrations, and N+1 query resolution.4---56# database78## Overview910Relational database design, query optimization, migration safety, connection pooling in serverless environments, and ORM usage across PostgreSQL, Prisma, and Drizzle.1112## When to Use1314Activate for tasks involving database schema design, migrations, indexing, relational models, ORM queries, transactions, or query performance tuning.1516## Rules & Patterns1718### Negative Constraints (What NOT to Do)19201. **NEVER do `SELECT *` in production**: Always select explicit columns required by the caller to minimize memory bandwidth and lock footprint.212. **NEVER run destructive migrations without backward compatibility**: Always follow expand-and-contract (Phase 1: add new column as nullable; Phase 2: backfill; Phase 3: make non-nullable & remove old column).223. **NEVER execute queries in loops (The N+1 Anti-Pattern)**: Always use batch loading (`inArray`, `DataLoader`, or relational `include` / `JOIN`).234. **NEVER leave foreign keys without indexes**: In PostgreSQL/MySQL, child foreign key columns must always have an index to prevent table-level locking on cascade deletes.245. **NEVER perform multi-entity writes without a database transaction**: Any operation touching multiple records must use `prisma.$transaction` or `db.transaction`.256. **NEVER open unpooled database connections in Serverless / Edge functions**: Serverless scale-outs will instantly exhaust PostgreSQL's `max_connections`.2627---2829### Zero-Downtime Migrations (Expand-and-Contract)3031When modifying schemas with zero downtime:32331. **Phase 1 (Expand)**: Add the new column as `NULLABLE` (or with a default value). Deploy the application code that reads from old column and writes to both old and new.342. **Phase 2 (Backfill)**: Run an asynchronous batch migration job in chunks (e.g. 1000 rows at a time) to populate data from old column to new column.353. **Phase 3 (Contract)**: Update application code to read and write exclusively from the new column.364. **Phase 4 (Cleanup)**: Once traffic is fully shifted, remove the old column and mark the new column as `NOT NULL` in a separate migration.3738---3940### Serverless & Edge Connection Pooling4142In serverless environments (AWS Lambda, Vercel Functions):4344- Always connect via a connection pooler:45 - **Prisma**: Use Prisma Accelerate or configure transaction mode connection URLs.46 - **Drizzle / Node-Postgres**: Use `@neondatabase/serverless` or connect to PgBouncer pooler port (`6543`) with `max: 1` per serverless container.47- Set strict statement timeouts (e.g. `statement_timeout = '5000'`) to prevent hanging queries from exhausting pool capacity.4849---5051### Indexing & Performance Rules5253- **B-Tree Indexes**: For high-cardinality filters (`status`, `user_id`, `created_at`).54- **Composite Indexes**: When querying multiple columns together (`WHERE organization_id = ? AND status = ?`), order columns in index by equality first, range second.55- **Partial Indexes**: For sparse boolean flags (`WHERE is_processed = false`).56- **Covering Indexes**: Include frequently selected columns (`INCLUDE (title, created_at)`) to enable index-only scans without table heap access.5758---5960## Code Examples6162### Zero-Downtime Column Rename (Drizzle ORM)6364```typescript65// Step 1 (Expand): Keep old column, add new column66export const users = pgTable('users', {67 id: uuid('id').primaryKey().defaultRandom(),68 fullName: varchar('full_name', { length: 255 }), // new column69 name: varchar('name', { length: 255 }), // old column kept during transition70});7172// App write logic during transition:73await db.insert(users).values({74 name: input.name,75 fullName: input.name76});77```7879---8081## Validation Checklist8283- [ ] All database queries select explicit required columns (no `SELECT *`).84- [ ] Foreign keys have matching indexes on child tables.85- [ ] Multi-table writes wrapped in ACID transactions.86- [ ] No N+1 queries in loops.87- [ ] Schema migrations tested against expand-and-contract pattern.88- [ ] Serverless database connection string uses pooling proxy.8990---9192## Common Mistakes9394- **Missing pagination limits**: Unbounded `findMany()` calls leading to Out-Of-Memory crashes under production volume.95- **Locking entire tables**: Adding `NOT NULL` columns with heavy compute defaults in PostgreSQL without concurrent index creation.9697---9899## Integration Notes100101- Interacts with `system-design`, `ddd`, and `security` (multi-tenant tenantId scoping).