向量库的存储别绑死一块盘:用 RustFS 给 Milvus 当 S3 对象底座
Milvus 跑起来,向量、索引和一堆相关对象默认写在它自己的本地卷里。单机验证很顺,可一旦向量量上来,本地盘的容量、IO 和单点就成了绕不开的墙。更麻烦的是,向量库的数据量和查询负载往往不是线性增长,一次模型切换就可能让向量规模翻几倍。Milvus 支持把兼容 S3 的对象存储当持久层,向量数据和索引直接落进 RustFS 桶,Milvus 节点本身只留运行时数据和缓存。把存储下沉到对象存储,向量库的扩缩就和数据量解耦了,加节点是加节点,加容量是加桶。
Milvus 把数据往哪写
Milvus(开源项目)把服务元数据放在 etcd,把向量数据、索引和对象持久化到对象存储的s3://my-bucket/milvus前缀下。运行时数据、索引缓存这些仍然需要本地 Milvus 卷,所以对象存储不是唯一的本地状态,但它承接了最占空间、最该持久的那部分。这一层换成 RustFS 之后,节点的本地盘只管缓存和临时状态,真正的向量都在桶里,节点挂了换一台,数据从桶拉回即可。
官方指南用 Milvus v2.6.0(配合 etcd v3.5.18、attu v2.6.5、pymilvus 2.6.0)验证通过。下面重点放在配置和验证,不重复从零装。etcd 那部分元数据量很小,还是留在本地卷或独立 etcd 集群,对象存储只接向量和索引这一大头。
落到桶里的具体是 Milvus 的段文件(segment)和索引文件,查询时按需要把段从对象存储读回本地缓存再算。这意味着冷查询第一次会多一层对象存储读取延迟,但缓存命中后和本地盘差异不大,而换来的是存储容量不再受单机盘限制。对象在桶里按milvus/下的子前缀组织,插入、索引、段各有自己的目录,排查时翻控制台能看到这些对象随集合的增删而增长,也能据此判断某次写入是否真的落了地。代价是对象存储的读取延迟比本地 NVMe 高,对延迟极度敏感的在线检索,本地盘加内存缓存可能更合适;对象存储更适合数据量大、冷热分明的场景,把成本摊薄。
一个 minio 配置键,实际指向 RustFS
Milvus 把兼容 S3 的存储设置归在minio这个配置键下面。名字虽然叫 minio,但并不要求后端真的是 MinIO,这里minio.address指向的就是 RustFS 服务。关键就是把common.storageType切成remote,再填一组minio.*:
common:storageType:remoteminio:address:rustfs:9000port:9000bucketName:my-bucketrootPath:milvususeSSL:falseuseIAM:falsecloudProvider:awsregion:us-east-1useVirtualHost:falseuseVirtualHost: false这一行决定走路径风格(path-style)请求,对 RustFS 是必须的,因为 RustFS 不走虚拟主机风格的桶寻址。useIAM: false表示用静态密钥而不是实例角色,配合cloudProvider: aws走标准的 S3 签名。主机名rustfs在 compose 网络内部解析,主机上的客户端用http://localhost:9000。同样的凭证也会通过环境变量交给 Milvus:MINIO_ADDRESS=rustfs:9000、MINIO_ACCESS_KEY_ID、MINIO_SECRET_ACCESS_KEY、MINIO_REGION=us-east-1,外加ETCD_ENDPOINTS=etcd:2379指向元数据服务。这套配置对分布式 Milvus 同样适用,区别只是 standalone 换成多个 coordinator 和 worker 节点,它们共享同一个minio配置指向 RustFS。无论哪种形态,给 Milvus 用的凭证都应该走专用的 IAM 子账号,只对这个桶有读写权,不要把管理员密钥塞进 compose 的环境变量,密钥泄漏的波及面要压到最小。
用 compose 把整栈拉起来
官方 demo 把 RustFS、etcd、Milvus standalone 和 Attu 塞进同一个 compose,还用一个create-bucket服务专门建my-bucket。桶必须存在,Milvus 自己不建。RustFS 服务镜像官方指南钉的是较早的rustfs/rustfs:1.0.0-alpha.83,生产环境建议换成当前稳定版rustfs/rustfs:1.0.4(2026-10-25),稳定版覆盖这些 S3 操作且经过充分验证。
dockercompose up-ddockercomposeps-acreate-bucket应以退出码 0 结束,etcd、rustfs、standalone进入健康状态。RustFS 控制台在http://localhost:9001,Attu 在http://localhost:8000,Milvus 健康端点是http://localhost:9091/healthz。Attu 通过 compose 网络连standalone:19530,浏览器端则用localhost:19530。
插几条向量,确认它们落进桶里
装一个和服务器同版本的客户端,插几条四维向量再搜一下:
frompymilvusimportMilvusClient client=MilvusClient(uri="http://localhost:19530")ifclient.has_collection(collection_name="rustfs_demo"):client.drop_collection(collection_name="rustfs_demo")client.create_collection(collection_name="rustfs_demo",dimension=4)client.insert(collection_name="rustfs_demo",data=[{"id":1,"vector":[0.1,0.2,0.3,0.4]},{"id":2,"vector":[0.2,0.3,0.4,0.5]},{"id":3,"vector":[0.9,0.8,0.7,0.6]},])client.flush(collection_name="rustfs_demo")插完刷完,用存储桶初始化镜像列一下milvus根路径下的对象,应该能看到 Milvus 在milvus/前缀下创建的对象:
dockercompose run--rm--entrypoint/bin/sh create-bucket-c\'/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" \ --region us-east-1 --bucket-lookup path >/dev/null && /usr/bin/rc find rustfs/my-bucket/milvus'插入、刷新、查询成功,加上 RustFS 对象列表里出现milvus/前缀的内容,这三件事合起来才算验证通了整条链路。Milvus 可能先缓冲或压缩再落盘,所以插完立刻查不到对象是正常的,给一点时间让它刷出去。
RustFS 当前稳定版是 1.0.4(2026-10-25),覆盖 Milvus 需要的这些 S3 操作。官方 GA 基准里 4KiB 小对象 PUT 大约能到 MinIO 的 2.3 倍,向量和索引多为中等大小的对象,吞吐稳定即可,这一层 RustFS 不拖后腿。控制台在/rustfs/console/路径下,进去能直接翻milvus/前缀确认对象到位。
几个别动的生产红线
- 端点用服务名:compose 内部必须写
rustfs:9000,写localhost:9000指向的是 Milvus 容器自己,连不上 RustFS; - useVirtualHost 保持 false:改回虚拟主机风格,RustFS 的路径风格请求就废了;
- 已有部署别动根路径:
minio.bucketName、minio.rootPath和 etcd 根路径一旦改动,旧数据对不上,恢复不了; - 本地卷仍要备份:运行时数据和缓存还在 Milvus 本地卷,对象存储只是持久层的一部分,整机备份别只盯桶;
- 官方指南是本地集成测试:文档声明不适用于生产环境,生产里要补上专用凭证、TLS、鉴权和资源规划。
接好之后,Milvus 的向量全在 RustFS 桶里,扩节点、换机器都不用搬数据,存储容量跟着桶走而不是跟着机器走。对于已经在用 RustFS 承接着日志、备份、镜像这类负载的团队,顺手把向量库也接进来,存储底座统一,运维心智和成本模型都跟着统一,不必为向量单独养一套分布式文件系统。
官方把 Milvus 接进 RustFS 的完整步骤在 Milvus 集成指南 里,包含 compose、插入验证与故障排查的示例。生产凭证的创建方式看 访问密钥管理,S3 兼容性细节看 S3 协议说明。