When it does not fit

A key passed along the row

A full B+-tree leaf that offers a key to its immediate neighbours before splitting leaves an ascending stream 67.2% full. Let it look one sibling further and the same stream fills its leaves completely, 2,048 leaves where there were 3,048, for about the same key moves. On random keys each doubling of the reach adds a few points of fill and more writes than it saves, and at the whole parent a key travels sixteen leaves on average. On a stream that is mostly in order the same reach costs almost nothing.

The sibling a full leaf asks first measured a rule borrowed from the B*-tree. When a leaf overflows, it offers a key to the leaf on either side before splitting. If both neighbours are full, it splits two full leaves into three. On random insertion that took leaves from 70.5% full to 84.3%, 2,429 leaves where even splits left 2,906, for 1.45 key moves per key inserted. It also survived the case that defeats the rule databases usually use: a stream that is ascending except for one key in a hundred arriving late.

It left one rule unexplored, and it named it. A leaf in a parent with sixty-four children has up to sixty-three candidates with room, all reachable without leaving the parent. Asking only the two adjacent ones is a choice, and the obvious alternative is a chain. Walk outwards until a leaf with room is found, and pass one key along each full leaf on the way. Every step is one key moved and one separator rewritten. A long chain is expensive, and it defers a split further than asking the neighbours can.

Reach, and what it fills

The rule is the adjacent rule with one parameter, the reach: how many siblings away, in either direction, a full leaf may look. With a reach of one it is exactly the adjacent rule, and the measurement checks that it builds the identical tree. A reach of 64 covers the whole parent.

Letting a full leaf look further for room: random insertion 84.3% full at one sibling and 92.6% at four; ascending 67.2% at one and 100.0% at two131,072 keys inserted into a B+-tree with leaves of 64 keys and 64 children a node. When a leaf overflows it looks outward through its parent's children, nearest first, up to the stated number of siblings away, for a leaf with room, and passes one key along every leaf in between; with none in reach it splits two full leaves into three. Random: 84.3%, 88.5%, 92.6%, 95.3%, 97.2%, 98.9%. Ascending: 67.2%, 100.0%, 100.0%, 100.0%, 100.0%, 100.0%. Descending: 67.2%, 100.0%, 100.0%, 100.0%, 100.0%, 100.0%. Ascending, 1% late: 84.2%, 88.0%, 90.7%, 93.8%, 95.9%, 97.7% at reaches of 1, 2, 4, 8, 16, 64.6080100how far a full leaf looks for room, in siblingsmean leaf fill, per cent12481664randomascendingdescendingascending, 1% late131,072 keys · leaves of 64 · fan-out 64a reach of 1 is the adjacent-sibling rule
Fig. 1 Mean leaf fill of a B±tree of 131,072 keys, leaves of 64 keys and a fan-out of 64, as the reach grows from 1 to 64 siblings. Random insertion: 84.3%, 88.5%, 92.6%, 95.3%, 97.2% and 98.9%. Ascending and descending: 67.2% at a reach of one and 100.0% from two onward. Ascending with 1% of keys arriving late: 84.2%, 88.0%, 90.7%, 93.8%, 95.9% and 97.7%.

The two ordered streams give the sharpest result on the plate. Ascending and descending insertion go from 67.2% full to 100.0% when the reach goes from one sibling to two. The tree has 2,048 leaves where the adjacent rule left 3,048. That is exactly 131,072/64131{,}072 / 64, every leaf full.

The mechanism is visible in what the adjacent rule does on an ordered stream. Every insertion lands at the same end of the tree. When the end leaf overflows, its one neighbour is full, because it was filled a moment ago, so the rule splits two full leaves into three, each two-thirds full. The next overflow happens at the new end leaf. Its neighbour is again full, and the two two-thirds-full leaves behind it are one step out of reach. The adjacent rule never goes back to them, and every leaf it leaves behind stays two-thirds full.

