UUID vs BIGINT: I Have Picked Both, Regretted Both, and Still Have Opinions
Primary key choice is one of those decisions that looks academic until you are migrating six months in. Here is the framework I use, and the cases where each one wins.
Notes for the problems that show up after launch: bad plans, awkward migrations, index debt, vacuum pressure, replica lag, and the small decisions that keep PostgreSQL, MySQL, MariaDB, and SQL Server easier to operate.
Find the queries dragging your Postgres down, read their plans, and fix them in order of impact.
Read the guide →Which indexes to add, which to drop, and how to spot redundant index debt before it costs you writes.
Read the guide →How autovacuum works, when it falls behind, and how to keep bloat from eating your storage and your buffer cache.
Read the guide →Sizing pools, PgBouncer modes, and how to stop idle-in-transaction sessions from exhausting max_connections.
Read the guide →Track lag, WAL throughput, and replication slots before they turn into stale reads or a full disk.
Read the guide →
Backups, restores, PITR, archive gaps, and recovery drills for PostgreSQL.
AWS RDS, Azure Database for PostgreSQL, Google Cloud SQL, AlloyDB, and managed PostgreSQL monitoring notes.
Connection storms, pool saturation, PgBouncer behavior, and max_connections pressure.
CPU, memory, disk, temp files, checkpoints, buffers, and workload pressure.
Index design, index debt, reindexing, constraint indexes, and evidence-driven DDL.
Lock waits, deadlocks, blocking chains, idle transactions, and transaction hygiene.
Galera clustering, InnoDB and Aria internals, replication, optimizer behavior, and MariaDB operations notes.
InnoDB internals, replication, locking, query performance, and MySQL operations notes.
HNSW, IVFFlat, recall, embedding search, and production RAG performance notes.
Replica lag, WAL growth, failover readiness, hot standby behavior, and replication slots.
Query plans, EXPLAIN ANALYZE, planner regressions, pagination, joins, and statistics.
Wait statistics, tempdb, Query Store, Always On availability groups, and SQL Server operations notes.
Autovacuum, dead tuples, bloat, wraparound, MVCC, freeze age, and visibility.
Primary key choice is one of those decisions that looks academic until you are migrating six months in. Here is the framework I use, and the cases where each one wins.
Choosing between `timestamp` and `timestamptz` looks like a stylistic preference until daylight saving moves the wrong customer's appointment by an hour.
Adding `deleted_at` to every table looks innocent and ages badly. Indexes get bigger, queries get more `WHERE deleted_at IS NULL`, and bugs hide in the rows you forgot to filter.
Multi-tenant Postgres design is not about elegance. It is choosing the failure, noisy-neighbor, backup, migration, and support story you can survive.
Index work is not about adding more indexes. It is about finding which indexes earn their write cost, which ones duplicate each other, and which missing one is hurting users.
ENUMs feel cleaner until product asks for a label, a translation, or a soft-delete. Here is the simple rule I use, and the migration I have run more times than I can count.
Normalization and denormalization are not opposing camps; they are two answers to two different questions. The trick is knowing which question you are actually asking.
Generated columns are useful when derived data is a database invariant, not an application convenience. The hard part is deciding what belongs in the schema.
Constraints are not pedantic. They are how the database protects you from the application bugs you have not written yet. Here is the set I always add and why.
Major version upgrades are intimidating until you have done one cleanly. Here is the playbook I use, the failure modes I have seen, and what to test before you commit.