Express course · No. 06
Languages and frameworks come and go. The data you store, and the shape you store it in, can outlive all of them — so choosing a database is choosing how your system remembers, and how hard it is to change its mind.
20 concepts · a diagram for each · cheat sheet at the end
A database is your system's long-term memory. Code gets rewritten; the data, and the shape you chose for it, tends to outlive everything around it.
The data outlives the code
A house gets repainted, refurnished, even re-roofed over decades — but the foundation poured on day one stays, because replacing it means lifting the whole house.
Languages change, frameworks get swapped, the UI is redrawn ten times. The data and its structure usually stay, because changing them on a live system full of real records is slow and risky. So the database is the first thing to think hard about, and the last thing to rush.
A database answers two questions
Choosing where to keep things asks two things: what shape are the things — books on shelves, or odd tools on pegs? — and how safe must they be — a vault, or a handy drawer?
Every store is defined by two things. First, its data model: how it shapes data — rigid tables, flexible documents, a graph of links. Second, its guarantees: strict correctness, or speed and availability with looser promises. Almost every database choice is a different answer to those two — and the rest of this course is the menu.
Access pattern — pick by how you read and write
A filing cabinet, a card catalogue, and a conveyor belt are all "storage" — but you choose by how you'll fetch things, not by which looks newest.
There is no best database. A store that's perfect for fast key lookups is wrong for deep relationship queries; one built for huge writes is wrong for strict bank transactions. The right question is your access pattern: what you read, what you write, how often, and how it must behave when things go wrong.
The map: 20 concepts in six groups
A metro map: first you see the whole lines, then you ride station by station.
The concepts in this course fall into six groups: the relational core, the NoSQL family, specialised stores, guarantees, scaling techniques, and how to combine it all.
Every concept below gets the same treatment: its English name (the term you'll search the docs for), a picture from life, a diagram, and a card with fields: problem, idea, example, cost, when to use, and related. A one-page cheat sheet waits at the end.
Code is easy to change. Data is not. Choose the store like a decision you'll live with for years — because you will.
For most systems the default is the relational database — and it earns that default. Start here, and leave only with a reason.
Relational Database — tables, rows, and relations
A spreadsheet done right: each sheet is one kind of thing (customers, orders), each row one record, and orders point back to the customer they belong to.
This is the relational database. It stores data in tables with a fixed schema, and links tables by reference — an order row holds the id of its customer. That reference is called a foreign key. The schema is a promise about shape: every order has the columns it should, checked on the way in. Structure up front, in exchange for sanity later.
- Problem
Data with structure and relationships that you'll ask many questions of.
- Idea
- Tables with a fixed schema; tables linked through foreign keys.
- Example
Postgres, MySQL: tables
customersandorders, each order has acustomer_id.- Cost
- The schema needs thought up front; changing it on live data is work.
- When to use
Almost always, as the starting point. Leave only with a reason.
- Related
- Speaks SQL, gives Transactions and ACID.
SQL — ask for what you want, not how to get it
You tell a librarian "every novel by French authors published after 1950, sorted by year" — and they work out how to find them. You describe the result, not the search.
SQL (Structured Query Language) is a declarative query language: you state the answer you want and the database plans how to fetch it, including joining tables together (JOIN). That plan is built by the query planner. SELECT ... JOIN ... WHERE ... can answer questions you never anticipated at design time — one of the relational model's quiet superpowers.
- Problem
- You need to ask the data questions without hand-coding every search.
- Idea
Describe the result; the database picks the plan (indexes, joins, sorting).
- Example
SELECT title FROM books JOIN authors ... WHERE country = 'FR' AND year > 1950 ORDER BY year.- Cost
Complex queries must be read carefully, and you check how the database actually runs them.
- When to use
- Wherever there's a relational database — it's its native tongue.
- Related
- The language of a Relational Database; made fast by an Index.
Transactions — all or nothing
A money transfer is two writes — minus here, plus there — but one intent. Either both happen or neither does; a half-done transfer is a disaster.
This is the transaction: a group of changes that either fully commits or fully rolls back, isolated from others, and survives a crash once confirmed. The set of those promises is called ACID (more in chapter 5). This is why banks, orders, and inventory live in SQL — when correctness matters more than raw speed, you want this promise.
- Problem
- One action is several writes; a crash in the middle leaves garbage.
- Idea
- Wrap the writes in
BEGIN … COMMIT: all of them are saved, or none. - Example
- A transfer:
A − 100andB + 100in one transaction. - Cost
- Some speed, and harder scaling across machines.
- When to use
Money, orders, inventory — anywhere a wrong number is unacceptable.
- Related
- The properties of a transaction are ACID.
Why it's the right default
A good general-purpose tool: not the fastest at any single job, but the one you reach for until a job specifically demands otherwise.
Structured data, real relationships, ad-hoc queries, strong guarantees — the relational database does all of it well, and modern Postgres also handles JSON, full-text search, and even vectors. Begin every project here; reach for something else only when a concrete pain — scale, shape, or access pattern — pushes you out.
Start with Postgres. The question isn't "why SQL" — it's whether you have a real reason not to.
Sometimes the rigid table is the wrong shape. "NoSQL" is really a family of stores, each bending the rules in a different direction for a different job.
Document Store — a JSON document per thing
A folder of filled-in forms, where each form can have slightly different fields — no central template forcing every one to match.
This is the document store. It keeps records as flexible JSON-like documents, nested and schema-less, so two records in one collection can differ. Great when shapes vary or evolve fast, and a whole thing — a product, a profile — lives as one document you fetch in a single read.
- Problem
- Record shapes differ or change fast; the table gets in the way.
- Idea
- Each thing is one nested JSON document, with no shared rigid schema.
- Example
- MongoDB: a product catalogue, user profiles.
- Cost
- Fewer guarantees and harder cross-document queries than SQL.
- When to use
A thing is read whole, and different records have different fields.
- Related
Postgres does documents too (JSONB); often lives by BASE rules.
Key-Value Store — a giant hash map
A coat check — you hand over a ticket number, you get back exactly your coat. No searching, no questions, instant.
This is the key-value store — the simplest shape: a key in, a value out, blazingly fast. Perfect for caching, sessions, rate-limit counters, and anything you fetch by a known id.
- Problem
- You need data back very fast by a known id.
- Idea
- Key → value, like a giant hash map.
- Example
- Redis, DynamoDB:
session:42, a page cache, a rate-limit counter. - Cost
- You can only look things up by key — no rich querying over the values.
- When to use
Cache, sessions, counters — anything where the id is known up front.
- Related
The typical "fast helper" next to the main database in Polyglot persistence.
Wide-Column Store — built for enormous writes
A vast warehouse with endless identical aisles, arranged so a thousand forklifts can stock shelves at once without bumping into each other.
This is the wide-column store. It spreads data across many machines by a partition key for massive write throughput and scale, trading rich queries and strict consistency for the ability to absorb a firehose. Powerful — and more operational work to run well.
- Problem
- So many writes that a single SQL box would buckle.
- Idea
- Spread data across many nodes by partition key; write in parallel.
- Example
- Cassandra: event logs, telemetry, feeds at scale.
- Cost
- No rich queries or strict consistency; harder to operate.
- When to use
A write firehose, with queries that are known up front and simple.
- Related
- Essentially built-in Sharding; usually lives by BASE.
Graph Database — when the links are the point
A detective's pinboard — photos connected by string. The value isn't in any one photo; it's in the web of who's tied to whom.
This is the graph database. It models data as nodes and the edges between them, making relationship queries — "friends of friends who like this," "the path between these two accounts" — natural and fast, where SQL would need painful many-way joins. Reach for it when the connections matter more than the records.
- Problem
The main questions are about links, and in SQL they become many-storey JOINs.
- Idea
- Nodes and edges are first-class data; a query walks the edges.
- Example
- Neo4j: social graphs, recommendations, fraud detection.
- Cost
- A narrow niche; awkward for ordinary records and reports.
- When to use
- The connections matter more than the records.
- Related
An alternative to a Relational Database when JOINs become the pain.
NoSQL isn't "newer SQL." Each store drops a guarantee to win something specific — know which one you're trading away.
Beyond the general stores sit databases built for one job, where a purpose-built engine beats a general one by a mile.
Time-Series Database — data stamped by the clock
A heart-rate monitor's tape — an endless strip of readings, each tied to a moment, where you almost always ask "what happened between these two times?"
This is the time-series database. It's tuned for data that arrives in time order and is queried by time — metrics, sensor readings, prices. It ingests huge streams and answers "average per minute over the last week" fast, with built-in expiry of old data (retention).
- Problem
A huge stream of readings, and every question is "what happened in this period".
- Idea
- Store and index by time; aggregate windows fast.
- Example
- InfluxDB, TimescaleDB: server metrics, sensors, prices.
- Cost
- One more store to run; a poor fit for ordinary business data.
- When to use
- Everything has a timestamp, and time is the main axis.
- Related
- TimescaleDB is a Postgres extension — see Postgres first.
Search Engine — full-text and relevance
A search box that forgives typos, ranks the best matches first, and finds "running" when you typed "run" — like a librarian who knows what you meant.
This is the search engine. It builds an inverted index — "word → documents" — for fast full-text search with ranking, fuzzy matching, and filters: what a LIKE '%word%' in SQL does slowly and badly. It usually runs alongside your main database, kept in sync, powering the search bar and log exploration.
- Problem
LIKE '%word%'is slow, doesn't forgive typos, and doesn't rank.- Idea
- An inverted index of words, plus a relevance score.
- Example
- Elasticsearch, OpenSearch: a site's search bar, log exploration.
- Cost
- Must be kept in sync with the main database.
- When to use
- Users search by text and expect "smart" search.
- Related
Not the source of truth — a fast lens onto it; part of Polyglot persistence.
Vector Database — search by meaning
Instead of matching exact words, you ask "find me things that feel like this one" — and it returns neighbours by similarity, not by keyword.
This is the vector database. It stores embeddings — numeric fingerprints of meaning — and finds the nearest neighbours, so you can search by similarity rather than exact text. This is the engine under semantic search and RAG, where an AI app retrieves the most relevant chunks to answer from.
- Problem
- You need things similar in meaning, but the words may not match.
- Idea
- Store embeddings and search for the nearest neighbours.
- Example
pgvector, Pinecone: the query "refund policy" finds "returns" and "money back".
- Cost
- Needs an embedding model, and syncing with the source data.
- When to use
- Semantic search and RAG — the new essential store of the AI era.
- Related
pgvector is a Postgres extension; a copy of the data, like a Search Engine.
Object Storage — where the big files go
A self-storage unit — not for index cards, but for the furniture: bulky things you keep by label and haul out whole.
This is object storage. It isn't a database, but it's where the large blobs live — images, video, backups, documents — addressed by a key inside a bucket and served cheaply at scale. The pattern is almost universal: keep the file in object storage, keep a row pointing to it in your database. Records in the DB, payloads in the bucket.
- Problem
- Big files inside the database bloat it and are costly to serve.
- Idea
The file goes in a bucket by key; the database keeps only a row with that key.
- Example
- S3 and its kin: photos, video, backups, documents.
- Cost
- Not a database: no queries over contents, only "put/get by key".
- When to use
- Any large file.
- Related
- Shaped like a Key-Value Store, just for bulky things.
Don't bend your main database into a search engine or a file server. Put the specialised job in the store built for it.
A database's deepest choice isn't its shape — it's what it promises when things go wrong. A little alphabet soup names that trade.
ACID — pessimistic, correct, careful
A bank teller who double-checks every entry and won't move on until the books balance — slower, but never wrong.
ACID is the strict promise relational databases make about transactions: every transaction leaves the data correct and complete, or doesn't happen at all. Four letters: Atomicity (all or nothing), Consistency (the data's rules always hold), Isolation (concurrent transactions don't interfere), Durability (what's confirmed survives a crash). For money, orders, and anything where a wrong number is unacceptable, this is the trade you want.
- Promise
- Every transaction is wholly correct, or didn't happen at all.
- Example
- A bank transfer, placing an order, taking stock out of inventory.
- Cost
- Some speed, and harder scaling across machines.
- When to use
- A wrong number is unacceptable.
- Related
- The properties of a Transaction; the opposite stance is BASE.
BASE — optimistic, available, eventually right
A busy shop that lets everyone keep buying even while the price tags are being updated — the numbers catch up in a moment, and that's fine.
Many NoSQL systems take the opposite stance: BASE (Basically Available, Soft state, Eventually consistent). They stay fast and available and accept that copies of the data may briefly disagree before converging. This is called eventual consistency.
- Promise
- The system always answers; copies converge "eventually".
- Example
- A like count, a feed, a view counter.
- Cost
- You may briefly see a stale value.
- When to use
Availability beats instant precision, and a second of staleness costs nothing.
- Related
- The opposite of ACID; the "A" choice in CAP.
CAP Theorem — choose when the network breaks
A team split by a downed phone line: each half can keep working alone (but they'll diverge), or down tools until reconnected (but you stop). You can't have both.
The CAP theorem says that when a network partition splits your database's machines, you must choose Consistency (refuse writes, stay correct) or Availability (keep serving, risk disagreement) — you can't have both during the split. Every distributed database makes this call. Knowing which your store picks tells you how it behaves on its worst day.
- Says
- During a network split you can't be both correct and available.
- Example
Two nodes lose contact: refuse the write (CP), or accept it on both and reconcile later (AP).
- Cost
- CP — downtime; AP — stale or conflicting data.
- When to think
- As soon as your data lives on more than one machine.
- Related
- Raised by Replication and Sharding; the AP side is BASE.
ACID for money, BASE for likes. The guarantee you need is set by the cost of being briefly wrong.
A handful of techniques turn a database from a single fragile box into something fast and survivable. You'll meet all of them.
Index — the book's index, for data
To find a word in a 900-page book, you don't read every page — you flip to the index at the back and jump straight there.
An index is a side structure that turns a full table scan into a direct lookup — the single biggest lever on read speed. The catch: each index costs storage and slows writes (every insert has to update it too).
- Problem
- A query reads the whole table, and gets slower as the data grows.
- Idea
- A sorted side structure on a column → a direct lookup of the row.
- Example
- An index on
email: find a user without scanning the whole table. - Cost
- Disk space, and every write gets slower.
- When to use
On the columns you actually filter and sort by — not every column, just in case.
- Related
- Speeds up SQL; a search engine is a special index over text.
Replication — keep copies, survive failure
A document kept in three safes in three buildings — if one burns down, the others still hold it, and three people can read at once.
Replication keeps copies of the data on several machines. All writes go to the leader, and the followers (replicas) receive a copy and serve reads. If the leader dies, a follower takes over (failover). The catch is replication lag: a follower can be a moment behind, so a value you just wrote might not show up on a replica yet.
- Problem
- One machine is one point of failure and one ceiling on reads.
- Idea
- The same data on several machines: the leader writes, followers read.
- Example
Postgres with two read replicas; if the leader falls, a replica becomes leader.
- Cost
- Lag: a fresh write may not have reached the replica yet.
- When to use
- You need reliability, or more reads than one machine can serve.
- Related
Copies the same data; Sharding splits different data. Both lead to CAP.
Sharding — split when one box isn't enough
One overflowing filing cabinet becomes A–M in one cabinet and N–Z in another. More room — but now you must know which cabinet a name lives in.
When the data or write load outgrows a single machine, sharding splits the data across many — users A–M here, N–Z there. Each part is a shard, and the field you split by is the shard key. It unlocks near-unlimited scale, but the shard key is a hard, near-permanent choice: pick badly and you get hotspots, or queries that have to hit every shard.
- Problem
- The data or the write stream doesn't fit on one machine.
- Idea
- Split the data by a key across many machines.
- Example
- Users A–M on shard 1, N–Z on shard 2.
- Cost
The key is near-permanent; a bad key → hotspots, or queries across every shard.
- When to use
- Last: after indexes and replicas.
- Related
Built into a Wide-Column Store; together with Replication it leads to CAP.
Normalization / Denormalization — one fact in one place, or copies for speed
One master address list everyone refers to (no contradictions) versus stapling a copy of the address onto every order (faster to read, but now there are copies to keep honest).
Normalization keeps each fact in exactly one place — clean, no contradictions, the relational default. Denormalization deliberately duplicates data so reads are faster and need fewer joins. Both are valid; the rule is to do it consciously, and to always know which copy is the source of truth and which is just a fast duplicate. (Same lesson as a cache.)
- Problem
The same fact in several places drifts apart; but joins slow reads down.
- Idea
- Normalize by default; denormalize on purpose where reads matter more.
- Example
- An address in one
addressestable vs a copy of it in every order. - Cost
Normalization — more JOINs; denormalization — copies that must be kept in sync.
- When to use
- Denormalize when measurably slow reads demand it.
- Related
The same "source of truth + fast copies" idea as Polyglot persistence.
Add an index before a server, a replica before a shard. The cheapest scaling is the kind you don't have to operate.
You don't pick one database and force everything into it. Real systems use a few, each for the job it's best at.
Polyglot Persistence — several stores, on purpose
A kitchen has a fridge, a freezer, and a pantry — not one box forced to do all three. Each keeps what suits it.
This is the polyglot persistence pattern. Mature systems usually use several stores: Postgres as the source of truth, Redis for caching and sessions, a search index for the search bar, maybe a vector store for AI features and object storage for files. Each store does what it's good at. The discipline is keeping clear which one holds the truth, with the others as fast copies kept in sync.
- Problem
- No single database can be the best at everything.
- Idea
- Each job gets its own store; one explicit source of truth.
- Example
- Postgres + Redis + a search index + a vector store + S3.
- Cost
- Every store must be run, backed up, and kept consistent.
- When to use
- When a real pain demands another store — not "just in case".
- Related
Starts from Postgres first; ties together almost every store in the course.
Postgres First — start with Postgres, grow out of it deliberately
A Swiss Army knife handles a startling amount before you ever need the full toolbox.
This is the Postgres first practice. Modern Postgres is astonishingly capable — relational core, JSON documents, full-text search, vectors, even a workable job queue. Most products should start with just Postgres and add a specialised store only when a real, measured pain demands it. Every extra database is another thing to run, back up, and keep consistent. The cheapest store is the one you didn't add.
- Problem
- Teams add databases before the pain shows up.
- Idea
- One Postgres, until a measured problem demands more.
- Example
JSONB instead of a separate document store, pgvector instead of a separate vector database.
- Cost
- At very large scale a specialised engine will still win.
- When to use
- At the start of almost any product.
- Related
The first step towards Polyglot persistence — once "for now" has ended.
How it all connects
A neighbourhood map after a long walk: now you see which streets lead where.
The concepts in this course don't live alone. The relational database is the source of truth with ACID guarantees; an index speeds up its reads; the search engine and the vector database are copies fed from it; object storage holds the files its rows point to. When one machine isn't enough, replication and sharding arrive — and with them the CAP choice. The NoSQL family often picks BASE — the opposite of ACID.
- What shape is the data — tables, documents, key-value, a graph?
- How will I query it — by id, by relationships, by text, by similarity, by time?
- What guarantees do I need — strict ACID, or is eventually consistent fine?
- What's the read/write balance and the rough scale?
- Could Postgres already do this before I add another store?
- Which store holds the source of truth, and which are just copies?
- You're running
LIKE '%...%'for search instead of a search engine. - A key-value store where you constantly need to query by the values.
- Five databases for a product two people maintain.
- You picked MongoDB to avoid schemas, then rebuilt validation by hand anyway.
- No clear source of truth — two stores both claim to be authoritative.
- The query you run most is the one the store is fastest at.
- The guarantee matches the stakes — strict for money, relaxed for likes.
- You can name the source of truth for every important fact.
- You added each store to solve a real, felt pain, not a hypothetical.
- You could explain the data model to a new engineer in a few minutes.
Cheat sheet: all 20 concepts
The relational core
- Relational DatabaseRDBMS
Tables with a fixed schema, linked by foreign keys. The default.
- SQLquery language
Describe what you want; the database decides how to fetch it.
- Transactionall or nothing
Several writes as one unit: COMMIT all or ROLLBACK all.
The NoSQL family
- Document Storedocument database
Flexible JSON documents; a thing is read whole.
- Key-Value Storekey → value
Key → value, instantly. Cache, sessions, counters.
- Wide-Column Storecolumn-family
Data across many machines to absorb a write firehose.
- Graph Databasegraph store
Nodes and edges; relationship queries without painful JOINs.
Specialised stores
- Time-Series DatabaseTSDB
Readings by time; queries by window, old data expires.
- Search Enginefull-text search
Full-text search with ranking and typo tolerance. A copy, not the truth.
- Vector Databasevector store
Embeddings and nearest-by-meaning search. The basis of RAG.
- Object StorageS3
Big files by key in a bucket; the database keeps only a pointer.
Guarantees
- ACIDthe strict promise
A transaction is wholly correct or didn't happen. For money.
- BASEeventual consistency
Always available, copies converge eventually. For likes.
- CAP Theorempick C or A
During a network split — either correctness or availability.
Scale
- IndexB-tree index
A pointer for fast reads; the cost is space and slower writes.
- Replicationleader–follower
The same data on many machines: reliability and reads. There is lag.
- Shardinghorizontal partitioning
Different data on different machines by key. The key is near-permanent.
- Normalization / Denormalizationone place vs copies
One fact in one place — or deliberate copies for speed.
Choosing
- Polyglot Persistenceseveral stores
Each job its own store, one source of truth.
- Postgres Firststart with Postgres
One Postgres, until a real pain demands more.
The best database decision is usually "Postgres, for now" — plus the wisdom to know exactly when "for now" has ended.