Data Modeling Best Practices
Model the domain, not the current UI screen. Use this list during schema reviews, migration PRs, and ADR updates.
Search across all documentation pages
Model the domain, not the current UI screen. Use this list during schema reviews, migration PRs, and ADR updates.
EXPLAIN and integrity queries - modeling mistakes show up as orphans and fan-out joins.checkout_summary_row becomes orders + line_items when the UI changes.NOT NULL, CHECK, UNIQUE, and FK constraints - not only application validation.COMMENT ON. Every production table has a single writer service or team name.timestamptz for instants. Store UTC; convert at display. Avoid timestamp without time zone for user events.bigint identity keys for OLTP. Natural keys belong in UNIQUE constraints when business-guaranteed.tag1, tag2) on a fact table.NOT NULL FK; optional: nullable FK plus LEFT JOIN in queries.ON DELETE deliberately. CASCADE, RESTRICT, and SET NULL should match business rules, not defaults.public from becoming a shared junk drawer.UPDATE tables they do not own.order_items.unit_price must not change when catalog price changes.snake_case, plural tables, and _id suffixes speed reviews.pg_stat_statements; document refresh invariants if you denorm.LEFT JOIN ... WHERE parent IS NULL should return zero rows.Screens change weekly; domain rules (an order has line items, a user has one email) last years. UI-shaped tables require painful migrations when navigation changes.
Primary key, created_at timestamptz, ownership comment, and constraints for every business rule you can name in one sentence.
After normalized design is live and pg_stat_statements proves join cost blocks SLOs. Document what must stay in sync and how you detect drift.
Separate schemas, separate DB roles, code review checklist, and optionally REVOKE cross-schema INSERT/UPDATE from app roles.
Comment tables and non-obvious columns (status enums, soft-delete flags, currency assumptions). Skip id-level noise.
Forgetting tenant_id on child tables or indexes that lead with tenant_id - causes cross-tenant leaks and slow queries.
Ask: which fields need FK integrity, indexing, and CHECK constraints? Unstructured JSON is a staging area, not a destination schema.
Rarely-changing closed sets with slow release cadence. Prefer text + CHECK when product adds values frequently.
Use versioned compat views for API stability during migrations. Do not let apps query ad-hoc views instead of owned tables without documentation.
ERD or table list, ownership matrix, migration expand-contract plan, and one query that proves the hot path index exists.
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, and PostGIS 3.5+.
Reviewed by Chris St. John·Last updated Jul 18, 2026