Research: prior art for a paged single-file CultCache store (serves variants:ruling:page-tree-store-research)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-27 06:47:16 +01:00
commit dbc94e023f

View file

@ -0,0 +1,146 @@
# Prior art: paged / log-structured single-file stores for CultCache
Serves ruling `variants:ruling:page-tree-store-research` (Operator 2026-10-27: prior-art pass and flush measurement; "the page-tree preference stands"; whether and when to build is decided on the research, after C2a and the AOT cuts) and follow-up `variants:follow_up:paged-single-file-store`. Read with ruling `cultlib-gaps:ruling:database-shaped-built-for-load` (database-shaped things are built for unreasonable load).
Facts with evidence pointers; no recommendation. Tags: VERIFIED (read in the fetched source or code this session), SECONDARY (search-result summary or third party), UNVERIFIED (from memory or inference, not read).
Gathered 2026-10-27 by Eyes. Tool note: the fetch tool summarises through a small model, so numbers are VERIFIED only where I also read the text (PDFs were converted with pdftotext and grepped).
## 0. CultLib today (code, not research)
- Single-file write path: `SingleFileBackingStore` ... `WriteSnapshotAtomically(FileInfo.FullName, SerializeStore(format, catalog, fileRecords))` at `src\GameCult.Caching\CultCache.cs` :4522 on branch `hands/variants-c2a`; the helper at :4831 writes `.{name}.{guid}.tmp` with `FileOptions.WriteThrough`, `Flush(true)` (one fsync of the new file), then `File.Replace` or `File.Move`. VERIFIED (read). Write cost per flush is the whole file plus one fsync; no directory fsync appears in that helper. VERIFIED.
- cultcache-rs already ships a redb-backed store: `RedbMessagePackBackingStore` and `OwnedRedbMessagePackBackingStore`, `packages\cultcache-rs\src\lib.rs` :1596-1660, :1975, :2348, :2483; `redb = "4.1.0"` in its `Cargo.toml` :21. Each polymorphic `(type, key)` is one redb row whose value is the MessagePack of its `CultCacheEnvelope`; one table `cultcache_envelopes`; opens a fresh redb handle per operation under the CultCache external lock because redb permits one writable handle per path. VERIFIED (read). Other runtimes have no reader for that file (no redb in C#/TS/Python): UNVERIFIED by search, but nothing in `src\GameCult.Caching` references redb.
- Kept research to cite, not repeat: `docs\research\2026-10-23-store-lock-user-code-prior-art.md` (user code under a store lock; names LMDB one-txn-per-thread and SQLite btmutex); `docs\research\cross-process-file-locking-prior-art.md` (sidecar `.lock`, atomic replace per runtime); `docs\research\windows-store-replace-prior-art.md` (Windows `ReplaceFile` lets lock-free readers see an empty store: 19% of opens on .NET 10, 0 with `MoveFileExW`); `docs\research\2026-10-23-schema-drift-write-back-prior-art.md`; `research\store-writes-behind-cache\prior-art-2026-10-27.md` (redb `DatabaseAlreadyOpen`, second handle refusal, section B). The trigger (a whole-view flush re-asserting records a writer does not own) is not covered by any of them.
- Size check: the ruling text says Aetheria.cc is 12.7 MB. The file at `F:\Projects\Aetheria\GameData\Aetheria.cc` in the operator checkout is 195,939 bytes. VERIFIED (ls). Not explained; the 12.7 MB figure may be another file or an LFS-resolved copy. Flush measurement not done here.
## 1. Page-tree single-file stores
### 1.1 SQLite (pager + B-tree)
- Page size: power of two 512..65536, one size per file, 2-byte field at offset 16; header integers big-endian; magic `"SQLite format 3\000"`. VERIFIED (sqlite.org/fileformat2.html).
- Table b-trees: 64-bit rowid key, data in leaves; index b-trees: arbitrary keys. Overflow: payload over threshold X (table leaf X = U-35) spills into a linked list of overflow pages, each starting with a 4-byte next-page number; local bytes M = ((U-12)*32/255)-23 minimum. Freelist: trunk pages listing leaf page numbers. VERIFIED (fileformat2).
- Rollback journal commit (synchronous=FULL): original page images to `-journal`, fsync; page count written into journal header, fsync again; changed pages written to the db file, fsync; delete (or zero/truncate) the journal = commit. Three flush points, plus directory sync on Unix for journal creation. NORMAL: one journal flush, protected by a per-page 32-bit checksum. Hot-journal recovery rolls back under an exclusive lock. VERIFIED (sqlite.org/atomiccommit.html). Assumptions listed there: 512-byte sector writes not atomic, linear writes, powersafe overwrite, atomic file delete, fsync honoured.
- WAL: commits append to `-wal`; FULL syncs the WAL on every commit, NORMAL only at checkpoint; auto-checkpoint at 1000 pages (about 4 MB at default page size) copies pages back, syncing WAL then db; one writer; readers see an end-mark snapshot; checkpoint starvation lets the WAL grow without bound; read cost grows with WAL size; the wal-index is an mmap'd `-shm` file, so all processes must be on one host and no network FS; page size cannot change in WAL mode; WAL transactions beyond about 100 MB are often faster in rollback mode (statement predates 3.11). VERIFIED (sqlite.org/wal.html).
- One-record update cost: root-to-leaf path pages (each a full page) written once to journal and once to db (rollback) or once to WAL plus once at checkpoint (WAL). Inferred from the above; UNVERIFIED as a measurement.
- Blob storage vs files: at about 10 KB blobs, 4 KiB pages, SQLite read about 35% faster than separate files and used about 20% less space; blob sizes 100 KB and 1 MB not measured on that page; the page cites Gray et al. (SQL Server) that database blob reads win below roughly 250 KiB to 1 MiB. VERIFIED (sqlite.org/fasterthanfs.html).
- Format stability: every release reads and writes files from 3.0.0 (2004); files portable across 32/64-bit and endianness; support planned to 2050; Library of Congress lists it as a preservation format. VERIFIED (sqlite.org/onefile.html, lts.html).
- Size: library about 155.8 KSLOC of C (3.42.0); test code about 590 times that; crash testing by a VFS that reorders and corrupts unsynced writes and by whole-filesystem snapshot replay. VERIFIED (sqlite.org/testing.html).
- Multi-process: file locks, one writer at a time in either journal mode. VERIFIED (wal.html, atomiccommit.html).
### 1.2 LMDB (copy-on-write B+tree, mmap)
- Design statement: copy-on-write pages, no WAL, no crash-recovery procedure, no compaction; one write transaction at a time, readers lock-free MVCC; freed pages tracked and reused; file bounded in normal use. VERIFIED (lmdb.h top-of-file).
- Two meta pages (`NUM_METAS 2`); commit writes dirty pages, fdatasync (`mdb_env_sync0`), then pwrite of the meta page body through a separately opened O_DSYNC handle (`me_mfd`), so the meta write syncs itself. Two sync points per commit; `MDB_NOMETASYNC` / `MDB_NOSYNC` drop them. VERIFIED (`mdb.c` :4205-4225, :4385-4470, from raw.githubusercontent.com/LMDB/lmdb/mdb.master; 11,552 lines in `mdb.c`).
- Large values: `P_OVERFLOW` pages are contiguous runs recorded by `mp_pages`; comment at :658-664 about the threshold at which items go into overflow pages. VERIFIED (mdb.c). Key limit 511 bytes (`MDB_MAXKEYSIZE`). VERIFIED (mdb.c :701).
- Caveats from the header: long-lived read transactions block page reuse and the file grows; stale readers left by aborted processes need `mdb_reader_check`; do not use on network filesystems; do not use an env after `fork()`; do not open the same db twice in one process (fcntl locks); one transaction per thread; readers need write access to the lock file. VERIFIED (lmdb.h).
- Portability: structs are native layout; the magic number "will reflect any byte order mismatches" (`mdb.c` :671); data version constant `MDB_DATA_VERSION 1`; TensorFlow docs say files differ between big- and little-endian hosts and an LMDB developer says 32- and 64-bit files are incompatible by default. VERIFIED for the magic comment, SECONDARY for the endianness and bitness statements (search snippets).
- Concurrency across processes: shared lock file with a reader table; single writer via lock-file mutex. VERIFIED (header caveats).
### 1.3 redb (Rust, copy-on-write B+tree)
- Layout: 512-byte super-header (64-byte db header plus two 128-byte commit slots), regions with buddy allocator pages (`PAGE_SIZE` constant 4096 in `header.rs` :85), little-endian integers; XXH3-128 checksums in a Merkle arrangement (each branch stores child checksums). VERIFIED (docs/design.md; header.rs).
- Commit: the "god byte" flips the primary slot. Default 1-phase-plus-checksum: write data and checksums under a new higher txn id, flip, one fsync; recovery keeps the primary if txn id is newer and checksums validate, else falls back to the secondary slot. 2-phase: fsync data, flip, fsync again (no reliance on checksums; design.md warns XXH3 is not collision resistant against a deliberate attacker). Non-durable commits kept in memory. VERIFIED (design.md).
- Free pages: pending-free until no reader can reference them; format v3 stores them in system tables; quick-repair saves allocator state each commit, otherwise full repair walks the trees. VERIFIED (design.md).
- Large values: `MAX_VALUE_LENGTH = 3 GiB` (`page_store/base.rs` :17); pages of higher order are power-of-two multiples of the base page; design.md does not say how an oversized value is stored. VERIFIED for the constant, UNVERIFIED for the mechanism (I did not read btree_base.rs).
- Multi-process: three modes (exclusive writer, single writer with read-only readers, multi-writer) using byte-range file locks; non-durable commits unavailable in multi-process modes; second writable handle refused by default. VERIFIED (design.md; README features "Multi-process access to one database, with a single or several writer processes").
- Format stability: README "file format is stable, and a reasonable effort will be made to provide an upgrade path"; versions 1 to 3 exist. VERIFIED. Pure Rust only (`no_std` build supported with a `StorageBackend` trait). VERIFIED (README).
- Size: `src` is 41,882 lines of Rust at `16c7107` (2026-10-25), of which `tree_store/page_store` is 12,606. VERIFIED (counted from a shallow clone; includes comments and in-file tests).
### 1.4 bbolt (Go, derived from Bolt)
- B+tree, mmap, copy-on-write, one read-write transaction at a time, MVCC readers; README says the code base is "<5KLOC". VERIFIED (README). Non-test Go under the repo excluding `cmd` and `tests` is 8,232 lines at `a4ef197` (2026-10-16). VERIFIED (counted).
- Commit: dirty pages written and fsynced, then a new meta page with incremented txn id written and fsynced (two fsyncs); meta pages carry checksums. VERIFIED (README).
- Caveats: files cannot shrink (free list reuse only); long read transactions block reclamation; a single exclusive file lock so no multi-process writers; endian-specific files because structures are memory-mapped; random writes slow, `Batch()` or a WAL suggested; mmap problematic on 32-bit. VERIFIED (README).
- Overflow: `Page.Overflow()` / `SetOverflow` in `internal/common/page.go` :206-210, large values span contiguous pages. VERIFIED that the field exists; the mechanism is UNVERIFIED beyond that.
## 2. B-epsilon trees and write-optimised designs
- Structure: B-tree whose internal nodes use B^e slots for pivots and the rest for a message buffer; messages (insert, delete tombstone, upsert) sorted by key then timestamp; flushing a full buffer moves messages one level down. e=1 is a B-tree; e=0 is a buffered repository tree. VERIFIED (Bender et al., ";login:" Oct 2015, usenix.org/system/files/login/articles/login_oct15_05_bender.pdf, read via pdftotext).
- Cost: batching divides insert cost by B^(1-e); with B=1024 and e=1/2 the article gives 16 times faster inserts than a B-tree; point-query cost has the same asymptotics as a B-tree (the article says queries "are no faster than in a B-tree"); nodes of a few hundred KB to a few MB are practical because insert bandwidth cost grows slowly with B. VERIFIED (same).
- Upserts: a message encoding "read-modify-write without the read"; applied at flush or on a query that finds the message in transit. LSM upserts defeat Bloom filters because a pending message may sit in every level. VERIFIED (same).
- Versus LSM: article claims Bloom-filter LSMs match B^e-trees for some workloads, B^e-trees asymptotically dominate for small range queries and upsert-heavy point queries. VERIFIED (same; it is an authors' claim).
- Measured: BetrFS (TokuDB underneath), 3 million 200-byte file creates: steady-state an order of magnitude faster than ext4/XFS/Btrfs/ZFS, 4 to 10 times fewer bytes written. Small random writes into a large file: "almost two orders of magnitude" better; TokuDB node typically 4 MB. VERIFIED (same article; BetrFS FAST'15 text cs.unc.edu/~porter/pubs/fast15-final.pdf, grep hits at lines 416, 643). Sequential large-file I/O is a place BetrFS is not ahead; the paper lists "few slower operations". SECONDARY (journal abstract snippet: "requires additional data-structure tuning to match ... on some operations").
- PerconaFT/TokuDB: node-size setting default 4 MiB (in-memory size; compressed on disk), leaves split into 128 KB default basement nodes (read block) so a point query reads a basement node rather than a 4 MB leaf; buffers serialised to disk with per-message sequence numbers. SECONDARY (Percona blog, slides, docs mirror; the PerconaFT source not read). TokuDB is deprecated in Percona Server (UNVERIFIED; not checked).
- The regime where the sources place B-trees ahead: queries (no asymptotic gain for the B^e-tree), small data that fits in cache, and read-modify-write patterns bound by the query. VERIFIED as the article's statements ("Avoid query-before-update"); no source read gives a data-size threshold below which the gain vanishes.
## 3. Log-structured alternatives
### 3.1 LSM (LevelDB, RocksDB, WiscKey)
- LevelDB: writes go to a log, then memtable, then a level-0 table at about 4 MB; level L limit 10^L MB; level-0 compacts at 4 files; output files about 2 MB; CURRENT names the latest MANIFEST (a log of file-set edits); recovery converts the leftover log into a level-0 table. The page does not state fsync behaviour. VERIFIED (github.com/google/leveldb doc/impl.md).
- Amplification (WiscKey paper, read via pdftotext): LevelDB write amplification 3.1 at 1 GB and 14 at 100 GB (16-byte keys, 1 KB values); worst case over 50; read amplification 8.2 at 1 GB and 327 at 100 GB; "I/O amplification in typical LSM-trees can reach a factor of 50x or higher". VERIFIED (Lu et al., FAST'16).
- RocksDB tuning guide: leveled multiplier 10; example write amplification about 33; space amplification about 1.14 for leveled; universal compaction trades lower write amplification for higher read and always higher space amplification (default 200% max size amplification), advised against above about 100 GB. VERIFIED (RocksDB wiki).
- WiscKey (key-value separation): keys in the LSM, values in an append-only value log with (key size, value size, key, value) tuples; effective write amplification worked example (10*16+1024)/(16+1024) = 1.14; GC reads from the tail and rewrites live values at the head, fsyncs the vLog, then updates the tree; crash consistency relies on appended bytes surviving as a prefix; range scans pay random reads and lose to LevelDB for small random-order values read sequentially. VERIFIED (same paper).
- Multi-process: LevelDB takes a LOCK file for one process (UNVERIFIED, impl.md lists LOCK only as a file name).
### 3.2 Bitcask
- A directory; one OS process writes; one active append-only data file rolled at a size threshold; records of CRC, timestamp, key size, value size, key, value (the field list is in a diagram I could not read; VERIFIED only that entries are a "linear sequence"). Tombstone value for delete. VERIFIED (Basho 2010 intro, riak.com/assets/bitcask-intro.pdf).
- In-memory keydir: hash map from every key to file id, offset, size; whole keydir must fit in RAM ("well under a GB" for many millions of keys); one disk seek per read. VERIFIED.
- Merge rewrites the immutable files keeping latest versions and writes hint files (key plus position and size, no values) so startup scans hints instead of data. Sync-on-put is an option. VERIFIED.
- Space reclaim only at merge; no compression. VERIFIED.
### 3.3 Append-only B-tree files (CouchDB / couchstore)
- Updates appended, never overwritten; commit is two steps: data and index flushed, then the header written as two consecutive identical copies in the first 4 KB and flushed (two fsync points). VERIFIED for the docs.couchdb.org description of the two steps. A Couchbase bug report (MB-10280) describes extending the file before the first fsync so the second can be an `fdatasync`. SECONDARY.
- couchstore format notes: header re-written at the tail on each change; 4096-byte blocks so the header can be located; headers larger than 4090 bytes split across blocks with a prefix byte. SECONDARY (search snippets of couchstore file_format.md and a couch_file commit message).
- Reclaim: compaction copies live data into a new file and swaps. VERIFIED (docs.couchdb.org).
### 3.4 Single-file log with periodic compaction
- No separate system read. The Bitcask hint-file idea and WiscKey's vLog/GC are the closest sources above. SQLite WAL is a bounded log with checkpoint (section 1.1). rkv "SafeMode" (Mozilla) keeps the whole database in memory and writes transactions synchronously to disk on commit; the sources do not say the file is rewritten whole. SECONDARY (rkv README, docs.rs snippets).
## 4. Per-family summary
Crash mechanism and syncs per commit:
| Family | Mechanism | Sync points per commit |
|---|---|---|
| Whole-file replace (CultLib today) | temp file + rename/replace | 1 data fsync (Flush(true)); no dir fsync in the C# helper. VERIFIED (code) |
| SQLite rollback journal | journal + hot-journal rollback | 3 (FULL), 2 (NORMAL). VERIFIED |
| SQLite WAL | append, checkpoint later | 1 per commit on FULL, 0 on NORMAL, plus checkpoint syncs. VERIFIED |
| LMDB | CoW + alternating meta pages | 2 (data fdatasync, then meta via O_DSYNC fd). VERIFIED |
| bbolt | CoW + checksummed meta | 2. VERIFIED |
| redb | CoW + god-byte slot flip | 1 (1PC+checksum, default) or 2 (2PC). VERIFIED |
| LSM | WAL + memtable + sorted tables + manifest | WAL sync optional per write; manifest edits on compaction. VERIFIED for structure; WAL sync policy not stated in impl.md |
| Bitcask | append + CRC | optional sync-on-put. VERIFIED |
| CouchDB | append + dual header | 2. VERIFIED |
Bytes written for a one-record update (all inferred from the page/tree structure; none measured here, UNVERIFIED as numbers):
- Whole-file replace: the whole file, every flush.
- CoW B-tree (LMDB, bbolt, redb): one new page per tree level from leaf to root (at 4 KiB pages, the tree depth times 4 KiB) plus the meta slot; larger record adds its overflow pages.
- SQLite: same path of pages, written twice (journal then db, or WAL then checkpoint).
- B^e-tree: one message into the root buffer, amortised flush cost.
- LSM: one WAL record, with 3 to 14 write amplification for the compaction later (measured on LevelDB, 1 GB and 100 GB respectively). VERIFIED (WiscKey).
- Bitcask/CouchDB-style append: the record (CouchDB adds tree path nodes plus the header).
Read path: B+tree families root-to-leaf, with mmap giving pointers into the file (LMDB, bbolt) or an internal page cache (redb, SQLite). LSM checks memtable then up to 14 files worst case (LevelDB, WiscKey §2.3). Bitcask is one seek via the in-memory keydir. VERIFIED per family above.
Space reclaim: LMDB and redb reuse freed pages once no reader holds them; bbolt reuses but cannot truncate; SQLite has a freelist plus VACUUM (VACUUM not read); LSM and Bitcask and CouchDB reclaim only by compaction/merge rewriting data; LevelDB leveled space amplification about 1.14 by RocksDB's example and universal compaction can need up to three times the data temporarily. VERIFIED except VACUUM (UNVERIFIED).
Multi-process / multi-writer: LMDB shared lock file, single writer, caveats around stale readers; bbolt exclusive file lock, no multi-process writers; redb three modes via byte-range locks; SQLite file locks, one writer, WAL needs same-host shared memory; Bitcask one writer process per directory; CouchDB serialises updates in one server. None of these give a real multi-writer on one file except redb's multi-writer mode, which requires quick-repair on each write. VERIFIED (design.md).
Variable-size values: SQLite overflow page chains (4-byte next pointer); LMDB contiguous overflow runs; bbolt contiguous overflow count on the page header; redb higher-order (power-of-two-sized) pages up to a 3 GiB value cap; B^e-trees use nodes of 0.25 to 4 MB with 128 KB basement nodes; LSM puts values in sorted tables (WiscKey moves values above roughly 1 KB to a vLog); Bitcask and CouchDB write values inline in the log. VERIFIED for SQLite, LMDB, redb cap, WiscKey; UNVERIFIED for bbolt and redb mechanism beyond the field names.
Mechanism size, small faithful implementations: bbolt about 5.6K lines root non-test Go (8.2K with internal packages); redb 41.9K lines total, 12.6K in `page_store`; LMDB `mdb.c` 11.6K lines of C; SQLite 155.8 KSLOC; LevelDB and RocksDB not counted. VERIFIED for counts stated; each counts differ in whether comments, tests and CLI are included.
## 5. Cross-runtime, one on-disk format
- SQLite: parity achieved by one C implementation embedded or wrapped in other languages, with a documented, big-endian, frozen format; the lts.html page does not itself name the C implementation as reference. VERIFIED (fileformat2, lts.html). Independent re-implementations I found: SQLJet (pure Java, reads and writes SQLite files, low-level API, no SQL execution beyond the schema, GPL with a commercial licence; Debian/Fedora packages at 1.1.10, upstream activity unclear) and Turso/Limbo (Rust rewrite; announcement says compatible at the language and file-format level; status labels conflict between sources). SECONDARY (search snippets). Their completeness was not tested.
- LMDB: bindings wrap the C library (lmdb-js, heed, Lightning.NET, lmdb-go and others named in the search; the search returned no pure re-implementation). SECONDARY/UNVERIFIED. Native-endian, pointer-sized structures make files non-portable across architectures. VERIFIED (mdb.c comment) and SECONDARY (TensorFlow docs, PowerDNS thread).
- bbolt: endian-specific because structures are mmap'd (README). VERIFIED. Pure Go only.
- redb: little-endian explicit; Rust only; `no_std` backend trait. VERIFIED. No other-language reader found. UNVERIFIED (negative search).
- LevelDB: its table format has a short spec (`doc/table_format.md`: fixed footer with magic `0xdb4775248b80fb57`, varint block handles, index and metaindex blocks) sufficient, in the fetch summariser's words, to implement a reader from the spec. Independent implementations named by search: syndtr/goleveldb (pure Go), kezhuw/leveldb (claims format conformance, "not production", last pushed 2020). On-disk compatibility of goleveldb with LevelDB was not confirmed by the search. VERIFIED for the spec content; SECONDARY for the ports.
- CouchDB/couchstore: Couchbase notes say its format is similar to but not identical to CouchDB 1.2's; i.e. two implementations diverged. SECONDARY.
- Reported hardness of parity (from the sources, not from any parity post-mortem I found): native struct layout and endianness (LMDB, bbolt); a format defined by one implementation's code with the spec secondary; checksums and crash recovery logic that must match, not just the layout (redb god byte plus txn id plus checksum rules; SQLite sector and hot-journal rules); platform persistence properties (Pillai et al. OSDI'14 find 60 crash vulnerabilities across 11 applications including LevelDB, LMDB, SQLite, PostgreSQL and ordering/atomicity assumptions that differ by file system; VERIFIED, read via pdftotext). I found no document on porting a page-tree engine across four runtimes with a parity test suite. UNVERIFIED (negative search).
- mmap: CMU CIDR 2022 "Are You Sure You Want to Use MMAP in Your DBMS?" (db.cs.cmu.edu/mmap-cidr2022/): authors argue mmap raises correctness and performance problems and several systems moved away from it after "significant engineering costs"; the page excerpt names no systems or four-way list. VERIFIED for those statements only. Python, TypeScript and C# each have different mmap availability (UNVERIFIED, not researched).
## 6. Published comparisons of whole-file rewrite vs paged/log stores at 10-100 MB
- None found. Searches for a benchmark of atomic whole-file replacement against SQLite or an LSM at 1, 10 and 100 MB returned nothing relevant; the closest are a SQLite forum thread where a maintainer says changing part of a JSON value means rewriting the whole row (SECONDARY), a VLDB paper on write amplification of small random SQLite updates on mobile flash (SECONDARY, vldb.org/pvldb/vol8/p1454-oh.pdf, not read), and the SQLite 10 KB blob benchmark (section 1.1). Mozilla's rkv SafeMode is a deployed in-memory store with synchronous writes on commit (section 3.4) but no published rewrite-vs-paged measurement was found.
- Measurement is therefore ours to take: whole-view flush cost on a real store (see the follow-up's own measurement item and section 0 size discrepancy).
## 7. Not done
- WiscKey, BetrFS, Bender, Bitcask and Pillai were read via pdftotext; BetrFS only grepped. SQLite pages were read through the fetch summariser.
- redb `btree_base.rs` large-value path, bbolt `tx.go` commit, PerconaFT source, RocksDB WAL sync documentation, SQLite VACUUM and incremental autovacuum, Windows-specific fsync and mmap behaviour for LMDB and bbolt, and the VLDB mobile-SQLite paper were not read.
- No code was run and nothing was benchmarked.
- Shallow clones used for line counts are in the session scratchpad (`...\scratchpad\repos\redb`, `...\bbolt`), not in the repo.