libdb 2026.09.2
A performance and test-infrastructure release — the first cut since the measurement gate exists, and so the first whose performance claim is measured rather than asserted.
Concurrent read scaling on a shared handle
DB->get allocates and frees a transient cursor per operation, and moving that cursor between the handle's free and active queues took dbp->mutex up to three times per get. With many threads sharing one DB handle, that single mutex serialized every read. Those queues are now sharded per handle, each partition with its own process-only mutex, selected by hashing the calling thread's id.
Measured on an idle 96-vCPU machine, 5 reps per point, uniform random reads through one shared DB_THREAD handle (medians, ops/s):
| threads | before | after | |
|---|---|---|---|
| 1 | 926,867 | 916,862 | unchanged |
| 8 | 556,739 | 2,456,548 | 4.4× |
| 16 | 292,833 | 3,294,383 | 11.3× |
| 24 | 174,288 | 3,706,043 | 21.3× |
| 48 | 79,234 | 3,590,889 | 45.3× |
| 96 | 51,872 | 2,028,715 | 39.1× |
A curve that declined past 8 threads now keeps climbing.
The honest limits. Single-threaded throughput is unchanged — there is no contention to remove, and the added hash plus per-partition mutexes cost nothing measurable. Hot-key workloads gain only about 1.18×, because they are bound by the lock partition rather than by cursor allocation. That asymmetry is what makes the large numbers credible: removing the cursor mutex should help uniform reads enormously and hot-key reads barely, and it does.
Not an ABI break, despite the partitions living inside struct __db in the public header. sizeof(DB) is 1456 both before and after, and sizeof(DBC) is 552, because the partitions replace the old single queue pair within the same footprint — verified by compiling the same probe against v5.3.36 and against this tree.
Worth recording: the work originally shipped with a defect that made it a 29–43% regression. Its partition selection hashed the process id, and on a default environment the thread-info block does not exist at all, so every allocation funnelled through one partition while still paying for the hash and eight mutexes. Both degeneracies were instrumented over 480,000 threaded gets before being fixed. Method, raw data and hardware provenance: test/bench/CURSOR-SHARD-RESULTS.md and test/bench/results/.
Replication has an executable isolation test
test/repiso runs a real two-process master/client pair over a socket and asserts that every page a replicated transaction modified appears in that transaction's commit lock list. Replication was the least-tested subsystem in the tree; this is its first isolation gate, and it closes the last "follows from source analysis and has not been directly observed" caveat outstanding against the fork (issue #140).
Also in this release
- A performance regression harness whose tolerances are derived from a measured noise floor rather than guessed, proven both to stay quiet on identical code and to fire on a deliberately slowed build.
- Coverage drivers that had been dead code in CI are now wired in, taking measured function coverage from 58.8% to 78.6%. The coverage ratchet is re-based to compare like with like — it had been checking a bounded subset against a full-suite ceiling it could never reach, and so carried no signal at all.
- Windows ARM64 build configurations, verified to emit
AA64images but untested at runtime, because neither AWS nor GitHub offers a Windows-on-ARM host. - The README now documents both user-visible changes with their limits, and lists the nine test tiers — which it previously did not mention at all.
Upgrading
No on-disk, log, or ABI change from 5.3.36. Recommended for anyone running multi-threaded readers against a shared DB handle; no action needed otherwise.
Qualification
From a pristine clone: autoconf (--enable-cxx) and meson both clean · db_verify -V reports 5.3.37 · 9/9 test/db runners · isolation, lockmatrix, repiso · soak (5 workloads, 0 unexpected) · fuzz 10/10 seeds · all four documentation gates (build, selfcheck, man coverage, spelling).