BOOK DEEP READ · Database Internals · Chapter 4

Implementing B-Trees

Database Internals · Ch 4 · Alex Petrov · 2019

中文 →

What is this chapter about?

From the last two chapters you know that a database finds data through a kind of directory tree, and that each node of that tree is one page on disk. But all of that was the static picture. This chapter asks the moving question: this ledger takes in millions of new records a day and loses millions of old ones, all while people are reading it — how does it grow, shrink and stay in order at the same time?

An analogy first

Picture a row of filing cabinet drawers. Each drawer holds cards in alphabetical order, and each has a label on the front: "A–F", "G–M".

The textbook stops right here: when a drawer fills up, split it into two, then add a line to the master index. Two sentences. Anyone who has actually run such a cabinet knows the trouble is entirely in what those two sentences leave out.

Why the old world is hard

Four things, none of them in the textbook.

One: after splitting, you have to tell the level above. But you are holding one drawer — how do you know which line of the master index carries its label? You could write "I belong to drawer 12" on the back of every card, but then splitting a drawer means rewriting the back of hundreds of cards.

Two: splitting too often. Halve a drawer the moment it fills and each half is only half full — so the cabinet is permanently running at half capacity while you buy twice as many cabinets.

Three: when the numbers only go up, halving is pure waste. New cards get ever-larger numbers, so they always go into the last drawer. Split that full drawer down the middle and nobody will ever put anything in the front half again. It sits half empty forever.

Four: what you remove doesn't come back. Pulling a card just leaves a gap. The drawer doesn't shrink, and the cabinet doesn't go away.

The core idea: four very plain answers

What it buys you

This explains two things you have probably watched happen. An auto-incrementing primary key is far faster and far smaller than a random one — sequential inserts pack every drawer nearly full, while random inserts scatter cards everywhere and trigger splits all over. And loading a hundred million rows into an empty table goes much faster if you load the data first and build the index last — that's "pack the drawers" instead of "insert one card at a time".

The honest cost: the fuller you pack a drawer, the sooner an insert in the middle forces a split — which is why serious databases deliberately leave about a tenth of each page empty, and accept the extra cabinets.

Remember one thing

"When a drawer fills, split it" is the easiest sentence in the chapter. The real craft is everywhere else: breadcrumbs to find the level above, borrow from a neighbour to split less, never halve a sequence, and pack whole drawers instead of inserting one at a time.

Want the breadcrumb stack under concurrency, how much space a right-leaning split saves, and the real defaults in PostgreSQL and InnoDB? → Switch to the deep read