Skip to content
About
Main content

B-trees and B+trees

Why is a B-tree's fanout set by the page size, and where does its write bill arrive?

B-tree and B+tree

The constraint, derived

A block device moves a minimum unit (a page). So any comparison structure should have a that fills exactly one unit: a node smaller than a page wastes the transfer, and a node larger than a page costs two transfers. The fanout is:

children per node
keys in the tree
binary tree (f = 2)HAMT (f = 32)B-tree, 16 KB page (f ≈ 680)
Data table
Levels (dependent reads) to find one key, by keys stored
Keys NseriesDepth (dependent reads)
1Kbinary tree (f = 2)9.97
1.3Kbinary tree (f = 2)10.3
1.68Kbinary tree (f = 2)10.7
2.18Kbinary tree (f = 2)11.1
2.82Kbinary tree (f = 2)11.5
3.65Kbinary tree (f = 2)11.8
4.73Kbinary tree (f = 2)12.2
6.13Kbinary tree (f = 2)12.6
7.94Kbinary tree (f = 2)13
10.3Kbinary tree (f = 2)13.3
13.3Kbinary tree (f = 2)13.7
17.3Kbinary tree (f = 2)14.1
22.4Kbinary tree (f = 2)14.5
29Kbinary tree (f = 2)14.8
37.6Kbinary tree (f = 2)15.2
48.7Kbinary tree (f = 2)15.6
63.1Kbinary tree (f = 2)15.9
81.8Kbinary tree (f = 2)16.3
106Kbinary tree (f = 2)16.7
137Kbinary tree (f = 2)17.1
178Kbinary tree (f = 2)17.4
230Kbinary tree (f = 2)17.8
299Kbinary tree (f = 2)18.2
387Kbinary tree (f = 2)18.6
501Kbinary tree (f = 2)18.9
649Kbinary tree (f = 2)19.3
841Kbinary tree (f = 2)19.7
1.09Mbinary tree (f = 2)20.1
1.41Mbinary tree (f = 2)20.4
1.83Mbinary tree (f = 2)20.8
2.37Mbinary tree (f = 2)21.2
3.07Mbinary tree (f = 2)21.6
3.98Mbinary tree (f = 2)21.9
5.16Mbinary tree (f = 2)22.3
6.68Mbinary tree (f = 2)22.7
8.66Mbinary tree (f = 2)23
11.2Mbinary tree (f = 2)23.4
14.5Mbinary tree (f = 2)23.8
18.8Mbinary tree (f = 2)24.2
24.4Mbinary tree (f = 2)24.5
31.6Mbinary tree (f = 2)24.9
41Mbinary tree (f = 2)25.3
53.1Mbinary tree (f = 2)25.7
68.8Mbinary tree (f = 2)26
89.1Mbinary tree (f = 2)26.4
115Mbinary tree (f = 2)26.8
150Mbinary tree (f = 2)27.2
194Mbinary tree (f = 2)27.5
251Mbinary tree (f = 2)27.9
325Mbinary tree (f = 2)28.3
422Mbinary tree (f = 2)28.7
546Mbinary tree (f = 2)29
708Mbinary tree (f = 2)29.4
917Mbinary tree (f = 2)29.8
1.19Bbinary tree (f = 2)30.1
1.54Bbinary tree (f = 2)30.5
2Bbinary tree (f = 2)30.9
2.59Bbinary tree (f = 2)31.3
3.35Bbinary tree (f = 2)31.6
4.34Bbinary tree (f = 2)32
5.62Bbinary tree (f = 2)32.4
7.29Bbinary tree (f = 2)32.8
9.44Bbinary tree (f = 2)33.1
12.2Bbinary tree (f = 2)33.5
15.8Bbinary tree (f = 2)33.9
20.5Bbinary tree (f = 2)34.3
26.6Bbinary tree (f = 2)34.6
34.5Bbinary tree (f = 2)35
44.7Bbinary tree (f = 2)35.4
57.9Bbinary tree (f = 2)35.8
75Bbinary tree (f = 2)36.1
97.2Bbinary tree (f = 2)36.5
126Bbinary tree (f = 2)36.9
163Bbinary tree (f = 2)37.2
211Bbinary tree (f = 2)37.6
274Bbinary tree (f = 2)38
355Bbinary tree (f = 2)38.4
460Bbinary tree (f = 2)38.7
596Bbinary tree (f = 2)39.1
772Bbinary tree (f = 2)39.5
1,000Bbinary tree (f = 2)39.9
1KHAMT (f = 32)1.99
1.3KHAMT (f = 32)2.07
1.68KHAMT (f = 32)2.14
2.18KHAMT (f = 32)2.22
2.82KHAMT (f = 32)2.29
3.65KHAMT (f = 32)2.37
4.73KHAMT (f = 32)2.44
6.13KHAMT (f = 32)2.52
7.94KHAMT (f = 32)2.59
10.3KHAMT (f = 32)2.67
13.3KHAMT (f = 32)2.74
17.3KHAMT (f = 32)2.82
22.4KHAMT (f = 32)2.89
29KHAMT (f = 32)2.96
37.6KHAMT (f = 32)3.04
48.7KHAMT (f = 32)3.11
63.1KHAMT (f = 32)3.19
81.8KHAMT (f = 32)3.26
106KHAMT (f = 32)3.34
137KHAMT (f = 32)3.41
178KHAMT (f = 32)3.49
230KHAMT (f = 32)3.56
299KHAMT (f = 32)3.64
387KHAMT (f = 32)3.71
501KHAMT (f = 32)3.79
649KHAMT (f = 32)3.86
841KHAMT (f = 32)3.94
1.09MHAMT (f = 32)4.01
1.41MHAMT (f = 32)4.09
1.83MHAMT (f = 32)4.16
2.37MHAMT (f = 32)4.24
3.07MHAMT (f = 32)4.31
3.98MHAMT (f = 32)4.38
5.16MHAMT (f = 32)4.46
6.68MHAMT (f = 32)4.53
8.66MHAMT (f = 32)4.61
11.2MHAMT (f = 32)4.68
14.5MHAMT (f = 32)4.76
18.8MHAMT (f = 32)4.83
24.4MHAMT (f = 32)4.91
31.6MHAMT (f = 32)4.98
41MHAMT (f = 32)5.06
53.1MHAMT (f = 32)5.13
68.8MHAMT (f = 32)5.21
89.1MHAMT (f = 32)5.28
115MHAMT (f = 32)5.36
150MHAMT (f = 32)5.43
194MHAMT (f = 32)5.51
251MHAMT (f = 32)5.58
325MHAMT (f = 32)5.66
422MHAMT (f = 32)5.73
546MHAMT (f = 32)5.81
708MHAMT (f = 32)5.88
917MHAMT (f = 32)5.95
1.19BHAMT (f = 32)6.03
1.54BHAMT (f = 32)6.1
2BHAMT (f = 32)6.18
2.59BHAMT (f = 32)6.25
3.35BHAMT (f = 32)6.33
4.34BHAMT (f = 32)6.4
5.62BHAMT (f = 32)6.48
7.29BHAMT (f = 32)6.55
9.44BHAMT (f = 32)6.63
12.2BHAMT (f = 32)6.7
15.8BHAMT (f = 32)6.78
20.5BHAMT (f = 32)6.85
26.6BHAMT (f = 32)6.93
34.5BHAMT (f = 32)7
44.7BHAMT (f = 32)7.08
57.9BHAMT (f = 32)7.15
75BHAMT (f = 32)7.23
97.2BHAMT (f = 32)7.3
126BHAMT (f = 32)7.37
163BHAMT (f = 32)7.45
211BHAMT (f = 32)7.52
274BHAMT (f = 32)7.6
355BHAMT (f = 32)7.67
460BHAMT (f = 32)7.75
596BHAMT (f = 32)7.82
772BHAMT (f = 32)7.9
1,000BHAMT (f = 32)7.97
1KB-tree, 16 KB page (f ≈ 680)1.06
1.3KB-tree, 16 KB page (f ≈ 680)1.1
1.68KB-tree, 16 KB page (f ≈ 680)1.14
2.18KB-tree, 16 KB page (f ≈ 680)1.18
2.82KB-tree, 16 KB page (f ≈ 680)1.22
3.65KB-tree, 16 KB page (f ≈ 680)1.26
4.73KB-tree, 16 KB page (f ≈ 680)1.3
6.13KB-tree, 16 KB page (f ≈ 680)1.34
7.94KB-tree, 16 KB page (f ≈ 680)1.38
10.3KB-tree, 16 KB page (f ≈ 680)1.42
13.3KB-tree, 16 KB page (f ≈ 680)1.46
17.3KB-tree, 16 KB page (f ≈ 680)1.5
22.4KB-tree, 16 KB page (f ≈ 680)1.54
29KB-tree, 16 KB page (f ≈ 680)1.58
37.6KB-tree, 16 KB page (f ≈ 680)1.62
48.7KB-tree, 16 KB page (f ≈ 680)1.65
63.1KB-tree, 16 KB page (f ≈ 680)1.69
81.8KB-tree, 16 KB page (f ≈ 680)1.73
106KB-tree, 16 KB page (f ≈ 680)1.77
137KB-tree, 16 KB page (f ≈ 680)1.81
178KB-tree, 16 KB page (f ≈ 680)1.85
230KB-tree, 16 KB page (f ≈ 680)1.89
299KB-tree, 16 KB page (f ≈ 680)1.93
387KB-tree, 16 KB page (f ≈ 680)1.97
501KB-tree, 16 KB page (f ≈ 680)2.01
649KB-tree, 16 KB page (f ≈ 680)2.05
841KB-tree, 16 KB page (f ≈ 680)2.09
1.09MB-tree, 16 KB page (f ≈ 680)2.13
1.41MB-tree, 16 KB page (f ≈ 680)2.17
1.83MB-tree, 16 KB page (f ≈ 680)2.21
2.37MB-tree, 16 KB page (f ≈ 680)2.25
3.07MB-tree, 16 KB page (f ≈ 680)2.29
3.98MB-tree, 16 KB page (f ≈ 680)2.33
5.16MB-tree, 16 KB page (f ≈ 680)2.37
6.68MB-tree, 16 KB page (f ≈ 680)2.41
8.66MB-tree, 16 KB page (f ≈ 680)2.45
11.2MB-tree, 16 KB page (f ≈ 680)2.49
14.5MB-tree, 16 KB page (f ≈ 680)2.53
18.8MB-tree, 16 KB page (f ≈ 680)2.57
24.4MB-tree, 16 KB page (f ≈ 680)2.61
31.6MB-tree, 16 KB page (f ≈ 680)2.65
41MB-tree, 16 KB page (f ≈ 680)2.69
53.1MB-tree, 16 KB page (f ≈ 680)2.73
68.8MB-tree, 16 KB page (f ≈ 680)2.77
89.1MB-tree, 16 KB page (f ≈ 680)2.81
115MB-tree, 16 KB page (f ≈ 680)2.85
150MB-tree, 16 KB page (f ≈ 680)2.89
194MB-tree, 16 KB page (f ≈ 680)2.93
251MB-tree, 16 KB page (f ≈ 680)2.97
325MB-tree, 16 KB page (f ≈ 680)3.01
422MB-tree, 16 KB page (f ≈ 680)3.05
546MB-tree, 16 KB page (f ≈ 680)3.08
708MB-tree, 16 KB page (f ≈ 680)3.12
917MB-tree, 16 KB page (f ≈ 680)3.16
1.19BB-tree, 16 KB page (f ≈ 680)3.2
1.54BB-tree, 16 KB page (f ≈ 680)3.24
2BB-tree, 16 KB page (f ≈ 680)3.28
2.59BB-tree, 16 KB page (f ≈ 680)3.32
3.35BB-tree, 16 KB page (f ≈ 680)3.36
4.34BB-tree, 16 KB page (f ≈ 680)3.4
5.62BB-tree, 16 KB page (f ≈ 680)3.44
7.29BB-tree, 16 KB page (f ≈ 680)3.48
9.44BB-tree, 16 KB page (f ≈ 680)3.52
12.2BB-tree, 16 KB page (f ≈ 680)3.56
15.8BB-tree, 16 KB page (f ≈ 680)3.6
20.5BB-tree, 16 KB page (f ≈ 680)3.64
26.6BB-tree, 16 KB page (f ≈ 680)3.68
34.5BB-tree, 16 KB page (f ≈ 680)3.72
44.7BB-tree, 16 KB page (f ≈ 680)3.76
57.9BB-tree, 16 KB page (f ≈ 680)3.8
75BB-tree, 16 KB page (f ≈ 680)3.84
97.2BB-tree, 16 KB page (f ≈ 680)3.88
126BB-tree, 16 KB page (f ≈ 680)3.92
163BB-tree, 16 KB page (f ≈ 680)3.96
211BB-tree, 16 KB page (f ≈ 680)4
274BB-tree, 16 KB page (f ≈ 680)4.04
355BB-tree, 16 KB page (f ≈ 680)4.08
460BB-tree, 16 KB page (f ≈ 680)4.12
596BB-tree, 16 KB page (f ≈ 680)4.16
772BB-tree, 16 KB page (f ≈ 680)4.2
1,000BB-tree, 16 KB page (f ≈ 680)4.24

