本地 POC 里只有一个 Chroma collection,add() 后马上就能查。到了多人使用的 MVP,团队把代码里的地址换成 Milvus,结果第一批数据还没导完就遇到字段超长、主键冲突、过滤表达式不一致和“刚写入却搜不到”。向量库迁移从来不只是换一个连接串。

在这里插入图片描述

本文只拆解其中可迁移的工程问题:如何把隐式契约盘点出来,如何回填并持续同步,如何证明新旧检索等价,以及怎样切流和回滚。

一句话概括:向量库迁移的交付物不是“Milvus 里有数据”,而是一个经过数据、检索、权限和运行指标共同验收的新检索服务。

一、先盘点 Chroma 里藏着的六份契约

Chroma 上手快,是因为很多选择被 collection 接住了。官方文档显示,collection 可以绑定 embedding function、metadata 与 HNSW configuration;query() 还会自动调用 collection 的 embedding function。迁移前如果只导出 documents,会漏掉真正决定结果的配置。

至少要冻结以下清单:

契约 要记录什么 漏掉后的现象
ID chunkId 生成规则、是否稳定 重跑后重复或无法覆盖
Embedding 模型、版本、维度、query/document prompt 新旧向量不可比较
Metric cosine、IP 或 L2,是否归一化 排名整体变化
Metadata 字段名、类型、缺省值、ACL 过滤结果不一致
Chunk strategyVersion、parentId、chunkIndex 引用与父子召回失效
Query Top-K、threshold、filter、排序和一致性 “数据一样,结果不一样”

在这里插入图片描述

建议把它们固化成 manifest,而不是写在迁移脚本注释里:

{
  "corpusVersion": "kb-2026-07",
  "embedding": {
    "modelId": "example-embedding-v1",
    "dimension": 768,
    "metric": "COSINE",
    "normalized": true,
    "queryPromptVersion": "q1",
    "documentPromptVersion": "d1"
  },
  "chunking": {
    "strategyVersion": "structure-router-v2"
  },
  "requiredMetadata": [
    "tenantId", "documentId", "parentId", "chunkIndex",
    "sourceUri", "acl", "status", "version"
  ]
}

迁移期间禁止顺便换 Embedding、改切块或重写 metadata。一次只改变一个大变量,检索差异才有可能归因。

二、Milvus Schema 不应该照抄 Chroma metadata

Milvus 的 dense vector 字段需要显式声明数据类型和 dim,标量字段也需要考虑长度、过滤和排序。不要把 Chroma metadata 整包塞进一个动态 JSON,然后期待生产检索与权限过滤依旧清晰。

一个知识库 collection 可以按以下思路建模:

SchemaManifest schema = SchemaManifest.builder()
        .primaryKey("chunk_id", VARCHAR, 128)
        .scalar("tenant_id", VARCHAR, 64)
        .scalar("document_id", VARCHAR, 128)
        .scalar("parent_id", VARCHAR, 128)
        .scalar("chunk_index", INT32)
        .scalar("corpus_version", VARCHAR, 32)
        .scalar("status", VARCHAR, 16)
        .scalar("content_hash", VARCHAR, 64)
        .text("content", VARCHAR, MAX_CONTENT_BYTES)
        .vector("embedding", FLOAT_VECTOR, manifest.dimension())
        .build();

这段是领域骨架,不是固定版本 SDK 的可编译代码。真正建表时使用项目所锁定的 Milvus Java SDK 请求类。

哪些字段值得提升为显式标量

满足任一条件就值得显式建字段:

  • 每次 Search 都用于 tenant / ACL / status / version 过滤;
  • 父子文档回查、分组或排序需要;
  • 要做唯一性、完整性或迁移校验;
  • 需要监控分布,例如不同 corpusVersion 的条数。

纯展示字段、低频扩展字段可以保留在 JSON,但不要把核心权限字段藏进去。

VARCHAR 上限按字节校验

Milvus 文档提醒,多字节字符可能占多个字节。建表前要对源数据做字节长度分布,而不是只看 Java 字符数:

int utf8Bytes = value.getBytes(StandardCharsets.UTF_8).length;
if (utf8Bytes > fieldMaxBytes) {
    quarantine(documentId, fieldName, utf8Bytes);
}

超长正文更适合存对象存储或关系库,Milvus 保存检索所需文本与引用。盲目把字段上限调得极大,会让 schema 失去约束价值。

三、迁移流水线要有快照、回填和增量三条水位

直接从 Chroma 读一页、向 Milvus 写一页,脚本中断后很难回答“哪些数据已经安全落地”。更稳的结构如下:

在这里插入图片描述

Chroma Snapshot
  → Export Page
  → Validate / Normalize
  → Migration Staging
  → Milvus Backfill
  → Count + Hash + Query Audit

Online Writes
  → Source of Truth
  → Dual Write / CDC Queue
  → Chroma + Milvus

1. 快照水位

先确定一个 snapshotAt。Chroma 官方 get(limit, offset) 支持分页获取记录,但 offset 分页期间若源数据仍变化,可能出现漏读或重复。工程上可以:

  • 短暂停写后做一致快照;
  • 或先记录快照边界,再把之后的变化写入增量日志;
  • 或从权威文档库重新生成,而不是把向量库当唯一真源。

最理想的 source of truth 是清洗后的文档与 manifest,向量库只是可重建索引。这样迁移无需依赖旧库导出的向量,还能验证整个入库链。

2. 回填水位

每个批次记录:

public record MigrationCheckpoint(
        String jobId,
        long sourceOffset,
        int batchSize,
        long exportedCount,
        long insertedCount,
        String batchHash,
        MigrationStatus status,
        Instant updatedAt) {
}

