Skip to content

libext: ext4 sequential-read performance (read-ahead + async prefetch) - #1447

Draft
gburd wants to merge 9 commits into
cloudius-systems:masterfrom
gburd:pr/ext4-perf
Draft

libext: ext4 sequential-read performance (read-ahead + async prefetch)#1447
gburd wants to merge 9 commits into
cloudius-systems:masterfrom
gburd:pr/ext4-perf

Conversation

@gburd

@gburd gburd commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Important

Draft: one previously cited number has been withdrawn as invalid. See "Withdrawn" below. The read results are retained but were measured on a base predating af27ba4, so they are pending re-validation. Not ready to merge until re-measured.

#1431 is now merged upstream as af27ba4, so this no longer depends on it and stands alone. Rebased onto current master (0e34e4d); the two fsync/dtime commits that used to sit at the base of this branch are gone, since upstream now carries them as af27ba4 and bf16817.

Rebased branch, since force-push is not available to me on this PR's head branch:

  • branch: pr/ext4-perf-v2
  • head: 8f95be01e2ecf9192b279aacf0370fd2e1157379
  • merges clean into master: git merge-tree --write-tree --messages upstream/master pr/ext4-perf-v2 exits 0 with no conflicts

The gap this addresses

ext_read issued one synchronous bio per read() with no prefetch (ext4_blocks_get_direct bypasses lwext4's block cache), so a stream of sequential reads left the NVMe queue idle during application compute. Linux keeps the device busy with multi-window async read-ahead. Baseline ext4 sequential read was roughly 560 MB/s against Linux's ~2300 MB/s on the same device, while 4K random read was already at or above Linux.

Changes (6 commits)

  1. benchmark (tst-ext4-bench): sequential write+fsync / sequential read / 4K random, reported in MB/s, filesystem-agnostic. Writes an absolute-offset pattern and verifies every byte on read, so it doubles as a read-path correctness test.
  2. direct user-buffer read/write: skip the per-op bounce allocation and extra uiomove copy on the single-iovec fast path; also fixes a latent free()/free_contiguous_aligned() mismatch. Correctness cleanup, not the bottleneck.
  3. synchronous 1 MiB read-ahead: on a sequential pattern, read one window and serve subsequent in-window reads from a memcpy.
  4. async double-buffered prefetch: two windows plus a per-vnode prefetch worker that fills next while the application consumes cur. A worker thread (not raw bio_done) reuses ext_internal_read verbatim; writes and truncation invalidate, ~ext_vdata joins the worker and frees.
  5. deep multi-window prefetch ring: generalises to an N-window ring with M workers.
  6. ring tuning to N=4/M=4, with RA_WINDOWS_N/RA_WORKERS_M overridable at build time.

Read results (pre-af27ba4b7 base, retained but pending re-validation)

Measured A/B on a 2-socket x86-64 bare-metal host booting OSv under KVM against local NVMe, ext4 4K/no-journal, median of 3, no VERIFY FAIL:

block size sync read-ahead async prefetch speedup
64 KiB ~505 MB/s ~1248 MB/s 2.5x
128 KiB ~534 MB/s ~1260 MB/s 2.4x
256 KiB ~600 MB/s ~1250 MB/s 2.1x

Reference on the same NVMe: Linux ext4 buffered read ~2300 MB/s; NVMe single-stream O_DIRECT ceiling 1.5 GB/s at 128K, 1.9 GB/s at 1M. The remaining gap to Linux is queue depth: Linux prefetches many windows, this prefetches a bounded ring, and virtio-blk's make_request() serialises submission under a single lock, which caps effective device queue depth at roughly 2 to 4.

Why these read numbers are probably still valid on the current base, stated as an argument a reviewer can check rather than something to take on trust:

  • af27ba4 changed ext_fsync from vop_nullop to a flush of lwext4's block cache, and added vop_cache for mmap page-cache warming.
  • The read path does not go through that block cache. ext_internal_read reaches the device via ext4_blocks_get_direct -> ext4_bdif_bread, which addresses the block device directly and never consults the cache ext_fsync flushes.
  • The benchmark's read phases use read()/pread(), not mmap, so vop_cache is not on their path either.
  • fsync() is called only inside bench_write's timing window (tst-ext4-bench.cc:56); neither read phase calls it.

Caveat that keeps this "probably" rather than "confirmed": the benchmark writes a file and then reads that same file. With a real fsync the dirty state at read time is different, so the read number could shift second-order even though the mechanism above does not touch the read path. Likely intact is not measured.

Withdrawn

The earlier revision of this PR cited roughly 620 MB/s for sequential write with fsync, and used it to claim write throughput was at parity with Linux. Both the number and the parity claim are withdrawn.

That measurement was taken on a base where ext_fsync was #define ext_fsync ((vnop_fsync_t)vop_nullop), so fsync() returned without flushing anything and the benchmark's timer stopped before the data was durable. It was not measuring durable write throughput. Since ext_fsync is now a real block-cache flush (af27ba4), the figure is invalid on the current base and I am not restating it with a caveat. There is currently no supported claim about write throughput in this PR.

Nothing in this series targets the write path, so this is a withdrawn measurement rather than a regression, but the honest position is that write performance on the current base is unmeasured.

To re-validate

On a 2-socket x86-64 bare-metal host (KVM, local NVMe, ext4 4K/no-journal), with a base containing af27ba4:

  1. The 64K / 128K / 256K sequential-read A/B sweep, median of 3, confirming the read speedup holds now that fsync is real.
  2. A fresh sequential-write-with-real-fsync number, to replace the withdrawn one and settle whether write is at, above, or below Linux parity.

Staying in draft until both are done.

@gburd
gburd force-pushed the pr/ext4-perf branch 2 times, most recently from 103c0ed to 3e8cfd5 Compare July 31, 2026 13:09
@wkozaczuk

Copy link
Copy Markdown
Collaborator

I see it is a draft that depends on #1431, which I hope to merge soon. I am really surprised by the performance results you list here - look very, very good compared to Linux. I am interested in what exactly your setup is; you mention nmve - emulated one? Do you compare to a Linux guest?

On my machine, when I run the fio read test with ext and compare it to zfs on OSv, I get pretty terrible results:

  • zfs
./scripts/build image=fio fs=zfs 

./scripts/run.py -e '/fio --thread --name=fiotest --filename=/tmp/test --numjobs=4 --rw=read --size=128M' 
OSv v0.57.0-383-g894d5879
eth0: 192.168.122.15
Booted up in 264.45 ms
Cmdline: /fio --thread --name=fiotest --filename=/tmp/test --numjobs=4 --rw=read --size=128M
fiotest: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=psync, iodepth=1
...
fio-3.29-dirty
Starting 4 threads
fiotest: Laying out IO file (1 file / 128MiB)
...
Run status group 0 (all jobs):
   READ: bw=1463MiB/s (1534MB/s), 366MiB/s-444MiB/s (383MB/s-466MB/s), io=512MiB (537MB), run=288-350msec
  • ext
./scripts/build image=fio fs=ext
 
./scripts/run.py -e '/fio --thread --name=fiotest --filename=/tmp/test --numjobs=4 --rw=read --size=128M' 
OSv v0.57.0-383-g894d5879
eth0: 192.168.122.15
Booted up in 189.80 ms
Cmdline: /fio --thread --name=fiotest --filename=/tmp/test --numjobs=4 --rw=read --size=128M
fiotest: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=psync, iodepth=1
...
fio-3.29-dirty
Starting 4 threads
fiotest: Laying out IO file (1 file / 128MiB)
...
Run status group 0 (all jobs):
   READ: bw=1113KiB/s (1140kB/s), 278KiB/s-278KiB/s (285kB/s-285kB/s), io=512MiB (537MB), run=471055-471062msec

1.5 K slower

When I increase the test block size to 128K from the default 4K, it gets a little better:

  • zfs
READ: bw=7314MiB/s (7670MB/s), 1829MiB/s-3200MiB/s (1917MB/s-3355MB/s), io=512MiB (537MB), run=40-70msec

-ext

READ: bw=40.4MiB/s (42.3MB/s), 10.1MiB/s-10.1MiB/s (10.6MB/s-10.6MB/s), io=512MiB (537MB), run=12667-12677msec

150-200 times slower.

You mention the baseline. Is it what you see before your changes or after?

@gburd

gburd commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for digging in, and the reconciliation is worth doing carefully because I think we are measuring two different things.

The setup

The numbers in the PR body are an A/B of ext-before vs ext-after this branch's four perf commits, single-stream, on the same disk:

  • Host: a 2-socket x86-64 bare-metal machine, OSv booting under KVM/QEMU with a local NVMe backing (no throughput cap), virtio-blk.
  • Guest FS: ext4 formatted -b 4096 with the journal off, mounted via lwext4.
  • Workload: tst-ext4-bench (in commit 1 of this branch), which does one sequential read() stream front-to-back over a single file at a fixed block size (64K/128K/256K), and verifies every byte. Median of 3 runs.
  • Linux reference: the identical file/pattern read on a Linux guest ext4 on the same NVMe, for the ~2300 MB/s buffered-read number.

So "baseline" in the table means ext on this branch's base (#1431), before the read-ahead/prefetch commits vs ext with them. It is not ext-vs-zfs.

Why your fio run shows the opposite

I think there are three separate reasons your fio result and this PR's result diverge, and none of them contradict each other:

  1. The build you tested has none of this PR's changes. Your console shows OSv v0.57.0-383-g894d5879, and 894d5879 is exactly the HEAD of libext: real fsync + page-cache bridge for ext2/3/4 #1431 (libext: set inode deletion time...), i.e. the base PR. The four read-ahead/prefetch commits live only on top of that, in libext: ext4 sequential-read performance (read-ahead + async prefetch) #1447. So your fs=ext run measured the un-prefetched path -- which is precisely the ~40 MB/s @128k "before" case this PR is trying to fix. In that sense your number corroborates the PR: baseline ext seq read is bad, and that is the whole motivation. To see the after-number you would need to build with this branch's tip, not libext: real fsync + page-cache bridge for ext2/3/4 #1431's.

  2. --numjobs=4 on one file is a very different access pattern from the bench. fio ... --numjobs=4 --filename=/tmp/test runs four threads all reading the same vnode. The read-ahead in this PR is a per-vnode single prefetch window (cur/next), so four concurrent streams on one vnode contend for and repeatedly reposition that one window instead of each getting a clean sequential run -- worst case for a single-window prefetcher, especially at the 4K default block size. tst-ext4-bench is single-stream, which is what the window is designed for. A single-numjobs, 128K psync fio would be the apples-to-apples read of this branch's tip.

  3. ext != zfs; the fix is lwext4-specific. Your fast number is zfs, which has the ARC plus its own multi-stream prefetch, so it is not surprising it beats un-prefetched lwext4 by a large factor. This PR does not touch the zfs path at all -- it is entirely in ext_read/lwext4. So "zfs is much faster than ext on OSv" is true and expected, and it does not bear on the ext-before/ext-after delta this PR claims. The remaining ext-vs-zfs gap is real and separate work (deeper prefetch pipeline, routing ext reads through the page-cache read path, a zero-copy borrow-and-pin bridge like the ARC one).

Suggested apples-to-apples

If you build this branch's tip and run tst-ext4-bench on your NVMe I would expect the ~1250 MB/s single-stream after-number to reproduce (or tell us where the host differs). A single-stream fio --numjobs=1 --bs=128k --ioengine=psync on the branch tip should land in the same ballpark; the --numjobs=4-same-file case will stay low until the prefetch pipeline is per-stream/deeper, which is the documented upgrade path here.

I do not want to overclaim: this closes most of the single-stream sequential gap for lwext4, it exceeds the 128K O_DIRECT single-stream ceiling on this host, and it is still ~within 2x of Linux buffered read and behind zfs. Multi-stream and the ext-vs-zfs gap are follow-ups.

gburd added 9 commits August 7, 2026 07:59
Two gaps in the ext (lwext4) filesystem module for durable, real-world use.

1. fsync durability.  ext mounts with lwext4 block-cache write-back enabled, so
   a write only reaches the disk when the block cache is flushed -- but
   vop_fsync was vop_nullop, so fsync(2)/fdatasync(2) on an ext file persisted
   nothing.  Implement ext_fsync() to flush the device's block cache (like
   ext_sync does at unmount), making written data durable.  The cache is shared
   per device, so this persists the file along with any other dirty buffers,
   which is correct if slightly more than the theoretical per-inode minimum.

