libmdbx Architecture: mechanisms and internal organization
Public edition for the amalgamated package: engine file names (
src/*.c,src/*.h) refer to the development tree; in the amalgamated build the same logic lives inmdbx.c/mdbx.h(mdbx.c++/mdbx.h++). Fragments of interest only to tree developers are marked with dist-cutoff markers and stripped on publication. Related material:deep-dive.en.md— the detailed "how it works",improvements.en.md— the catalog of improvements over LMDB.
1. MVCC and snapshots
- The database file is a B+tree of pages in a shared mmap. Every write transaction builds a new version of the pages it changes (copy-on-write); old versions stay available to readers.
- A reader registers a slot in the reader table (RLT) and pins a snapshot
number (
txn_id); the oldest active snapshot (the detention point) defines the page-reuse watermark. - Metadata is committed atomically through three meta pages (Troika): a two-phase update with a minimum of memory barriers; the triple state machine covers all transitions between versions.
- Reading is wait-free: the read path takes no locks; 64-bit fields are read via safe64 atomics.
2. The durability/flush path
- Modified pages accumulate in the dirty page list (DPL); when the limit is
exceeded (
dp_limit, by default ≈ 1/42 of the total plus available RAM), part of the pages is written out early (spilling). - The commit pipeline proceeds in stages (measurable via
mdbx_txn_commit_ex()→MDBX_commit_latency): preparation → GC-update → audit → write → sync → ending. - Synchronization modes (
MDBX_SYNC_DURABLE,MDBX_NOMETASYNC,MDBX_SAFE_NOSYNC,MDBX_UTTERLY_NOSYNC,MDBX_WRITEMAP) define what exactly is flushed to disk (data / meta / nothing) and how (direct writes /fdatasync/msync). - Threshold-based auto-sync (
mdbx_env_set_syncbytes/mdbx_env_set_syncperiod,MDBX_opt_presync_threshold) with a cheap poll (mdbx_env_sync_poll).
3. GC/freelist
- Freed pages are stored as GC records in the FREE_DBI tree, keyed by the retiring transaction number; the value is a page-number list (PNL).
- Reuse is allowed only for records keyed ≤ the detention point; pages newer than the oldest snapshot are "frozen".
- Selection policies: FIFO (default) or LIFO (
MDBX_LIFORECLAIM). - BigFoot splits giant free lists into chains of records with consecutive txnids.
- Search cost is regulated by
MDBX_opt_rp_augment_limitandMDBX_opt_gc_time_limit; profiling —MDBX_ENABLE_PROFGC(fields inMDBX_commit_latency.gc_prof). - Refund / loose pages: freed tail pages return to the unallocated space without a GC record; the loose cache speeds up intra-transaction reuse.
4. File growth and geometry
- Geometry is set by
mdbx_env_set_geometry():size_lower,size_now,size_upper,growth_step,shrink_threshold,pagesize. - When GC is exhausted/frozen, the writer allocates pages from the
unallocated tail, growing the file in
growth_stepsteps on the fly (no process restarts). - Shrinking happens only when the tail is free and invisible to anyone;
madvise(MADV_DONTNEED/REMOVE)plus file truncation; hysteresis is required (the shrink step must exceed the growth step). - At
size_upperwith GC/detention exhausted —MDBX_MAP_FULL; before that, mitigation kicks in: a new steady point, the HSR callback, eviction of parked readers.
5. mmap and memory
- The file is fully mapped into memory; reads are copy-free, writes go through
CoW page copies (or directly in
MDBX_WRITEMAPmode). madvise(MADV_NOHUGEPAGE)is applied to the mapping — THP is opted out; the database page size is geometry-driven (a power of two, 256…65536) and is not tied to OS pages.- Large values are overflow page chains; fragmentation is controlled by spilling and GC limits.
- Weak memory models (ARM/AArch64/PPC/MIPS/RISC-V) — careful atomics (acquire/release, CAS, safe64); atomicity is verified at build time.
6. Single-writer
- At any moment exactly one write transaction is allowed; the lock is
implemented with OS-specific primitives (POSIX
fcntl/OFD, SysV semaphores, WindowsLockFileEx). - Ownership discipline violations are detected and reported with explicit
codes:
MDBX_THREAD_MISMATCH,MDBX_TXN_OVERLAPPING,MDBX_BAD_RSLOT,MDBX_BUSY. MDBX_NOSTICKYTHREADSallows handing a transaction between threads (for pools/coroutines);mdbx_txn_park()/unpark()lets a long-lived reader release its slot.
7. Layers and modular organization
The engine is split into subsystems with one-way top-down dependencies:
flowchart TD
P[Public headers: mdbx.h, mdbx.h++]
A[Thin public-API implementations]
TX[Transactions: txn cores, MVCC readers, spill/refund, coherency]
BT[B+tree: nodes, cursors, dirty-page list, pages, walk, sort, comparators]
ST[Storage: meta pages, DBI, tables, mmap layer, histograms, PNL]
GC[GC: reclamation get/put, txnid intervals]
OS[OSAL: locks, atomics, unaligned access, platform imports]
UT[Utils: audit, logging, chk, defrag]
P --> A
P --> CXX[C++ API: mdbx.h++ and its implementation]
A --> TX
A --> BT
A --> ST
TX --> BT
TX --> GC
TX --> ST
BT --> ST
TX --> OS
BT --> OS
ST --> OS
GC --> OS
TX --> UT
Includes are layered bottom-up: platform predefines → base types and utilities
→ internal structures (transaction, dirty-page list, DBI state, txnid
intervals) → modules. The cross-module function index is a single prototypes
header. In the amalgamated build all modules compile as one translation unit
(MDBX_INTERNAL becomes static), giving the compiler full visibility for
inlining and dead-code elimination.
The precise per-subsystem file map (dev): public API — src/api-*.c;
transactions — src/txn*.c, src/mvcc*.c, src/rthc.c, src/spill.c,
src/refund.c, src/coherency.c; B+tree — src/node.c, src/cursor*.c,
src/dpl.c, src/page-*.c, src/tree*.c, src/walk.c, src/sortzone*.c;
storage — src/meta*.c, src/dbi.c, src/table.c, src/dxb*.c,
src/histogram.c, src/pnl.c, src/global.c; GC — src/gc-get.c,
src/gc-put.c, src/rkl.c; OSAL — src/osal*.c, src/lck-*.c,
src/atomics-*.h, src/windows-import.c; utils — src/utils.c,
src/audit.c, src/logging_and_debug.c, src/chk.c, src/defrag.c.
Module map: structure.md; build rules:
build.md (dev).
8. Key data structures
| Structure | Role |
|---|---|
MDBX_env |
Environment: DB mmap, lock-file mmap, options, DBI sequences, reader slots |
MDBX_txn |
Transaction: numbers (front/txnid), geometry, table descriptors (dbs[]), DBI states, cursor heads, the working set (meta troika, repnl, GC queues, dirtylist, retired/loose/spilled) |
meta_t / troika_t |
Meta page and the meta-version triple state machine |
tree_t |
Table descriptor: root, height, page/item counters, mod_txnid |
geo_t |
DB geometry: lower/current/upper bounds, growth/shrink steps, pagesize |
page_t |
Page header: txnid, flags, number, lower/upper free-space pointers |
node_t |
B+tree node: key/data sizes, flags (N_BIG, N_DUP, ...), inline payload |
MDBX_cursor |
Cursor: a (page, index) stack, state (poor/hollow/pointed/filled), comparator; dupsort uses a nested sub-cursor |
dpl_t/dp_t |
Dirty-page list: a lazily sorted pgno→page map with a reserve gap |
pnl_t |
Page-number list (a counter-prefixed sorted array) |
rkl_t |
A sorted txnid set = a contiguous interval + list (GC bookkeeping) |
txl_t |
A plain txnid list |
clc_t/kvx_t |
Comparators and key/value search callbacks, shared per environment |
reader_slot_t, lck_t |
Reader-table slot and the shared lock-file state (mutexes, reader table, statistics) |
9. Transaction lifecycles
9.1 Reading
mdbx_txn_begin(RDONLY)
├─ register a slot in the reader table (the lock file)
└─ pick the recent/steady meta version → snapshot (txnid, geometry, trees)
reads via cursors → tree search → page_get (mmap, lock-free)
mdbx_txn_abort/reset → release or park the slot
Read transactions can be cloned and parked (mdbx_txn_park); parking
lets a long-lived reader release its slot, and "laggard" readers are kicked
(the HSR protocol) when their snapshots block page reuse.
9.2 Writing
mdbx_txn_begin(RW) → take the global write mutex + prepare the basal transaction
CRUD:
tree search → page_get (a page from mmap) → page_touch (a CoW copy into the dirtylist)
node insert/delete → page split/rebalance as needed
freed pages → the retired list (+ the loose cache for fast reuse)
commit (stages: preparation → GC-update → audit → write → sync):
1. GC-update: freed pages are recorded into the FREE_DBI GC tree
(FIFO by default; LIFO with MDBX_LIFORECLAIM — the policy picks records by txnid)
2. refund: tail pages return to the unallocated space
3. spilling: when the dirtylist overflows — bulk page writes (+ fsync)
4. audit (MDBX_CHECKING≥2): verify the page accounting totals
5. two-phase meta commit + synchronization
6. release the mutex, update readers, purge dead slots
abort → discard the dirtylist, restore the meta, release the lock
Nested transactions: the parent dirtylist is extended, children clone dirty pages; the environment owns a reusable basal write transaction.
9.3 GC mechanics
- Freed pages are stored as GC records keyed by the retiring txnid in the FREE_DBI tree; the values are PNL page-number lists.
- Allocation: FIFO reuse by default;
MDBX_LIFORECLAIMenables LIFO. Both policies support dense sequences and honor reclaiming obstacles (slow readers): a page reachable from an active older snapshot cannot be reused — the blockage is resolved by the HSR protocol. - Reused/comeback record intervals are tracked by a dedicated structure (interval + list); "bigfoot" handling of large spans.
- Space-distribution histograms feed geometry decisions about growing and shrinking the file.
10. Cursors and search
- A cursor is a stack of (page, index) pairs down to a leaf; positioning via tree search (fast paths for first/last, branch search callbacks of the comparators).
- The state machine:
poor(unset) /hollow(no data) /pointed/filled; end-of-data subtleties distinguish a "soft" EOF (logically at the end, the last row still readable) from a "hard" EOF (beyond the end, reading is forbidden) — this is themdbx_cursor_eof()contract. - Mutating operations go through CoW + node insert/delete with splitting (1→3 fallback), rebalancing, key propagation upwards and tree deepening.
- Massive deletions cut whole branches; multi-value (dupsort) is handled by nested sub-cursors and fast paths for fixed-size data.
11. Backup, integrity check, defragmentation
- Backup (
mdbx_env_copy*): an ordered walk over all pages → bulk writes → a consistent snapshot without locking readers. - Integrity check (
mdbx_chk): structure validation, GC consistency, key ordering; per-area reports. - Defragmentation (
mdbx_defrag): a live-page map (parent/flags), moving live pages towards the file tail in cycles followed by remapping; coexists with GC.
12. Amalgamation and build variants
make dist produces the flat distribution (mdbx.c, mdbx.h, mdbx.h++,
mdbx.c++, mdbx-internals.h, tools): header inlining and stripping of
dev-only fragments by the dist-cutoff markers. The development tree is built
as a single "alloyed" translation unit by default; debug builds may use
per-module objects. Details — in the installation and building guide
(the "Getting started" section).
13. Refactoring invariants (dev)
MDBX_INTERNALfunctions stay module-internal unless exported by the prototypes header.- The read path must not take locks and must not allocate in hot loops (wait-free readers).
- Page states (
frozen/spilled/shadowed/modifiable/tmp) must be derivable frommp->txnidvstxn->txnid/front_txnid— predicates rely on it everywhere. - The two-phase meta commit order (
txnid_b=0→txnid_a=txnid→ bootid →txnid_b=txnid) must not be reordered. - GC must never return a page reachable from any active snapshot (reclaiming obstacles, HSR, interval bookkeeping).
- The TLS destructor contract: on thread exit all of its reader registrations are removed across all envs.
- The
txn->wrlayout and__restrictDBI-state arrays are performance-critical; changes require benchmarks andpgop_stat. - The on-disk format (
MDBX_MAGIC,MDBX_DATA_VERSION 3, meta/tree/geo) is frozen — refactoring must not change the bytes written to disk. - Cursor EOF semantics (
z_eof_soft/z_eof_hard) are the load-bearing contract ofmdbx_cursor_eof(); pinned by tests. MDBX_CHECKING/MDBX_DEBUGlevels gate panic/assert/log paths; disabled checks must cost zero.
The detailed mechanism exposition — deep-dive.en.md;
the catalog of improvements over LMDB —
improvements.en.md; the module map and build rules —
structure.md, build.md (dev).