Chroma 和 Milvus 怎么选?向量数据库选型指南

向量数据库MilvusChroma数据库选型

Q: Chroma 和 Milvus 怎么选?

一句话答案

Chroma 是轻量嵌入式,pip install 即用,适合原型验证和个人项目;Milvus 是分布式生产系统,适合大规模、高可用场景。Demo 用 Chroma,上线用 Milvus。

核心对比

维度ChromaMilvus
定位嵌入式向量数据库分布式向量数据库
部署方式pip install chromadb,本地文件存储分布式集群,支持 K8s/Docker Compose
数据规模百万级(全内存)十亿级
持久化本地文件(sqlite + parquet)etcd(元数据)+ MinIO/S3(数据)
标量过滤无索引,全量扫描INVERTED/BITMAP 索引
expr 能力支持基本 filter,性能随数据量线性下降完整 expr + 倒排加速
分布式原生支持,多 shard + 多副本
高可用无(单进程崩溃即丢失)多副本 + 故障恢复
客户端Python 优先Python/Java/Go/Node/REST
运维成本几乎为零较高(etcd + MinIO + K8s)
社区开源,活跃开源 + Zilliz 商业支持
适用阶段原型、Demo、个人项目生产环境、企业级

代码对比

# ============= Chroma =============
import chromadb

# 启动客户端(数据存本地)
client = chromadb.PersistentClient(path="./chroma_data")

# 创建集合
collection = client.create_collection(
    name="douyin_videos",
    metadata={"hnsw:space": "cosine"}
)

# 插入
collection.add(
    ids=["1", "2"],
    embeddings=[[0.1, 0.2], [0.3, 0.4]],
    metadatas=[
        {"title": "视频A", "author": "张三", "likes": 10000},
        {"title": "视频B", "author": "李四", "likes": 20000},
    ],
    documents=["视频A的文本", "视频B的文本"]
)

# 搜索
results = collection.query(
    query_embeddings=[[0.15, 0.25]],
    n_results=5,
    where={"likes": {"$gte": 10000}},   # 标量过滤
    where_document={"$contains": "视频"}  # 全文搜索
)

# ============= Milvus =============
from pymilvus import Collection, CollectionSchema, FieldSchema, DataType, connections

connections.connect("default", host="localhost", port="19530")

fields = [
    FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
    FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=768),
    FieldSchema(name="title", dtype=DataType.VARCHAR, max_length=500),
    FieldSchema(name="author", dtype=DataType.VARCHAR, max_length=100),
    FieldSchema(name="likes", dtype=DataType.INT64),
]
schema = CollectionSchema(fields)
collection = Collection("douyin_videos", schema)

# 建索引
collection.create_index("embedding", {"index_type": "IVF_FLAT", "metric_type": "L2", "params": {"nlist": 128}})
collection.create_index("likes", {"index_type": "INVERTED"})
collection.load()

# 搜索
results = collection.search(
    data=[[0.1, 0.2, ...]],
    anns_field="embedding",
    param={"metric_type": "L2"},
    limit=5,
    expr="likes >= 10000",
    output_fields=["title", "author", "likes"]
)

选型决策树

数据量多少?
├── < 100万 → 是否需要分布式?
│   ├── 否 → Chroma ✅
│   └── 是 → 是否需要标量过滤?
│       ├── 简单过滤 → Chroma | Qdrant
│       └── 复杂过滤 → Milvus
├── 100万~1000万 → 是否需要高可用?
│   ├── 否 → Qdrant / Milvus Standalone
│   └── 是 → Milvus ✅
└── > 1000万 → Milvus ✅

面试追问

[!question] 追问详析

Q1: Chroma 的持久化可靠吗?

有限可靠。Chroma 用 sqlite 存元数据 + parquet 存向量,单进程写入。

问题:

  • 多个 Python 进程同时写同一个 PersistentClient → sqlite 锁冲突
  • 进程 crash → 未 flush 的数据丢失(Chroma 没有 WAL)
  • 没有多副本,数据存本地,机器挂了就没了
# ❌ 多进程写入 Chroma — sqlite 锁冲突
# Process 1
chromadb.PersistentClient(path="./data")
# Process 2 — OperationalError: database is locked
chromadb.PersistentClient(path="./data")

# ✅ 单进程 + 读写分离(但 Chroma 原生不支持)

所以 Chroma 不适合生产环境做写密集型。

Q2: 为什么面试官喜欢问这种对比?