2. Page-cache bridge (vop_cache).  The vop_cache slot was null, so mmap faults
   on ext files went through the block layer on every fault, unlike ROFS and
   ZFS which populate the shared page cache.  Add ext_map_cached_page(): on a
   VOP_CACHE call it reads one page-aligned page of file data into a freshly
   allocated page and hands it to pagecache::map_read_cached_page(), warming the
   read cache so subsequent faults and readahead are served from it.  This is an
   allocate-and-copy bridge (lwext4's block-cache buffers are not
   page-aligned/shareable the way ROFS's read-around cache is); a zero-copy
   borrow-and-pin bridge like the ZFS ARC one can follow if it shows up hot.

Because the module is built with -fno-rtti and <osv/pagecache.hh> pulls in
<osv/trace.hh> (which uses typeid), the two page-cache symbols we need are
declared minimally instead of including the heavy header (the uio carries the
hashkey opaquely, so its layout is never needed).

Add tests/tst-ext4-rw.cc: mmap a pre-populated ext4 file and verify the pattern
survives the vop_cache bridge (first fault + cached re-read), then write a file,
fsync it, and read it back (plus fdatasync and a fresh re-open).  Verified on
OSv under KVM with an ext4 second disk (created with mkfs.ext4 -b 4096
-O ^64bit,^metadata_csum, which lwext4 supports).

Known pre-existing limitation (not introduced here, out of scope for this PR):
libext's inode-delete path does not set the inode dtime, so Linux e2fsck flags
"deleted inode has zero dtime" on a disk after OSv deletes a file.  A fresh disk
that OSv only reads/writes+fsyncs (no delete) fscks clean.  Tracked as a
follow-up in the ext write-path correctness work.
Follow-up to the fsync/page-cache work: libext's inode-delete path freed the
inode from the bitmap but never set its on-disk deletion time (dtime), so after
OSv created and deleted a file, Linux e2fsck flagged "Deleted inode NN has zero
dtime" and reported the filesystem as still having errors.

lwext4's own delete path marks the inode with ext4_inode_set_del_time(inode,
-1L) before ext4_fs_free_inode(), but that symbol is not exported from
liblwext4.so.  Add a small ext_mark_inode_deleted() helper that sets
inode->deletion_time = 0xffffffff directly (byte-order invariant, so no
to_le32() needed) and call it before each ext4_fs_free_inode() in the module
(unlink, rmdir, delete-on-last-close, delete-outstanding-on-unmount, and the
dir_link allocation-rollback path).

