anchor.diff:  
   Thus we dramatically improve the performance of insertions without hurting the IO cost of queries.
   
  -### Functional Persistence
  +==== Functional Persistence
   
   Now that we get the sketch of how to combine event logs and B+ trees, let's see the beauty of making the whole thing functional and persistent!
   Since the combined B+/log data structure primarily only modifies nodes near the root, we can take advantage of the reduced modification to achieve reduced IO when persisting the tree.
  
positive.code: This gives great performance, since the structure is designed to avoid needing to copy entire paths--most writes will only touch the root.
  Furthermore, we can batch many modifications together, and wait to flush the tree, in order to further batch IO.
  
  ## Code Structure
  
  The hitchhiker tree's core implementation lives in 2 namespaces: `hitchhiker.tree.core` and `hitchhiker.tree.messaging`.
  `hitchhiker.tree.core` implements the B+ tree and its extensibility hooks; `hitchhiker.tree.messaging` adds the messaging layer (aka log) to the B+ tree from `core`.
  
  ### Protocols
  
  In `hitchhiker.tree.core`, we have several important protocols:
  
  #### `hitchhiker.tree.core/IKeyCompare`
  
  This protocol should be extended to support custom key comparators.
  It's just like `clojure.core/compare`.
  
  #### `hitchhiker.tree.core/IResolve`
  
  This protocol is the functionality for a minimal node.
  Not only will every node implement this, but also backends will use this to implement stubs which automatically & lazily load the full node into memory during queries.
  
  `last-key` is used for searches, so that entire nodes can remain unloaded from memory when we only need their boundary key.
  
  `dirty?` is used to determine whether the IO layer would need to flush this node, or whether it already exists in the backing storage.
  
  `resolve` loads the node into memory--this could return itself (in the case of an already loaded node), or it could return a new object after waiting on some IO.
  
  #### `hitchhiker.tree.core/INode`
  
  This protocol implements a node fully in-memory.
  Generally, this shouldn't need to be re-implemented;
  however, if the hitchhiker was to be enhanced with key & value size awareness during splits and merges, you'd want to adjust the methods of this protocol.
  
  #### `hitchhiker.tree.core/IBackend`
  
  This protocol implements a backend for the tree.
  
  `new-session` returns a "session" object, which is a convenient way to capture backend-specific stats.
  
  `write-node` will write a a node to storage, returning the stub object which implements `IResolve` for that backend. It can record stats by mutating or logging to the session.
  
  `anchor-root` is called by the persistence functionality to ensure that the backend knows which nodes are roots; this is a hint to any sorts of garbage collectors.
  
  `delete-addr` removes the given node from storage.
  
  #### `hitchhiker.tree.messaging/IOperation`
  
  This protocol describes an operations to the tree.
  Currently, there are only `InsertOp` and `DeleteOp`, but arbitrary mutation is supported by the data structure.
  
  ### Useful APIs
  
  `hitchhiker.tree.core/flush-tree`:
  
  This takes a tree, does a depth-first search to ensure each node's children are durably persisted before flushing the node itself. It returns the updated tree & the session under which the IO was performed. `flush-tree` does block on the writing IO--a future improvement would be to make that non-blocking.
  
  `hitchhiker.tree.messaging/enqueue`:
  
  This is the fundamental operation for adding to the event log in a hitchhiker tree.
  `enqueue` will handle the appending, overflow, and correct propagation of operations through the tree.
  
  `hitchhiker.tree.messaging/apply-ops-in-path`:
  
  This is the fundamental operation for reading from the event log in a hitchhiker tree.
  This finds all the relevant operations on the path to a leaf node, and returns the data
  that leaf node would contain if all the operations along the path were fully committed.
  This is conveniently designed to work on entire leaf nodes, so that iteration is as easy as using the same logic as a non-augmented B+ tree, and simply expanding each leaf node from the standard iteration.
  
  `lookup`, `insert`, `delete`, `lookup-fwd-iter`:
  These are the basic operation on hitchhiker trees. They correspond to `get`, `assoc`, `dissoc`, and `subseq` on sorted maps.
  
negative.code: Their internal nodes only contain enough data to maintain the sort on the data structure.
  We'll come back to why we like B+ trees later on.
  
  ==== Event logs win at writing data
  
  Do you know the fastest way to write data?
  Append it to the end of the file.
  There's no pointers, no updating of data structures, no extra IO costs incurred.
  
  Unfortunately, to perform a query on an event log, we need to replay all the data to build a temporary data structure, which we can then query.
  That replay costs `O(n)`, since it touches every event written.
  
  ==== Unifying B+ trees and event logs
  
  The first idea to understand is same as the core idea in the fractal tree: how can we combine the write performance of an event log with the query performance of a B+ tree?
  The answer is that we're going to "overlay" an event log on the B+ tree!
  