---
name: database-architect
type: workflow
description: "Designs relational and NoSQL database schemas, indexing strategies, migration plans, and data modeling patterns. Use when designing a database or when the user mentions database architecture, schema design, or data modeling."
effort: 5
allowed-tools: Read, Glob, Grep, Write, Edit, Bash
argument-hint: "[project type or tech stack]"
user-invocable: true
when_to_use: "When selecting database technologies, designing schemas from scratch, or planning data layer migrations"
Database Architect
Workflow
- Understand domain: Access patterns, scale targets, consistency needs, compliance requirements
- Select technology: Match DB type to workload (see matrix below)
- Design schema: Normalization level, relationships, constraints, temporal data strategy
- Plan indexing: Query-pattern-driven index design (not speculative)
- Design caching: Layer strategy with invalidation
- Plan migration: Zero-downtime approach, rollback procedures
- Document decisions: ADR with rationale and trade-offs
Technology selection matrix
| Workload |
Primary choice |
Alternative |
| OLTP / relational |
PostgreSQL |
MySQL |
| Flexible documents |
MongoDB |
Firestore |
| Key-value / cache |
Redis |
DynamoDB |
| Time-series / IoT |
TimescaleDB |
InfluxDB |
| Analytical / OLAP |
ClickHouse |
BigQuery |
| Graph relationships |
Neo4j |
Amazon Neptune |
| Full-text search |
Elasticsearch |
Meilisearch |
| Globally distributed |
CockroachDB |
Google Spanner |
| Multi-tenant SaaS |
PostgreSQL (row-level security) |
Schema-per-tenant |
Decision rule: Choose PostgreSQL by default; deviate only when access patterns demand it with documented rationale.
Non-obvious rules
- Normalize first, denormalize with evidence — premature denormalization creates update anomalies; measure before optimizing
- Index on access patterns, not columns — index the query, not the table; one slow-query explain plan is worth more than any speculation
- Foreign keys always — letting the application enforce referential integrity is a data corruption waiting to happen
- JSONB for flexible attributes, not as a schema escape hatch — use JSONB when fields are genuinely variable; not to avoid schema discipline
- Partition late — partition tables only once you have row counts >50M or explicit I/O pressure; early partitioning adds complexity with zero benefit
- UUID v7 over v4 — v7 is time-ordered (k-sortable), avoids index fragmentation, same uniqueness guarantees
Schema design patterns
-- Multi-tenancy: row-level security (best for <1000 tenants, shared infra)
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
-- Soft delete + audit trail (never DELETE production data)
ALTER TABLE users ADD COLUMN deleted_at TIMESTAMPTZ;
ALTER TABLE users ADD COLUMN updated_by UUID REFERENCES users(id);
CREATE INDEX idx_users_active ON users(id) WHERE deleted_at IS NULL;
-- Temporal / slowly-changing dimensions
CREATE TABLE product_prices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_id UUID NOT NULL REFERENCES products(id),
price NUMERIC(10,2) NOT NULL,
valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),
valid_until TIMESTAMPTZ -- NULL = current price
);
Indexing rules
-- Composite index: most selective column FIRST
CREATE INDEX idx_orders_user_status ON orders(user_id, status, created_at DESC);
-- Partial index: filter out the 95% noise
CREATE INDEX idx_orders_pending ON orders(created_at) WHERE status = 'pending';
-- Covering index: index-only scan (no heap access)
CREATE INDEX idx_users_email_name ON users(email) INCLUDE (name, avatar_url);
-- JSONB GIN index for flexible attribute queries
CREATE INDEX idx_metadata_gin ON events USING gin(metadata jsonb_path_ops);
Migration strategy (non-negotiable steps)
-- 1. Expand: add new column nullable (no lock)
ALTER TABLE users ADD COLUMN phone VARCHAR(20);
-- 2. Backfill: batch update (never one giant UPDATE)
UPDATE users SET phone = '' WHERE phone IS NULL AND id BETWEEN x AND y;
-- 3. Constrain: add NOT NULL only after backfill complete
ALTER TABLE users ALTER COLUMN phone SET NOT NULL;
-- 4. Switch: deploy code using new column
-- 5. Contract: drop old column in separate release
ALTER TABLE users DROP COLUMN old_phone;
Zero-downtime rule: Never add a NOT NULL column without a default in a single migration on a live table — it acquires an ACCESS EXCLUSIVE lock.
Caching architecture
| Layer |
Tool |
Strategy |
Invalidation |
| Hot data |
Redis |
Cache-aside |
TTL + event-driven |
| Query results |
PostgreSQL materialized views |
Refresh on schedule |
REFRESH MATERIALIZED VIEW CONCURRENTLY |
| Session data |
Redis |
Write-through |
TTL |
| Static references |
App memory |
Eager load on startup |
Deploy |
Scope
- Query tuning on existing system →
database-optimizer
- Database operations, backups, maintenance →
database-admin
- System-wide performance →
performance-engineer
- ORM-specific patterns →
prisma-expert / drizzle-orm-expert
1---2name: database-architect3description: ---4---5---6name: database-architect7type: workflow8description: "Designs relational and NoSQL database schemas, indexing strategies, migration plans, and data modeling patterns. Use when designing a database or when the user mentions database architecture, schema design, or data modeling."9effort: 510allowed-tools: Read, Glob, Grep, Write, Edit, Bash11argument-hint: "[project type or tech stack]"12user-invocable: true13when_to_use: "When selecting database technologies, designing schemas from scratch, or planning data layer migrations"14---1516# Database Architect1718## Workflow19201. **Understand domain**: Access patterns, scale targets, consistency needs, compliance requirements212. **Select technology**: Match DB type to workload (see matrix below)223. **Design schema**: Normalization level, relationships, constraints, temporal data strategy234. **Plan indexing**: Query-pattern-driven index design (not speculative)245. **Design caching**: Layer strategy with invalidation256. **Plan migration**: Zero-downtime approach, rollback procedures267. **Document decisions**: ADR with rationale and trade-offs2728## Technology selection matrix2930| Workload | Primary choice | Alternative |31|---|---|---|32| OLTP / relational | PostgreSQL | MySQL |33| Flexible documents | MongoDB | Firestore |34| Key-value / cache | Redis | DynamoDB |35| Time-series / IoT | TimescaleDB | InfluxDB |36| Analytical / OLAP | ClickHouse | BigQuery |37| Graph relationships | Neo4j | Amazon Neptune |38| Full-text search | Elasticsearch | Meilisearch |39| Globally distributed | CockroachDB | Google Spanner |40| Multi-tenant SaaS | PostgreSQL (row-level security) | Schema-per-tenant |4142**Decision rule**: Choose PostgreSQL by default; deviate only when access patterns demand it with documented rationale.4344## Non-obvious rules4546- **Normalize first, denormalize with evidence** — premature denormalization creates update anomalies; measure before optimizing47- **Index on access patterns, not columns** — index the query, not the table; one slow-query explain plan is worth more than any speculation48- **Foreign keys always** — letting the application enforce referential integrity is a data corruption waiting to happen49- **JSONB for flexible attributes, not as a schema escape hatch** — use JSONB when fields are genuinely variable; not to avoid schema discipline50- **Partition late** — partition tables only once you have row counts >50M or explicit I/O pressure; early partitioning adds complexity with zero benefit51- **UUID v7 over v4** — v7 is time-ordered (k-sortable), avoids index fragmentation, same uniqueness guarantees5253## Schema design patterns5455```sql56-- Multi-tenancy: row-level security (best for <1000 tenants, shared infra)57ALTER TABLE orders ENABLE ROW LEVEL SECURITY;58CREATE POLICY tenant_isolation ON orders59 USING (tenant_id = current_setting('app.current_tenant_id')::uuid);6061-- Soft delete + audit trail (never DELETE production data)62ALTER TABLE users ADD COLUMN deleted_at TIMESTAMPTZ;63ALTER TABLE users ADD COLUMN updated_by UUID REFERENCES users(id);64CREATE INDEX idx_users_active ON users(id) WHERE deleted_at IS NULL;6566-- Temporal / slowly-changing dimensions67CREATE TABLE product_prices (68 id UUID PRIMARY KEY DEFAULT gen_random_uuid(),69 product_id UUID NOT NULL REFERENCES products(id),70 price NUMERIC(10,2) NOT NULL,71 valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(),72 valid_until TIMESTAMPTZ -- NULL = current price73);74```7576## Indexing rules7778```sql79-- Composite index: most selective column FIRST80CREATE INDEX idx_orders_user_status ON orders(user_id, status, created_at DESC);8182-- Partial index: filter out the 95% noise83CREATE INDEX idx_orders_pending ON orders(created_at) WHERE status = 'pending';8485-- Covering index: index-only scan (no heap access)86CREATE INDEX idx_users_email_name ON users(email) INCLUDE (name, avatar_url);8788-- JSONB GIN index for flexible attribute queries89CREATE INDEX idx_metadata_gin ON events USING gin(metadata jsonb_path_ops);90```9192## Migration strategy (non-negotiable steps)9394```sql95-- 1. Expand: add new column nullable (no lock)96ALTER TABLE users ADD COLUMN phone VARCHAR(20);9798-- 2. Backfill: batch update (never one giant UPDATE)99UPDATE users SET phone = '' WHERE phone IS NULL AND id BETWEEN x AND y;100101-- 3. Constrain: add NOT NULL only after backfill complete102ALTER TABLE users ALTER COLUMN phone SET NOT NULL;103104-- 4. Switch: deploy code using new column105-- 5. Contract: drop old column in separate release106ALTER TABLE users DROP COLUMN old_phone;107```108109**Zero-downtime rule**: Never add a NOT NULL column without a default in a single migration on a live table — it acquires an ACCESS EXCLUSIVE lock.110111## Caching architecture112113| Layer | Tool | Strategy | Invalidation |114|---|---|---|---|115| Hot data | Redis | Cache-aside | TTL + event-driven |116| Query results | PostgreSQL materialized views | Refresh on schedule | `REFRESH MATERIALIZED VIEW CONCURRENTLY` |117| Session data | Redis | Write-through | TTL |118| Static references | App memory | Eager load on startup | Deploy |119120## Scope121122- Query tuning on existing system → `database-optimizer`123- Database operations, backups, maintenance → `database-admin`124- System-wide performance → `performance-engineer`125- ORM-specific patterns → `prisma-expert` / `drizzle-orm-expert`