Verified: after OSv boots an ext4 second disk, mmaps/reads a file, writes and
fsyncs another, then deletes it, `e2fsck -n -f` on the disk now exits 0 (clean),
where before it reported the zero-dtime error.  tst-ext4-rw still passes.
A filesystem-agnostic micro-benchmark (tst-ext4-bench) measuring sequential
write+fsync, sequential read, and 4K random read throughput in MB/s, so the
ext4/libext path can be compared A/B against raw virtio-blk and against Linux
ext4 on the same storage. Point its argument at any mount (ext, zfs, rofs) for
a cross-filesystem comparison.
ext_read allocated a full-size aligned bounce buffer, had lwext4 read into it,
then uiomove()'d it to the caller - a per-read malloc plus a second full-size
memcpy on every read; ext_write mirrored this in reverse.  On the common path
(a single contiguous iovec) hand lwext4 the caller's buffer directly.

This removes the allocation and one memcpy per read/write and fixes a latent
free()/free_contiguous_aligned() mismatch in both error paths.  It is a
correctness/cleanup change: an A/B on local NVMe showed throughput
unchanged (the memcpy is not the bottleneck), which confirms the sequential-read
gap vs Linux is the absence of async read-ahead, not copy overhead - that is a
separate, larger change (see the ext4 perf notes).
The local-NVMe A/B showed ext4 sequential reads at ~33% of Linux while writes
were at parity and random reads ~86%. The gap is that ext_read issues one
synchronous bio per read() with no prefetch (ext4_blocks_get_direct bypasses
lwext4's block cache), so a stream of small sequential reads cannot keep the
device pipeline busy - larger reads were measurably faster purely from
amortizing the per-read() round-trip.

Add a per-vnode sequential read-ahead cache (in ext_vdata): on a detected
sequential single-iovec read smaller than the window, fill a 1 MiB window from
disk once and serve that read and subsequent in-window reads from it (a memcpy,
no I/O). This turns N small synchronous reads into 1 large read + N copies.
The window is invalidated on any write to the file and freed when the vnode
goes inactive; access is serialized by a per-vnode mutex.

tst-ext4-bench now writes an absolute-offset pattern and verifies every byte on
the sequential read, so the benchmark doubles as a read-ahead correctness test
(no VERIFY FAIL = the cache returns correct data across window boundaries).
The synchronous 1 MiB read-ahead (6a7990d) reads a window, lets the app
consume it, then reads the next window - so the NVMe queue goes idle
during the app's compute. Linux keeps the device busy via async prefetch.

Add double buffering: two 1 MiB windows (cur + next) and a per-vnode
worker pthread. While the app consumes cur (served by memcpy), the worker
prefetches next in the background. When a read crosses out of cur, next is
promoted to cur and the following window is kicked off. On a sequential
pattern the next window is already in memory, keeping the device queue
full and hiding read latency behind compute.

Correctness: writes and truncation cancel any in-flight prefetch and drop
both windows (ext_ra_invalidate); the worker uses its own inode_ref
(lwext4 locks its block cache internally); ~ext_vdata joins the worker and
frees both buffers on vnode inactive. The tst-ext4-bench byte-for-byte
verify passes (no VERIFY FAIL) for 64K/128K/256K reads.
The async double-buffered read-ahead only kept ~1-2 windows ahead of the
consumer, so a single sequential stream drove the NVMe queue to depth ~1-2
and topped out ~1.25 GB/s vs Linux's ~2.3 GB/s (Linux issues many concurrent
readahead requests, keeping the device queue deep).

