如何用 ExportSnapshot 与 RestoreExternalSnapshot 跨桶导出和恢复 Milvus 快照
【免费下载链接】milvusMilvus is a high-performance, cloud-native vector database built for scalable vector ANN search项目地址: https://gitcode.com/GitHub_Trending/mi/milvus
默认情况下,Milvus 快照只归属于一个集群和对象存储桶:RestoreSnapshot恢复的是目标集群元数据里已经存在的快照。当你想把某个集合的数据备份到另一个桶、或者让另一个集群从对象存储的元数据 URI 直接恢复时,需要用ExportSnapshot把快照打成一个自包含的 bundle 写到目标桶,再在目标侧调用RestoreExternalSnapshot恢复。两个接口都是异步任务:提交后返回job_id,通过状态轮询接口确认完成。
本文的操作路径基于仓库中的设计文档 外部快照导出与恢复、Go SDK 实现 snapshot.go 与端到端测试用例 snapshot_test.go。
开始前的准备
先创建一个普通快照。ExportSnapshot的输入是本地已存在的快照,所以先用CreateSnapshot在源集群创建快照并命名,导出、恢复都基于这个快照名和集合名进行。
确认凭证模型。跨桶复制必须满足一个前提:存在一个对象存储 provider 端的复制请求,其凭证既能读源对象、又能写目标对象。API 不引入独立的凭证抽象,只有两层解析方式:
- Layer 1:实例凭证 + 桶策略。请求中
external_spec留空时,使用 Milvus 实例的对象存储凭证;需要为该 principal 授予缺失桶的权限(导出时是外桶写权限,恢复时是外桶读权限)。 - Layer 2:请求中的
external_spec.extfs。可携带与存储配置兼容的字段:provider、region、endpoint、TLS 模式、use_iam、AK/SK,或 GCP 服务账号 JSON(credential_json)。显式的请求凭证会覆盖实例凭证,且use_iam=true、原始 AK/SK、credential_json三者互斥。通用的role_arn、SAS 作为凭证模式、匿名认证都会被拒绝。
一个安全注意点:恢复链路会把external_spec从 Proxy 一路透传到 DataCoord、WAL、任务状态和 DataNode,其中的原始密钥会被持久化。设计文档把这一点标为运维红线,建议优先使用 Layer 1 或use_iam=true这类环境身份字段,避免在请求里传明文密钥;日志与错误信息中的 spec 会自动脱敏。
权限。ExportSnapshot、GetExportSnapshotState和RestoreExternalSnapshot都是 Global RBAC 操作:导出的提交和状态查询使用PrivilegeExportSnapshot权限。db_name仍保留在请求中,用于数据库路由与命名空间上下文,但不是 RBAC 检查对象。
跨桶复制的边界。跨桶复制是 provider 侧复制能力,没有流式兜底:不同 provider、不同 endpoint(包括相互独立的 MinIO/S3 兼容服务)之间无法用一次服务端复制请求完成,Milvus 会在调度前直接拒绝,而不是先跑任务再失败。
第一步:创建源快照
在源集群对目标集合创建快照。Go SDK 中:
err := client.CreateSnapshot( ctx, milvusclient.NewCreateSnapshotOption("snapshot_20260608", "source_collection"), )用ListSnapshots确认快照名已出现在集合的快照列表中,即可进入导出步骤。如果后续走的是"referenced 快照"恢复路径(见文末说明),此时用DescribeSnapshot拿到的s3_location就是可直接恢复的元数据 URI;导出 bundle 的路径则由下面几步完成。
第二步:用 ExportSnapshot 导出到目标桶
exportJobID, err := client.ExportSnapshot( ctx, milvusclient.NewExportSnapshotOption( "snapshot_20260608", // 第一步创建的快照名 "source_collection", // 源集合名 "s3://foreign-bucket/export-root", // 导出目标根路径 ).WithExternalSpec(`{"extfs":{"cloud_provider":"aws","region":"us-west-2","use_iam":"true"}}`), )以上示例值取自设计文档,替换为你自己的快照名、集合名和导出根。提交后接口立即返回exportJobID,任务在后台执行。
几个执行语义需要在提交前理解:
- 目标根路径。每个被接受的任务都会把自包含 bundle 写到
<target_s3_path>/exports/<export-id>下,其中<export-id>是系统生成的随机命名空间,两个集群可以请求同一个目标根而不会互相覆盖元数据或对象。目标可以是配置的源桶,也可以是另一个桶;指向源桶内对象 key 的目标同样被接受。 - 同桶覆盖保护。如果目标路径属于源桶,DataCoord 会在复制开始前构建完整的源对象集合,只要生成的目标元数据、segment manifest 或数据对象 key 与源快照对象有交集,任务直接失败。不同桶中 key 相同不算冲突,仍会正常复制。
- external collection 被拒绝。外部集合的 lake fragment 不在快照文件集内,
ExportSnapshot会在枚举和复制之前拒绝这类集合。 - 复制方式。对象由 provider 侧完成复制,不经过 Milvus 节点中转;单次导出任务的总生命周期由
dataCoord.snapshot.exportJobTimeout约束。
external_spec的字段规则以设计文档第 4 节为准;如果 metadata URI 中已编码了 endpoint/provider/region/TLS 信息,与之冲突的external_spec值会被拒绝。
第三步:轮询导出状态,拿到 metadata URI
exportInfo, err := client.GetExportSnapshotState( ctx, milvusclient.NewGetExportSnapshotStateOption(exportJobID), ) metadataURI := exportInfo.GetSnapshotMetadataUri() // 仅 Completed 后可用按设计文档的测试用例 snapshot_test.go 中的做法,用ExportSnapshotCompleted作为成功条件、ExportSnapshotFailed作为失败条件做轮询(用例中失败时读取info.GetReason()获取原因)。任务完成后的验证点:
- 状态为
Completed,snapshotMetadataUri非空,且与DescribeSnapshot返回的s3_location不同(后者指向源快照,前者指向导出 bundle); totalBytes为正数,等于唯一复制的数据对象加上生成的 segment manifest 与最终元数据的字节数;- 只有
Completed状态才暴露 metadata URI 和totalBytes,内部Publishing状态在公开 API 上映射为Executing且进度 99。
对远程对象存储,DescribeSnapshot.s3_location与完成的导出元数据位置都是不带凭证的完整 URI:标准 S3 兼容 provider 形如https://<endpoint>/<bucket>/<object-key>,原生 GCS 为gs://,Azure 为azure://<account-endpoint>/<container>/<object-key>。endpoint 保留在 URI 里,是为了让另一个集群恢复时仍能定位 provider。
第四步:用 RestoreExternalSnapshot 恢复
在目标集群提交恢复请求,输入是恢复后的集合名和上一步拿到的 metadata URI:
jobID, err := client.RestoreExternalSnapshot( ctx, milvusclient.NewRestoreExternalSnapshotOption( "restored_collection", // 恢复后创建的集合名 "s3://foreign-bucket/export-root/exports/<export-id>/snapshots/100/metadata/1.json", ).WithExternalSpec(`{"extfs":{"cloud_provider":"aws","region":"us-west-2","use_iam":"true"}}`), )其中<export-id>是导出时生成的命名空间,snapshots/100/metadata/1.json是 bundle 内部的真实路径示例;实际操作中直接粘贴第三步GetExportSnapshotState返回的 metadata URI 即可,不要手工拼路径。URI 必须是带 scheme 和 host 的完整地址,只给对象 key 会被拒绝。
然后轮询恢复状态:
info, err := client.GetRestoreSnapshotState( ctx, milvusclient.NewGetRestoreSnapshotStateOption(jobID), )恢复完成的验证方式与测试用例一致:状态达到RestoreSnapshotCompleted后,确认目标集合已存在(HasCollection),加载集合(LoadCollection并等待完成),再用强一致性级别对恢复后的集合查询行数,与导出前源集合的行数核对。状态为RestoreSnapshotFailed时读取reason字段定位原因。
可选:手动搬迁整个 bundle 后再恢复
自包含 bundle 的目录布局固定为:
<root>/snapshots/{collectionID}/metadata/{snapshotID}.json <root>/snapshots/{collectionID}/manifests/... <root>/files/...ExportSnapshot写入的是export-root/exports/<export-id>/snapshots/.../metadata/....json加export-root/exports/<export-id>/files/...。如果你把整个 bundle 原样复制到新的根前缀(例如restored/x/snapshots/...与restored/x/files/...),恢复时只需把新的 metadata URI 传给RestoreExternalSnapshot:Milvus 会从导出时元数据里取oldRoot、从恢复请求 URI 取newRoot,自动把自包含路径从oldRootrebase 到newRoot,不需要额外的 root 重写参数。
搬迁有两个硬性约束:bundle 内部布局不能变(files/不能改名、层级不能拆),且 metadata URI 必须保留snapshots/.../metadata/...锚点。如果搬成restored/x/meta.json这类没有snapshots锚点的布局,Milvus 无法推断根是restored还是restored/x,请求会失败,这是设计上的 fail-closed 行为,不是可以通过参数绕过的错误。
REST 调用方式
REST 路由使用 camelCase 字段,external_spec对应 JSON 里的externalSpec:
# 提交导出(字段值需替换为你的实例地址、令牌与实际名称) curl -X POST "$MILVUS_ADDR/v2/vectordb/jobs/snapshot/export" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{ "dbName": "default", "collectionName": "source_collection", "snapshotName": "snapshot_20260608", "targetS3Path": "s3://foreign-bucket/export-root", "externalSpec": "{\"extfs\":{\"cloud_provider\":\"aws\",\"region\":\"us-west-2\",\"use_iam\":\"true\"}}" }'# 查询导出状态 curl -X POST "$MILVUS_ADDR/v2/vectordb/jobs/snapshot/export/describe" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"jobId":"12345"}'# 提交外部恢复 curl -X POST "$MILVUS_ADDR/v2/vectordb/jobs/snapshot/restore_external" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{ "dbName": "default", "targetCollectionName": "restored_collection", "snapshotMetadataURI": "s3://foreign-bucket/export-root/exports/<export-id>/snapshots/100/metadata/1.json", "externalSpec": "{\"extfs\":{\"cloud_provider\":\"aws\",\"region\":\"us-west-2\",\"use_iam\":\"true\"}}" }'# 查询恢复状态 curl -X POST "$MILVUS_ADDR/v2/vectordb/jobs/snapshot/describe" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"jobId":"12345"}'$MILVUS_ADDR与$TOKEN替换为你自己的实例地址和认证令牌,<export-id>以导出完成后状态接口返回的 metadata URI 为准。REST 测试用例 test_snapshot_operations.py 展示了完整的调用序列:提交导出返回jobId,轮询到ExportSnapshotCompleted后取snapshotMetadataURI和totalBytes,再用该 URI 提交restore_external并轮询到RestoreSnapshotCompleted。
相关配置项
以下配置位于 milvus.yaml 的dataCoord.snapshot段(915–922 行附近),按默认值即可执行本文流程,仅在大数据量或高并发导出时需要调整:
| 配置项 | 默认值 | 作用 |
|---|---|---|
exportCopyConcurrency | 16 | 单个导出任务的 provider 端对象复制并发上限;非法或非正值回落到 16 |
exportMaxConcurrentJobs | 1 | DataCoord 并发执行的导出任务数 |
exportJobTimeout | 43200(12 小时,秒) | 被接受的导出任务总生命周期,含排队等待 |
exportJobRetention | 10800(3 小时,秒) | 终态任务在 pin 清理后保留终态信息的时长 |
crossBucketEndpointAllowlist | 空 | 使用自定义对象存储 endpoint 做服务端跨桶复制时的 endpoint 白名单;由cloud_provider和region推导出的标准云 endpoint 不需要配置 |
限制与已知行为
- 不支持跨 provider、跨 endpoint 复制,也没有流式兜底;provider、endpoint、region 或凭证探测显示无法表达为单次 provider 端复制请求时,请求在调度前被拒绝。
- referenced 快照恢复要求源文件保持可读。如果直接用
DescribeSnapshot.s3_location恢复(referenced 布局),元数据仍指向原始 segment/index 文件,恢复期间源快照和被引用文件必须可读;源快照被删除且 GC 清掉了引用文件时,恢复会失败。自包含 bundle 没有这个外部依赖,这也是备份场景推荐走ExportSnapshot的原因。 - 失败的导出可能留下无引用的孤儿对象。Milvus 不会自动删除它们,因为对象路径可能已被旧的已发布 bundle 共享;后续重试可以安全地覆盖不可变的快照对象。
- external collection 暂不支持导出,详见前文说明。
- 凭证模式互斥:
use_iam=true、原始 AK/SK、credential_json只能选一种;请求级的ssl_ca_cert会被接受但忽略,自定义 CA 信任必须来自 Milvus 实例的存储配置。
【免费下载链接】milvusMilvus is a high-performance, cloud-native vector database built for scalable vector ANN search项目地址: https://gitcode.com/GitHub_Trending/mi/milvus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考