Repository navigation
[设计提案] 客户端多轨 RDMA 读路径:单 Worker 多网卡并行读取同一对象 #29
Description
Activity
更新(2026-10-01):按项目负责人的选择,下面的增量草稿 PR 已关闭,不作为本次参赛交付;我们将从 DaoCloud/ContextStore 的
main独立实现。以下旧分支分析仅保留为技术讨论,后续会另行提交独立设计与代码链接。我在现有 #28 的
c155086基础上提交了一份增量补丁,原 PR 的多轨主体实现及历史 RXE 实验仍归原作者所有:可审查的补丁 PR,新增提交2fa729d。补丁主要处理两个验收风险:
LookupObject对同一存储节点只公布一个主 RDMA endpoint。现在可把这个 endpoint 按本地 Rail 静态映射到该节点的不同 listener;调度和基准不再改写 Placement,每条 Rail 的任务走独立连接。task_max_stripes拆出的任务也有独立 QP lane。- 原安全读取在部分 Rail 已写入后失败时,会留下被部分改写的调用方缓冲区;取消、校验失败等提前返回路径还可能跳过非 sticky MR 清理。普通 Rust/Python 入口现在使用请求私有暂存区,全部成功后才提交;MR 清理未确认则拆除连接并报错。另加入暂存、注册内存估算和在途字节预约上限,以及 Python 可选的读后 Descriptor 复核。
在 Linux ARM64 容器中,发现服务端固定
O_DIRECT=0o40000实际对应O_DIRECTORY,导致条带测试报ENOTDIR;补丁已修复。验证:Rust RDMA 特性 SDK 38 项单测、普通双节点 4 项集成测试,服务端 70/95 项两种特性组合单测,Python 118 项测试通过。本补丁尚未在双 RXE 或实体 HCA 上复测,也没有新的带宽数据。希望社区确认两点:静态
advertised->listener路由是否适合作为首版配置,还是希望服务端在 Placement 中显式公布同一节点的所有 Rail endpoint?普通读取以失败原子性为默认、保留显式unsafe高性能路径,是否符合预期接口语义?独立实现进展(2026-10-02): 我们从 DaoCloud
main的b5c6451独立开发,没有以 #28 为基底。公开草稿 PR #32 当前提交81f437d;之前基于 #28 的增量草稿已关闭,不计入本实现。Rust SDK 的
KvClient::read_multi_rail_into在同一个 Worker 中对同一对象的条带进行多 Rail 调度、独立 QP/CQ/MR 传输、完整汇总与版本复查,保持已有磁盘布局和上层读接口。服务端补齐 Tier A 指针流完成事件及 tag-15 回退地址映射;CQ 完成状态不确定时关闭该连接,客户端 GET 控制超时也退休自己的 QP,避免晚到 CQE/回复被下一请求误认。总/单 Rail 预算、注册内存与进程RLIMIT_MEMLOCK的提前背压均已覆盖。真实 Verbs 结果分开报告:
- 实体 HCA 单轨: SKV node1→node2 ConnectX-6 Dx 完成同对象还原、slab/回退、断连、取消、校验损坏与恢复、超时后晚到 WRITE 和缓冲区复用。原始计数与限制见单轨 HCA JSON。两个条带目录在同一 SATA SSD,不能证明多盘收益。
- 双 Soft-RoCE: 在同一宿主机上建立两台隔离 Ubuntu KVM 来宾,以两座独立 tap/bridge、四个 RXE 设备、两个 listener 运行标准 Verbs。单个 Worker 的 64 MiB 读取由两 Rail 各传 32 MiB,内容与单轨一致;第二 Rail 停链/恢复、旧 Generation、条带校验失败、双轨在途取消、第一轨已完成而第二轨晚到、CQ/控制回复连接退休、双轨 slab 回退均已验证。原始配对样本、拓扑和逐轨计数、故障测试收据及部署脚本已公开。
- 性能边界: 同对象/布局/来宾/并发的 64、128、256 MiB 配对测量中,双轨相对单轨的中位时延比约 1.071×、1.086×、1.113×;并发 4、64 MiB 的聚合吞吐改善只有 1.8%,CPU 与 RSS 也上升。这些是 RXE/QEMU 软件路径结果,不能宣称实体双 HCA、PCIe/NUMA 或多 NVMe 聚合性能。七台 SKV 节点各只有一条在线物理 HCA 端口;现有 OFED 下宿主 RXE 模块不兼容,我们没有卸载共享驱动。
目前 PR 保持 draft;官方 Actions 对 fork PR 标记
action_required、尚未启动 job,维护者 Review 与比赛平台回执未取得。双实体 HCA 性能和最终 3–5 分钟演示视频仍作为独立门槛。希望维护者优先反馈首版显式advertised endpoint → listener映射及旧对象无校验和时的兼容策略。Independent
main-based draft PR #32 now includes the final safety regression fix in commitc439c59: legacy complete-object GET retains slab pins, extents and registered fallback sources until QP destruction when WRITE completion is uncertain, and never reuses the old QP/CQ for fallback. An isolated RXE test produced a 2 s server CQ timeout (0/64 completions), server connection retirement, client QP retirement, and an unchanged reused destination after six seconds; receipt hashes are in the PR.The four-minute two-RXE demonstration release contains the MP4, cover image and complete live command/output capture JSON. It shows one Worker reading the same 64 MiB / 16-stripe object through two independent RXE rails (32 MiB each), second-link shutdown/recovery, checksum fault/recovery and a late second-rail WRITE. The video clearly labels itself an edited, narrated terminal record from a real Soft-RoCE/KVM environment. This evidence validates Verbs behavior and software scaling; it does not claim dual physical-HCA bandwidth aggregation. Maintainer design/review feedback on the draft PR is welcome.
Independent
main-based PR #32 has advanced toc1c1de1. The added design note describes an optionalPlacementDescriptor.rdma_railsfield: KVService advertises listeners only for actual stripe owners, and the Rust client matches their fabric IDs to configured local Verbs devices. Stored stripes and existing read APIs remain unchanged; manual routes still work with old servers, and old clients ignore the new protobuf field.On two isolated real-Verbs RXE paths, the client configured only its local
fabric-a/rxe_c0andfabric-b/rxe_c1paths, discovered both listeners viaLookupObject, and read the same 64 MiB/16-stripe object at 32 MiB per rail. An actual second-link shutdown caused overall failure with the first rail complete, second rail failed and caller buffer unchanged; link recovery succeeded. Old-client/new-server and new-client/old-server manual reads also passed. The source-matched raw receipts preserve all outputs and SHA-256 values. Latest x86_64 release checks: server 102 passed, client 44 passed/1 intentional Mock benchmark ignored, isolated two-node E2E 4 passed, and strict RDMA client Clippy passed.We would welcome maintainer feedback on the Placement capability field, fabric naming/upgrade semantics and whether this discovery contract fits ContextStore's roadmap. As before, the dual-RXE results do not claim physical dual-HCA aggregation.
背景与动机
条带化大对象的多盘聚合带宽可能超过单张网卡的传输能力,使网络成为大对象 KV Cache 加载的新瓶颈:即使后端磁盘仍有余量,客户端 Worker 也可能因单条 RDMA 路径饱和而无法继续缩短读取时间。本提案在 KVService 客户端侧实现多轨(multi-rail)RDMA 读路径,让单个 Worker 通过多张 RDMA 网卡并行读取同一对象(对应 DaoCloud 智算云赛道赛题《ContextStore 多轨网络设计与实现》)。
设计概要
CS_RDMA_DEVICES多网卡监听能力,无需改动。validate_placement(缺失/重复/越界/长度不符在发 IO 前报类型化错误)→build_plan(交叉相乘的字节精确加权均衡,平局偏向高权重轨)→plan_waves(按连接上限分波)。num_chunks数量、条带 xxh3-64 校验和(与服务端twox-hash编码一致)。Stop→ join worker →ibv_destroy_qp→ MR 反注册;QP 销毁后残余重传打到已释放 QPN/失效 rkey,被传输层丢弃,不可能 DMA 进已释放或复用的内存。RailLimits(单轨/总连接数、单轨/总在途字节、io_timeout、轨冷却窗口),规划分波保证连接上限,派发线程在字节预算不足时带期限等待。RailSnapshot每轨吞吐、时延(avg/max)、错误/超时数、在途请求与字节、注册内存、连接创建/静默数,Display单行表格化。rdma-ffi新增cs_mr_*C ABI(cs_mr_new/cs_mr_read/cs_mr_rail_stats/cs_mr_free)+src/contextstore/storage/multirail_client.pyctypes 绑定,Worker 无需 Rust 依赖。实现与验证
multirail-read,5 个提交,+3730/-16,15 个文件)StaleDescriptor触发重新 lookupmake e2e、clippy(default 与 rdma 双模式)、cargo fmt、Python 集成 pytestcompetition:docs/multirail-quickstart-zh.md、scripts/rxe-testbed-setup.sh)兼容性
RdmaClient公共 API 仅增不改(新增 builder 均有默认值);上游rdma_bench/rdma-ffi既有接口不受影响;gRPC-only 构建(无 rdma feature)零依赖变化--features rdma下build_put_request单测缺参编译失败;tier_a执行器read_aligned_into_ptr_batch未实现导致 RDMA 条带读取静默返回bytes=0(已在文档记录,建议上游补实现)希望与社区对齐的问题
device[:port[:gid[:weight[:mtu]]]][@ep[;ep...]]作为正式配置面是否可接受?cs_mr_*FFI 是否可作为 Python Worker 的长期多轨入口,还是希望收敛到现有cs_rdma_*命名风格?CS_MR_*环境变量驱动)是否值得在 CI 以 soft-roce 形式常驻?欢迎维护者与社区评审指正,详细设计文档见 PR 描述与参赛分支
docs/multirail-design-zh.md。