With a reach of two, the overflowing leaf sees past its full neighbour to a two-thirds-full leaf and passes keys back into it until it too is full. The row fills from behind, and each split leaves nothing permanently short. The keys that arrive late found that the rule systems adopt for ascending keys, keeping the left node full on a split, gets 100% on an ascending stream and 50% on a descending one. A reach of two gets 100% on both.

Random insertion climbs steadily instead of jumping: 88.5% at a reach of two, 92.6% at four, 95.3% at eight, 98.9% across the whole parent. The nearly ascending stream follows a similar curve, a little below random at every reach.

What the reach costs

A chain that passes a key along dd leaves writes each of those leaves and the parent whose separators it changed. The currency a store pays is pages written, so the cost here counts pages: the leaf each insertion lands in, each leaf a key is passed into, each leaf a split creates, and each parent whose separators change.

What the reach costs in pages written a key: 1.59 to 17.07 on random insertion, and almost flat once the order is mostly ascending131,072 keys inserted into a B+-tree with leaves of 64 keys and 64 children a node. When a leaf overflows it looks outward through its parent's children, nearest first, up to the stated number of siblings away, for a leaf with room, and passes one key along every leaf in between; with none in reach it splits two full leaves into three. Random: 1.587, 2.150, 3.013, 4.431, 6.979, 17.071. Ascending: 2.046, 2.686, 2.686, 2.686, 2.686, 2.686. Descending: 2.046, 2.686, 2.686, 2.686, 2.686, 2.686. Ascending, 1% late: 2.249, 2.268, 2.317, 2.322, 2.341, 2.364 at reaches of 1, 2, 4, 8, 16, 64. Pages written count each insertion's leaf, every leaf a key is passed into, every leaf a split creates, and each parent whose separators change.051015how far a full leaf looks for room, in siblingspages written a key inserted12481664randomascendingdescendingascending, 1% late131,072 keys · leaves of 64 · fan-out 64leaf and parent writes
Fig. 2 Pages written per key inserted, at reaches of 1 to 64. Random insertion: 1.587, 2.150, 3.013, 4.431, 6.979 and 17.071. Ascending and descending: 2.046 at a reach of one and 2.686 from two onward. Ascending with 1% late: 2.249, 2.268, 2.317, 2.322, 2.341 and 2.364. An even split, for comparison, writes 1.04 to 1.06 pages a key on every order.

The two kinds of stream separate completely here.

On random insertion the reach is expensive, and more so the further it goes. Doubling the reach from four to eight adds 1.4 pages a key and 2.7 points of fill. Going from 16 to 64 adds 10 pages a key for 1.7 points. At the widest reach a random insertion writes seventeen pages, eleven times what the adjacent rule writes.

On an ordered or nearly ordered stream the reach is almost free. A nearly ascending stream writes 2.249 pages a key at a reach of one and 2.364 across the whole parent, 5% more for 13.5 more points of fill. The ordered streams pay 0.64 pages a key once, going from one sibling to two, for 33 points of fill. After that nothing changes, because nothing more is available to fill.

The difference is in how far keys actually travel.

How far a key travels with the whole parent in reach: random, mean 16.00 siblings and 5% at one; ascending, 1% late, mean 1.20 siblings and 88% at oneThe distance each redistribution walked, in siblings, with a reach of 64, for 131,072 keys. Random: 123,542 redistributions, mean 16.00, 4.6% at one sibling, the longest 63. Ascending, 1% late: 78,325 redistributions, mean 1.20, 87.8% at one sibling, the longest 49. Walks beyond 32 are drawn at 32.00.2500.5000.750102030siblings a key was passed along before finding roomshare of redistributionsreach 64 · 131,072 keyslight: random · dark: ascending, 1% late
Fig. 3 How many siblings each redistribution passed a key along, with the whole parent in reach. Random insertion: 123,542 redistributions, a mean of 16.00 siblings, 4.6% at one, the longest 63. Ascending with 1% late: 78,325 redistributions, a mean of 1.20, 87.8% at one, the longest 49. Walks past 32 are drawn at 32.

