Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLIPublic
Assigneesstack72

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_FULLFSYNC waits 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

  1. On macOS, sync chunk files and their shard directories with F_BARRIERFSYNC instead of F_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. Call fcntl through 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_BARRIERFSYNC is 85 in <sys/fcntl.h>.
    • The compiled binary already has --allow-ffi. Check it under scripts/compile.ts's exact flags.
  2. Fallback: if libc can't be loaded, or fcntl returns ENOTSUP or EINVAL (some volumes, network filesystems), fall back to today's FsFile.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.
  3. Linux and Windows are unchanged.
  4. Ordering caveat: F_BARRIERFSYNC orders writes to the same device. Chunks and commits.db are on the same device for a filesystem datastore, since the datastore path holds both. If the checkout's commits.db copy (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.
  5. Docs: update design/enablers/datastores.md on the barrier's durability on macOS, and on cost against main.

Measure (against main)

On macOS arm64, using the cost section's method:

  • scripts/bench_v3_chunk_barrier.ts, branch vs main, with an A/A control, for 1, 10 and 200 new chunks;
  • the uat#496 v3 write line, interleaved branch and main runs;
  • both on an idle disk and under background write load (any steady writer, such as dd to 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. ENOTSUP falls back to sync() 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.

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 6 MOREPR_LINKED+ 2 MORESESSION_SUMMARIZED

Shipped

10/21/2026, 7:43:53 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack7210/21/2026, 6:02:36 PM

Sign in to post a ripple.