因为选型能力 = 工程经验的浓缩表现。能选对的人:

  • 知道每种工具的适用边界
  • 理解”运维成本”、“数据规模”、“一致性”这些非功能需求
  • 不会拿锤子看什么都像钉子

回答时一定要给出分阶段的选型演进策略:不是”我用 Milvus”,而是”初期 Chroma 快速验证 → 数据百万级且需要多服务共享 → 迁 Milvus”。

Q3: Chroma 的 metadata filter 和 Milvus expr 的根本差距?

Chroma filter 是在 Python 层面用 dict 条件匹配,数据全在内存里遍历:

# Chroma — 内存遍历式过滤
where = {"likes": {"$gte": 10000}, "author": "张三"}
# 等价于 Python: [x for x in data if x.likes >= 10000 and x.author == "张三"]

Milvus expr 是在 C++ 层面用倒排索引做:

# Milvus — 倒排索引式过滤
expr = "likes >= 10000 and author == '张三'"
# 用预建的 INVERTED 索引 + BITMAP 加速,O(logN)
数据量Chroma filterMilvus expr
1万<1ms<1ms
10万5-10ms<1ms
100万50-100ms1-2ms
1000万卡死5-10ms

Chroma 数据量到百万级后 filter 会明显变慢,Milvus 有索引所以几乎不受数据量影响。

Q4: 有没有替代方案?

工具适合场景定位
Chroma原型、个人项目最轻量
Milvus大规模生产最重型
Qdrant数据量中等+需要分布式中间态
Weaviate需要集成 NLP 能力自带推理
Pinecone不想运维全托管
FAISS只需要向量检索纯索引库

如果你的数据在 100万~1000万 之间,又不想上 K8s,Qdrant 是最佳平衡点。单二进制文件启动,有分布式能力,性能接近 Milvus。

避坑

[!warning] 常见坑点

坑1:Chroma 的 embeddings 参数和 documents 参数

# Chroma 支持两种搜索方式

# 方式一:自己算 embedding
collection.query(query_embeddings=[[...]], ...)

# 方式二:让 Chroma 用默认模型算
collection.query(query_texts=["视频A"], ...)  # 默认用 all-MiniLM-L6-v2

# ❌ 混合用两个参数会报错
collection.query(query_embeddings=[[...]], query_texts=["视频A"])
# ValueError: can't use both

坑2:Chroma 的 PersistentClient 路径冲突

# ❌ 两个客户端用同一个路径
c1 = chromadb.PersistentClient(path="./data")
c2 = chromadb.PersistentClient(path="./data")  # 锁住

# ✅ 不同项目用不同路径
c1 = chromadb.PersistentClient(path="./project_a_data")
c2 = chromadb.PersistentClient(path="./project_b_data")

坑3:Chroma 的 hnsw 参数在 create_collection 时设

# ❌ 建完 collection 后再改就晚了
collection = client.create_collection("videos")

# ✅ 创建时指定
collection = client.create_collection(
    "videos",
    metadata={"hnsw:space": "cosine", "hnsw:construction_ef": 200}
)

标量过滤的 where 条件里,key 必须是 metadata dict 里的键,不能嵌套。

坑4:Milvus 的运维负担被低估

# 启动 Milvus 需要至少:
# etcd + MinIO/S3 + Milvus Proxy/QueryNode/DataNode/IndexNode
# docker-compose.yml 至少 6 个 service

# 对比 Chroma:
pip install chromadb
python -c "import chromadb; chromadb.PersistentClient()"

面试官问”为什么选 Chroma”时,除了性能还应该提运维成本:一个 10 人的小团队扛不起 Milvus 集群。

坑5:数据迁移成本

# Chroma → Milvus 没有官方迁移工具,需要自己写脚本

# 1. 从 Chroma 读出所有数据
all_data = collection.get(include=["embeddings", "metadatas", "documents"])

# 2. 格式化
entities = [
    all_data["ids"],
    all_data["embeddings"],
    [m["title"] for m in all_data["metadatas"]],
    ...
]

# 3. 写入 Milvus
milvus_collection.insert(entities)

选型时一定要考虑迁移成本,Chroma → Milvus = 自己写脚本,Milvus → Chroma = 几乎不可逆(Milvus 的索引和分区概念 Chroma 没有)。

相关笔记

  • [[Q-Milvus的扁平元数据是什么]]
  • [[Q-Milvus的expr过滤是什么]]
  • [[Q-FAISS和Milvus怎么选]]
  • [[Q-向量数据库选型全景]]