Repository navigation
bcachefs: a sync with nothing uncommitted writes no superblock - #1098
Conversation
Review of #1098 at 55970f7ArchitectureThe decision sits with the right owner. The format knows whether its tree changed, and the file server's cache knows whether the disk owes a Flush. Comparing whole superblocks, cursor aside, is better than a dirty flag: it covers every mutation without each mutation site having to remember to set something. The change adds about 16 production lines (the field, four constructors, the branch in Durability and crash consistency, traced
I found no path where Evidence checked
Findings
SEND BACK |
55970f7 to
59dd113
Compare
Review r2 of #1098 at 59dd113The r1 fixes
Findings
SEND BACK |
Mounted::sync committed unconditionally: it flushed the device, wrote both superblock copies and flushed again. Under the file server's cache the first flush costs nothing when nothing is dirty, but the superblock write always left one owed, so every fsync on DATA paid two block writes and an NVMe Flush. #1097's AMD readback has persist.fsync_clean at 1.23 ms against 57-70 us under Linux on the same disk. A mount now keeps the superblock its last commit made durable. A commit whose superblock equals it, the allocation cursor aside, writes none: under shadowing no node the landed root reaches is written over, so an equal root is the landed tree. The device is still flushed first, for the data pages and bitmap blocks written in place, so a sync still returns only once everything written before it is on the medium. The cursor is excluded because a commit's own frees move it back after its superblock lands, and where the next scan starts is no reason to commit. Nothing is known landed at mount, after mkfs or after a refused sync, so each of those commits whole. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…None The record's exit is met: the host test counts no request from a clean sync, and the AMD bench reads persist.fsync_clean at 39.8 and 38.6 us against 1.23-1.26 ms before (the T14: 15.9 and 16.1 us). The T14 record's sentence that named it as the remaining cost now says what the clean fsync reads. `landed` is still Some when committed() refuses a free: the superblock did land. Its comment said None after any refused sync, which was false. The comparison is Superblock::same_commit, with the cursor's exclusion stated where it is defined. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59dd113 to
2a599bb
Compare
Problem
bcachefs::Mounted::synccommitted unconditionally: flush, write both superblock copies, flush again. The file server's cache already skips a device Flush it doesn't owe, but the superblock write always created one. So every fsync on DATA paid two block writes and an NVMe Flush, even with nothing to make durable. Before this change,persist.fsync_cleanread 1.23–1.26 ms on the AMD bench and 0.46–3.4 ms on the T14, against Linux's 57–70 µs and 23 µs on the same disks.Change
A mount keeps the superblock its last commit made durable (
landed). A commit whose superblock names the same commit (Superblock::same_commit: equal apart from the allocation cursor) writes no superblock and issues no second flush.io.flush()still runs, so everything written before the sync, data pages and bitmap blocks included, is on the medium beforesyncreturns. Under the file server's cache that flush asks the disk for nothing when nothing is dirty. The freescommitted()makes in a sync reach the medium at the next flush, as before.landedisNone. At mount, after mkfs, afterinto_formatted, and after a sync refused before its superblock landed. Each of those commits whole. A sync whose superblock landed but whosecommitted()frees were then refused keepslanded: the commit did land, andpendingkeeps the frees for the next sync.The sync right after a commit still writes the bitmap blocks that commit freed, and so owes one Flush. Every later clean sync asks the disk for nothing.
The defect's record,
issues/a-clean-fsync-on-data-writes-the-superblock-and-flushes-the-disk.md, is deleted: both halves of its exit are met (the host request count, and AMD in tens of µs). The T14 durable-write record (toyos-durable-writes-on-the-t14s-nvme-run-six-times-slower-in-some-boots.md) cited it. It now states the measured 15.9/16.1 µs, sourced to evidencepr-1099at ac6b89a,metal-t14/. It also notes that the slow-mode boot B-run2 still read 16 µs, so the slow mode appears only where a Flush or a write is owed.Checks
Evidence:
host checks, red controls and AMD: https://github.com/ToyOSOrg/evidence/tree/a45b2742bc6fd009d4c68fd5560a48a0b906cabd (
r2/,metal-amd/)T14: https://github.com/ToyOSOrg/evidence/tree/ac6b89a373b6dc24fbdf64ba1089a8c2d8c3b708/metal-t14 (the same boots serve fileserver: a volume's cache keeps an eighth of physical memory clean #1099)
Host, at 59dd113 (rebased onto a5b330f); the head 2a599bb is the same code rebased onto 28fdfd7, its only change under
issues/:cargo run -- --ci host, exit 0, 105 steps green (r2/ci-host.log). Theerror: test failedlines are all inside the CI'scontrolsections.New host tests:
fs::tests::a_sync_with_nothing_uncommitted_writes_nothing: a clean sync writes no block and asks for exactly one device sync; a later change commits and remounts.fs::tests::a_sync_after_a_refused_one_commits_again: a commit whose superblock was written and whose sync was refused is written again by the next.data::tests::a_sync_with_nothing_new_asks_the_disk_for_nothing: through the file server's cache, a clean sync issues no disk write and no Flush, and a write over a page the file holds is written and flushed.Red controls, each applied as a patch and restored in the same run:
same_commitalways false (r2/red.patch): both "nothing" tests fail.red/red2.patch): the refused-sync test fails.Metal, base four boots of osbench: persist and the disk's cached rows run on ToyOS's DATA, the bench's NVMe #1097 at 069cbbe against two boots of 34409c9, which is osbench: persist and the disk's cached rows run on ToyOS's DATA, the bench's NVMe #1097's head plus this change and fileserver: a volume's cache keeps an eighth of physical memory clean #1099. All passed.
metal-amd/):persist.fsync_clean1,225,111–1,263,124 ns down to 39,787 and 38,645 ns.pr-1099metal-t14/): 464,505–3,418,250 ns down to 15,898 and 16,124 ns.bcachefscode in that treatment is this PR's;mem.anon.mapandproc.spawn_wait.large, which moved on AMD, do not callMounted::syncand are fileserver: a volume's cache keeps an eighth of physical memory clean #1099's to explain.Coordination and risks
issues/an-fsync-of-a-served-file-syncs-its-whole-volume.mdis not changed. The format commits one root, so a one-file fsync would still commit the whole tree, and no persist row needs a narrower one.🤖 Generated with Claude Code