How a Database Stores Data

A database is not magic — it's a program that turns your rows into bytes on a disk and finds them again quickly, correctly, and without losing them when the power fails. Understanding the machine underneath the SQL is what separates someone who writes queries from someone who knows why they're slow.

You type SELECT * FROM users WHERE id = 42 and a row comes back in a millisecond. Between that query and the answer is a remarkable amount of engineering: a storage engine, pages, a buffer pool, a write-ahead log, indexes, a transaction manager, and a query planner. This series opens each of those boxes. It starts here, with the most fundamental question — how does a database actually keep your data on disk and get it back? — because every later topic is a refinement of this answer.

The core problem

A database has to satisfy a set of requirements that are individually easy and collectively hard:

The tension at the heart of it all is the gap between memory and disk. Memory (RAM) is fast but volatile — it vanishes on power loss — and too small to hold most datasets. Disk (SSD/HDD) is durable and large but orders of magnitude slower than memory, especially for random access. Every design decision in a database is, at bottom, a strategy for getting durable storage on slow disk to behave like fast memory. Hold that framing and the rest of the series clicks into place.

The layers of a database

It helps to see the whole stack before diving into any layer. A request flows top to bottom:

        SQL query
           │
   ┌───────▼────────┐
   │ Query planner  │  parse, plan, optimize  →  execution plan
   ├────────────────┤
   │ Execution      │  run the plan: scans, joins, filters
   ├────────────────┤
   │ Transaction &  │  ACID: isolation, concurrency control (MVCC/locks)
   │ concurrency    │
   ├────────────────┤
   │ Storage engine │  B-tree / LSM: how rows become pages
   ├────────────────┤
   │ Buffer pool    │  cache of disk pages in memory
   ├────────────────┤
   │ Disk (files)   │  pages + write-ahead log, durably stored
   └────────────────┘

The bottom half — storage engine, buffer pool, disk, and the write-ahead log alongside them — is how data is stored, and it’s the focus of the first half of this series. The top half — transactions, planning, execution — is how data is accessed correctly and efficiently, the second half. This post surveys the bottom.

The storage engine

The storage engine is the component that actually reads and writes data on disk. It’s a deliberately separable layer: the same SQL front-end can sit on different engines (MySQL famously supports several, like InnoDB), because “how do I lay bytes on disk and retrieve them” is a self-contained problem. The storage engine answers: how are rows grouped into on-disk structures, how are they found again, and how are writes made durable?

Two dominant families of storage engine exist, and the choice between them shapes a database’s entire performance character:

The next post is dedicated to this fork, because it’s the single most consequential decision in a storage engine. For now: the engine’s job is to map your logical rows onto physical on-disk structures and retrieve them efficiently.

Everything is pages

Databases do not read and write your data one row or one byte at a time. They work in fixed-size blocks called pages (typically 4–16 KB). A page is the unit of transfer between disk and memory: to read one row, the database reads the entire page that row lives on; to change one row, it modifies the page in memory and eventually writes the whole page back.

Why pages? Because disk I/O has high fixed cost per operation — reading 8 KB costs almost the same as reading 100 bytes, since the expense is in making the request, not the bytes. Batching data into pages amortizes that cost. This single fact explains an enormous amount of database behavior: why row layout matters, why an index that lets you touch fewer pages is so valuable, and why “random” access patterns that scatter reads across many pages are so much slower than sequential ones that read pages in a row. When you optimize a database, you are very often really optimizing how many pages it has to touch.

Memory as a cache: the buffer pool

Since disk is slow and pages are the unit of I/O, databases keep recently-used pages in memory in a cache called the buffer pool (or page cache). A read first checks the buffer pool; a hit is served from fast memory, and only a miss goes to disk (and loads that page into the pool for next time). Writes modify pages in the buffer pool first — marking them “dirty” — and flush to disk later, batched.

This is the mechanism that makes disk behave like memory: a well-tuned database serves the overwhelming majority of its reads from the buffer pool, hitting disk only for cache misses. The buffer pool’s hit rate is one of the most important numbers in database performance, and it’s why “just add more RAM” so often speeds up a database — more RAM means more of the working set fits in the pool. A later post details how the pool decides which pages to keep and evict.

Durability lives in the log

There’s a catch in “write to the buffer pool first, flush later”: if the database crashes after acknowledging a write but before flushing the dirty page to disk, that write is gone — violating persistence. The solution, foundational enough to get its own post, is the write-ahead log (WAL): before a change is considered done, the database appends a record of it to a sequential log file and flushes that to disk. The log is append-only (fast sequential writes) and is the source of truth for recovery — after a crash, the database replays the log to reconstruct any changes that hadn’t yet reached their data pages. This is why a database can be both fast (defer the expensive random page writes) and durable (the cheap sequential log write already happened). The pattern — write the intent to a log first — recurs throughout systems engineering.

Where the series goes

From this foundation, the first half of the series drills into storage: B-trees vs LSM-trees (the engine fork), pages and the buffer pool (memory management), and the write-ahead log (durability and crash recovery). The second half moves up the stack: indexes (finding rows without scanning), transactions and isolation (correctness under concurrency), MVCC (how modern databases isolate without blocking reads), and query planning (turning SQL into an efficient execution plan). Throughout, the recurring lens is the one from this post — memory is fast and scarce, disk is slow and durable, and a database is the art of reconciling the two.

Key takeaways

Further reading

Sources & References

A readable on-disk database format