Skip to content

Commit 33792ec

Browse files
Add RDMA / NVIDIA GPU Direct Storage support (#2233)
1 parent 6bb13f7 commit 33792ec

8 files changed

Lines changed: 421 additions & 0 deletions

File tree

.github/workflows/go-rdma.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Build (RDMA)
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- master
7+
8+
concurrency:
9+
group: ${{ github.workflow }}-${{ github.head_ref }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
build:
14+
name: Build with -tags=rdma against minio-cpp (${{ matrix.config.arch }})
15+
runs-on: ${{ matrix.config.os }}
16+
strategy:
17+
fail-fast: false
18+
matrix:
19+
config:
20+
- { os: ubuntu-latest, arch: "amd64" }
21+
- { os: ubuntu-24.04-arm, arch: "arm64" }
22+
23+
steps:
24+
- name: Checkout minio-go
25+
uses: actions/checkout@v4
26+
with:
27+
path: "minio-go"
28+
29+
- name: Checkout minio-cpp
30+
uses: actions/checkout@v4
31+
with:
32+
repository: minio/minio-cpp
33+
path: "minio-cpp"
34+
35+
- name: Checkout vcpkg
36+
uses: actions/checkout@v4
37+
with:
38+
repository: microsoft/vcpkg
39+
path: "vcpkg"
40+
41+
- name: Install system dependencies
42+
run: |
43+
sudo apt-get -qy update
44+
sudo apt-get -qy install cmake libibverbs-dev librdmacm-dev libnuma-dev
45+
46+
- name: Build and install libminiocpp.so with RDMA
47+
shell: bash
48+
run: |
49+
./vcpkg/bootstrap-vcpkg.sh
50+
cd minio-cpp
51+
../vcpkg/vcpkg install
52+
cmake . -B ./build \
53+
-DCMAKE_BUILD_TYPE=Release \
54+
-DBUILD_SHARED_LIBS=ON \
55+
-DCMAKE_INSTALL_PREFIX=/usr/local \
56+
-DCMAKE_TOOLCHAIN_FILE=../vcpkg/scripts/buildsystems/vcpkg.cmake \
57+
-DMINIO_CPP_ENABLE_RDMA:BOOL=ON
58+
cmake --build ./build --config Release -j 4
59+
sudo cmake --install ./build
60+
cuobj_arch=$([ "${{ matrix.config.arch }}" = "amd64" ] && echo x86_64 || echo aarch64)
61+
sudo cp -P vendor/cuobj/lib/${cuobj_arch}/* /usr/local/lib/
62+
sudo ldconfig
63+
64+
- name: Set up Go
65+
uses: actions/setup-go@v5
66+
with:
67+
go-version: "1.25.x"
68+
69+
- name: Build minio-go with -tags=rdma
70+
working-directory: minio-go
71+
run: |
72+
go build -tags=rdma ./...
73+
go build -tags=rdma -o /tmp/rdma-test ./cmd/rdma-test

api-get-object.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,22 @@ func (c *Client) GetObject(ctx context.Context, bucketName, objectName string, o
4646
}
4747
}
4848

49+
if opts.RDMABuffer != nil && c.rdmaEnabled {
50+
n, err := c.getObjectRDMA(ctx, bucketName, objectName, opts)
51+
if err != nil {
52+
return nil, err
53+
}
54+
return &Object{
55+
mutex: &sync.Mutex{},
56+
isClosed: true,
57+
objectInfo: ObjectInfo{
58+
Key: objectName,
59+
Size: n,
60+
},
61+
objectInfoSet: true,
62+
}, nil
63+
}
64+
4965
gctx, cancel := context.WithCancel(ctx)
5066

5167
// Detect if snowball is server location we are talking to.

api-get-options.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"net/url"
2424
"strconv"
2525
"time"
26+
"unsafe"
2627

2728
"github.com/minio/minio-go/v7/pkg/encrypt"
2829
)
@@ -48,6 +49,12 @@ type GetObjectOptions struct {
4849
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html
4950
Checksum bool
5051

52+
// RDMABuffer, when non-nil and Options.EnableRDMA=true, downloads directly
53+
// into a contiguous buffer via libminiocpp.so. The returned *Object's
54+
// Read() returns EOF immediately; bytes-transferred is in Stat().Size.
55+
RDMABuffer unsafe.Pointer
56+
RDMABufferSize int
57+
5158
// To be not used by external applications
5259
Internal AdvancedGetOptions
5360
}

api-put-object.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"sort"
2929
"strings"
3030
"time"
31+
"unsafe"
3132

3233
"github.com/minio/minio-go/v7/pkg/encrypt"
3334
"github.com/minio/minio-go/v7/pkg/s3utils"
@@ -111,6 +112,12 @@ type PutObjectOptions struct {
111112
ConcurrentStreamParts bool
112113
Internal AdvancedPutOptions
113114

115+
// RDMABuffer, when non-nil and Options.EnableRDMA=true, selects the RDMA
116+
// path via libminiocpp.so. Must reference RDMABufferSize contiguous bytes.
117+
// When set, the reader / size args to PutObject are ignored.
118+
RDMABuffer unsafe.Pointer
119+
RDMABufferSize int
120+
114121
customHeaders http.Header
115122
}
116123

@@ -322,6 +329,9 @@ func (a completedParts) Less(i, j int) bool { return a[i].PartNumber < a[j].Part
322329
func (c *Client) PutObject(ctx context.Context, bucketName, objectName string, reader io.Reader, size int64,
323330
opts PutObjectOptions,
324331
) (info UploadInfo, err error) {
332+
if opts.RDMABuffer != nil && c.rdmaEnabled {
333+
return c.putObjectRDMA(ctx, bucketName, objectName, opts)
334+
}
325335
if size < 0 && opts.DisableMultipart {
326336
return UploadInfo{}, errors.New("object size must be provided with disable multipart upload")
327337
}

api.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,15 @@ type Client struct {
109109

110110
trailingHeaderSupport bool
111111
maxRetries int
112+
113+
// RDMA dispatch state. rdmaEnabled mirrors Options.EnableRDMA;
114+
// the rest are only touched by rdma.go (built with -tags=rdma) but
115+
// have to live on the struct so the stub and the tagged build share
116+
// one shape.
117+
rdmaEnabled bool
118+
rdmaOnce sync.Once //nolint:unused
119+
rdmaHandle *rdmaClientHandle //nolint:unused
120+
rdmaInitErr error //nolint:unused
112121
}
113122

114123
// Options for New method
@@ -156,6 +165,11 @@ type Options struct {
156165
// Number of times a request is retried. Defaults to 10 retries if this option is not configured.
157166
// Set to 1 to disable retries.
158167
MaxRetries int
168+
169+
// EnableRDMA causes PutObject / GetObject to dispatch to libminiocpp.so
170+
// when the caller supplies PutObjectOptions.RDMABuffer / GetObjectOptions.RDMABuffer.
171+
// No-op unless built with -tags=rdma.
172+
EnableRDMA bool
159173
}
160174

161175
// Global constants.
@@ -311,6 +325,7 @@ func privateNew(endpoint string, opts *Options) (*Client, error) {
311325
}
312326

313327
clnt.trailingHeaderSupport = opts.TrailingHeaders && clnt.overrideSignerType.IsV4()
328+
clnt.rdmaEnabled = opts.EnableRDMA
314329

315330
// Sets bucket lookup style, whether server accepts DNS or Path lookup. Default is Auto - determined
316331
// by the SDK. When Auto is specified, DNS lookup is used for Amazon/Google cloud endpoints and Path for all other endpoints.

cmd/rdma-test/main.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2024-2026 - MinIO, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// rdma-test exercises the unified minio-go RDMA path against a running
5+
// MinIO server. Requires -tags=rdma at build time and libminiocpp.so on
6+
// the host's library search path.
7+
//
8+
// go build -tags=rdma -o rdma-test ./cmd/rdma-test
9+
// MINIO_ENDPOINT=coe01:9000 MINIO_ACCESS_KEY=... MINIO_SECRET_KEY=... ./rdma-test
10+
11+
package main
12+
13+
import (
14+
"bytes"
15+
"context"
16+
"fmt"
17+
"log"
18+
"os"
19+
"unsafe"
20+
21+
minio "github.com/minio/minio-go/v7"
22+
"github.com/minio/minio-go/v7/pkg/credentials"
23+
)
24+
25+
const (
26+
testBucket = "rdma-test"
27+
testObject = "test-object-cpu"
28+
testSize = 1 << 20 // 1 MiB
29+
)
30+
31+
func envOr(k, d string) string {
32+
if v := os.Getenv(k); v != "" {
33+
return v
34+
}
35+
return d
36+
}
37+
38+
func main() {
39+
if err := run(); err != nil {
40+
log.Fatal(err)
41+
}
42+
}
43+
44+
func run() error {
45+
endpoint := envOr("MINIO_ENDPOINT", "coe01:9000")
46+
accessKey := envOr("MINIO_ACCESS_KEY", "minioadmin")
47+
secretKey := envOr("MINIO_SECRET_KEY", "minioadmin")
48+
49+
fmt.Printf("endpoint=%s rdma_available=%v\n", endpoint, minio.IsRDMAAvailable())
50+
51+
client, err := minio.New(endpoint, &minio.Options{
52+
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
53+
Secure: false,
54+
EnableRDMA: true,
55+
})
56+
if err != nil {
57+
return fmt.Errorf("New: %w", err)
58+
}
59+
60+
ctx := context.Background()
61+
62+
exists, err := client.BucketExists(ctx, testBucket)
63+
if err != nil {
64+
return fmt.Errorf("BucketExists: %w", err)
65+
}
66+
if !exists {
67+
if err := client.MakeBucket(ctx, testBucket, minio.MakeBucketOptions{}); err != nil {
68+
return fmt.Errorf("MakeBucket: %w", err)
69+
}
70+
}
71+
72+
src := minio.AlignedBuffer(testSize)
73+
if src == nil {
74+
return fmt.Errorf("AlignedBuffer(%d) returned nil", testSize)
75+
}
76+
defer minio.FreeAlignedBuffer(src)
77+
srcSlice := unsafe.Slice((*byte)(src), testSize)
78+
for i := range srcSlice {
79+
srcSlice[i] = byte(i)
80+
}
81+
82+
fmt.Print("PutObject (RDMA)... ")
83+
info, err := client.PutObject(ctx, testBucket, testObject, nil, 0, minio.PutObjectOptions{
84+
RDMABuffer: src,
85+
RDMABufferSize: testSize,
86+
})
87+
if err != nil {
88+
return fmt.Errorf("PutObject: %w", err)
89+
}
90+
fmt.Printf("ok etag=%s size=%d checksum=%s\n", info.ETag, info.Size, info.ChecksumCRC64NVME)
91+
92+
dst := minio.AlignedBuffer(testSize)
93+
if dst == nil {
94+
return fmt.Errorf("AlignedBuffer(%d) returned nil", testSize)
95+
}
96+
defer minio.FreeAlignedBuffer(dst)
97+
dstSlice := unsafe.Slice((*byte)(dst), testSize)
98+
99+
fmt.Print("GetObject (RDMA)... ")
100+
obj, err := client.GetObject(ctx, testBucket, testObject, minio.GetObjectOptions{
101+
RDMABuffer: dst,
102+
RDMABufferSize: testSize,
103+
})
104+
if err != nil {
105+
return fmt.Errorf("GetObject: %w", err)
106+
}
107+
stat, err := obj.Stat()
108+
if err != nil {
109+
return fmt.Errorf("Stat: %w", err)
110+
}
111+
fmt.Printf("ok size=%d\n", stat.Size)
112+
113+
if !bytes.Equal(srcSlice, dstSlice) {
114+
return fmt.Errorf("FAIL: roundtrip data mismatch")
115+
}
116+
fmt.Println("PASS: roundtrip verified")
117+
return nil
118+
}

0 commit comments

Comments
 (0)