Skip to content

Performance

These results compare flexBlock with the NVMe it runs on. One instance, one drive: a raw partition of the local NVMe against the flexBlock device whose cache is a sibling partition of the same drive, plus cold reads from the bucket and PostgreSQL and MariaDB benchmarks on both.

Each fio number is one 30-second run after a 5-second ramp, except the cold sequential reads, which cover the region once. Each database number is a full pgbench or sysbench run.

HeadlineResult
Sequential throughput100% of raw NVMe
Warm random 4 KiB read IOPS605k
Cold sequential read from S3, prefetch 642.6 GiB/s
MariaDB write-only OLTP117% of raw NVMe

fio over io_uring with O_DIRECT on a 20 GiB region that is resident in the cache and already flushed: the steady state of a working set that fits.

TestRaw NVMeflexBlockRatio
Sequential read, 1 MiB, QD324,0694,069100%
Sequential write, 1 MiB, QD323,2123,213100%
Random read, 128 KiB, QD164,1174,06999%
TestRaw NVMeflexBlockRatio
Read, QD1 (74 vs 83 µs)13,33111,80589%
Read, QD32, one job336,876242,01772%
Read, QD32, four jobs584,602604,909103%
Write, QD1 (16 vs 19 µs)56,92948,07484%
Write, QD32, one job330,675298,44390%
Write, QD32, four jobs330,678330,035100%
O_SYNC write, QD1 (17 vs 23 µs)55,64641,07474%

Mixed 70/30 read/write, 16 KiB, QD16, four jobs

Section titled “Mixed 70/30 read/write, 16 KiB, QD16, four jobs”
TestRaw NVMeflexBlockRatio
Total IOPS219,633182,21283%

PostgreSQL 18 on XFS, scale 500, 32 clients, an fsync on every commit. TPC-B mean latency was 0.58 ms raw and 0.69 ms on flexBlock.

WorkloadRaw NVMeflexBlockRatio
Load 50 M accounts (scale 500)20.4 s23.1 s88%
TPC-B read/write, 120 s, tps55,27646,04583%
Select-only, 60 s, tps424,940417,71798%

MariaDB 11.8 on XFS, sysbench OLTP, 16 tables of 1 M rows, 32 threads, a redo flush on every commit. Read/write p95 latency was 3.89 ms raw and 3.49 ms on flexBlock.

WorkloadRaw NVMeflexBlockRatio
Load 16 M rows13.8 s13.6 s101%
OLTP read/write, 120 s, tps13,64814,077103%
OLTP read-only, 60 s, tps20,78819,96396%
OLTP write-only, 60 s, tps49,54257,912117%

After detach and attach the cache is empty, so every read is a miss served from the bucket. Sequential reads trigger the read prefetch. A random 4 KiB miss fetches its whole chunk, so the region warms within the run and later reads hit NVMe.

Cold readResult
Seq 1 MiB QD32, 20 GiB, prefetch 642,637 MiB/s
Same read, default prefetch 321,449 MiB/s
Mean / p99 latency, prefetch 6412 / 39 ms
Mean / p99 latency, default22 / 76 ms
Chunks fetched, bytes moved10,240, 17.5 GiB
Random 4 KiB QD32, 30 s, cold157,717 IOPS
Region fully resident afterunder 30 s

The 2.6 GiB/s cold figure uses --read-prefetch 64 and --concurrent-downloads 128. The defaults, which favor mixed workloads because deep prefetch competes with foreground I/O, gave 1.4 GiB/s on the same region in the same session.

Compression helps a cold read as far as the data compresses. This run’s random buffers barely compressed, so 17.5 GiB crossed the network for 20 GiB read, while an earlier run whose data compressed two to one moved 9.6 GiB.

With the working set resident in the cache, flexBlock performs like the NVMe drive itself:

  • Sequential throughput and multi-queue random reads and writes match the raw partition within noise.
  • The mixed read/write run reaches 83% of raw.
  • The read-only database runs stay within a few percent of raw.
  • MariaDB write-only beats raw by 17%, and pgbench’s load and TPC-B reach 88% and 83%.

