Skip to content

B+ trees

A B+ tree keeps every record in its leaves and links the leaves in order, so a range of keys costs one descent and a walk along the leaves.

Helps to know firstB-trees

Helps to know first
This topic

First, the picture

What if every record sat in a leaf, and the leaves were chained in order? Watch a range read with one descent.

Reading a range of keys in order

One descent, then a walk along the linked leaves

static const struct bpnode *find_leaf(const struct bpnode *x, int key){    reads++;    while (!x->leaf) {        int i = 0;        while (i < x->n && key >= x->keys[i])            i++;        x = x->child[i];        reads++;    }    return x;} void range(const struct bpnode *root, int lo, int hi){    const struct bpnode *leaf = find_leaf(root, lo);    while (leaf != NULL) {        for (int i = 0; i < leaf->n; i++) {            if (leaf->keys[i] > hi)                return;            if (leaf->keys[i] >= lo)                printf("%d ", leaf->keys[i]);        }        leaf = leaf->next;        if (leaf != NULL)            reads++;    }}    range(root, 30, 70);    printf("\n%d nodes read\n", reads);

Right of 50: b, [70 90]. The tree is built.

nodes read
0
keys printed
0

Output

0.00 / 10.50 s
Describe this picture

A picture across the frame, with the code under it. The tree has three levels: the root 50; under it the inner nodes 30 and 70 90; and five leaves along the bottom, 10 20, 30 40, 50 60, 70 80 and 90 95, each with an arrow to the next leaf on its right. Each node is a box with one slot per key. A ring marks the node the program holds, nodes it has already read are marked as visited, and each key it prints is lit in its leaf. The code pane shows find_leaf, range and main’s call, with the line about to run marked. The clip runs range(root, 30, 70), about 11 seconds. The descent reads the root 50 (30 is less, so go left), the node 30 (30 is not less, so go right) and the leaf 30 40. Then the walk prints 30 and 40, follows the arrow to 50 60 and prints both, follows it to 70 80, prints 70, and stops at 80, which is past 70. Under the picture are one sentence on the current step and the readouts “nodes read” and “keys printed”, which end at 5 and 5. The transport plays and pauses, steps one beat back or forward, and scrubs; a paused step is kept in the page address as b-plus.step.

Signposts and leaves

In a B-tree, a key and its record can sit in any node. A B+ tree separates the two jobs:

  • the leaves hold every record, in key order, and each leaf points to the next one;
  • the inner nodes hold only signposts: keys that say which way to go, and pointers to children.

A signpost k sends smaller keys to the child on its left and keys equal to k or larger to the child on its right. So a signpost often repeats a key that is also in a leaf: in the clip, 30, 50, 70 and 90 each appear twice. The copy in the inner node only guides the search; the record is the copy in the leaf.

A range query, every key from lo to hi, is then one descent and a walk: go down to the leaf where lo belongs, then read leaves left to right along their links until a key passes hi. The walk never goes back up.

Key idea

After the descent, a range is read in order straight from the leaves. A key found in an inner node is only a signpost; its record is in a leaf.

The code and its cost

The node

The full program, range.c, uses one node type for both kinds:

#define MAXK 4

struct bpnode {
    bool leaf;
    int n;
    int keys[MAXK];
    struct bpnode *child[MAXK + 1];
    struct bpnode *next;
};

An inner node uses keys as signposts and child for its children; a leaf uses keys as its records and next for the link to the leaf on its right. The struct is 72 bytes: leaf at offset 0, 3 bytes of padding, n at 4, the keys from 8 to 23, the five child pointers from 24 to 63, and next at 64. main builds the tree by hand, as Binary trees built its tree, and links the leaves with l1->next = l2 and so on.

The descent

reads++;
while (!x->leaf) {
    int i = 0;
    while (i < x->n && key >= x->keys[i])
        i++;
    x = x->child[i];
    reads++;
}
return x;

find_leaf moves right past every signpost the key is equal to or larger than, then goes down. It counts each node it reads in reads. For 30: at the root, 30 < 50, so child[0]; at the node 30, 30 ≥ 30, so child[1], the leaf 30 40. Three reads.

The walk

const struct bpnode *leaf = find_leaf(root, lo);
while (leaf != NULL) {
    for (int i = 0; i < leaf->n; i++) {
        if (leaf->keys[i] > hi)
            return;
        if (leaf->keys[i] >= lo)
            printf("%d ", leaf->keys[i]);
    }
    leaf = leaf->next;
    if (leaf != NULL)
        reads++;
}

Each leaf is read once, in order. The first key larger than hi ends the query; keys smaller than lo in the first leaf are skipped. For the range 30 to 70 the walk reads two more leaves, 50 60 and 70 80, and stops at 80: 5 nodes read in all.

Inserting

The program builds its tree by hand, but insertion follows the B-tree rules with one difference. A new key goes into its leaf. A full leaf splits in two and copies the first key of the right half up as a new signpost, because that key’s record must stay in a leaf. A full inner node splits as in a B-tree and moves its middle key up, since it was only a signpost. The new leaf is linked between its neighbours, so the chain stays in order.

What it costs

A range query reads one node per level to reach the first leaf, then one leaf per leaf’s worth of keys in the range: about log n + k/B reads for k keys in the range and B keys per leaf. Because inner nodes carry no records, only keys and pointers, many more fit in a block than in a B-tree node, so the tree has fewer levels.

Running it yourself

gcc -std=c11 -Wall -Wextra -pedantic range.c -o range
./range

It prints 30 40 50 60 70, then 5 nodes read.

Worked example

Insert 35. The descent goes left of 50, right of 30, to the leaf 30 40, which has room for up to 4 keys: it becomes 30 35 40. No split, and no signpost changes.

Fan-out. An inner node of 4,096 bytes with 8-byte keys and 8-byte child addresses holds k keys and k + 1 addresses in 16k + 8 bytes, so k = 255 keys and 256 children. Three inner levels reach 256³ = 16,777,216 leaves.

The range 55 to 95. The descent reads the root (55 ≥ 50, go right), the node 70 90 (55 < 70, go left) and the leaf 50 60, where 50 is skipped and 60 printed. The walk reads 70 80 and 90 95 and prints all four keys; then next is NULL. 5 nodes read, printing 60 70 80 90 95.

Two checks. How many nodes does the range 10 to 95 read? (7: the root, the node 30, the leaf 10 20, then four more leaves.) Where is the record for 70? (In the leaf 70 80; the 70 in the inner node is a signpost.)

Where it’s used

Database indexes. The B+ tree is the main index structure of relational databases: a query such as “every order between two dates” is one descent and a walk along the leaves.

File systems. Several file systems keep directories and file extents in B+ trees.

Key-value stores. Any store that must answer “every record between these two keys” benefits from leaves kept in order and linked.

Reference card

Code or ideaMeaning
signpostan inner node’s key: smaller keys left, equal or larger right
recordalways in a leaf
nexta leaf’s link to the leaf on its right
find_leaf(root, lo)one descent: one read per level
range querydescend to lo, then walk the leaves until a key passes hi
leaf splitcopies the right half’s first key up
inner splitmoves the middle key up
sizeof (struct bpnode)72 bytes

Platform: C11, gcc on x86-64 Linux: int 4 bytes, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.

Keep reading with a free account.

Trees is a members’ section. An account costs nothing.

By creating an account you accept the terms of use.

Sign in

Already have an account? Use the way you signed up.

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

End of the lesson

Where to go next.

New lessons by email

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

Phasorium

Account

Sign inWith Google, GitHub or your email

Subjects

Site

Look