On random insertion, once the leaves have been filled towards full by earlier redistributions, the nearest leaf with room is usually far away. The walk lengths spread across the whole parent, thinning only slowly with distance, and the mean is sixteen. The rule has made the tree full, and a full tree is exactly the state in which there is no nearby room. The chain’s cost comes from its own success. Each point of fill it adds makes the next insertion search further.

On a nearly ascending stream, 88% of redistributions stop at the first sibling. The late keys land among leaves that ordered insertion already left with some room, the room is near, and the few long walks are the rare late key landing in a stretch that has already been filled. The longest, 49 siblings, is three times random insertion’s mean walk, but it is one walk in tens of thousands, and the mean barely notices it.

The trade, point by point

Leaves saved and pages written are different currencies. A leaf saved is a page less in the index for its whole life, read by every range scan and cached by every lookup. A page written is paid once, at insertion. Which one matters more depends on the ratio of reads to writes, and nothing here measures that. The shape of the trade can be drawn without choosing a ratio.

On random insertion, leaves against pages written a key: even split 2,906 at 1.04; one sibling 2,429 at 1.59; 2 siblings 2,313 at 2.15; 4 siblings 2,211 at 3.01Each point is one rule on 131,072 keys in random order: the leaves the tree ends with against the pages written for each key inserted. Even split: 2,906 leaves, 70.5% full, 1.044 pages a key; One sibling: 2,429 leaves, 84.3% full, 1.587 pages a key; 2 siblings: 2,313 leaves, 88.5% full, 2.150 pages a key; 4 siblings: 2,211 leaves, 92.6% full, 3.013 pages a key; 8 siblings: 2,148 leaves, 95.3% full, 4.431 pages a key; 16 siblings: 2,107 leaves, 97.2% full, 6.979 pages a key; 64 siblings: 2,071 leaves, 98.9% full, 17.071 pages a key. The horizontal axis is logarithmic.pages written a key insertedleaves in the finished tree2,2002,4002,6002,80024816even splitone sibling2 siblings4 siblings8 siblings16 siblings64 siblings131,072 keys, randomdown and left is better
Fig. 4 On random insertion, the leaves in the finished tree against pages written per key. Even split: 2,906 leaves at 1.04 pages a key. Reach one: 2,429 at 1.59. Two: 2,313 at 2.15. Four: 2,211 at 3.01. Eight: 2,148 at 4.43. Sixteen: 2,107 at 6.98. The whole parent: 2,071 at 17.07.

On random insertion the curve bends early. The adjacent rule saves 477 leaves for 0.55 more pages a key. The next doubling saves 116 for 0.56 pages. From four to eight saves 63 for 1.42, and from sixteen to sixty-four saves 36 for 10.1. Each further doubling of the reach buys fewer leaves than the one before, at a higher price. A reach of two or four is where the trade is still reasonable for a write-heavy index. Beyond eight it only makes sense for an index that is written once and read for years.

On ascending, 1% late insertion, leaves against pages written a key: even split 3,931 at 1.06; one sibling 2,433 at 2.25; 2 siblings 2,327 at 2.27; 4 siblings 2,258 at 2.32Each point is one rule on 131,072 keys in ascending, 1% late order: the leaves the tree ends with against the pages written for each key inserted. Even split: 3,931 leaves, 52.1% full, 1.060 pages a key; One sibling: 2,433 leaves, 84.2% full, 2.249 pages a key; 2 siblings: 2,327 leaves, 88.0% full, 2.268 pages a key; 4 siblings: 2,258 leaves, 90.7% full, 2.317 pages a key; 8 siblings: 2,183 leaves, 93.8% full, 2.322 pages a key; 16 siblings: 2,135 leaves, 95.9% full, 2.341 pages a key; 64 siblings: 2,096 leaves, 97.7% full, 2.364 pages a key. The horizontal axis is logarithmic.pages written a key insertedleaves in the finished tree2,0002,2002,4002,6002,8003,0003,2003,4003,6003,8004,0002even splitone sibling2 siblings4 siblings8 siblings16 siblings64 siblings131,072 keys, ascending, 1% latedown and left is better
Fig. 5 The same trade on an ascending stream with 1% of keys arriving late. Even split: 3,931 leaves at 1.06 pages a key. Reach one: 2,433 at 2.25. Two: 2,327 at 2.27. Four: 2,258 at 2.32. Eight: 2,183 at 2.32. Sixteen: 2,135 at 2.34. The whole parent: 2,096 at 2.36.