Every write is acknowledged from NVMe and every warm read is served from it, so bandwidth-bound and multi-queue workloads see the drive, not the daemon. Each request does make a short round trip through the kernel to the flexBlock daemon, a few microseconds. That cost shows only at queue depth one or on a single submitting thread: about 9 µs on a lone 4 KiB read, 72% of raw for one thread at queue depth 32, and 74% for O_SYNC writes. More queues close the gap.

Cold reads are bounded by the object store and the prefetch depth, not by the cache device.

Latency in microseconds, mean / p99, from the same runs. Queue-depth-32 latencies are dominated by queueing, so compare like with like: the QD1 rows are the per-request cost, and the mixed rows show tail latency while the flush loop uploads in the background.

TestRaw NVMeflexBlock
Sequential read, 1 MiB, QD327,835 / 10,0277,843 / 17,170
Sequential write, 1 MiB, QD329,918 / 13,6979,927 / 22,151
Random read, 4 KiB, QD174 / 19383 / 205
Random read, 4 KiB, QD3294 / 138131 / 224
Random read, 4 KiB, QD32 ×4218 / 415210 / 280
Random write, 4 KiB, QD116 / 3219 / 26
Random write, 4 KiB, QD3295 / 224106 / 222
Random write, 4 KiB, QD32 ×4386 / 610386 / 485
Mixed 70/30, 16 KiB, reads320 / 618393 / 2,768
Mixed 70/30, 16 KiB, writes217 / 415246 / 2,342
Random read, 128 KiB, QD16482 / 692487 / 593
O_SYNC write, 4 KiB, QD117 / 3123 / 99

Tail latency in the mixed run is where write-back is visible: while dirty chunks upload, the flush loop reads them from the same NVMe, and a fraction of requests wait behind that traffic.

Commits stay cheap. This cache drive reports a write-through cache, so flexBlock completes writes durably and the kernel sends no FLUSH or FUA commands: a commit costs its writes and no cache flush.

The same build ran a checksum-verified fuzz suite on the S3-backed volume before the measurements, with fio verifying a CRC32C header on every block and zero error counters throughout.

  • Random sizes and offsets. 4 KiB to 1 MiB writes over 6 GiB, two jobs, read back and verified.
  • Mixed read/write at 4 KiB to 256 KiB with a verification backlog, four jobs, 60 s.
  • Trim, write-zeroes, O_SYNC. Trims mixed with writes and verified to read as zeros; native WRITE_ZEROES including an unaligned range across chunk boundaries; 4 KiB O_SYNC writes.
  • Crash mid-write. The daemon was killed with SIGKILL 12 s into a random write run. The kernel held the queued I/O, the restarted daemon recovered the device, and fio completed and verified every block.
  • Cold verification. After detach and attach, every region was read back from the bucket and verified.
  • Database integrity. Both databases were reopened cold from the bucket. PostgreSQL passed pg_amcheck over every table and index with heap-to-index cross-checks, its account, teller and branch balances agreed to the unit, and a further run of 1,014,125 transactions matched its recorded history exactly. MariaDB passed CHECK TABLE EXTENDED on all 16 tables and their checksums matched the ones taken before the restart.
ItemDetail
HostAWS i7i.4xlarge, 16 vCPUs, 128 GiB, Linux 7.0. One local NVMe drive holds the raw test partition and the flexBlock cache. Object storage is Amazon S3 in the instance’s region.
fiofio 3.41, io_uring, O_DIRECT, 20 GiB region, 5 s ramp then 30 s per row; both devices written once sequentially before the read tests. Warm: region written through flexBlock and fully flushed, flush loop active. Cold: daemon stopped, cache detached and re-attached empty, daemon restarted; the sequential reads cover the whole region once.
pgbenchPostgreSQL 18 on XFS, data and WAL on the device under test, page checksums on, scale 500, 32 clients on 16 threads, shared_buffers 2 GB, wal_level minimal, TPC-B 120 s, select-only 60 s.
sysbenchsysbench 1.0.20 OLTP against MariaDB 11.8 on the same XFS, 16 tables of 1 M rows, 32 threads, 2 GB buffer pool, redo flushed on every commit, O_DIRECT data files; read/write 120 s, read-only and write-only 60 s.
flexBlockflexBlock 1.0.0 defaults (2 MiB chunks, zstd, zero copy, one queue per physical core), a 200 GiB volume on a 250 GiB cache partition.