Relationships
#3737 v3 chunk barrier on macOS: use F_BARRIERFSYNC (ordering) instead of F_FULLFSYNC (full drive-cache flush), cutting ~10-25 ms (200 ms+ on a busy disk) per command
Opened by stack72 · 10/21/2026· Shipped 10/21/2026
Background
swamp-club#3617 (shipped f469323c3) closed an S1 on v3 filesystem datastores. A power cut could leave a durable commit naming a chunk that was still only in the page cache, which wedged every checkout. The fix is a chunk barrier: before a commit group is prepared or appended, its new chunk files are written as temp files, synced in parallel, renamed, and their shard directories synced once (FileSystemStorage in src/infrastructure/persistence/fs_datastore_storage.ts, sync_directory.ts).
On macOS, Deno's FsFile.sync() is fcntl(F_FULLFSYNC), which flushes the whole drive cache. commits.db itself uses SQLite's default fullfsync=0, so the commit only gets a plain fsync. So the barrier makes the chunk more durable than the commit that names it. That isn't needed: what's needed is ordering, the chunk on disk no later than the commit.
The cost (measured for #3617, macOS arm64, APFS, against main 270eb2fe6)
In-process bench (
scripts/bench_v3_chunk_barrier.ts):Case main branch 1 new chunk 0.5 ms 8.4 ms 10 new chunks 2.8 ms 21 ms 200 new chunks 52 ms 181 ms uat#496 real CLI: the v3 write (
model method run) went from 566 ms to 762 ms, +196 ms (+35%). A direct A/B gave +217 ms. Other processes were writing 1–2 GB/s to the disk at the time:F_FULLFSYNCwaits for everything they wrote.Summary: about 10–25 ms per command that stores a new non-inline chunk on an idle Mac, and 200 ms or more on a busy disk.
Linux (Docker, ext4): +1.2 ms for one chunk, and no measurable uat#496 difference. Linux isn't the problem.
The decision when #3617 shipped (2026-10-20): ship correctness first, and fix the macOS cost before v3 is dogfooded and benchmarked (datastore rework Phase 4 I, where the kill criteria are checked).
What to do
- On macOS, sync chunk files and their shard directories with
F_BARRIERFSYNCinstead ofF_FULLFSYNC. It's an I/O barrier: everything written before it reaches the disk before anything written after it, without flushing the drive cache. That gives the ordering the barrier exists for: the chunk is on disk no later than the commit.- Deno doesn't expose
F_BARRIERFSYNC. Callfcntlthrough libc FFI on a descriptor opened through libc (open(path, O_RDONLY),fcntl(fd, F_BARRIERFSYNC),close(fd)). Use the libc loading pattern the codebase already has (process_ancestry.ts).F_BARRIERFSYNCis 85 in<sys/fcntl.h>. - The compiled binary already has
--allow-ffi. Check it underscripts/compile.ts's exact flags.
- Deno doesn't expose
- Fallback: if libc can't be loaded, or
fcntlreturnsENOTSUPorEINVAL(some volumes, network filesystems), fall back to today'sFsFile.sync()(F_FULLFSYNC) for that call. Remember the fallback per volume or process, so it isn't retried for every chunk. Never fall back to no sync. - Linux and Windows are unchanged.
- Ordering caveat:
F_BARRIERFSYNCorders writes to the same device. Chunks andcommits.dbare on the same device for a filesystem datastore, since the datastore path holds both. If the checkout'scommits.dbcopy (replicated datastores) could be on a different device from the chunk store, say how that case is handled. Using the full flush there is fine. - Docs: update
design/enablers/datastores.mdon the barrier's durability on macOS, and on cost againstmain.
Measure (against main)
On macOS arm64, using the cost section's method:
scripts/bench_v3_chunk_barrier.ts, branch vsmain, with an A/A control, for 1, 10 and 200 new chunks;- the uat#496 v3 write line, interleaved branch and
mainruns; - both on an idle disk and under background write load (any steady writer, such as
ddto a scratch file).
Report the numbers in the PR. Linux needs no new numbers, beyond confirming nothing changed there.
Tests
- Unit (macOS only, skipped elsewhere): the barrier path calls
F_BARRIERFSYNC, through an injectable seam for the fcntl call.ENOTSUPfalls back tosync()once and is remembered. A libc load failure falls back. - The #3617 guarantees still hold: the existing barrier tests, which check that chunks and directories are synced before prepare and append, pass on every platform.
- swamp-uat passes unchanged.
Done when: the uat#496 v3 write line on macOS is within a few ms of main on an idle disk, and close to it under load. The fallback is pinned by a test.
Blocked by: nothing. It runs in parallel with bug hunt group 8 (the recovery-sweep scope), which touches the read model, not chunk storage.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.