On the nearly ascending stream the curve is almost vertical. The whole parent in reach saves 337 leaves beyond the adjacent rule, 14%, for 0.11 more pages a key. For this kind of stream there is no real trade: widen the reach to the parent and take the leaves.

That kind of stream is common. Keys generated by a clock or a counter arrive almost in order, with some late arrivals from delayed writers or retried transactions. The writes nobody counted measured how a store’s structure multiplies each logical write into several physical ones. Here the multiplier depends on the order the keys arrive in, and it is small exactly for the order that is common.

Against the rule systems already use

The comparison that matters in practice is not with even splits but with the rightmost rule. On a split caused by the largest key in a leaf, that rule keeps the left leaf full, and it is what indexes use to cope with ascending keys. It costs almost nothing: it moves no keys between leaves, and it writes 1.03 to 1.06 pages a key on every order measured here.

On a perfectly ascending stream it is unbeatable. It fills every leaf, 2,048 of them, at 1.031 pages a key, where a chain of reach two reaches the same 2,048 leaves at 2.686. On anything else it collapses. On a descending stream it leaves leaves 50.0% full and 4,095 of them. On the stream with 1% of keys late it leaves 53.4% full and 3,835 leaves, the failure the sibling a full leaf asks first set out to repair. On random insertion it is 69.4%, no better than an even split.

The chain across the whole parent on that late stream ends with 2,096 leaves where the rightmost rule ends with 3,835, 45% fewer, for 1.3 more pages a key. And the leaf count crosses a threshold that the fill percentage hides. With a fan-out of 64, a tree of 3,835 leaves needs four levels, and a tree of 2,096 needs three. The rightmost rule’s tree on the nearly ascending stream is a level taller than the chain’s, and a level is one more page read on every lookup for the life of the index. That is paid on reads for as long as the index exists. The chain’s extra writes are paid once, at insertion.

A stream that is ascending except for its late keys is the one where a run is a property of the input found that “nearly sorted” needs a measurement before it means anything. Here the measurement is the share of late keys, and one per cent is enough to take the rightmost rule from the best rule on the plate to one of the worst.

What a fuller leaf is worth to a scan

A leaf saved is a page less in the index, and the saving compounds for any query that reads many leaves. A range scan over 1% of these keys, 1,311 of them, reads about as many leaves as it takes to hold 1,311 keys at the tree’s fill. At the 70.5% of even splits on random insertion that is 30 leaves. At the adjacent rule’s 84.3% it is 25, at a reach of four’s 92.6% it is 23, and at the whole parent’s 98.9% it is 21. Those are arithmetic from the fills, not timed scans. The difference between the adjacent rule and a reach of four is two pages a scan, paid on every scan, against 1.4 pages written a key, paid once per insertion.

Whether that trade is worth taking is a ratio of scans to insertions, and it depends on the workload. One dial between two structures found the same kind of ratio deciding between a B-tree and a log-structured store, and the block that is not a block listed what a model of pages leaves out of a real machine. That list applies to every page count here: read-ahead makes a sequential scan of adjacent leaves cheaper than its page count suggests, and a chain’s writes to adjacent leaves are sequential too.

As close as a bulk load, without the sort

The best a B±tree of these keys can look is the tree built by loading them already sorted, every leaf full: 2,048 leaves and three levels. Getting there needs the keys sorted first, and for an index that is being built from a stream there is no “first”. The keys arrive, and they have to be findable as they do.

