Database Skill
When to use this skill
Use when designing schemas, writing migrations, creating queries, modeling data relationships, or working on data access layers.
Data Modeling Principles
1. Start with the domain, not the database
- Model your domain entities first (in
types/)
- Then map them to database tables/collections
- The domain model drives the schema, not the other way around
2. Normalize, then denormalize intentionally
- Start with normalized schema (3NF)
- Denormalize only when you have measured performance evidence
- Document every denormalization with the reason why
3. Schema is a contract
- Schema changes are breaking changes — treat them with the same care
- Every schema change needs a migration
- Every migration needs a rollback plan
Schema Design Rules
Naming conventions
| Element |
Convention |
Example |
| Tables |
plural snake_case |
user_profiles, order_items |
| Columns |
singular snake_case |
first_name, created_at |
| Primary keys |
id |
id (auto-generated) |
| Foreign keys |
<referenced_table_singular>_id |
user_id, order_id |
| Boolean columns |
is_/has_ prefix |
is_active, has_verified |
| Timestamps |
_at suffix |
created_at, updated_at, deleted_at |
| Indexes |
idx_<table>_<columns> |
idx_users_email |
Required columns on every table
id -- Primary key (UUID or auto-increment)
created_at -- When the record was created (UTC timestamp)
updated_at -- When the record was last modified (UTC timestamp)
Soft delete pattern
Prefer soft deletes over hard deletes for important data:
deleted_at -- NULL if active, timestamp if soft-deleted
Always filter by WHERE deleted_at IS NULL in queries (or use a view/scope).
Migrations
Rules
- One change per migration — don't bundle unrelated schema changes
- Always write both
up and down — every migration must be reversible
- Never modify a deployed migration — create a new migration instead
- Test migrations against real-sized data — tiny test DBs hide performance problems
- Name migrations descriptively —
add_email_index_to_users not migration_042
Migration checklist
Zero-downtime migration pattern
For breaking schema changes, use the expand-contract pattern:
- Expand — add new column/table alongside old one
- Migrate — backfill data, update code to write to both
- Switch — update code to read from new location
- Contract — remove old column/table after verification
Query Patterns
Rules
- Always parameterize queries — never string-concatenate user input
- Select only needed columns — no
SELECT * in application code
- Paginate large result sets — never return unbounded rows
- Use transactions for multi-step operations — ensure atomicity
- Index columns used in WHERE, JOIN, ORDER BY — but don't over-index
Query performance checklist
Pagination
Prefer cursor-based pagination for large/changing datasets:
-- Instead of: OFFSET 1000 LIMIT 20 (slow for high offsets)
-- Use: WHERE id > :last_seen_id ORDER BY id LIMIT 20
Use offset pagination only for small, static datasets.
Indexing Strategy
When to add an index
| Scenario |
Index type |
Filter by column (WHERE x = ?) |
Single column index |
| Filter by multiple columns |
Composite index (most selective column first) |
Sort results (ORDER BY x) |
Index on sort column |
| Unique constraint |
Unique index |
| Full-text search |
Full-text / GIN index |
| JSON field queries |
GIN / expression index |
When NOT to add an index
- Tables with < 1000 rows (full scan is faster)
- Columns with very low cardinality (e.g., boolean flags)
- Write-heavy tables where index maintenance outweighs read benefits
- Columns rarely used in WHERE/JOIN/ORDER BY
Data Integrity
Constraints (use the database, not just the application)
- NOT NULL — unless the column is genuinely optional
- UNIQUE — for columns that must be unique (email, username, slug)
- FOREIGN KEY — for all references between tables
- CHECK — for value constraints (e.g.,
quantity > 0, status IN (...))
- DEFAULT — for columns with sensible defaults
Validation layers
- UI — immediate feedback (client-side)
- API — schema validation at the boundary
- Database — constraints as the last line of defense
All three layers should agree, but the database is the ultimate truth.
Backup & Recovery
- Define backup frequency per data criticality
- Test restore procedures regularly
- Document recovery time objectives (RTO) and recovery point objectives (RPO)
- Store backups in a different region/zone than primary data
PR Checklist for Database Changes
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: database-83description: Guidelines for schema design, migrations, queries, data modeling, indexing, and data integrity Use when this capability is needed.4---56# Database Skill78## When to use this skill910Use when designing schemas, writing migrations, creating queries, modeling data relationships, or working on data access layers.1112---1314## Data Modeling Principles1516### 1. Start with the domain, not the database17- Model your domain entities first (in `types/`)18- Then map them to database tables/collections19- The domain model drives the schema, not the other way around2021### 2. Normalize, then denormalize intentionally22- Start with normalized schema (3NF)23- Denormalize only when you have measured performance evidence24- Document every denormalization with the reason why2526### 3. Schema is a contract27- Schema changes are **breaking changes** — treat them with the same care28- Every schema change needs a migration29- Every migration needs a rollback plan3031---3233## Schema Design Rules3435### Naming conventions36| Element | Convention | Example |37|---------|-----------|---------|38| Tables | plural snake_case | `user_profiles`, `order_items` |39| Columns | singular snake_case | `first_name`, `created_at` |40| Primary keys | `id` | `id` (auto-generated) |41| Foreign keys | `<referenced_table_singular>_id` | `user_id`, `order_id` |42| Boolean columns | `is_`/`has_` prefix | `is_active`, `has_verified` |43| Timestamps | `_at` suffix | `created_at`, `updated_at`, `deleted_at` |44| Indexes | `idx_<table>_<columns>` | `idx_users_email` |4546### Required columns on every table47```sql48id -- Primary key (UUID or auto-increment)49created_at -- When the record was created (UTC timestamp)50updated_at -- When the record was last modified (UTC timestamp)51```5253### Soft delete pattern54Prefer soft deletes over hard deletes for important data:55```sql56deleted_at -- NULL if active, timestamp if soft-deleted57```58Always filter by `WHERE deleted_at IS NULL` in queries (or use a view/scope).5960---6162## Migrations6364### Rules65- **One change per migration** — don't bundle unrelated schema changes66- **Always write both `up` and `down`** — every migration must be reversible67- **Never modify a deployed migration** — create a new migration instead68- **Test migrations against real-sized data** — tiny test DBs hide performance problems69- **Name migrations descriptively** — `add_email_index_to_users` not `migration_042`7071### Migration checklist72- [ ] Has both `up` and `down` (forward and rollback)73- [ ] Tested with existing data (not just empty tables)74- [ ] Doesn't lock tables for extended periods on large datasets75- [ ] Backward compatible (old code can still work during deploy)76- [ ] Documented in the exec plan decision log7778### Zero-downtime migration pattern79For breaking schema changes, use the expand-contract pattern:801. **Expand** — add new column/table alongside old one812. **Migrate** — backfill data, update code to write to both823. **Switch** — update code to read from new location834. **Contract** — remove old column/table after verification8485---8687## Query Patterns8889### Rules90- **Always parameterize queries** — never string-concatenate user input91- **Select only needed columns** — no `SELECT *` in application code92- **Paginate large result sets** — never return unbounded rows93- **Use transactions for multi-step operations** — ensure atomicity94- **Index columns used in WHERE, JOIN, ORDER BY** — but don't over-index9596### Query performance checklist97- [ ] Uses indexes effectively (check EXPLAIN plan)98- [ ] No N+1 query patterns (use JOINs or batch loading)99- [ ] Results are paginated (limit + offset or cursor-based)100- [ ] Large operations use batch processing101- [ ] Timeouts configured for long-running queries102103### Pagination104Prefer **cursor-based pagination** for large/changing datasets:105```106-- Instead of: OFFSET 1000 LIMIT 20 (slow for high offsets)107-- Use: WHERE id > :last_seen_id ORDER BY id LIMIT 20108```109110Use offset pagination only for small, static datasets.111112---113114## Indexing Strategy115116### When to add an index117| Scenario | Index type |118|----------|-----------|119| Filter by column (`WHERE x = ?`) | Single column index |120| Filter by multiple columns | Composite index (most selective column first) |121| Sort results (`ORDER BY x`) | Index on sort column |122| Unique constraint | Unique index |123| Full-text search | Full-text / GIN index |124| JSON field queries | GIN / expression index |125126### When NOT to add an index127- Tables with < 1000 rows (full scan is faster)128- Columns with very low cardinality (e.g., boolean flags)129- Write-heavy tables where index maintenance outweighs read benefits130- Columns rarely used in WHERE/JOIN/ORDER BY131132---133134## Data Integrity135136### Constraints (use the database, not just the application)137- **NOT NULL** — unless the column is genuinely optional138- **UNIQUE** — for columns that must be unique (email, username, slug)139- **FOREIGN KEY** — for all references between tables140- **CHECK** — for value constraints (e.g., `quantity > 0`, `status IN (...)`)141- **DEFAULT** — for columns with sensible defaults142143### Validation layers1441. **UI** — immediate feedback (client-side)1452. **API** — schema validation at the boundary1463. **Database** — constraints as the last line of defense147148> All three layers should agree, but the database is the ultimate truth.149150---151152## Backup & Recovery153154- Define backup frequency per data criticality155- Test restore procedures regularly156- Document recovery time objectives (RTO) and recovery point objectives (RPO)157- Store backups in a different region/zone than primary data158159---160161## PR Checklist for Database Changes162163- [ ] Migration has both up and down164- [ ] Migration tested with existing data165- [ ] No `SELECT *` in new queries166- [ ] Queries are parameterized (no SQL injection risk)167- [ ] Appropriate indexes added for new queries168- [ ] Constraints added (NOT NULL, UNIQUE, FK, CHECK)169- [ ] Backward compatible during deployment170- [ ] Performance tested with realistic data volume171- [ ] Rollback plan documented in exec plan172173---174> Converted and distributed by [TomeVault](https://tomevault.io/claim/xmenq) — claim your Tome and manage your conversions.175<!-- tomevault:4.0:skill_md:2026-04-14 -->