小规模可批量 insert;大规模可把规范化数据准备为 Milvus bulk import 支持的文件,再轮询 job id。不要用无界 upsert 充当全量导入,Milvus 文档明确提醒大规模 upsert 可能增加 DataNode 内存压力。

3. 增量水位

回填期间线上仍会新增、修改和删除文档。要么双写,要么通过 outbox/消息队列把变化投递给两个索引消费者。事件必须携带稳定版本:

public record ChunkIndexEvent(
        String eventId,
        String chunkId,
        long sourceVersion,
        Operation operation,
        String contentHash,
        Instant occurredAt) {
}

消费者只接受更高 sourceVersion,删除也要有 tombstone。否则重试可能让旧内容覆盖新内容。

四、迁移校验不能只做 count 对账

两边条数相等仍可能存在内容错位、向量错配和过滤失效。建议分四层校验。

第一层:结构校验

  • collection schema 与 manifest 一致;
  • vector dim、metric、index type 明确;
  • 必填字段非空,字符串不超长;
  • chunkId 唯一,parentId 与 corpusVersion 合法。

第二层:数据校验

  • 总数、按 tenant/status/version 分组数;
  • 随机与分层抽样的 contentHash
  • 删除记录与失败隔离数量;
  • embedding 非空、维度正确、无 NaN/Infinity。

第三层:检索校验

对固定 Query Set 同时查询新旧两套索引:

public record RetrievalDiff(
        String queryId,
        List<String> oldTopK,
        List<String> newTopK,
        double overlapAtK,
        boolean expectedEvidenceInOld,
        boolean expectedEvidenceInNew,
        Duration oldLatency,
        Duration newLatency) {
}

不要要求 Top-K 顺序完全一致。近似索引实现不同,本来就可能交换相近结果;真正门禁应围绕标注证据的 Recall、MRR、NDCG、过滤正确率与无答案行为。

第四层:业务校验

  • 同一 tenant 看不到其他 tenant 数据;
  • parent 回查、引用、图片 URL 仍可用;
  • 刚写入数据在所选 consistency level 下何时可见;
  • P50/P95/P99、错误率、超时与资源水位满足预算。

Milvus 提供多种一致性级别。功能验收可以使用更强的一致性减少“刚写完还不可见”的干扰,线上则根据新鲜度与延迟需求选择,不能默认值照搬到底。

五、切流用稳定 Alias,不让业务绑定物理 Collection

Milvus alias 可以把一个逻辑名称指向具体 collection。应用只访问 knowledge_active,物理 collection 使用版本名:

knowledge_v1_chroma-compatible
knowledge_v2_milvus_backfill
knowledge_active → knowledge_v1

切流前先做影子查询:线上请求继续使用旧结果回答,同时异步查询 Milvus 并记录 diff。通过门禁后再按租户或流量比例灰度,最终修改 alias:

knowledge_active → knowledge_v2

回滚不是 drop 新 collection,而是把 alias 指回旧版本。旧索引至少保留一个观察窗口,并停止双写前确认没有未消费事件。

六、五种常见失败

在这里插入图片描述

1. 边迁移边换 Embedding

一旦检索变差,无法判断来自向量库、索引参数还是模型。库迁移与模型迁移拆成两个发布单元。

2. 只导 documents,不导 metadata 与 IDs

文本存在却无法过滤、引用和幂等覆盖。迁移对象应是完整 Chunk Contract。

3. 用 count 相等宣布成功

条数相等不能证明 id-content-vector 对齐。至少加 Hash 抽样与固定 Query Set。

4. 忽略写后可见性

新数据刚写入时,查询一致性选择会影响可见性。测试脚本必须等待明确状态或使用合适一致性级别。

5. 业务直接写物理 collection 名

每次切换都需要改配置和重启。稳定 alias 才能把切流变成可回滚控制面动作。

七、面试时怎么讲

我会先冻结 Chroma 的 ID、Embedding、metric、metadata、chunk 和 query 六类契约,再按 Milvus 的显式 schema 重新建模。迁移分为快照回填和线上增量两条链路,每个批次有 checkpoint、content hash 和幂等 sourceVersion。验收不只对 count,还比较维度、字段分布、Hash、固定问题集的 Evidence Recall、过滤权限和延迟。线上先做 shadow query,再灰度,最后通过 collection alias 切流;回滚只需把 alias 指回旧 collection。

上线检查清单

在这里插入图片描述

  • 迁移 manifest 已冻结,期间不换模型和切块策略;
  • 主键、向量维度、metric、过滤字段和长度上限显式定义;
  • 快照边界、回填 checkpoint 和增量水位可追踪;
  • 写入使用稳定版本与幂等规则,删除有 tombstone;
  • count、字段分布、contentHash、向量合法性均通过;
  • 固定 Query Set 比较证据 Recall、排名、过滤和延迟;
  • 一致性级别经过写后可见性测试;
  • 业务使用 alias,保留旧 collection 与一键回滚路径。

总结

Chroma 到 Milvus 的变化不只是从单机工具走向分布式组件,它迫使团队把原来隐含的检索契约写清楚。Schema、索引、数据水位、一致性、影子对比和 alias 切流共同组成迁移方案。

真正可靠的迁移,允许你回答三个问题:哪条数据到了哪里、为什么新旧结果不同、出现问题怎样在分钟级回退。答不上这三句,即使 Milvus 已经装好,迁移也还没有完成。

参考资料

  1. Chroma Manage Collections
  2. Chroma Query and Get
  3. Chroma Configure Collections
  4. Milvus Dense Vector
  5. Milvus Index Vector Fields
  6. Milvus Manage Aliases
  7. Milvus Consistency
  8. Milvus Insert, Upsert & Delete
  9. Milvus Java Bulk Import
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