Postgres ENUMs vs Lookup Tables: Why I Stopped Reaching for ENUMs
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.
Notes for the problems that show up after launch: bad plans, awkward migrations, index debt, vacuum pressure, replica lag, and the small decisions that make PostgreSQL easier to operate.
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.
Zero-downtime migrations work when you split correctness from rollout: expand, backfill, dual-read or dual-write, validate, switch, then clean up.
VACUUM problems are cleanup debt. Dead tuples, bloat, frozen XIDs, and autovacuum lag turn normal queries into incident material when nobody is watching.
Long transactions quietly hold back vacuum, keep locks alive, exhaust pools, and make old row versions stick around long after users moved on.
Read Committed, Repeatable Read, Serializable. The textbook treatment puts most readers to sleep. Here is what each one actually means for your application.
Deadlocks are the symptom of a design that allows two transactions to hold each other's locks. The fix is rarely retry logic. Here is what to look for instead.
ALTER TABLE is dangerous when you review syntax instead of locks. Some changes are instant, some queue behind writes, and some rewrite the whole table.
Transaction ID wraparound is rare, dramatic, and entirely avoidable. Here is what FREEZE actually does, and the autovacuum settings that keep you out of single-user mode.