Repository navigation
[Store]Redesign of NVMe‑oF High‑Speed Read‑Write Layer - #3292
Luisuantech wants to merge 29 commits into
Conversation
zxpdemonio
left a comment
There was a problem hiding this comment.
I left a few inline comments focused on correctness and lifecycle issues in the NVMe-oF hot path. The multi-qpair/pipeline direction looks useful, but these should be fixed before this can be considered ready.
| config_ = config; | ||
| } | ||
|
|
||
| ssize_t SpdkWrapper::PipelineRead(nof_seg_handle *handle, void *buf, |
There was a problem hiding this comment.
This pipeline API is implemented in the wrapper/segment layer, but I do not see callers from the real Mooncake NoF transfer path (TransferSubmitter still submits individual SpdkNofTasks through SubmitRequest/poll). If the PR is claiming pipeline optimization for Mooncake, please wire this into the hot path or narrow the PR description/docs to say the pipeline API is currently unused.
|
One more community-facing cleanup request: please keep code, docs, and comments in English. This PR currently contains Chinese text in source comments / docs, which makes the upstream code harder for the broader community to review and maintain. |
zxpdemonio
left a comment
There was a problem hiding this comment.
I did another pass over the lifecycle/error paths. In addition to the inline notes below: please also check the submit-failure path in SpdkNofWorkerPool::workerThread(): after a SpdkNofSubTask is popped from sub_task_pool, a SubmitRequest() failure does not return it to the pool, so repeated submit failures can drain the pool because no callback will recycle that subtask.
| // Per-TransferSubmitter handle 缓存 — 同一 client 复用连接, | ||
| // 避免每次 put() 都创建新连接耗尽 QID + SPDK 内部状态。 | ||
| nof_seg_handle* seg_handle = nullptr; | ||
| auto cache_it = nof_handle_cache_.find(handle.transport_endpoint_); |
There was a problem hiding this comment.
nof_handle_cache_ is accessed without synchronization. If the same TransferSubmitter receives concurrent NoF submissions, the first-use path can race on this std::map, open duplicate connections for the same endpoint, and violate the QID budget this cache is meant to protect. Please guard the cache or make the single-threaded submitter contract explicit and enforced.
zxpdemonio
left a comment
There was a problem hiding this comment.
Adding a few more comments for points that were not covered in the earlier pass.
|
One higher-level suggestion: this PR mixes several separable concerns: multi-qpair connection management, worker-pool lifecycle/backpressure, pipeline API/docs, QID degradation/rebalance, client-buffer allocator cleanup, and documentation. It would be much easier to review and validate if this were split into smaller PRs, starting with the minimal hot-path change that wires multi-qpair NoF into the existing worker path with tests. Also, spdk_wrapper.cpp still has an ApplyCtrlrOptsFromEnv helper that forces keep_alive_timeout_ms to 0; if it is dead code now, please remove it, and if it is still used, it conflicts with the positive keep-alive behavior this PR wants. |
|
Thanks for the contribution! One small request regarding the copyright notices: would it be possible to avoid adding or changing copyright statements? Since the changes in this PR are not specific to any single vendor or company, we’d prefer to keep the existing copyright headers unchanged and not introduce additional company-specific copyright notices. Also, could you please use English for comments in the code, so they remain accessible to the broader community? Thanks again for your contribution and understanding! |
|
This PR adds For an upstream Mooncake contribution, source headers should follow the repository existing copyright/license convention. Adding a company-specific and non-English copyright line to community code creates unnecessary ownership/licensing ambiguity, and it is also inconsistent with the rest of the project. Please remove these custom copyright headers, or replace them with the standard Mooncake/KVCache.AI Apache-2.0 header only where the project normally requires a file header. For existing files, please avoid unrelated copyright/header churn. |
|
Thanks for the update. I reviewed the latest head ( Required stability issues:
Correctness / configuration:
Performance-path / documentation consistency:
Style / community-readiness:
Positive confirmations:
CI note: the latest checks still show |
# Conflicts: # mooncake-store/src/CMakeLists.txt
Temporarily committing staged work to enable clean merge with upstream/main. Message will be revised after merge.
The TransferPathCloseOrdering test used a plain std::vector for the PipelineRead I/O buffer. Heap allocations have no MR registration, so spdk_nvme_ns_cmd_read fails at nvme_rdma_req_init() with rc -22 (EINVAL) when the SPDK RDMA transport cannot resolve a translation for the source pointer. Observed in the SPDK log: spdk_rdma_get_translation: No translation for ptr 0x... nvme_rdma_get_memory_translation: rc -22 Replace the heap allocation with spdk_zmalloc(..., SPDK_MALLOC_DMA) so the buffer is DMA-registered and the RDMA translation succeeds. Co-Authored-By: Claude <noreply@anthropic.com>
- Remove `// Added includes` and `// Added APIs` markers from spdk_wrapper.h; replace the latter with a descriptive comment that also notes Pipeline is a lower-level API. - Update mooncake-store/src/spdk/README.md so the Overview, the architecture diagram, the `Pipeline I/O` section heading, the Multi-Qpair Parallelism diagram, and the changelog all explicitly state that PipelineRead/PipelineWrite are not part of the Mooncake transfer hot path. These changes close review feedback on doc/consistency between the SPDK module's source comments and its README. Co-Authored-By: Claude <noreply@anthropic.com>
|
Blocking stability finding: the poll-error path still has a task/callback lifetime bug, and I can reproduce a process crash on the current head The problematic sequence is:
Reproduction on
env LD_LIBRARY_PATH=/opt/spdk-26.05/build/lib:/usr/local/lib \\
MC_NVME_NUM_IO_QUEUES=1 MC_NVME_MAX_INFLIGHT_PER_QPAIR=4 \\
MC_NOF_WORKERS=1 MC_NVME_KEEP_ALIVE_TIMEOUT_MS=60000 \\
/mnt/vdb/build-pr3292/mooncake-store/benchmarks/nof_worker_pool_bench \\
--endpoints 'traddr:192.168.22.70 trsvcid:4420 subnqn:nqn.2026-08.io.spdk:cnode1 trtype:TCP adrfam:IPv4 ns:1' \\
--rw randwrite --io_size 131072 --iodepth 64 \\
--range_bytes 67108864 --duration_sec 5 --warmup_sec 1 \\
--report_interval_ms 1000 --nof_workers 1Observed on two runs: The community/base binary completed the same command successfully (for example, |
|
Performance result from a like-for-like run on
The PR's Could you please provide the exact methodology behind the reported
With the same conditions, please also report the write result. On this setup the PR write run currently crashes before producing a valid throughput number. |
|
Blocking stability re-review on the latest head I rebuilt this head in a clean directory on The original
The accounting path explains the hang:
The new regression coverage also does not currently validate this fix:
Please keep this PR blocked until the transport-error state machine has a terminal outcome. The fix needs to stop new submissions, drain/cancel/reset or retire the affected connection, retain callback state and buffers until a real terminal fence, and release every logical counter exactly once. The regression test should exercise the production worker path, require the fault to trigger, assert all task/QoS/worker counters return to zero, and prove both futures and teardown complete within a bounded time. |
after a qpair reported a transport error, because WaitForInflightCompletion had no fast-path for a dead qpair and Phase 1 of DrainDrainingPoolsUntilQuiescent used kDefaultDrainTimeoutMs (30 s) as its budget. When a sibling qpair's CQE arrived during that window the worker missed the future completion deadline and ~TransferSubmitter took >35 s to destroy. Repro: Reproducer_DrainTimeout_FutureCompletesNotHangs — arm one qpair to return -1 from PollAll while another qpair has an in-flight IO whose CQE arrives later. Fix: - Add kWorkerDrainTimeoutMs = 1000 for the worker-side drain Phase 1 deadline. Keep kDefaultDrainTimeoutMs = 30000 for the probe path. - WaitForInflightCompletion: on PollAll() < 0 force-zero inflight_count_ and force the pool into DRAINING immediately. The fallback deadline path remains for slow-but-not-dead targets. Late CQEs are silenced by the CAS-saturating DecrementInflight clamp and the trampoline DRAINING short-circuit. - Phase 1 of DrainDrainingPoolsUntilQuiescent now uses kWorkerDrainTimeoutMs instead of kDefaultDrainTimeoutMs. Tests: - New WaitForInflightCompletionFastPathOnPollError asserts the fast-path returns within 1 s even when a 30 s deadline is passed. - Reproducer_DrainTimeout_FutureCompletesNotHangs: tighten the future-completion deadline from 35 s to 5 s, measure the submitter destructor with a 5 s wall-clock assertion. - pre-commit run --files <11 PR files>: all hooks pass clean. - 4/4 ctest tests pass. Resource-release invariants unchanged: every heap object, refcount, atomic counter, and SPDK handle is still released exactly once on every error path, with try_complete() CAS arbitrating task completion
Redesign of NVMe‑oF High‑Speed Read‑Write Layer
Description
Redesign the Mooncake Store NVMe-oF I/O subsystem from single-qpair to multi-qpair pipeline architecture. Achieves 5.4× throughput improvement (616 MiB/s → 3.26 GiB/s) on FORINN targets.
Module: Store —
mooncake-store/src/spdk/Type: New feature + bug fixes (existing
SpdkWrapperAPI preserved, new methods added)Problem Statement
keep_alive_timeout_ms=0(legacyApplyCtrlrOptsFromEnv)Architecture
Files Changed
New (5)
include/spdk/nof_config.hNofConfigstruct + env var parsing (FromEnv,ForRead,ForWrite,ForProbe)include/spdk/nof_connection.hNofQpairPool+NofConnectionclass declarationssrc/spdk/nof_connection.cppConnect()factory, qpair allocation, TryGrow recoveryinclude/spdk/nof_segment.hNofSegmentclass +PipelineCtxsrc/spdk/nof_segment.cppPipelineIOcore loop,SubmitRead/Write,PollCompletionModified (7)
include/spdk/spdk_wrapper.hQidPressureGauge, new segment APIs, internal state privateinclude/transfer_task.hSpdkNofTask/SubTask/Qos,SpdkNofWorkerPool,nof_cache_mutex_src/spdk/spdk_wrapper.cppsrc/transfer_task.cppsrc/client_buffer.cppfree_memory()src/spdk/CMakeLists.txtsrc/spdk/README.mdCore Implementation
PipelineIO — high-throughput path (
nof_segment.cpp)NofConnection::Connect() — multi-qpair factory (
nof_connection.cpp)probe_cb: opts->num_io_queues = config.num_io_queues; opts->keep_alive_timeout_ms = config.keep_alive_timeout_ms; // Unique hostnqn per connection (nqn.2024-08.mooncake:c<N>) attach_cb: ctx->ctrlr = ctrlr; // Sequential allocation — take as many as available, fail if < min_io_queues for (i = 0; i < requested; i++) { qp = spdk_nvme_ctrlr_alloc_io_qpair(ctrlr, nullptr, 0); if (!qp) break; qpairs.push_back(qp); }Key Design Decisions
start_lba_translationCloseNofSegmentno-op on stale handleset_completed(FAIL)before discardnof_handle_cache_OpenNofSegmentoutside lockconnect_mutex_inProbeNofSegmenttoospdk_nvme_probe()not thread-safe; heartbeat + I/O must serialize~TransferSubmitterfetch_add)nq × inflight_per_qpair × blocks_per_chunkerrnoreset beforestrtoull,#include <cerrno>MC_NOF_TRTYPE=TCPworks end-to-endPipelineRead/Writeavailable for future hot-path integrationBenchmark Results
Test: FORINN HWE62P447T6L00LN target, single SSD, 8 MiB random read,
MC_NVME_MAX_INFLIGHT_PER_QPAIR=4.MC_NVME_NUM_IO_QUEUES=8,MC_NVME_MAX_INFLIGHT_PER_QPAIR=4