That is the whole argument for the B-tree. It is not an asymptotic argument. It is that log base 680 of anything you will ever store is a small number.

Move the medium and the fanout moves with it. In memory the transfer unit is a 64-byte , so the best in-memory B-tree has a fanout in the single digits and looks nothing like the disk version. This is why cache-conscious B+trees and ART beat red-black trees in memory for exactly the reason B-trees beat them on disk. The structure is invariant. The constant is set by the layer below. (Batch-size law again.)

B+ versus B: a range-query argument

In a plain B-tree every node holds keys and values. In a B+tree values are stored only in the leaves (the bottom level), and the leaves are linked into a sorted list. Two gains: internal nodes have higher fanout (no payload competes for page space), and a range scan becomes a sequential walk over linked pages instead of repeated descents from the root. Sequential I/O is one to two orders of magnitude cheaper than random I/O, and range predicates are the majority of real query load, so there is no remaining case for a plain B-tree in storage.

The write path is where the bill arrives

In-place update means each row update is a read-modify-write of a full page. The is page size ÷ row size.

Key order relocates cost

Key order is a lever that moves cost. It is not a detail.

  • Random keys (for example UUIDv4 as the primary key) land everywhere in the tree. Pages split all over the place and in steady state are only about 69% full (the classic ln 2 result; ln 2 = 0.693). To store the same data you need 1/0.69 ≈ 1.45× the pages: a 45% space overhead. Your working set (the pages you must keep in memory) inflates by the same factor.
  • Sequential keys (auto-increment numbers, , ULID: identifiers that begin with a timestamp) append at the right edge of the tree. Pages fill completely and fragmentation disappears. In exchange, you create one hot page and its that every concurrent inserter must pass through.

You did not fix the problem. That is .

Concurrency is why B-trees are hard, not why they are slow

Latch coupling (also called crabbing) means: take the child's latch before you release the parent's. A split that propagates upward then serializes against the root. add a pointer from each node to its right sibling, so a reader that arrives in the middle of a split can follow the pointer sideways instead of holding the parent. This removes the need for a top-down exclusive latch in the common case. The root latch is a coherency term (the cost of keeping many cores consistent): a fixed serial fraction that no number of extra cores dissolves.

Copy-on-write B-trees

Copy-on-write B-trees (LMDB, ZFS, Btrfs) write a new path from leaf to root on every update instead of changing pages in place. This gives crash safety without a and MVCC snapshots (consistent views of old data) for free: a reader simply holds an old root pointer. It costs and needs a to free pages that are no longer reachable. That is the , now inside a structure rather than a storage device.

The honest boundary condition

B-trees are best when the internal nodes fit in memory and updates are not the dominant operation. They lose badly when (update rate × page/row ratio) exceeds the device's write bandwidth. That is precisely the regime the was invented for.