Replace the two-window (cur/next) scheme with a ring of RA_WINDOWS windows
(256 KiB x 8 = 2 MiB per open file, same memory as before) filled by a pool
of RA_WORKERS worker pthreads. Each worker calls the existing synchronous
ext_internal_read() into a ring slot, so RA_WORKERS fills are in flight at
once -> device queue depth ~RA_WORKERS. As soon as the consumer drains a
window the ring slides forward and re-arms that slot for the window
RA_WINDOWS ahead, keeping the pipeline full.

Correctness: per-slot state (EMPTY/FILLING/READY) + a ring generation. All
worker-shared fields are under ra_cvmtx; the read path holds ra_lock (outer)
and takes ra_cvmtx to inspect slots. Workers never take ra_lock, so no
deadlock. Seek/write/truncate bump the generation and drain in-flight fills
before reusing buffers (no use-after-free); stale worker results whose
generation no longer matches are discarded. Reads larger than a window or
spanning two windows fall through to the direct read path. ~ext_vdata
broadcasts shutdown, joins all workers, then frees the buffers.

Reuses lwext4's existing bio path (ext_internal_read -> ext4_blocks_get_direct
-> one bio + bio_wait) rather than issuing raw bios, which would mean
re-implementing lwext4's extent->block mapping; a worker pool blocked in
bio_wait maps directly to device queue depth and keeps all that code correct.

