This document describes the current persistent ARTrie design used by libdictenstein. It supersedes the older hybrid owned-tree/B-trie proposal.
Three crate-specific terms recur below. WAL (write-ahead log) is the durable,
append-only record of changes written before they become visible (see
05-buffer-management). CAS (compare-and-swap) is the
single atomic instruction used to publish a new overlay root without a global
mutation lock (see the SwizzledPtr lifecycle in
04-persistent-art). CX is this crate's path-compressing
overlay↔dense checkpoint codec, which serializes the live overlay into a compact
on-disk image. The persistent ARTrie is version-persistent in the sense of
Driscoll et al. 1989 (DOI:10.1016/0022-0000(89)90034-2):
published overlay nodes are immutable, so a mutation path-copies rather than
overwrites.
| Goal | Current mechanism |
|---|---|
| Crash-durable key/value dictionaries | WAL-before-publish writes and checkpoint/recovery |
| Concurrent reads and writes | Immutable overlay nodes and atomic publication |
| Native byte, Unicode, and u64 labels | ByteKey, CharKey, and U64Key encodings |
| Compact checkpoints | CX overlay serialization with variant-specific projection |
| Large durable vocabularies | PersistentVocabARTrie overlay plus rebuilt reverse map |
The persistent ARTrie is optimized for dictionary traversal. It is not an LSM tree, not a read-only FST, and not the old owned-tree snapshot design.
Dictionary API
-> KeyEncoding converts public keys to units
-> immutable OverlayNode path is cloned and edited
-> WAL record is appended before visibility
-> new root/child pointer is published atomically
-> checkpoint serializes the overlay into a dense CX image
Published overlay nodes are immutable. This gives readers a stable view without
holding a global mutation lock. Writers publish a new version through CAS after
durability requirements are satisfied. The immutable node, its owned-Child edge
representation, and the path-copy write / hazard-protected read paths are specified
in lock-free-overlay.md.
| Encoding | Unit | Public profile |
|---|---|---|
ByteKey | u8 | PersistentARTrie |
CharKey | u32 Unicode scalar value | PersistentARTrieChar |
U64Key<PREFIX> | u64 | PersistentARTrieU64Compact and PersistentARTrieU64Prefix3Compat |
The u64 profiles keep 64-bit labels native. They do not expand every label into eight byte transitions, which is important for time-series and token workloads.
The shared overlay uses immutable adaptive edge storage:
This is the common architecture for byte, char, vocab, and u64 ARTrie variants. Its
systems-level specification — the AdaptiveEdgeStore tiers and how each key width
selects among them — is in
storage-backends.md.
PersistentARTrieU64Compact is the default profile:
U64Key<U64_CX_PREFIX_COMPACT>u64 edge labelsPersistentARTrieU64Prefix3Compat is explicit:
U64Key<U64_CX_PREFIX_COMPAT>The old native bincode snapshot/WAL path is not retained in source. When a historical control is needed, create a git worktree at the relevant historical commit and benchmark that implementation separately.
The byte/char/vocab ARTrie paths use the Order-A discipline:
append durable WAL record
-> publish overlay root by CAS
-> record commit rank / committed watermark where applicable
-> acknowledge
The u64 profile follows the same log-before-publish visibility rule with shared
WAL records and a u64 CX checkpoint image. Durable u64 writes append the data
record, publish by CAS, append CommitRank, and advance the committed-prefix
watermark. Checkpoint capture serializes the published overlay into a dense disk
image, records the safe checkpoint_lsn, and retains the WAL tail for recovery.
Recovery loads the checkpoint, reconciles ranked WAL records, and replays only
operations not covered by the checkpoint watermark. The Order-A protocol, the
committed watermark, the checkpoint flip, and crash recovery are specified in
durability-and-recovery.md; the
17-byte WAL record frame and its replay rule are in
wal-format.md.
Do not apply this write-concurrency claim to the persistent suffix graph family: those types use snapshot reads and serialized graph rebuild/publish writes. The full lock hierarchy (the F4 collapse), MVCC snapshot reads, and the epoch/GC reclamation discipline are specified in concurrency-model.md; the two-family split is in families.md.
The u64 compact profile was benchmarked with a seeded time-series workload. An earlier fixed-sample run showed:
656,679 bytes for native prefix-4 vs 1,585,249 bytes for
byte-encoded u64 keys350.72 ns/query native prefix-4 vs 455.01 ns/query encoded control320.97 bytes/entry prefix-4 vs 336.74 prefix-3Welch's t-test found statistically significant improvement for prefix-4 storage
versus prefix-3 and for native prefix-4 lookup versus byte-encoded lookup. Raw
samples were appended to pgmcp artifacts 111 and 112.
The post-watermark/CommitRank run on 2026-06-13 appended a registered pgmcp experiment set:
357.25 ns/query native prefix-4 vs 455.35 ns/query byte-encoded
u64 control, accepted at p = 2.82e-35148.35 ns/read native prefix-4 vs
204.30 ns/read byte-encoded u64 control, accepted at p = 4.42e-9453.98 bytes/entry prefix-4 vs 469.76 prefix-3,
accepted at p = 4.61e-127929,096 bytes native prefix-4 vs 1,585,249
byte-encoded u64 controlRaw samples are in
docs/experiments/persistent-u64-watermark-commitrank-2026-06-13.md
and pgmcp experiments 53-55 with artifact 132.
This chapter is the theory-tier summary of the design; the systems-tier implementation corpus documents each subsystem it names, end to end:
BlockStorage
seam (mmap / io_uring), on-disk format, adaptive edge storage, and the u64
profile formats.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |