NoSQL

Tool

A family of databases that store data without fixing its shape in advance, used for records whose fields vary or whose volume grows past one machine.

Why it exists

For a long time there was effectively one way to store data. You defined a table of rows and columns, and which columns existed had to be decided before any data went in.

Web services broke that assumption as they grew. The records users generated had a different shape for every feature, and each new feature brought new fields with it. Adding a column meant touching millions of existing rows, and the service slowed down or stopped while that ran. Volume was the other half of it. Once the data no longer fits on one machine it has to be spread across several, and reading tables back together gets expensive the moment the pieces live on different servers.

Pagedeck ran into the same wall. Theo built one table for activity records and found that a document edit needed the document ID and the editor, a comment needed a comment ID and a list of mentioned people, and a share needed a recipient and a permission level. Columns kept getting added until a row had twenty of them, sixteen of which were empty at any given time. Every new feature scheduled another column migration.

NoSQL puts down one assumption at this point: that every record has to have the same shape. Let records carry different fields and the column-adding work disappears. In exchange, tying records to one another becomes the application's job rather than the database's.

What it solves

There are four problems here, and different families in the group handle different ones.

  1. Records with varying fields. Document-oriented stores keep each record whole, as a single unit. In that activity log, an edit record and a comment record sit side by side carrying different fields, and a new feature's records arrive with their own fields without disturbing anything already stored.
  2. Data too large for one machine. Sharding, which splits data across servers, is built in, so adding servers splits the data automatically. Since nothing is being matched across tables, more pieces don't make reads more complicated.
  3. Values that have to be fetched very fast, very often. A key-value store hangs one value on one key and retrieves it only by that key — the right place for login sessions or a frequently used computation parked for a while.
  4. Finding things by meaning. A vector database converts the meaning of text into a list of numbers and searches by closeness rather than by characters, which is how "how do I get a refund" and "I want my money back" come back as the same question.

There is a price for what you get. In many of these products, transactions — bundling several operations so they either all succeed or all get undone — are weaker than on the relational side, because locking data scattered across machines as one unit is hard. It's also common for a value you just wrote to briefly read as its older version from another server. For an activity feed that gap costs nothing, since a record showing up a second late changes nothing for the reader. For a balance or an inventory count it costs a great deal, which is why those tend to stay on the relational side even in systems that otherwise run on this family.

Alternatives

The first alternative is the relational database you already run. It does fix the table shape, but current products can store a free-form blob inside a single column, so the records whose fields vary go there. That usually ends the twenty-column activity-log problem on its own, and it leaves accounts and payments — the data that has to be exactly right — sitting in ordinary tables.

If volume is the issue, there's an order to try things in. Heavy read traffic goes to a few replicas; frequently used computations get parked in a key-value store. More services survive on that combination than you might expect. Switch families after the data genuinely stops fitting on one machine, not before. Worrying about scalability at a few million rows and migrating early trades one SQL statement for several lines of code to avoid a load that hasn't arrived.

The choice is driven by how data comes out, not by how it looks going in. If one screen always pulls a whole record at once, document-oriented fits. If the same data gets queried under conditions that keep changing, tables fit. Running both in one system is common, and picking per workload rather than per company saves a lot of argument.

How it sounds at work

"The shape of this is going to keep changing. Should we put it in NoSQL?"

The records will carry different fields over time, which makes a fixed table awkward to keep migrating.

"Relational is fine at our size. Take it in a JSON column."

Don't switch families; put the free-form part in a column of the existing table.

"There's no join here, so this screen makes four separate queries."

Linking records falls to the application, which splits one read into several.

Pagedeck moved only the activity log to a document store. Theo dropped the twenty-column table and wrote each record whole, and new features stopped dragging a schema change along with them. A schema is the blueprint recording which tables exist and which columns each one holds.

Accounts and billing stayed where they were. When Sofia checks invoices she asks by company, then by period, then by plan, changing the conditions as she goes, and tables handle that kind of question far better. Splitting the two families inside one service like this is the most common arrangement in practice, and it means the question is rarely which family a company uses, but which family each piece of data belongs in.

What people get wrong

The name leads people to read it as a family that doesn't use SQL. The negation up front looks like a rejection of the query language. What the name actually points at is not being tied to table structure, and most current products offer a query language of their own that looks a great deal like SQL. Carry the misreading into a migration expecting simpler queries and you end up rewriting a one-statement report as application code.

The other frequent mistake is believing there's no schema at all. Nothing is declared before data goes in, so none appears to exist. It didn't disappear — it moved into the code. What knows which fields a record holds is now the application rather than the database, so renaming a field in one place quietly breaks the code that reads records written in the old shape. The checking didn't stop being necessary; the team just does it now.

Easily confused with

TermHow it stores dataHow it differs
Relational databaseIn tables of rows and columns, linked to each other by valuesThe database itself links records. Strong when query conditions keep changing, at the cost of defining the structure up front

Where to go next

  • Database

    see where choosing a family sits in the larger picture

  • Relational database

    the other side of the choice, and most services' default

Last updated 2026-09-16