Strings, lists, sets, sorted sets, hashes, hyperloglogs, bitmaps, and streams each solve a different class of problem. Here is the decision tree, the memory cost of each type, and the production patterns that separate the "I learned Redis in a tutorial" tier from the "I run it in production" tier.
Your Node.js validation library catches 90% of bad data. The remaining 10% quietly corrupts your database and breaks downstream consumers. Here is how to push business rules into PostgreSQL constraints so bad data never reaches disk.
Your application computes the same derived values over and over: full names from first and last, price with tax, JSON field extracts. Move that logic into Postgres generated columns and eliminate the code path entirely. This post covers the DDL, the stored vs virtual trade-off, index strategies, and the migration pattern that does not require downtime.
TypeScript will happily let you pass a UserId where you meant OrderId, and the bug slips into production. Branded types add nominal typing with zero runtime cost, catching mismatched IDs, raw strings, and domain violations before they hit your database.
Your tags table has 12 million rows and every product query needs a three-way join. Postgres arrays can collapse that to a single row lookup, but misuse them and you trade a join for a sequential scan. Here is the decision framework, the GIN indexing strategy, and the queries that separate a fast array from a slow one.
Two users click "Book" at the same millisecond and your application check passes for both. Here is how Postgres EXCLUDE constraints eliminate the race condition at the database level, with the DDL, the GiST index, and the Node.js error handling you need to ship it.
UUIDv4 randomness turns your clustered index into a write bottleneck. Here is how UUIDv7 and ULID fix the insertion hotspot, with Node.js generation code, Postgres index analysis, and the one case where ULID still wins.
Two-phase commit is the textbook answer for distributed transactions. It also doesn't survive contact with real systems. The saga pattern (orchestrated or choreographed) is what production systems actually use. Here is the difference, the implementation patterns, and the compensation logic that handles the inevitable failure cases.
A 2 TB events table is hard to manage and impossible to clean. Time-based partitioning turns it into 30 small tables you can drop on a cron. Here is the working pattern with declarative partitioning, automated partition management, and the three traps that catch teams new to it.
The UUID-vs-bigint debate is religious in some teams and absent in others. The numbers behind it are surprisingly clean: random UUIDs cost 30%+ more disk and a measurable insert-time penalty, but UUID v7 (time-ordered) closes most of the gap. Here is the data, the patterns each fits, and the “use both” design that ends the argument.
Most TypeScript domain models are a single interface with twenty optional fields, half of which are mutually exclusive. Discriminated unions are the feature that turns that into the actual shape of the data, and lets the compiler narrow precisely. Here is the pattern, applied to four real domain shapes.
JSONB columns let you ship features without a migration, which is exactly why they end up holding half your domain model. Here is the rule for when JSONB is the right call, the four queries that decide whether to promote a key to a column, and the GIN-index pattern that keeps it fast.