Verified with tests/tst-ext4-bench.cc (absolute-offset byte verification) at
bs 4K/64K/128K/256K/262143/1M and files smaller than the ring: no VERIFY
FAIL.
Sweep on a local-NVMe host, ext4 4K/no-journal, O_DIRECT so reads hit the
device, 512 MiB, median of 3):

  config        64K      128K     256K   prefetch mem/file
  N=1  M=1     ~0.69    ~0.85    ~0.85     256 KiB   (~QD1)
  N=4  M=2     ~1.20    -        ~1.28       1 MiB
  N=4  M=4     ~1.26    ~1.23    ~1.29       1 MiB   <- knee
  N=8  M=4     ~1.28    ~1.28    ~1.31       2 MiB
  N=8  M=8     ~1.28    ~1.28    ~1.29       2 MiB
  N=16 M=16    ~1.25    -        ~1.21       4 MiB

Throughput climbs steeply from N=1/M=1 to the knee at N=4/M=4 (~1.28 GB/s) and
then plateaus: deeper rings or more workers give no further gain. The cap is
downstream of this code -- OSv's virtio-blk make_request() serialises submission
under a single _lock and a single virtqueue, so effective device queue depth
tops out at ~2-4 regardless of how many prefetch windows are in flight. (Linux
fio O_DIRECT on the same raw NVMe: ~1.32 GB/s @128k QD1, ~1.68 GB/s @128k QD>=2,
i.e. the device itself saturates at QD2; the remaining OSv gap is the guest
virtio-blk path, not prefetch depth.)