A chain across the whole parent on the nearly ascending stream ends 2.3% above that bound, 2,096 leaves, and on random insertion 1.1% above it, 2,071 leaves. On the ordered streams a reach of two reaches it exactly. So an incremental rule that looks far enough recovers almost the whole of what a bulk load gives. On the nearly ordered stream it does that for 2.36 pages a key. On random insertion the price is 17.

A bulk load pays for sorting instead. Runs twice as long as memory measured the external sort that loading a large index needs, with its merge passes counted in blocks. For keys that arrive nearly in order that sort is cheap too, because the runs are long. That is the same observation from the other side: when the order is nearly right, both the sort and the chain have little to do. The chain’s advantage is that it never stops the index to do it.

The separator every step rewrites

Each step of a chain rewrites a separator in the parent, and that has a consequence the page counts understate. The parent is written once per redistribution however many separators change, so its page count stays low. But every separator change is a change a concurrent reader might see halfway. A B±tree that allows concurrent readers during an insertion has to lock or version every leaf a chain touches, and the parent.

A walk of one sibling locks two leaves and a parent, the same footprint as a split. A walk of sixteen locks seventeen leaves. On random insertion at the widest reach, that is the typical redistribution. The adjacent rule’s footprint is fixed. The chain’s grows with the reach, and on random insertion so does the time an insertion holds its locks. Nothing here measures concurrency. It is the reason a real index would cap the reach even where writes are cheap, and the walk histogram is the quantity a cap would be chosen from: on the nearly ascending stream about 99% of walks are four siblings or fewer.

What is settled and what is not

Settled by building each tree and checking every separator and every key’s order: with leaves of 64 and a fan-out of 64, a reach of one reproduces the adjacent rule exactly. A reach of two fills ascending and descending insertion completely, 2,048 leaves for 131,072 keys, for 2.69 pages a key against 2.05. On random insertion fill rises from 84.3% to 98.9% as the reach goes from one to 64, and pages a key rise from 1.59 to 17.07, with the mean walk reaching sixteen siblings. On a stream with 1% of keys late, fill rises from 84.2% to 97.7% while pages a key rise from 2.25 to 2.36.

Not settled:

Reach across parents. Every chain stops at the parent’s edge. A leaf at the edge of its parent has room on one side only, and a chain that crossed into a neighbouring parent would update two parents and a grandparent. Nothing here measures how much of the fill that is left comes from leaves stranded at parent boundaries.

Deletions. Every workload inserts only. A chain fills leaves close to full, and a tree close to full has no slack for the next wave of inserts after deletions have scattered holes through it. How a chain-filled tree ages under mixed traffic is a separate measurement.

Reads. A fuller tree has fewer leaves to scan and to cache, and the value of that depends on a workload’s reads. The leaf counts here are what a range scan would pay, but no scan is timed.

Still open: a reach that follows the stream

The two orders ask for opposite reaches. Random insertion wants a small one, because long walks cost more than the leaves they save. A nearly ascending stream wants the whole parent, because walks are short however far the leaf may look. A tree does not know which stream it is receiving, but the walks it has already made tell it. A run of short walks means room is near, and a run of long walks means the neighbourhood is full.

The measurement that follows gives the tree an adaptive reach. It keeps a running mean of recent walk lengths, widens the reach while walks stay short, and narrows it when they lengthen. It measures fill and pages per key on the four orders here and on a stream that changes from random to ascending halfway. The prediction is that the adaptive tree tracks the better fixed reach on each pure stream within a few per cent, and that on the changing stream it beats both fixed reaches. The number to watch is how long it takes to notice the change, measured in insertions.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

The objects this essay names

Each one links to every other essay that touches it.

B-treeBlock transferDesign parameterHonest limitIndex maintenanceInsertion orderMeasured countNode fillPresortednessSpace accountingTrade offWrite amplification