enhance: add DumpMessages to the Go SDK client (#50343)

issue: #47598

## Summary

The `DumpMessages` streaming RPC (added in #47599 for data salvage) is
exposed by the proto and the low-level `milvuspb` stub, but the
high-level Go SDK client (`client/milvusclient`) had **no wrapper** —
unlike the sibling replicate APIs (`UpdateReplicateConfiguration`,
`GetReplicateConfiguration`, `GetReplicateInfo`,
`CreateReplicateStream`) that already live in `replicate.go`.

This adds `Client.DumpMessages`, mirroring `CreateReplicateStream`: it
returns the server-streaming client whose `Recv()` yields
`DumpMessagesResponse` frames (each carrying either an error status or a
non-system message).

For reference, the Python SDK already exposes this API in
milvus-io/pymilvus#3573.

## Changes

- `client/milvusclient/replicate.go`: add `Client.DumpMessages(ctx, req,
opts...) (milvuspb.MilvusService_DumpMessagesClient, error)`.
- `client/milvusclient/replicate_example_test.go`: add
`ExampleClient_DumpMessages` showing the salvage flow
(`GetReplicateInfo` → salvage checkpoint → `DumpMessages` → iterate to
`io.EOF`).

## Test Plan

- [x] `go build ./milvusclient/` and `go vet ./milvusclient/` pass
- [x] `go test ./milvusclient/ -run TestReplicate` passes
- [x] Example compiles (built as part of the package test binary)
- [x] gofmt clean

Signed-off-by: bigsheeper <yihao.dai@zilliz.com>
This commit is contained in:
yihao.dai
2026-06-08 11:38:18 +08:00
committed by GitHub
parent 8b04c2f960
commit 545f7bd4e7
2 changed files with 82 additions and 0 deletions
+24
View File
@@ -62,3 +62,27 @@ func (c *Client) CreateReplicateStream(ctx context.Context, opts ...grpc.CallOpt
})
return streamClient, err
}
// DumpMessages streams messages from a WAL range for data salvage.
// It is typically used after a force failover: callers obtain the salvage
// checkpoint from GetReplicateInfo and pass its message id as the request's
// StartMessageId to recover messages that were not yet synchronized.
//
// The returned stream yields DumpMessagesResponse frames; each frame carries
// either an error status or a non-system message. Iterate with stream.Recv()
// until io.EOF.
func (c *Client) DumpMessages(ctx context.Context, req *milvuspb.DumpMessagesRequest, opts ...grpc.CallOption) (milvuspb.MilvusService_DumpMessagesClient, error) {
var streamClient milvuspb.MilvusService_DumpMessagesClient
err := c.callService(func(milvusService milvuspb.MilvusServiceClient) error {
var err error
streamClient, err = milvusService.DumpMessages(ctx, req, opts...)
if err != nil {
return err
}
if streamClient == nil {
return errors.New("stream client is nil")
}
return nil
})
return streamClient, err
}
@@ -19,6 +19,7 @@ package milvusclient_test
import (
"context"
"io"
"log"
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
@@ -118,3 +119,60 @@ func ExampleClient_CreateReplicateStream() {
// Here you can continue to use the stream for data transmission
// For example: stream.Send(&milvuspb.ReplicateMessage{...})
}
func ExampleClient_DumpMessages() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cli, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
Address: exampleMilvusAddr,
})
if err != nil {
log.Fatal(err)
}
// After a force failover, use GetReplicateInfo to obtain the salvage
// checkpoint, then dump the unsynchronized messages from that position.
info, err := cli.GetReplicateInfo(ctx, &milvuspb.GetReplicateInfoRequest{
SourceClusterId: "source-cluster",
TargetPchannel: "source-channel-dml_0",
})
if err != nil {
log.Printf("Failed to get replicate information: %v", err)
return
}
ckpt := info.GetSalvageCheckpoint()
if ckpt == nil {
log.Println("No salvage checkpoint available")
return
}
stream, err := cli.DumpMessages(ctx, &milvuspb.DumpMessagesRequest{
Pchannel: ckpt.GetPchannel(),
StartMessageId: ckpt.GetMessageId(),
StartTimetick: ckpt.GetTimeTick(),
})
if err != nil {
log.Printf("Failed to dump messages: %v", err)
return
}
for {
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Printf("Failed to receive dumped message: %v", err)
return
}
if msg := resp.GetMessage(); msg != nil {
log.Printf("Dumped message: %v", msg.GetId())
} else if status := resp.GetStatus(); status != nil {
log.Printf("Received error status: %v", status)
break
}
}
log.Println("Dump messages completed")
}