Database Design Best Practices

Pulling it all together β€” patterns, anti-patterns, and production realities


Table of Contents


Introduction: The Evolution of My Blog Schema

When I started this series, I showed you a single posts table with five columns. By now, working through relationships, indexes, transactions, views, security, and backups, that humble table has grown into a production-grade schema.

But databases don't spring into existence fully formed. They evolve β€” and how you manage that evolution is just as important as how you design the initial schema.

This final article is about the design decisions, naming conventions, advanced PostgreSQL features, and monitoring practices that separate a hobby project database from a production system. It's a synthesis of everything we've covered, plus the operational wisdom I accumulated through years of running real systems.


Schema Design Principles

1. Design for Queries, Not Just Storage

The most common mistake is designing a schema that "makes sense" conceptually but performs poorly for actual queries. Before finalising your schema, list the top 10 queries your application will run. Design around them.

2. Stable Primary Keys

Use surrogate keys (SERIAL, BIGSERIAL, UUID) as primary keys, not natural keys (email, username, slug). Natural keys change; surrogate keys don't.

3. UUID vs BIGSERIAL

Recommendation: Use BIGSERIAL for single-server applications. Use UUID v7 for distributed systems where IDs must be globally unique.

4. Soft Deletes vs Hard Deletes

Soft deletes are essential when:

  • You need an audit trail of deletions

  • Users may want to "undo" a deletion

  • Regulatory compliance requires data retention


Naming Conventions That Save You Pain

Consistent naming is invisible when you get it right and excruciating when you get it wrong.

Tables and Columns

Indexes and Constraints


Data Types: Choosing Correctly the First Time

Poor type choices are expensive to fix later. Get them right upfront.

Text Columns

Numeric Columns

Date and Time

JSONB: When to Use It


Schema Evolution and Migrations

Your schema will change. Managing migrations safely is crucial.

Safe Migration Patterns

Zero-Downtime Index Creation

Migration Tooling

Structure SQL migrations as numbered, sequential files:

Use a migration tool to track which have run:

  • Flyway or Liquibase for Java ecosystems

  • Alembic for Python

  • Prisma Migrate for Node.js/TypeScript (see ORM 101 series)

  • golang-migrate for Go

  • dbmate for a database-agnostic choice


PostgreSQL Advanced Features in Practice

Generated Columns

Table Partitioning

For very large tables (millions of rows), partitioning splits the physical storage:

CTEs with RETURNING

UPSERT with ON CONFLICT


Connection Pooling and Resource Management

Each PostgreSQL connection is an OS process consuming ~5–10 MB of RAM. Connection pooling is essential for web applications.

spinner

PgBouncer Configuration

Configure postgresql.conf for Your Workload

Use PGTunearrow-up-right to generate settings tuned to your hardware.


Monitoring and Observability

Key Metrics to Track

Autovacuum Tuning


Common Anti-Patterns to Avoid

1. The EAV (Entity-Attribute-Value) Trap

2. Storing Comma-Separated Values

3. SELECT * in Application Code

4. Missing Updated_at

5. Not Using Transactions for Multi-Step Operations

This was covered in the Transactions article, but it bears repeating as the #1 source of data corruption in hobby applications. Any operation that touches more than one table must be wrapped in BEGIN...COMMIT.


The Production-Ready Blog Schema

Here is the complete, production-ready blog schema incorporating everything from this series:


Bridging to ORMs

At this point, you understand what's happening "under the hood" when an ORM like Prisma generates SQL.

Knowing the SQL behind ORM calls means you can:

  • Spot N+1 query problems in ORM code

  • Know when to use raw SQL for complex queries

  • Understand and design migrations correctly

  • Debug performance issues intelligently

The ORM 101 series covers Prisma in depth β€” you're now fully prepared for it.


What I Learned Designing Databases

Looking back at that first five-column posts table, the distance travelled is remarkable. Here are the lessons that mattered most:

  1. Design for your queries first β€” EXPLAIN ANALYZE before you ship any query

  2. Name things consistently β€” you'll thank yourself in 6 months

  3. Choose types precisely β€” TIMESTAMPTZ not TIMESTAMP, NUMERIC not FLOAT for money

  4. Migrations are not optional β€” every schema change needs a migration script

  5. Transactions for multi-step operations β€” no exceptions

  6. Index foreign keys and WHERE columns β€” lean on pg_stat_statements to find what else needs indexing

  7. JSONB for genuinely flexible data β€” not as a replacement for proper columns

  8. Test your backups β€” a backup you haven't tested doesn't exist

  9. Least privilege always β€” your application user should never be superuser

  10. Monitor, then optimise β€” never guess; measure


Your Database Journey Continues

You've completed the Database 101 series. Here's where to go next:

Immediate Next Steps

  • ORM 101 β†’ β€” Prisma with PostgreSQL: type-safe database access from Node.js/TypeScript

  • Advanced PostgreSQL β€” Window functions at scale, logical replication, pg_partman for partition management

Intermediate Topics

  • Database Administration: pgAdmin, pg_activity, connection pooling at scale

  • Performance Tuning: auto_explain, pg_stat_statements dashboards, Grafana + Prometheus for PostgreSQL metrics

  • High Availability: Patroni for automated failover, pgBackRest for enterprise backup management

Advanced Topics

  • PostgreSQL Logical Replication: replicate specific tables to downstream systems

  • Time-Series Data: TimescaleDB as a PostgreSQL extension

  • Multi-Tenant Architectures: Schema-per-tenant vs RLS-based tenancy

  • NoSQL Comparison: When MongoDB, Redis, or Cassandra make more sense than PostgreSQL


You started with a spreadsheet. You now have the knowledge to build production-grade data systems. That's a big deal.

← Previous: Backup and Recovery


Part of the Database 101 Series

Last updated