SQL vs NoSQL: Choose by Access Pattern, Not Hype
The SQL-versus-NoSQL debate is usually framed as a religion. It's actually an engineering decision driven by one question: how will you read and write this data? Here's the framework I use.
"Should we use SQL or NoSQL?" is asked like it's a values question — old and safe versus new and scalable. It isn't. It's an engineering question with a boring, reliable answer: it depends on how you access the data. Model your read and write patterns first, and the choice usually makes itself.
What "relational" actually gives you
A relational database (Postgres, MySQL) stores data in tables with enforced relationships, and it's built around two guarantees most apps quietly depend on:
- Joins — combine related data at query time, so each fact lives in one place and you assemble views on demand.
- ACID transactions — multiple changes commit all-or-nothing, so you never leave money debited but not credited.
The trade is that the schema is fixed up front, and scaling writes horizontally (across many machines) is genuinely hard, because joins and transactions assume the data is together.
What "NoSQL" actually means (it's several things)
"NoSQL" isn't one thing — it's a family, each tuned for a different access pattern:
- Document (MongoDB) — stores flexible, nested documents. Great when data is naturally a self-contained blob you fetch by id and the shape varies.
- Key-value (Redis, DynamoDB) — a giant, fast hash map. Great for lookups by a known key, at scale.
- Wide-column (Cassandra) — built for enormous write throughput across many nodes.
- Graph (Neo4j) — built for data that's mostly relationships (social graphs, recommendations).
The common thread is that they relax something relational databases enforce — usually joins, sometimes strong consistency — in exchange for flexibility or horizontal scale.
The question that actually decides
Forget the labels and ask how the data is used:
- Is the data highly relational — lots of entities referencing each other, queried in combinations? Relational. Joins are exactly this job, and reimplementing them in application code is a bug factory.
- Do you need multi-record transactions — must several writes succeed or fail together? Relational, strongly. This is the thing NoSQL most often gives up.
- Is it self-contained documents fetched by id, with a shape that varies per record? A document store fits naturally.
- Is it simple lookups by key at huge scale/speed? Key-value.
- Are relationships themselves the query ("friends of friends")? Graph.
Polyglot persistence: it's not either/or
Real systems often use both, each for what it's good at. On the backends I've built, the pattern recurs: Postgres as the source of truth for relational, transactional core data; Redis for caching and fast ephemeral state; a document store where data is genuinely schemaless. That's polyglot persistence — pick the right store per data shape, not one store for everything.
Postgres → users, orders, payments (relational, transactional)
Redis → sessions, rate limits, cache (fast, ephemeral, key-value)
Mongo → flexible per-tenant config (varying document shapes)The mistake in both directions
Two failure modes, equally common. One is forcing genuinely relational, transactional data into a document store and then reinventing joins and consistency in application code — slowly, buggily. The other is reaching for NoSQL for "scale" you'll never hit, and paying in lost transactions and query flexibility from day one.
Neither is about which technology is better. Model your access patterns, match the store to the shape of the data, and the argument dissolves. The database is a tool; the access pattern is the spec.