feat(storage): add optional RDMA piece transport over libfabric#1945
Draft
YQ-Wang wants to merge 1 commit into
Draft
feat(storage): add optional RDMA piece transport over libfabric#1945YQ-Wang wants to merge 1 commit into
YQ-Wang wants to merge 1 commit into
Conversation
On a host with an RDMA fabric, a single TCP flow leaves most of that fabric idle, and the piece protocol opens one connection per piece. This adds an optional transport that carries piece bytes over the fabric instead, behind the `rdma` cargo feature and disabled by default. Both AWS EFA and RoCE/InfiniBand are driven through libfabric, because EFA uses Scalable Reliable Datagram rather than reliable connected queue pairs and so cannot be driven by an RC-based ibverbs implementation. Control messages (request, capability negotiation, metadata, flow control, errors) travel over a TCP rendezvous connection; bulk bytes travel as two-sided tagged messages on a shared FI_EP_RDM endpoint. Two-sided messaging avoids exposing remote-access memory keys to peers, which in a P2P system means exposing them to anyone who can reach the daemon. Capability is discovered on the TCP piece port peers already know, so no announcement or scheduler change is needed, and discovery is fail-closed: an older peer, a peer built without the feature, or a peer whose fabric has failed all leave the client on TCP. Peers speak RDMA only when they agree on the concrete provider and on an operator-supplied fabric tag, because libfabric will report a working provider for two nodes that cannot actually reach each other. Received windows are handed to storage still resident in registered memory, so a piece goes from the NIC to the page cache without a bounce buffer. The parent may not send a window until the client reports its receives posted, and the parent recomputes each window itself, so a peer cannot replay a window, skip ahead, or claim more chunks than the piece holds. Registrations are pooled and bounded; exhausting the budget degrades a transfer to one window at a time rather than failing it. Every RDMA failure falls back to the TCP piece server for that piece. Measured on two p6-b200.48xlarge EFA nodes against the TCP piece server, best of three at each concurrency: 6.2x at one stream to 2.1x at 32 on the workload dfdaemon runs, or 10.8x to 3.7x measuring only the transport. The gap narrows because a single TCP flow on this network caps near 5 Gbps while the piece server uses one connection per piece, so TCP scales with streams as RDMA saturates; past ~16 streams RDMA is bound by receive-side CPU rather than by the fabric, which accounts for under 1% of a transfer. Adds docs/rdma-p2p.md describing the design and every new config option. Signed-off-by: Yiqing Wang <yiqingwang@roblox.com>
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.
Description
Adds an optional peer-to-peer piece transport that carries piece bytes over an RDMA
fabric instead of the TCP piece server. It is behind the
rdmacargo feature, disabledby default, Linux-only, and every failure falls back to the TCP piece server for that
piece.
Design, in short (full write-up in
docs/rdma-p2p.md):no reliable connected queue pairs, so an RC-based ibverbs implementation cannot drive
it. libfabric covers both EFA and RoCE/InfiniBand (
verbs;ofi_rxm) behind one API.control, errors) go over a TCP rendezvous connection; bulk bytes go as two-sided
tagged messages on a shared
FI_EP_RDMendpoint.keys to peers, which in a P2P system means handing them to anyone who can reach the
daemon. With two-sided tagged messaging the receiver posts its own buffers and no
remote key leaves the process.
announcement or scheduler change is needed. It is fail-closed: an older peer, a peer
built without the feature, or a peer whose fabric has failed all leave the client on
TCP. Answers are cached for 60s.
on an operator-supplied reachability label, because libfabric will happily report a
working provider for two nodes that cannot actually reach each other.
registered memory, so a piece goes from the NIC to the page cache without an
intermediate copy. The parent may not send a window until the client reports its
receives posted, and the parent recomputes each window itself, so a peer cannot
replay a window, skip ahead, or over-claim chunks.
exhausting it degrades a transfer to one window at a time rather than failing it.
New config lives under
storage.server.rdma(12 options, all documented indocs/rdma-p2p.md), plusdownload.protocol: rdmato download over it. Serving anddownloading are enabled independently.
Scope: 24 files, +7507/−17. No behaviour changes when the feature is off — the
default build and the TCP path are untouched.
Related Issue
There is no existing issue for this; I am opening this as a draft to start the
discussion, and happy to file one if you would prefer that first.
Related prior art in the project:
dragonflyoss/apidefinesExchangeIBVerbsQueuePairEndpointonDfdaemonUpload, andpiece_collector.rsstillcarries a comment describing
download_ipas the channel for exchanging IBVerbs queuepair endpoints. This PR deliberately does not use that RPC (see below), so if the
IBVerbs direction is the intended one I would like to know before going further.
Motivation and Context
On a host with an RDMA fabric, a single TCP flow leaves most of that fabric idle, and
the piece protocol opens one connection per piece. For model and image distribution on
GPU nodes — exactly where the fast fabric was bought — that shortfall is the bottleneck.
Measured on two
p6-b200.48xlargeEFA nodes (one rail), 24 GiB of 512 MiB pieces servedfrom tmpfs, best of three runs at each concurrency, in Gbps:
"CRC32 + write" is the work dfdaemon actually does per piece; "transport only" isolates
the wire. The gap narrows with concurrency because a single TCP flow on this network
caps near 5 Gbps while the piece server uses one connection per piece, so TCP scales
with streams as RDMA saturates. Past roughly 16 streams the RDMA side is bound by
receive-side CPU (digest and write), not by the fabric, which accounts for under 1% of
a transfer.
Testing
dragonfly-client-storage/tests/rdma_transfer.rsplus unittests covering framing, capability negotiation, window validation, and the write path.
--features rdma.Known limitations
fabricTagis an operator assertion; a wrong tag degrades to TCP fallback ratherthan failing loudly.
could aim bytes at another peer's endpoint; the digest check catches it, so the
effect is a failed download and a TCP retry rather than corruption.
parent's
BUSYandINCOMPATIBLErendezvous codes into one generic transportfailure, so a merely loaded parent gets the same escalating backoff as a broken one;
and the downloader returns success once the
Readyframe parses, so a transfer thatdies mid-stream never reaches the backoff bookkeeping.