The subtleties of proper B+Tree implementation
ayende.com
ayende.com
EDIT: I just noticed you're the bcachefs author. I'm guessing you're dealing with much larger key/value pairs than I am (40+40)?
[1]: https://www.phoronix.com/review/bcachefs-benchmarks-linux67
But I've had to optimize for memory usage, performance when working set does not fit into ram - we also don't have a serialize/deserialize step, that will really hurt you when working set doesn't fit in ram.
You want to validate when you're reading in a btree node, but you want to keep that as cheap as possible.
For avoiding the serialize/deserialize, the thing to use now would be Cap'n Proto.
FWIW, my "deserialize" doesn't involve copying all the data -- I just construct pointers into the (serialized) page. Serializing has to copy data though.
This is correct. Most disk-oriented DBMSs use fixed-size pages. So you can't go larger than the node size (not entirely true if using auxiliary data structures to buffer changes like an inefficient -epsilon tree).
This way, you avoid extra allocation, copying, etc.
I certainly haven't read the entire thing but gosh there's some good bits. E.g. "B-trees Versus Hash Indexes" - tons of insights jampacked together into a couple pages for anyone who hasn't already been exposed to B-trees in detail (e.g. in a research context vs the usual data structures class)
I did it this way for a few reasons. One was I could release old key pages after new pages were written, avoiding data loss in IO errors. Second was key (prefix) compression became a lot more easy. I never realised it, but it avoids the trouble in both blog post too.
It comes with some copy overhead, unfortunately.
Or maybe
> all the students were B+s or B*s
I'll see myself out now
LOL! I love that
That one for me must have been pricing reverse exchangeable notes in R
For example, saying that you need "real world data" to catch "edge cases" like this... no, proper unit tests will do just fine. Also, the problem of splitting different-sized keys in the way the author first tries to do does not strike me as a mistake anyone would fall into or that is hard to get out of.
C++ at least tries but falls so far short.
For this particular case, key-based containers need to know if their key is a homogeneous sequence. But that's actually slightly too restrictive - in particular, a fixed-size prefix of a different type isn't actually any harder to optimize for, assuming the introspection API is good enough.
But for other things ... even "just give me the list of fields that this class has" involves containers. Implementing compare/hash efficiently (with short-circuiting!) requires peeking inside the heterogeneous tuple. Writing a partial JSON-like object literal? That's a container, and one with very common uses. Making a copy of an object with certain fields replaced? A container. Functions that take keyword arguments? A container which you would really like to propagate sanely. But most compilers that deal with such things don't actually support the usual container APIs, often even lacking simple concatenation (speaking of which - what about compile-time duplicate key detection?).
The reason to use B-family trees over binary trees is to avoid said memory slowness in the first place.
For all but the most basic data structures, the variations are so many that rolling your own is often a good design choice.