Leer en español · Lire en français · Auf Deutsch lesen
Section 7.2 of 7.8 · Intermediate · 25 min read
Choosing a database: relational, document, key-value, column, graph and time-series
Video lesson
5 min · narrated, with captionsListen only (audio)
Read the transcript
A database is one of the hardest parts of a system to replace. In this lesson you'll learn what each kind of database is good at, and how to choose one by evidence.
Choosing a database is like choosing storage for a home. A filing cabinet with labelled folders suits paperwork you cross-reference: a relational database. A wardrobe of complete outfits suits things you take out together: a document store. A row of numbered lockers is quickest when you know the number: a key value store. And a corkboard with string between pins shows who knows whom: a graph. Most homes need a filing cabinet plus one or two specialised pieces, because every extra piece needs space and someone who knows how it works.
For business data, relational remains the sensible default, for four reasons. First, the database itself enforces the rules, such as a payment and its ledger entries committing together. Second, flexibility. You don't need to know every future question when you design the schema. Third, maturity: decades of tooling for backup, replication and monitoring, and engineers who already know it. Fourth, range. Modern relational systems also store JSON, search text and find similar vectors, so one well-run database covers many needs at moderate scale.
Each family of database organises data differently, and that shape decides which queries are cheap and which are impossible. A relational database keeps rows in tables with fixed columns, linked by keys and joins. A document store keeps each record as a nested document, read as a whole, such as a restaurant's menu. Querying it in another shape is harder. A key value store simply gets and puts values by key. It never looks inside the value, so it's extremely fast and easy to spread across machines. A graph database treats relationships as first-class records, which suits fraud detection and recommendations. A time series database appends measurements in time order and queries them by range. Below are three more. Wide column stores handle massive write volumes, with a table designed per query. Search engines rank text by relevance. Vector stores find the nearest matches in meaning.
So how do you choose? Per data set, not once for the whole system. Start with the shape of the data and the queries that will run against it. Then consistency. Balances, stock and bookings need transactions. For likes, a few seconds of staleness is harmless. Then scale, measured rather than guessed. A store that scales to petabytes is no advantage for fifty gigabytes. Then operations and team skills. Who will back it up, upgrade it and debug it at three in the morning? A good habit: write down the most important queries and their target latencies before choosing, and test with realistic volumes, not a thousand rows on a laptop.
Now the food delivery platform. At peak it takes about three hundred orders a minute, has twenty thousand couriers online and two thousand searches a second. The team already runs PostgreSQL well. Orders and payments need transactions and strict rules, so they go in PostgreSQL. There's no case for anything else. Menus have a nested shape, but a document store would add a second technology without meeting any requirement PostgreSQL misses. Courier locations are different: five thousand writes a second, and only the latest position matters, for about thirty seconds. An in-memory key value store with expiry fits. Search gets a search engine, and analytics a data warehouse, both fed from the systems of record and rebuildable from them. A graph database, a separate time series database and distributed SQL were all rejected. No measured requirement needs them yet.
Here's the result. Ordering and Menus each write to their own PostgreSQL database: the only two systems of record. Courier positions live in memory and expire after thirty seconds. Losing them on restart costs one update cycle. The search engine is a derived index of menus, fed by change data capture with a few seconds of lag. And the data warehouse is hours old, by design. Everything except the two systems of record is derived and can be rebuilt.
Using different stores for different data is called polyglot persistence. It follows naturally from database per service. But every extra database type needs its own expertise, backups, monitoring, upgrades and on-call knowledge. For a small team, five databases can take more effort than the product. In his 2015 essay Choose Boring Technology, Dan McKinley argued that a company gets only a few innovation tokens for unfamiliar tools. So justify each new store by a requirement the others cannot meet. And a common mistake: letting a search index or cache become the only copy of some data.
The full section compares every family and adds a step by step decision guide. Next, in Section seven point three, we look at transactions and isolation. See you there.
What you need to know
- Relational databases remain the default for business data because they provide transactions, constraints, flexible queries, mature tooling and widely available skills, and now also handle JSON, text, geospatial and vector data at moderate scale.
- Document, key-value, wide-column, graph, time-series, search and vector stores each make particular access patterns cheap and others expensive; they excel as specialised or derived stores.
- Distributed SQL (Spanner, CockroachDB, YugabyteDB, TiDB) keeps SQL and transactions while scaling across nodes and regions, at the cost of write latency, money and operational complexity.
- Choose per data set using evidence: data shape, access patterns, consistency needs, measured scale, operational burden and team skills; write the key queries and volumes down first.
- Separate systems of record, which need the most conservative technology, from derived stores such as search indexes and caches, which can be specialised because they can be rebuilt.
- Polyglot persistence lets each data set use a fitting store, but every extra technology adds lasting operational and cognitive cost; McKinley's 'Choose Boring Technology' (2015) argues for spending innovation sparingly.
Why the choice deserves care
A database is one of the hardest components to replace. Applications are rewritten around their data, not the other way round; migrating terabytes of live data while the business keeps running can take a year. The choice of store is therefore a one-way door that deserves an explicit decision, while many teams make it by habit or by fashion.
Chapter 0 introduced relational databases and the four NoSQL families. This section goes further: what each family is really good at, the newer categories (time-series, search, vector and distributed SQL), how to choose by evidence rather than enthusiasm, and what it costs to run several kinds at once. The answer is rarely one database for everything, and almost never a different database for every service.
Data models at a glance
Relational: still the default, for good reasons
A relational database stores data in tables with declared types, keys and constraints, and answers declarative SQL queries, an idea from Edgar Codd's 1970 paper. Fifty years on it remains the sensible default for business data, for four reasons. First, transactions and constraints: the database itself guarantees that a payment and its ledger entries commit together and that no order points at a missing customer, so these rules do not have to be reimplemented, imperfectly, in every service. Second, query flexibility: you do not need to know every future question when you design the schema; a new report is a new query, not a new table. Third, maturity: decades of tooling for backup, replication, monitoring, migration and performance diagnosis, and engineers who already know it. Fourth, range: modern relational systems store and index JSON (PostgreSQL's jsonb), support full-text search, geospatial data (PostGIS), time partitioning and vector similarity (the pgvector extension), so one well-run database can cover many needs at moderate scale.
The real limits are specific. Writes on a single primary are bounded by one machine; very large tables need careful partitioning; schema changes on huge tables need technique (schema evolution); and some access patterns, such as deep graph traversals or relevance-ranked text search, are served much better by specialised engines. A single modern server with fast solid-state drives can serve thousands of transactions per second and terabytes of data, which covers the needs of most organisations.
The non-relational families
A document database stores each record as a self-contained document, usually JSON or a binary form of it. It fits aggregates in the domain-driven design sense: things read and written as a whole, such as a restaurant's menu with its sections, items and option groups. Reading one document is one lookup with no joins. The trade-off appears when the same data must be queried in another shape (all items containing peanuts across all menus) or changed in many documents at once; joins and multi-document transactions exist in modern products but are costlier and less natural than in SQL.
A key-value store is a distributed dictionary: put a value under a key, get it back. Because the store never looks inside values, it can be extremely fast and easy to partition. Redis and its open-source fork Valkey keep data in memory with optional persistence and add data structures (sorted sets, streams, geospatial indexes) and expiry; Amazon DynamoDB is a managed, partitioned key-value and document store. They suit sessions, carts, rate-limit counters, feature flags, caches and the latest position of each courier.
A wide-column store (Bigtable, Apache Cassandra, ScyllaDB, HBase) groups rows by a partition key and stores them sorted by a clustering key within each partition. It is designed for massive write volumes spread across many nodes and data centres. The discipline it demands is query-first modelling: you design one table per query, duplicating data freely, because queries that do not start from the partition key are expensive or impossible. Cassandra descends in part from Amazon's 2007 Dynamo paper, whose replication ideas appear in the next section but one.
A graph database stores nodes and relationships as first-class records and answers traversal queries such as customers within three hops of this fraud ring
without the cascading joins that make such queries slow in SQL. Neo4j's Cypher is the best-known query language, and ISO published a standard graph query language, GQL (ISO/IEC 39075), in 2024. Graphs are excellent for fraud detection, recommendations and network analysis, and usually a poor primary store for transactional business data.
Time-series, search and vector stores
A time-series database is optimised for timestamped measurements that arrive in order and are rarely updated: metrics, sensor readings, prices, location history. It compresses heavily, partitions by time, downsamples old data (keep every point for a week, one-minute averages for a year) and deletes expired data cheaply by dropping whole time partitions. Examples include InfluxDB, Prometheus (for monitoring) and TimescaleDB, which is a PostgreSQL extension and shows how often a relational database can stretch to fit.
A search engine such as Elasticsearch, OpenSearch or Apache Solr (all built on the Apache Lucene library) maintains an inverted index from words to the documents containing them, with text analysis (stemming, synonyms, typo tolerance) and relevance ranking. It answers vegan ramen near me
in milliseconds. It should almost never be a system of record: indexes are refreshed in near real time rather than transactionally, and the usual design feeds the search engine from the owning database and rebuilds the index from it when needed.
A vector database stores embeddings, lists of numbers that represent the meaning of text, images or products, and finds the items nearest to a query vector. Exact search compares the query with every vector, so these stores use approximate nearest-neighbour indexes such as HNSW graphs, trading a little recall for speed. They power semantic search, recommendations and retrieval for large language models, covered in retrieval-augmented generation. Dedicated products exist (Milvus, Qdrant, Pinecone, Weaviate), but for millions rather than billions of vectors, an extension to an existing database such as pgvector is often enough.
Distributed SQL
Distributed SQL databases, sometimes called NewSQL (a term coined by the analyst Matthew Aslett in 2011), keep the relational model, SQL and ACID transactions but partition and replicate data automatically across many nodes, often across regions. Google Spanner, described in a 2012 OSDI paper, uses tightly synchronised clocks (its TrueTime API, backed by GPS and atomic clocks) to give globally consistent transactions. CockroachDB, YugabyteDB and TiDB offer similar models on ordinary hardware; CockroachDB uses serializable isolation by default.
The price is physics and operations. A transaction that must be agreed by replicas in several regions waits for a consensus round trip, so single-row writes take milliseconds to tens or hundreds of milliseconds rather than well under a millisecond. Query planning across partitions is subtler, costs are higher, and fewer engineers know the failure modes. Distributed SQL is the right tool when you genuinely need relational transactions beyond one server's capacity or across regions (a global payments ledger, a multi-region bank), and an expensive one when a single PostgreSQL primary with replicas would do.
Comparison of database families
| Family | Data model and query | Strengths | Weaknesses | Examples |
|---|---|---|---|---|
| Relational | Tables, keys, SQL with joins and aggregates | Transactions and constraints, flexible ad hoc queries, mature tooling, widely known | Vertical scaling limit for writes on one node; rigid schema needs migrations | PostgreSQL, MySQL, SQL Server, Oracle |
| Document | JSON-like documents, queries on fields and nested values | Natural fit for aggregates read as a whole; flexible structure | Joins and cross-document transactions weaker or costlier; schema enforced in code | MongoDB, Couchbase, Firestore |
| Key-value | Opaque value per key; get, put, delete | Very fast, simple, easy to partition | No queries across values; modelling burden on the application | Redis, Valkey, DynamoDB, etcd |
| Wide-column | Rows grouped by partition key, sorted by clustering key | Huge write throughput, linear horizontal scale, multi-data-centre | Tables built per query; limited joins and ad hoc queries | Apache Cassandra, ScyllaDB, HBase, Bigtable |
| Graph | Nodes and edges with properties; traversal queries | Multi-hop relationship queries | Harder to partition; smaller ecosystem | Neo4j, Amazon Neptune |
| Time-series | Timestamped points, tags; range and rollup queries | Compression, retention, downsampling | Poor at general updates and relationships | InfluxDB, TimescaleDB, Prometheus |
| Search engine | Inverted index over documents; relevance-ranked text queries | Full-text search, fuzzy matching, facets | Not a system of record; near-real-time refresh | Elasticsearch, OpenSearch, Apache Solr |
| Vector | Embeddings; approximate nearest-neighbour search | Semantic similarity for retrieval and recommendations | Approximate results; index build cost; young tooling | pgvector, Milvus, Qdrant, Pinecone |
| Distributed SQL | Relational model, SQL, partitioned and replicated | Transactions and SQL with horizontal scale and multi-region survival | Higher write latency, cost and operational complexity | Google Spanner, CockroachDB, YugabyteDB, TiDB |
Choosing: criteria that matter
Choose per data set, not per system. For each important data set, collect evidence on six criteria and write the decision in an ADR.
Data shape. Is it naturally tabular with many relationships, a self-contained aggregate, a network, a time series, free text or vectors? Access patterns. Which queries run, how often, with what latency targets, and how many are ad hoc? Read-heavy or write-heavy? Always by key, or by many attributes? Consistency needs. Does the data need multi-item transactions and constraints (balances, stock, bookings), or is a few seconds of staleness harmless (likes, recommendations)? See consistency models. Scale. Current and projected data size, writes per second and growth, measured, not guessed; a store that scales to petabytes is no advantage for 50 gigabytes. Operations. Who will run it: backups, upgrades, failover, monitoring, security patches? Is there a managed service in your cloud, and what does it cost at your volumes? Team skills. What does the team already know well enough to debug at 3 a.m.? Unfamiliar technology carries unknown failure modes.
Two habits improve the choice. Write down the five or ten most important queries with their target latencies before choosing, and test the candidate with realistic data volumes; many disappointments come from a store that was benchmarked on a laptop with a thousand rows. And separate the system of record from derived stores: the system of record needs the strongest guarantees and the most conservative technology, while derived stores (search indexes, caches, read models) can be specialised precisely because they can be rebuilt.
Decision guide
Decision guide: which kind of store for this data?
Ask these questions for each data set (not once for the whole system), in this order. Stop at the first yes that fits; if you reach the end, the relational default stands.
- Is this data a system of record that needs multi-row transactions, constraints or ad hoc queries (orders, payments, accounts, bookings)?YesUse a relational database. Only look further if you can show it cannot meet a measured scale or latency requirement.NoContinue: the data may be derived, specialised or extremely high volume.
- Is it a copy derived from a system of record to serve one special query (full-text search, recommendations, similarity search)?YesUse the specialised store for that query (search engine, graph or vector index), fed from the system of record and rebuildable from it.NoContinue.
- Is it read and written as a single item by key, with no queries across items (sessions, carts, latest positions, feature flags)?YesA key-value store fits, often in memory with expiry. Decide what happens if it is lost.NoContinue.
- Is it an append-mostly stream of timestamped measurements queried by time range (metrics, sensor readings, location history)?YesA time-series database, or time-partitioned tables in a relational database at moderate volumes.NoContinue.
- Will write volume or data size exceed what one well-tuned server can handle, with access always by a known key?YesA wide-column or key-value store designed for partitioning (Cassandra, DynamoDB), with tables designed around the queries.NoContinue.
- Do you need relational transactions and SQL across regions or beyond one server's capacity?YesConsider distributed SQL (Spanner, CockroachDB, YugabyteDB), accepting higher write latency and operating cost.NoStay with the relational default, and revisit when a measured requirement changes.
Polyglot persistence and its cost
Polyglot persistence means using different storage technologies for different data within one system, chosen by how the data is used. Martin Fowler popularised the term in 2011, crediting Scott Leberknight with first using it. It follows naturally from database per service: each owner can pick the right tool.
The cost is easy to underestimate. Each additional database type needs its own expertise, backup and restore procedures, monitoring, capacity planning, security hardening, upgrade cycle and on-call knowledge. Data now moves between stores, so you need pipelines (CDC, events) and must reason about the lag and failure of each. Consistency between stores is eventual at best. For a small team, five databases can consume more effort than the product. A reasonable rule is to justify each new store by a requirement the existing ones demonstrably cannot meet, and to prefer managed services for the specialised ones.
Worked example
Worked example: stores for the food-delivery platform
The platform operates in 40 cities. At the evening peak it takes about 300 orders a minute, has 20,000 couriers online, serves 50,000 restaurant menus and is searched about 2,000 times a second. The team of 35 engineers already runs PostgreSQL well.
| Orders and payments | System of record; multi-row transactions (order, lines, payment authorisation); strict rules; ad hoc queries by support. About 5 new orders a second at peak (a few dozen row writes). PostgreSQL, with a replica for failover. No case for anything else. |
|---|---|
| Menus | Read-heavy, hierarchical (sections, items, option groups), edited by restaurants a few thousand times a day. Prices must be exactly what the order records. PostgreSQL with a relational core (restaurant, item, price) and jsonb for variable options. A document store would fit the shape, but would add a second technology without meeting any requirement PostgreSQL misses. |
| Courier locations | 20,000 couriers each sending a position every 4 seconds is 5,000 writes a second, of which only the latest per courier matters for dispatch, and only for about 30 seconds. In-memory key-value store (Redis or Valkey) with geospatial queries and a 30-second expiry. Losing it on restart costs one update cycle. A sample of points (say one every 30 seconds) goes to the warehouse for analytics. |
| Restaurant and dish search | Full-text, typo-tolerant, filtered by location, open hours and dietary tags, ranked by relevance. Search engine (OpenSearch), fed from the menus database by change data capture, rebuildable from it. Accepts a few seconds of lag after a menu edit. |
| Analytics | Weekly revenue, delivery times by city, courier utilisation: large scans and aggregates over months of history. Cloud data warehouse fed by CDC and events, hours old by design. See analytics and streaming. |
| Rejected | A graph database for recommendations (not yet needed; revisit with evidence), a separate time-series database for locations (the warehouse sample answers the questions asked), and distributed SQL (the whole platform's orders, about 5 a second at peak, fit comfortably on one primary; there is no multi-region transaction requirement). |
The result is two systems of record on one well-known technology plus three specialised, derived stores. Each extra technology is justified by a requirement the default cannot meet: 5,000 short-lived writes a second, and relevance-ranked text search.
Writing the queries and numbers down first made the decisions almost obvious, and gave the team a record of the assumptions to revisit when volumes change.
The resulting store landscape
Common mistakes
Résumé-driven choices: picking a store because it is new or because a large company uses it, without the requirement that justified it there. Choosing for imagined scale: designing for a billion users with a product that has a thousand, paying the complexity tax from day one. Using a derived store as a system of record: a search index or cache that becomes the only copy of data, with weaker durability than anyone realised. Ignoring the query side: picking a key-value or wide-column store and then discovering the business needs ad hoc reports and filters it cannot serve. Underrating operations: forgetting that someone must restore it from backup, upgrade it without downtime and diagnose it under load. Finally, treating the choice as permanent truth: record the assumptions (volumes, queries, team skills) so the decision can be revisited when they change.
Check your understanding
Why should a search engine usually not be the system of record?
Its index is refreshed in near real time rather than transactionally and its durability guarantees are weaker; it should be fed from the owning database and rebuilt from it when needed.
Give two signs that a wide-column store may be a good fit.
Very high write volume beyond one server, and access that always starts from a known partition key so tables can be designed per query.
What does an 'innovation token' represent in McKinley's essay?
One of a small number of chances a company has to adopt unfamiliar technology, each of which brings unknown failure modes and lasting operational cost.
When is distributed SQL worth its cost?
When you need relational transactions and SQL beyond one server's capacity or across regions, such as a global ledger; not when a single primary with replicas meets the requirements.
Sources and further reading
Have a question about this section? ArchitectBot, the course’s AI study assistant, answers from the course first.
Ask: “Our team wants to use MongoDB for a new orders service because it is more flexible. How do I decide whether that is a good idea compared with PostgreSQL?”