使用Milvus时存储源数据方案
1. 直接使用 MinIO 存储原始内容的推荐场景
如果满足以下条件,推荐直接使用 MinIO:
- 原始内容为大型文件:如图片、视频、PDF 文档等非结构化数据。
- 无需复杂元数据查询:仅需通过简单路径或 URL 访问文件。
- 已有 MinIO 运维经验:熟悉其 API 和权限管理。
- 高扩展性需求:预计数据量会快速增长,需分布式存储支持。
操作示例:
// 插入数据到 Milvus 时关联 MinIO 路径
InsertParam insertParam = InsertParam.newBuilder()
.withCollectionName("images")
.withFieldsData(Arrays.asList(
FieldData.newBuilder().withName("id").withIntData(1001).build(),
FieldData.newBuilder().withName("vector").withFloatVectorData(vector).build(),
FieldData.newBuilder().withName("minio_path").withStringData("mybucket/images/1001.jpg").build()
))
.build();
milvusClient.insert(insertParam);
优点:
- 无缝集成:MinIO 已与 Milvus 部署在同一环境,无需额外服务。
- 成本低:直接复用现有对象存储,节省资源。
- 高性能:对象存储专为大文件优化,适合高吞吐场景。
缺点:
- 元数据管理弱:需自行维护
minio_path与向量 ID 的映射关系。 - 复杂查询困难:如需要根据文件属性(如上传时间、标签)筛选内容,需额外开发逻辑。
2. 更优方案推荐(不直接使用 MinIO 的场景)
如果遇到以下需求,建议采用其他方案:
场景 1:需要复杂元数据查询
-
需求:根据文件属性(如标签、作者、时间范围)联合查询。
-
方案:MinIO + 关系型数据库(如 PostgreSQL)。
-
分工:
- MinIO:仅存储原始文件。
- 关系型数据库:存储文件的元数据和 MinIO 路径。
- Milvus:存储向量和关联的元数据 ID。
-
操作流程:
-
上传文件到 MinIO,生成唯一路径。
-
将元数据(如标签、描述、路径)写入 PostgreSQL:
INSERT INTO images (id, minio_path, tags, upload_time) VALUES (1001, 'mybucket/images/1001.jpg', '风景,夏季', NOW()); -
插入向量到 Milvus,关联元数据 ID:
InsertParam insertParam = InsertParam.newBuilder() .withCollectionName("images") .withFieldsData(Arrays.asList( FieldData.newBuilder().withName("metadata_id").withIntData(1001).build(), FieldData.newBuilder().withName("vector").withFloatVectorData(vector).build() )) .build();
-
-
查询示例:
-- 联合查询:先通过 Milvus 搜索向量,再通过 ID 关联元数据 SELECT * FROM images WHERE id IN (SELECT metadata_id FROM milvus_results) AND tags LIKE '%风景%'; -
优点:
- 支持复杂 SQL 查询(如 JOIN、聚合)。
- 事务支持,保证数据一致性。
-
缺点:
- 需维护两个系统(Milvus + PostgreSQL)。
-
场景 2:多模态数据(文本 + 向量 + 文件)
-
需求:同时管理结构化数据、非结构化文件和向量。
-
方案:MongoDB(多模型数据库)。
-
设计:
- MongoDB:存储原始内容(如 JSON 文档)、文件路径或 Base64 编码的小文件。
- Milvus:存储向量,通过文档 ID 关联。
-
插入示例:
// MongoDB 插入文档 Document doc = new Document() .append("_id", 1001) .append("description", "夏季风景照片") .append("minio_path", "mybucket/images/1001.jpg"); mongoCollection.insertOne(doc); // Milvus 插入向量 InsertParam insertParam = InsertParam.newBuilder() .withCollectionName("images") .withFieldsData(Arrays.asList( FieldData.newBuilder().withName("doc_id").withIntData(1001).build(), FieldData.newBuilder().withName("vector").withFloatVectorData(vector).build() )) .build(); -
查询示例:
// 先通过 Milvus 搜索向量,获取关联的 doc_id List<Long> docIds = milvusSearchResults.getIDs(); // 从 MongoDB 查询完整数据 Bson filter = Filters.in("_id", docIds); FindIterable<Document> results = mongoCollection.find(filter); -
优点:
- 灵活存储多模态数据(JSON、文件路径、二进制)。
- 内置索引支持快速查询。
-
缺点:
- 大文件仍需依赖 MinIO 或 GridFS(MongoDB 的分布式文件存储)。
-
场景 3:极致简化架构
-
需求:最小化运维成本,接受轻度性能损失。
-
方案:Milvus 标量字段存储小文件元数据。
-
设计:
- 小文件(如小于 1MB 的文本、缩略图)直接 Base64 编码后存入 Milvus 标量字段。
- 大文件:仍用 MinIO 存储路径。
-
示例:
// 将小图片转换为 Base64 字符串 String imageBase64 = Base64.getEncoder().encodeToString(imageBytes); // 插入到 Milvus InsertParam insertParam = InsertParam.newBuilder() .withCollectionName("images") .withFieldsData(Arrays.asList( FieldData.newBuilder().withName("id").withIntData(1001).build(), FieldData.newBuilder().withName("vector").withFloatVectorData(vector).build(), FieldData.newBuilder().withName("thumbnail").withStringData(imageBase64).build() )) .build(); -
优点:
- 无需外部存储,架构简单。
-
缺点:
- 性能下降(Base64 编解码开销)。
- 仅适合极小文件(Milvus 单条记录大小有限制)。
-
3. 决策树:如何选择存储方案?
原始内容类型?
├── 大文件(图片、视频) → MinIO + 元数据管理(PostgreSQL/MongoDB)
├── 小文本/简单元数据 → Milvus 标量字段
└── 混合多模态数据 → MongoDB + MinIO
总结
- 推荐直接使用 MinIO:如果系统以大规模非结构化文件为主,且无需复杂元数据查询。
- 推荐结合关系型数据库:如果需要事务支持和复杂查询。
- 推荐多模型数据库:如果处理多模态数据且希望简化架构。
最终选择应基于以下优先级:
- 数据类型和大小:大文件选 MinIO,小文本选 Milvus 标量字段。
- 查询复杂度:复杂查询需引入关系型数据库。
- 运维成本:MinIO 方案最简单,但功能有限;多系统方案功能强大,但维护复杂。
可以根据实际场景混合使用这些方案,例如:
- 核心图片存储在 MinIO。
- 元数据和频繁查询的标签存在 PostgreSQL。
- 向量存在 Milvus。