Why storage internals matter, and when to care
Every database sits on a storage engine that decides how data is laid out on disk, how an update is made safe, and how a lookup finds a row. Two designs dominate: the B-tree, which updates pages in place, and the LSM-tree (log-structured merge-tree), which appends writes and merges them later. Knowing the difference explains why one database is fast at reads and another at writes, and how an index helps.
It gives you two things interviewers always ask about:
- Performance: which engine and which indexes fit a workload that is read-heavy, write-heavy or mixed.
- Durability: how a write survives a crash, through a write-ahead log.
Care about this when:
- You choose a database for a write-heavy or read-heavy service and must justify it.
- A query is slow and you need to decide which index to add.
- You must explain how data survives a crash.
Skip it, or use something else, when:
- The data is small enough to fit in memory and any database is fine.
- A managed database already makes the choice, and the workload is ordinary.
Try it
Run a workload against each engine and compare the disk work. Then crash it.
Storage Internals and Indexing
Step 1 of 14 · Press Space to play, arrow keys to stepInitial state. A B-tree keeps keys sorted in pages on disk and updates pages in place. Every change is logged first.
- tick:
- 0
- write I/O:
- 0
- read I/O:
- 0
- merge I/O:
- 0
- lost writes:
- 0
1 tick = one step of the simulation.
Things to try:
- Run B-tree with the mixed workload. Every write rewrites a page in place, and every read walks the same few pages.
- Switch to LSM-tree on write-heavy and compare write I/O. Then run read-heavy and see reads check more files.
- Choose Crash after 5 writes with both engines and watch the log bring the data back.
What to watch:
- The I/O numbers are relative units, not milliseconds. A log append and a page write each count as one unit, so the sequential versus random difference is explained by the captions rather than priced in.
- Compare the total at the end, not only write I/O. The LSM-tree moves cost into the merge I/O counter, so its write I/O alone flatters it.
- The B-tree is simplified: each write is charged a page write, whereas a real engine may keep the page in memory for a while and write it later. The LSM-tree has no per-file filters, so its reads look worse here than the table above suggests.
- Before you press play, predict which engine will have the lower total on each workload, then check.
Storage engine designs
| Design | How it works | Good when | Weakness |
|---|---|---|---|
| B-tree | Sorted pages in a tree. A write finds the page and updates it in place | Read-heavy and mixed workloads, range scans | Random writes, and pages that split |
| LSM-tree | Writes go to memory, then flush as sorted files that are merged in the background | Write-heavy workloads, time series, logs | Reads may check several files, and compaction uses I/O |
| Hash index | A hash table maps keys to locations | Exact-match lookups only | No range queries, and in simple designs the table of keys must fit in memory |
| Column store | Each column is stored together | Analytics that scan a few columns | Slow for single-row updates |
| Append-only log | Data is only added at the end | Event streams | Needs another structure to look up by key |
Terms used here:
- Page: the fixed-size block (often a few kilobytes) that a B-tree reads and writes.
- Memory table (memtable): the sorted in-memory buffer where an LSM-tree holds recent writes.
- Checkpoint: a point up to which changes are known to be safely on disk, so older log records are not needed.
- Filter: a small structure, usually a Bloom filter, that says a key is definitely not in a file, so the file can be skipped.
- Query plan: the database's chosen way to run a query, such as a table scan or an index lookup.
B-tree vs LSM-tree
- B-tree: a read costs the depth of the tree, a handful of pages. The depth grows only slowly as the table grows. A write is a random disk write, and a page may need to split. Most relational databases use it.
- LSM-tree: a write is an append to a log and an insert in memory, which is cheap. The cost moves to reads, which may check the memory table and several files, and to compaction, which merges files in the background. Many NoSQL and time-series stores use it.
Neither is better. Pick by the workload, then remember both use the same safety idea: the write-ahead log.
The cost shows up as amplification, meaning extra work for each byte the application writes or reads:
- Write amplification: compaction rewrites data several times, and a B-tree rewrites a whole page for a small change. For illustration, a row of 100 bytes might cause a page of several kilobytes to be rewritten.
- Read amplification: an LSM-tree read may check the memory table and several files, while a B-tree read walks one page per level.
- Space amplification: old versions stay on disk until compaction removes them, and a B-tree leaves pages part empty after splits.
An LSM-tree usually turns random writes into sequential ones, but it does not always write fewer bytes in total.
How a write survives a crash
- The engine first appends the change to the write-ahead log (WAL) on disk and waits for it to be flushed.
- Only then does it tell the client the write succeeded.
- It updates the page (B-tree) or the memory table (LSM) afterwards, which may sit in memory for a while.
- After a crash, the engine replays the log from the last checkpoint and repairs or rebuilds whatever was in memory.
- Old log records are trimmed after a checkpoint (B-tree) or after a flush (LSM).
This is why the log is written sequentially and first: appending is fast and its order tells the engine what to replay. "Flushed" means the operating system was told to force the bytes to the disk (an fsync), not just to hold them in a buffer. A write acknowledged before that point can be lost in a power cut.
How indexes help
An index is an extra sorted structure that maps a column's values to rows, so a query does not scan the table.
- A primary index orders the table itself in some engines. InnoDB (MySQL) stores the table in primary-key order, while PostgreSQL keeps rows in a separate heap and treats the primary key as an ordinary index.
- A secondary index is a separate structure on another column. It speeds up reads but adds work to every write, because each index must be updated.
- A composite index on several columns helps queries that filter on the leading columns, in that order.
- A covering index contains every column a query needs, so the table is not read at all.
Add indexes for the queries you really run, and no more.
Storage design vs the alternatives
| Concern | B-tree engine | LSM-tree engine | Plain files with a scan |
|---|---|---|---|
| Point reads | Fast and steady | Fast with filters | Slow |
| Heavy writes | Slower | Fast | Fast to append |
| Range scans | Fast | Good | Slow |
| Background work | Little | Compaction | None |
| Predictable latency | Mostly (page splits and checkpoints can still stall) | Spikes during compaction | Poor |
| Best for | General databases | Write-heavy stores | Small data |
Choose a B-tree as the default, and an LSM-tree when writes dominate.
Avoiding a new single point of failure
The storage engine is on one machine, so durability beyond that machine needs replication: copy the log to other nodes before acknowledging, or to a standby. Keep the log on reliable disks, take backups, and test restores. You accept a slower write when it waits for another copy. If the copy is made asynchronously, the write is acknowledged sooner, but a crash of the primary can lose the latest writes that had not yet arrived.
Common mistakes
- Indexing every column. Each index slows every write and uses space.
- Wrong column order in a composite index. It helps only for queries using its leading columns.
- Skipping the log flush for speed. A crash can then lose acknowledged writes.
- Choosing an LSM-tree for a read-heavy workload without checking read amplification.
- Ignoring compaction. It competes for I/O and can cause latency spikes.
- Never testing recovery. A backup you have never restored is a hope, not a plan.
How to talk about storage internals in an interview
- State the workload first: reads versus writes, point lookups versus ranges.
- Name the engine type and its cost: B-tree for steady reads, LSM-tree for heavy writes.
- Explain the write-ahead log as the reason a write is durable.
- Add indexes for the main queries and name the trade-off: faster reads cost slower writes and more space.