Skip to content

b+tree: index a node's entries through one accessor, and write up the occupancy work - #158

Open
psiha wants to merge 2 commits into
bt/5-local-redistributionfrom
bt/6-entry-accessor
Open

b+tree: index a node's entries through one accessor, and write up the occupancy work#158
psiha wants to merge 2 commits into
bt/5-local-redistributionfrom
bt/6-entry-accessor

Conversation

@psiha

@psiha psiha commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Two commits.

1 — one entry accessor. Seventy-odd sites reached into node.keys[ i ] directly, which states in each of them that entry i of a node lives at offset i of its key array. That is true today and it is the only reason the tree cannot yet leave a gap at the front of a node.

key_at( node, i ) is that statement, made once. It is still exactly node.keys[ i ] — no behavioural change, same codegen — but there is now a single place answering "where does a node's first live entry sit", which is what a devector node needs and what a map's parallel value array needs.

The raw-array search helpers keep taking a bare Key const keys[]: their caller has already resolved the base, and they must stay callable on a span that is not a node at all (the bulk merge path passes source keys straight in).

2 — doc/b+tree_occupancy_and_variants.md. What fill a b+tree actually reaches, what the classical variants do about it, and what shipping systems actually do — read at source rather than taken from secondary literature:

  • No true B* among the eight engines checked. SQLite's balance_nonroot redistributes over up to three siblings nn+1 and its delete-side floor is ~1/3, the opposite of B*; Apple's HFS docs say "B*-tree" but InsertLevel rotates into the left sibling only and then splits 1→2. PostgreSQL, InnoDB, LMDB, Berkeley DB and WiredTiger all split 1→2 with no sibling redistribution at all.
  • The naming. Comer 1979 opens with "Perhaps the most misused term in B-tree literature is B*-tree": Knuth's B* is 2/3 occupancy maintained by local redistribution and a two-into-three split. What b+tree: relieve an overflowing leaf into a sibling before splitting #157 implements is the local redistribution without the floor and without the 2→3 split.
  • Why not go further. Three independent reasons, including that min_values = ceil( max / 2 ) is not a constant that can be raised (see b+tree: state the bounds a node actually has #155) and that the one careful measurement of B* puts it at −15% space for +31% cycles, beaten on both axes by deferred compaction.
  • Which of LeanStore's node-level techniques transfer to a container with no buffer manager and no synchronisation — and which plainly do not.

Stacked on #157.

@psiha
psiha force-pushed the bt/5-local-redistribution branch from ffc76fe to 684c220 Compare September 9, 2026 10:34
@psiha
psiha force-pushed the bt/6-entry-accessor branch from a33f3b1 to 01ea915 Compare September 9, 2026 10:34
@psiha
psiha force-pushed the bt/5-local-redistribution branch from 684c220 to 58d1a41 Compare September 9, 2026 10:39
@psiha
psiha force-pushed the bt/6-entry-accessor branch from 01ea915 to 7256dc9 Compare September 9, 2026 10:39
@psiha
psiha force-pushed the bt/5-local-redistribution branch from 58d1a41 to f1f4b93 Compare September 9, 2026 10:43
@psiha
psiha force-pushed the bt/6-entry-accessor branch from 7256dc9 to 0540117 Compare September 9, 2026 10:45
@psiha
psiha force-pushed the bt/5-local-redistribution branch from f1f4b93 to 2a37547 Compare September 9, 2026 10:49
@psiha
psiha force-pushed the bt/6-entry-accessor branch from 0540117 to f938b2b Compare September 9, 2026 10:49
@psiha
psiha force-pushed the bt/5-local-redistribution branch from 2a37547 to b0eebe7 Compare September 9, 2026 10:56
@psiha
psiha force-pushed the bt/6-entry-accessor branch from f938b2b to 2951200 Compare September 9, 2026 10:56
@psiha
psiha force-pushed the bt/5-local-redistribution branch from b0eebe7 to 29ec4e0 Compare September 9, 2026 11:13
@psiha
psiha force-pushed the bt/6-entry-accessor branch 2 times, most recently from 430f3a6 to 3dc55c7 Compare September 9, 2026 11:35
Seventy-odd sites reached into node.keys[ i ] directly, which states in each
of them that entry i of a node lives at offset i of its key array.  That is
true today and it is the only reason the tree cannot yet leave a gap at the
front of a node.

key_at( node, i ) is that statement, made once.  It is still exactly
node.keys[ i ] - no behavioural change, and it compiles to the same thing -
but there is now a single place where "where does a node's first live entry
sit" is answered, which is what a devector node needs and what the parallel
value array of a map needs.

The raw-array search helpers keep taking a bare Key const keys[]: their caller
has already resolved the base, and they must stay callable on a span that is
not a node at all (the bulk merge path passes source keys straight in).
What fill a b+tree actually reaches, what the classical variants do about it,
what shipping systems actually do (read at source, not from secondary
literature - no true B* among them), why this container is not becoming one,
and which of LeanStore's node-level techniques transfer to a container with
no buffer manager and no synchronisation.
@psiha
psiha force-pushed the bt/5-local-redistribution branch from 29ec4e0 to 262b915 Compare September 9, 2026 12:01
@psiha
psiha force-pushed the bt/6-entry-accessor branch from 3dc55c7 to b7dc27d Compare September 9, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant