Lesson 1 of 5
SQL vs NoSQL
"The question is not 'SQL or NoSQL.' The question is 'which shape of data problem am I actually holding?'" — a paraphrase of every senior engineer who has survived a migration
For seven parts you have treated the relational database as the ground beneath your feet — tables, keys, joins, transactions, the whole disciplined machinery that has run the world's data since Codd. Now we lift the floorboards. Around the turn of the 2010s a family of databases arrived that deliberately gave up some of what you have learned to love — the rigid schema, the joins, the strict transaction — in exchange for things the relational model made hard: schema-free flexibility, effortless horizontal scaling, and blistering throughput for one specific access pattern. We call them, imprecisely but permanently, NoSQL.
This chapter is the map for Part VII. It will not make you a MongoDB or Cassandra expert — later chapters go deep on each family. Its job is bigger and more useful: to give you the decision framework so you never again pick a database because it was trending, and never again dismiss one because it wasn't relational. By the end you will know what NoSQL actually means, why it emerged, the four families and what each is for, the real trade-offs (schema flexibility versus integrity, horizontal scale versus joins and transactions, ACID versus BASE), and — the part that matters in interviews and in production — when to choose which.
Given a new system to build, how do you decide whether your data belongs in a relational database, a NoSQL store, or — increasingly — several of them at once?
What "NoSQL" actually means
The name is a marketing accident. "NoSQL" started as a Twitter hashtag for a 2009 meetup and stuck to a movement it barely described. It does not mean "no SQL" — several NoSQL systems now speak SQL-like languages. The useful reading is "Not Only SQL": a broad label for databases that are non-relational, that relax one or more of the classic relational guarantees, and that were designed first and foremost to scale horizontally — across many cheap machines — rather than vertically on one big one.
That is the whole idea in one sentence: relational databases were built to be correct on one machine; NoSQL databases were built to be available across many. Everything else — flexible schemas, no joins, eventual consistency — flows from that founding choice.
Where this came from NoSQL was born from operators, not academics. In the mid-2000s the largest web companies hit walls the relational world had never faced. Google needed to store the entire web and published Bigtable (2006). Amazon needed a shopping cart that never went down, even during a datacenter partition, and published Dynamo (2007) — choosing availability over strict consistency on purpose. Those two papers are the Old Testament of NoSQL: Bigtable begat HBase and Cassandra's storage model; Dynamo begat Cassandra's distribution model, Riak, and later Amazon's own DynamoDB. NoSQL is what happened when the people running the internet's biggest systems wrote down what they'd had to build.
Why it emerged — the four pressures
NoSQL did not appear because relational databases were bad. It appeared because four specific pressures, all peaking at once in the Web 2.0 era, pushed against the relational model's design assumptions:
- Web-scale volume and traffic. When your data no longer fits on the biggest single server you can buy — and no single server can serve the read/write rate — you must spread it across many machines. Relational databases can be sharded, but it's painful and it breaks joins and transactions across shards. NoSQL systems were built to shard from day one.
- Flexible, evolving schemas. A social feed item, a product catalog with wildly different attributes per category, a user profile that grows new fields every sprint — forcing all of that into a fixed table of columns, with a migration for every change, is friction. Document stores let each record carry its own shape.
- Developer velocity. Storing an object graph as one JSON document — the way it lives in application memory — skips the "object-relational impedance mismatch," the tax of translating between objects and normalized tables. For fast-moving product teams this was a genuine speedup.
- Specific, known access patterns. A cache that only ever does
GET key/SET key. A session store. A time-series firehose. When you know exactly how the data will be read, a purpose-built store beats a general-purpose one by an order of magnitude.
Notice the pattern: every pressure is a case where the relational model's generality — its ability to answer questions you never anticipated — is a cost you're willing to trade away for scale, flexibility, or speed on a narrow job.
The four families
"NoSQL" is not one thing; it's four quite different things sharing a hashtag. Each gets a dedicated chapter later in Part VII — here is the map so the trade-offs below make sense.
| Family | Data model | Think of it as | Star products | Best for |
|---|---|---|---|---|
| Document | Self-describing documents (JSON/BSON), one per record | A folder of JSON files you can query | MongoDB, Couchbase, Firestore | Evolving schemas, content/catalogs, an object saved whole |
| Key–value | Opaque value fetched by a single key | A giant hash map that survives restarts | Redis, DynamoDB, Riak | Caching, sessions, feature flags, known-key lookups |
| Wide-column | Rows with dynamic columns, grouped by a partition key | A sparse, distributed spreadsheet spanning a cluster | Cassandra, HBase, Bigtable | Massive write throughput, time-series, event logs |
| Graph | Nodes and edges as first-class citizens | A network you traverse by relationship | Neo4j, Amazon Neptune | Deeply connected data: social graphs, fraud, recommendations |
Two of these are cousins: document and wide-column stores are both really about storing flexible, denormalized records at scale. Key–value is the minimalist extreme — the fastest possible thing, at the cost of the database understanding nothing about your value. Graph is the odd one out: it's non-relational not because it scales differently, but because it optimizes for the relationships between records rather than the records themselves — the very thing a SQL join computes on the fly, a graph database stores as a permanent, walkable edge.
Analogy A relational database is a well-run library — every book catalogued by strict rules, cross-referenced, no duplicates, any question answerable by the index. A document store is a filing cabinet where each folder holds everything about one case, however messy. A key–value store is a coat check: give the ticket, get the coat, no questions asked. A wide-column store is a colossal warehouse with a fixed aisle system built for forklifts moving pallets in and out all day. A graph database is a detective's cork-board of photos connected by string — the whole point is the string.
Preview: document data, in SQL
You don't need MongoDB to feel what document storage is like — modern SQLite (and Postgres, and MySQL) can store and query JSON directly. This is the "blurring" we'll return to at the end, but it makes an excellent preview. Here we store each profile as a self-describing document and pull fields out with json_extract. Click Run:
Click Run to execute the query.
Look at what just happened: two rows with completely different shapes — one has tags, the other has a nested plan object — sat happily in the same table, and missing fields came back as NULL instead of an error. That is the document-store superpower (schema flexibility) demonstrated inside a relational engine. You can even query and aggregate on fields buried in the JSON:
Click Run to execute the query.
Convenient — but note the cost hiding in plain sight: nothing stopped us from inserting {"amount":"oops"}, and no index makes $.type fast unless we build one explicitly. That tension — flexibility bought with integrity and, sometimes, speed — is the heart of the whole SQL-versus-NoSQL debate. Let's name the trade-offs directly.
The real trade-offs
Schema flexibility vs. integrity
A fixed relational schema is a contract: every orders row will have a numeric amount and a customer_id that must point at a real customer, or the write is rejected. That rigidity is exactly what lets you trust the data years later. A schemaless document store lets you add a field without a migration and store ragged records freely — wonderful for velocity, until three app versions have each written the field a different way and there's no database rule to stop garbage. NoSQL doesn't remove the need for a schema; it moves the schema from the database into your application code (and your discipline), which is faster on day one and riskier on day one thousand.
Horizontal scale vs. joins and transactions
This is the deepest trade. To spread data across many machines, NoSQL systems partition it by a key — and the price is that operations spanning partitions get hard or disappear. So most NoSQL stores drop or restrict two things you rely on:
- Joins — instead of normalizing and joining at read time, you denormalize: pre-combine and duplicate data so each query hits one place. You trade storage and write-time complexity for read speed and scalability.
- Multi-record transactions — classic ACID across arbitrary rows assumes coordination that's expensive across a cluster. Many NoSQL systems offer only single-record atomicity (though this has improved a lot — DynamoDB and MongoDB now support multi-document transactions with caveats).
The relational world answers questions you didn't plan for; the NoSQL world answers the questions you did plan for, extremely fast, forever. Which is why NoSQL modeling starts from the query, not the entity.
CAP, and eventual consistency
You met the CAP theorem in Volume 5 (System Design): when a network Partition happens between nodes — and at scale it will — a distributed system must choose between Consistency (every read sees the latest write) and Availability (every request still gets an answer). You cannot have both during the partition. Relational databases traditionally sit on the CP side: they'd rather refuse than serve stale data. Amazon's Dynamo lineage chose AP: stay up, accept that a read might briefly return an old value, and reconcile afterward.
That "reconcile afterward" is eventual consistency — the promise that if writes stop, all replicas will converge to the same value, just not instantly. For a shopping cart or a like-count, a few hundred milliseconds of staleness is invisible and worth the uptime. For a bank ledger, it is unacceptable. Choosing a database is often really choosing where on this line your data can live.
ACID vs. BASE
The two philosophies even have opposing acronyms — and yes, the chemistry pun is deliberate.
| ACID (relational default) | BASE (NoSQL tendency) | |
|---|---|---|
| Stands for | Atomic, Consistent, Isolated, Durable | Basically Available, Soft state, Eventual consistency |
| Priority | Correctness — never show a wrong answer | Availability — always answer, converge later |
| Consistency | Strong, immediate | Eventual |
| Scaling model | Scale up (bigger box); sharding is hard | Scale out (more boxes) natively |
| Ideal for | Money, inventory, anything with invariants | Feeds, catalogs, telemetry, caches, session data |
| CAP leaning | CP | AP |
Neither is "better." ACID is what you want the moment two facts must stay in agreement (a debit and its matching credit). BASE is what you want when being up and fast matters more than being instantly perfect, and a moment of staleness is harmless.
When to choose which
Here is the decision, distilled. Reach for relational by default — and the earlier chapter meant it — when you have:
- Rich relationships and ad-hoc queries. You don't yet know every question you'll ask. SQL and joins let you ask new ones without re-modeling.
- Transactions and invariants. Money, inventory, bookings — anything where partial updates or double-spends are catastrophic.
- Integrity that must not drift. Constraints, foreign keys, and types enforced by the engine, not hoped for in app code.
- Moderate, single-cluster scale — which is most applications, for far longer than founders expect.
Reach for NoSQL when you have a specific, earned reason:
| Signal | Family that fits |
|---|---|
| Known, simple key lookups; caching; sessions | Key–value (Redis, DynamoDB) |
| Flexible/evolving documents; object saved whole; content & catalogs | Document (MongoDB, Firestore) |
| Extreme write volume; time-series; event logs; global scale | Wide-column (Cassandra, Bigtable) |
| Data whose value is in the connections | Graph (Neo4j, Neptune) |
The trap to avoid is picking NoSQL for prestige. "Web scale" is a problem you should be lucky enough to earn; most systems that reached for a distributed NoSQL store on day one would have shipped faster and slept better on Postgres.
In production Serious systems rarely pick one database — they practice polyglot persistence: the right store for each job, wired together. A typical e-commerce backend keeps orders and payments in PostgreSQL (ACID, non-negotiable), the product catalog in MongoDB (flexible per-category attributes), sessions and hot inventory counters in Redis (speed), the clickstream in Cassandra (write firehose), and "customers who bought this" in Neo4j (graph traversal). Each store does what it's best at; the application (or an event bus) keeps them coordinated. The skill isn't loyalty to one database — it's knowing which shape of problem you're holding.
Here is that idea as a picture — one system, several stores, each earning its place:
The lines are blurring
The neat "SQL vs NoSQL" war of 2012 is quietly ending, from both directions:
- Relational databases learned NoSQL tricks. PostgreSQL's
JSONBtype stores and indexes real documents inside a relational table — you can have strict, typed columns for the facts that matter and a flexible JSON blob for the rest, in the same row, in the same transaction. MySQL and SQLite have JSON support too (you just used it). For many teams this erases the reason to add a separate document store at all. - NoSQL learned SQL's tricks. The hardest thing NoSQL gave up — ACID transactions at scale — is being rebuilt. Google Spanner and CockroachDB are "NewSQL" / distributed-SQL systems: they speak SQL, offer real ACID transactions and joins, and scale horizontally across regions, using synchronized clocks and consensus protocols to pull off what CAP said was hard. DynamoDB added transactions; MongoDB added multi-document ACID. The industry, having split the atom, is carefully putting it back together.
So the modern question is less "SQL or NoSQL?" and more "how much consistency, flexibility, and scale does this particular data need, and which of today's increasingly-overlapping tools delivers that mix?"
Common Mistakes
- Choosing NoSQL for hype, not need. "It's what big companies use" isn't a requirement. Start relational; migrate a specific bottleneck to a specific NoSQL store when data tells you to.
- Assuming NoSQL means "no schema, no thought." It means the schema lives in your code and your access patterns. NoSQL data modeling is harder, not easier — you design around queries up front, with no join to save you later.
- Expecting joins and multi-record transactions from a store that dropped them. Denormalization and single-record atomicity are the deal you signed. Fighting it in app code recreates a slow, buggy relational database.
- Treating eventual consistency as strong consistency. If your read-after-write logic assumes the value is instantly current, you'll ship bugs. Know your store's consistency model before you rely on it.
- Adding a second database too early. Every store is another thing to run, back up, secure, and keep in sync. A
JSONBcolumn in the Postgres you already have often beats a whole new document database. - Reaching for a graph database for shallow relationships. One or two joins are what SQL is for. Graphs earn their keep on deep, variable-length traversals ("friends of friends of friends"), not on a foreign key.
Think about it You're designing a URL shortener. It must handle enormous read traffic (redirects), the data is dead simple (short code → long URL), the access pattern is a single known-key lookup, and a redirect being a few milliseconds stale is completely harmless. Which family fits like a glove — and why would defaulting to a full relational database here be over-engineering? Now change one requirement: the business wants detailed, ad-hoc analytics on click patterns by country, referrer, and hour. Does your answer change, or do you now want two stores?
Hands-on Feel the document/relational blur yourself. Below, each user is stored as a JSON document with a different shape. (1) Run it. (2) Add a third row with a brand-new field no other row has — note that no migration was needed. (3) Add a WHERE clause that keeps only users whose city is 'Lagos' using json_extract. (4) Reflect: what integrity check did the relational world give you for free that this flexible design just lost?
Click Run to execute the query.
Summary
- NoSQL means "Not Only SQL" — non-relational databases built to scale horizontally, relaxing some relational guarantees to gain flexibility, scale, or speed on a known access pattern.
- It emerged from four real pressures: web-scale volume, flexible schemas, developer velocity, and specific access patterns — pioneered by Google's Bigtable and Amazon's Dynamo papers.
- The four families — document, key–value, wide-column, graph — each fit a different data shape; know which problem each solves.
- The core trade-offs: schema flexibility vs. integrity, horizontal scale vs. joins/transactions, and ACID (CP, correctness) vs. BASE (AP, availability + eventual consistency) — grounded in the CAP theorem from Volume 5.
- Default to relational for relationships, transactions, ad-hoc queries, and integrity; choose NoSQL for an earned, specific reason — and often use both via polyglot persistence.
- The lines are blurring: Postgres
JSONBbrings documents into relational tables, while Spanner and CockroachDB bring ACID and SQL to horizontal scale.
Next, we descend into the first and most popular NoSQL family — document databases — to see how storing an object whole, schema and all, reshapes the way you model, query, and think about data.
Editorial reflection The engineers who get database decisions right are rarely the ones with the strongest opinions about which technology is "best." They're the ones who can hold a system in their head and ask, honestly, what does this data actually need — to be perfectly correct, to always be available, to change shape freely, or to be found in a microsecond? Then they pick the tool that answers that question, and no more tool than that. Taste in databases is really taste in trade-offs: the discipline to want the boring, correct default until the data earns you the right to something exotic.
Q&A · 0
Enrol to ask questions and join the discussion.
No questions yet — be the first to ask.