Repository navigation
perf(umbp): port the pure-SSD/multi-drive work onto the backend-agnostic SSD path - #554
Merged
TianDi101 merged 4 commits intoAug 14, 2026
Merged
Conversation
…tic SSD path The backend-agnostic refactor unwired SSD (Phase 0) and re-landed it as SsdBackend : MediumBackend (#542, #543). That re-landing was a clean-room implementation and did not carry forward the performance work from feat/umbp-pure-ssd-multidrive, leaving a structurally correct but performance-naive tier: single drive, buffered I/O, single-threaded CRC, one device read per key, no read coalescing, no instrumentation. This ports that work onto the new backend boundary. Below the backend boundary (ports near-verbatim): * Record format v3 — records and values padded to a 4096B boundary, which is the precondition for O_DIRECT. Capacity is now charged in PADDED on-disk bytes, so a store of small values can no longer overrun capacity_bytes (16x for 512B values, <0.1% for the multi-MiB KV pages this tier normally holds). kFlagNoCrc + KeyMeta::crc_valid keep a store written with checksums off readable by a process with them on. * Direct I/O (UMBP_SSD_DIRECT_IO) with a startup probe that falls back to buffered on tmpfs/overlayfs rather than failing to come up. Buffered I/O can serve an entire working set from page cache and move zero bytes to the device, which makes drive-count and DRAM-vs-SSD comparisons meaningless. * DropUnparsedTailsLocked: startup repair for stale-version and torn-tail records, which would otherwise make every later append unreadable. * ShardedSsdTier + MakeFileSsdBackend — comma-separated UMBP_SSD_DIR fans one logical tier across N drives with capacity-aware placement and parallel per-shard IO. * ParallelFor over UMBP_SSD_TIER_IO_THREADS for the CRC/assembly phases, matching the DRAM tier's 4-thread default so a comparison is not silently 4 threads against 1. * ssd_perf.h — the [SsdPerf/tier|peer] breakdown behind UMBP_SSD_TIMING. At the backend boundary (adapted, not copied): * Single-flight read coalescing in PeerSsdManager. Ported memcpy fan-out only; the RDMA-WRITE push path depends on the peer becoming an RDMA initiator via GetPeerInfo reverse-registration and SSD-specific batch RPCs, neither of which exists in the agnostic proto. * PeerSsdManager::WriteBatch / PrepareReadBatch, with SsdBackend:: BatchCommit / BatchResolve restructured from per-key loops into one-lock/one-batch/one-lock passes. Without this a multi-drive tier could never be saturated: the per-key path costs one device read per key and idles every drive but one. * MediumBackend::Counters() — a defaulted-empty virtual, so SSD's metrics reach Prometheus without a dynamic_cast<SsdBackend*> in the metrics path. PoolClient forwards whatever a backend names and never learns which medium produced it. * Hugepage-backed staging arena and UMBP_PEER_{MIN,MAX}_POLLERS. Not ported, and why: UMBP_ROUTE_PUT_SSD_MODE and IsPureSsdMode() have no analogue to port — `medium = SSD` already makes a node an SSD put target, and the routing plane no longer ranks tiers. Round-robin put selection, the RDMA read fan-out push, and the copy-pipeline metrics remain open. Build + 44/44 ctest green (the SSD tests need the ionic/libibverbs bind-mounts; without them 4 integration tests fail "no active RDMA device", which is environmental). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…easures Two bugs that silently invalidated any SSD number the bench produced. 1. os.makedirs(cfg.ssd.storage_dir) with a comma-separated multi-drive path created ONE directory literally named "a,b" instead of one per drive. 2. The bench builds UMBPConfig directly and never calls FromEnvironment(), so none of the UMBP_SSD_* knobs reached it. direct_io in particular stayed off, and buffered reads can be served entirely from page cache — moving zero bytes to the device and making any drive-count or DRAM-vs-SSD comparison meaningless. Applied through the pybind fields instead. Measured on n06-21 (2x Micron 7450, 32 GiB dataset, 2 MiB values, direct I/O): without this the "SSD" tier reported 95 GB/s, within 1% of DRAM. With it, 10.2 GiB/s end-to-end and 767 GiB of verified device reads on the host counters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The SSD tier is itself a cache, so buffered I/O gives it a second, unmanaged DRAM cache underneath. That is not a free bonus: it evicts host memory the DRAM tier and the engine are also competing for, it makes the node report DRAM it is actually consuming as free, and it makes any measurement of drive behaviour meaningless -- a buffered run can serve an entire working set from page cache and move ZERO bytes to the device. Safe as a default because the failure mode is already handled: SSDTier probes O_DIRECT at startup and falls back to buffered with a warning on a filesystem that cannot do it (tmpfs/overlayfs), rather than failing to come up. It also pairs with the existing io.backend default of io_uring; the queue-depth-1 POSIX+direct combination keeps its own warning. Set UMBP_SSD_DIRECT_IO=0 (or ssd.direct_io=False) to deliberately measure or exploit the page cache. Verified on n06-21 (2x Micron 7450, 32 GiB dataset, 2 MiB values) with NO env var set: [SSDTier] reports direct_io=true, and host /sys/block counters show 96.0 GiB of device reads against 96.0 GiB of bench reads -- every read hit the drive. 1 drive 5465 MiB/s, 2 drives 10233 MiB/s (1.87x; 2.07x at the tier). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…SSD path
No behavior change; 44/44 ctest still green. The branch measured 4098 added
lines, of which only ~59% was code. This trims it to ~3930 without touching
what the tier does.
Duplication:
* One shared umbp/common/parallel_for.h replaces THREE separate thread
fan-out helpers -- LocalParallelFor (pool_client.cpp), ParallelFor
(ssd_tier.cpp) and RunPerShard (sharded_ssd_tier.cpp). The shared one
keeps the work-stealing loop, which is strictly better than the
contiguous-chunk split it replaces in ssd_tier: a batch whose per-key
cost is uneven can no longer strand a worker.
* ShardedSsdTier::BatchWrite and BatchReadIntoPtr were the same function
twice over -- bucket, fan out, time, scatter -- differing only in the
pointer type and the backend call. Factored into FanOutBatches<PtrT, Op>
plus BusyShards().
* Read/ReadIntoPtr/Exists/GetLocationId each re-implemented the
lock-then-lookup preamble that ShardOf() already was; they now call it.
Dead code:
* ssdperf::Accumulator -- a whole class, never constructed anywhere.
* pure-ssd-multi-drive-explained.md -- 106 lines built around four
 references to a doc/img/ directory that does not exist,
so every diagram was a broken link. Its section 02 also documented the
RDMA-WRITE push fast path, which this branch explicitly did NOT port.
The one thing in it worth keeping -- the measured L1/L2/L3 TTFT table --
moves into pure-ssd-mode.md.
Stale comment fixed: PeerSsdManager was still documented as "Dormant:
unwired from the distributed data plane ... so a future SsdBackend :
MediumBackend adapter can forward to them". That adapter is no longer
future -- SsdBackend exists and forwards to it on every call.
Also: test_sharded_ssd_tier used system("rm -rf") where its sibling test
used std::filesystem::remove_all; dropped the shell-out.
Comment prose trimmed to the "why" in config.h, ssd_perf.h,
sharded_ssd_tier.h, aligned_buffer.h and ssd_backend_factory.h -- the
UMBPSsdConfig block alone was 76 lines for 6 fields.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TianDi101
added a commit
that referenced
this pull request
Sep 3, 2026
…tic SSD path (#554) - Add multi-drive SSD backend
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports the pure-SSD / multi-drive performance work from
feat/umbp-pure-ssd-multidriveonto the backend-agnostic SSD path, then fixes the benchmark that measures it and flips direct I/O on by default.The backend-agnostic refactor unwired SSD (Phase 0) and re-landed it as
SsdBackend : MediumBackend(#542, #543). That re-landing was a clean-room implementation and did not carry the performance work forward, leaving a structurally correct but performance-naive tier: single drive, buffered I/O, single-threaded CRC, one device read per key, no read coalescing, no instrumentation.Commits
bae92878— perf(umbp): port the pure-SSD/multi-drive work onto the backend-agnostic SSD pathBelow the backend boundary (ports near-verbatim):
O_DIRECT. Capacity is now charged in padded on-disk bytes, so a store of small values can no longer overruncapacity_bytes.kFlagNoCrc+KeyMeta::crc_validkeep a store written with checksums off readable by a process with them on.UMBP_SSD_DIRECT_IO) with a startup probe that falls back to buffered on tmpfs/overlayfs rather than failing to come up.DropUnparsedTailsLocked— startup repair for stale-version and torn-tail records, which would otherwise make every later append unreadable.ShardedSsdTier+MakeFileSsdBackend— comma-separatedUMBP_SSD_DIRfans one logical tier across N drives with capacity-aware placement and parallel per-shard IO.ParallelForoverUMBP_SSD_TIER_IO_THREADSfor the CRC/assembly phases, matching the DRAM tier's 4-thread default.ssd_perf.h— the[SsdPerf/tier|peer]breakdown behindUMBP_SSD_TIMING.At the backend boundary (adapted, not copied):
PeerSsdManager(memcpy fan-out only; the RDMA-WRITE push path depends on reverse-registration and SSD-specific batch RPCs that do not exist in the agnostic proto).PeerSsdManager::WriteBatch/PrepareReadBatch, withSsdBackend::BatchCommit/BatchResolverestructured from per-key loops into one-lock/one-batch/one-lock passes. Without this a multi-drive tier can never be saturated — the per-key path costs one device read per key and idles every drive but one.MediumBackend::Counters()— a defaulted-empty virtual, so SSD's metrics reach Prometheus without adynamic_cast<SsdBackend*>in the metrics path.UMBP_PEER_{MIN,MAX}_POLLERS.Not ported, and why:
UMBP_ROUTE_PUT_SSD_MODE/IsPureSsdMode()have no analogue —medium = SSDalready makes a node an SSD put target, and the routing plane no longer ranks tiers. Round-robin put selection, the RDMA read fan-out push, and the copy-pipeline metrics remain open.efd48551— fix(umbp/bench): make umbp_bench actually configure the SSD tier it measuresTwo bugs that silently invalidated any SSD number the bench produced:
os.makedirs(cfg.ssd.storage_dir)with a comma-separated multi-drive path created one directory literally named"a,b"instead of one per drive.UMBPConfigdirectly and never callsFromEnvironment(), so noUMBP_SSD_*knob reached it.direct_ioin particular stayed off, and buffered reads can be served entirely from page cache.Without the fix the "SSD" tier reported 95 GB/s, within 1% of DRAM. With it, 10.2 GiB/s end-to-end and 767 GiB of verified device reads on the host counters.
7591f8a7— feat(umbp): make SSD direct I/O the defaultThe SSD tier is itself a cache, so buffered I/O gives it a second, unmanaged DRAM cache underneath — it evicts host memory the DRAM tier and the engine are competing for, it makes the node report DRAM it is consuming as free, and it makes any measurement of drive behaviour meaningless.
Safe as a default because the failure mode is already handled:
SSDTierprobesO_DIRECTat startup and falls back to buffered with a warning on a filesystem that cannot do it. SetUMBP_SSD_DIRECT_IO=0(orssd.direct_io=False) to deliberately exploit the page cache.Test plan
"no active RDMA device", which is environmental.test_sharded_ssd_tier.cpp,test_ssd_direct_io.cpp,test_peer_ssd_single_flight.cpp.[SSDTier]reportsdirect_io=true, and host/sys/blockcounters show 96.0 GiB of device reads against 96.0 GiB of bench reads — every read hit the drive. 1 drive 5465 MiB/s, 2 drives 10233 MiB/s (1.87x end-to-end; 2.07x at the tier).🤖 Generated with Claude Code