So N=4/M=4 is the sweet spot: same throughput as the deeper configs at half the
memory (1 MiB vs 2 MiB per open file). It also matches the prior 2-window async
design (~1.25 GB/s here) while using a general N-window ring. Make N and M
build-time tunables (-DRA_WINDOWS_N / -DRA_WORKERS_M via $(RA_FLAGS)) so the
sweep is reproducible when the virtio-blk submission path is later parallelised.

No VERIFY FAIL at any config or block size (4K/64K/128K/256K/262143/1M, files
smaller than the ring).
@gburd

gburd commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto master; converted the test entries to standalone ext-only-tests += tst-ext4-rw.so / ext-only-tests += tst-ext4-bench.so lines so they no longer conflict in modules/tests/Makefile (see #1469). (Stacked on #1431.)

@gburd gburd changed the title [STACKED on #1431] libext: ext4 sequential-read performance (read-ahead + async prefetch) libext: ext4 sequential-read performance (read-ahead + async prefetch) Sep 5, 2026
@gburd

gburd commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current master (0e34e4d) and the body rewritten. Summary of what changed and why one number is gone.

Standalone now. #1431 is merged upstream as af27ba4, so the two fsync/dtime commits that used to sit at the base of this branch are dropped and this reduces to the six performance commits. Title prefix removed.

Rebased branch. Force-push is not available to me on this PR's head branch, so the rebase is on a new branch rather than an update in place:

  • branch: pr/ext4-perf-v2
  • head: 8f95be01e2ecf9192b279aacf0370fd2e1157379 (signed)
  • verified clean: git merge-tree --write-tree --messages upstream/master pr/ext4-perf-v2 exits 0, no conflicts

Retarget this PR to that branch, or say the word and I will open a fresh one. Three things conflicted against current master and are resolved:

  • .gitignore and tests/tst-ext4-rw.cc (add/add): both came from the two libext: real fsync + page-cache bridge for ext2/3/4 #1431 commits, which are dropped because upstream now carries them. tst-ext4-rw.cc is upstream's file now and this branch does not re-add it.
  • modules/libext/ext_vnops.cc: resolved so upstream's merged work coexists with the prefetch ring. The vnops table is byte-identical to master's, and ext_fsync, ext_map_cached_page, the inode refcounting from c085446 and the dtime marking from bf16817 are all intact on top of the ring. Compiles clean under the module's own flags (-std=gnu++11 -Wall -fno-exceptions -fno-rtti -O2) against current OSv and lwext4 headers.

One number withdrawn. The earlier revision cited roughly 620 MB/s for sequential write with fsync and used it to claim write parity with Linux. That was measured when ext_fsync was vop_nullop, so fsync() flushed nothing and the timer stopped before the data was durable. It was not measuring durable write throughput, and af27ba4 makes ext_fsync a real block-cache flush. I have deleted the number and the parity claim rather than restating them with a caveat. Write performance on the current base is unmeasured.

Read numbers retained, explicitly pending re-validation. They were taken on a pre-af27ba4b7 base. I checked whether af27ba4 could have flattered them and believe it cannot, for reasons a reviewer can verify: ext_internal_read reaches the device through ext4_blocks_get_direct -> ext4_bdif_bread, which never consults the block cache that ext_fsync flushes; the read phases use read()/pread() rather than mmap, so vop_cache is not on their path; and fsync() is called only inside bench_write's timing window. The residual caveat is that the benchmark reads back the same file it just wrote, so real-fsync dirty state at read time could move the read number second-order. Likely intact is not measured, so the label stays.

Staying in draft until both are re-run on a 2-socket bare-metal host with local NVMe on a base containing af27ba4: the 64K/128K/256K read sweep, and a fresh write-with-real-fsync number. I would rather leave this draft with an honest gap than mark it ready on a number I know was measured against a no-op fsync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants