1506 字
8 分钟
Vector数据库选型踩坑录:从Chroma到Milvus的迁移之路

Vector数据库选型踩坑录:从Chroma到Milvus的迁移之路#

做一个RAG项目的时候,我花了一整个周末选型向量数据库。看了一堆对比文章,最后选了”最适合原型开发”的Chroma。

后来我换了Weaviate,又换了Milvus。

不是我有选择困难症,是业务场景在变,最初觉得”刚刚好”的方案很快就撑不住了。写这篇不是为了说哪个数据库好哪个不好——它们各有适用场景。而是想记录我在选型时没考虑到但后来踩到的坑

第一阶段:Chroma,入门刚刚好#

项目初期,需求很简单:把大概2000条文档片段向量化,存进去,然后按查询做相似度检索。

Chroma完美匹配:

import chromadb
client = chromadb.Client()
collection = client.create_collection("docs")
collection.add(
documents=["文档片段1", "文档片段2"],
ids=["1", "2"]
)
results = collection.query(
query_texts=["搜索内容"],
n_results=5
)

十行代码就跑起来了。不需要装数据库,不需要配置服务,pip install完直接能用。对于个人项目来说,这体验是一流的。

但坑在哪里?

第一个坑出现在文档量涨到5万条的时候。

插入速度明显变慢了。查了一下,Chroma默认的SQLite存储在大量数据下性能下降很明显。官方文档提到了PersistentClient可以持久化到磁盘,但没说的是——磁盘IO成了新的瓶颈。

我当时的插入速度降到了大概每秒200条。2000条的时候没感觉,5万条的时候,每次批量插入要等好几分钟。

第二个坑更致命:并发支持极差。

我们的RAG系统需要同时服务多个用户的查询请求。Chroma在并发写入和读取时,偶尔会出现锁竞争导致查询超时。这个问题在开发环境(单用户)根本测不出来。

第二阶段:Weaviate,看起来很美#

看到Chroma的问题后,我开始找更生产级的方案。Weaviate当时看起来很合适:

  • 支持混合检索(向量+关键词)
  • 自带GraphQL接口
  • 有Docker一键部署

迁移过程比想象中顺利。Weaviate的Python客户端设计得不错,API跟Chroma有差异但不大。

import weaviate
client = weaviate.connect_to_local()
collection = client.collections.get("Docs")
# 插入
collection.data.insert({
"content": "文档片段",
"source": "manual"
}, uuid=uuid4())
# 查询
result = collection.query.near_text(
query="搜索内容",
limit=5
)

跑了一周,感觉很好。并发没问题了,插入速度也快了不少。

但坑又来了。

第一个坑:混合检索的配置。

Weaviate的混合检索需要同时配置BM25和向量索引。BM25的tokenizer对中文的支持不够好——默认按空格分词,而中文句子没有空格。这意味着关键词检索的效果大打折扣。

后来找到了解决方案:用自定义tokenizer或者把BM25权重调低。但这意味着我们最看重的”向量+关键词混合检索”优势,在中文场景下被削弱了很多。

第二个坑:内存占用。

5万条文档、1024维向量的情况下,Weaviate进程占用了大概3GB内存。这对服务器来说不算夸张,但对我们的部署环境(一台4GB内存的云服务器)来说太吃紧了。

第三阶段:Milvus,终于稳定了#

从Weaviate迁移到Milvus,是这次选型中最折腾的一步。

Milvus是分布式架构,部署复杂度比前两个高一个量级。docker-compose文件里要启动etcd、MinIO、还有Milvus本身三个服务。

# 简化版的docker-compose
services:
etcd:
image: quay.io/coreos/etcd:v3.5.0
minio:
image: minio/minio:latest
milvus:
image: milvusdb/milvus:v2.4.0
depends_on: [etcd, minio]

部署完之后我有点后悔——因为运维成本明显上去了。

但为什么最后还是留下来了?

两个字:性能。

同样的5万条数据、同样的查询负载,Milvus的响应时间比Weaviate快了约40%,内存占用反而更低(约1.5GB)。

更重要的是,它原生支持过滤表达式,在查询时可以做精细的元数据过滤,不需要额外的应用层处理。

from pymilvus import connections, Collection
connections.connect()
collection = Collection("docs")
collection.load()
results = collection.search(
data=[query_vector],
anns_field="embedding",
param={"metric_type": "COSINE", "params": {"nprobe": 10}},
limit=5,
expr="source == 'manual' and date > 1700000000" # 原生过滤
)

这个过滤表达式功能在我们后来的项目中变得非常重要——比如只搜索特定来源、特定时间范围内的文档。

选型建议#

经历了这一轮折腾,我的建议是:

你的场景推荐方案为什么
个人项目/原型验证Chroma零配置,10分钟跑起来
数据量10万以内,需要混合检索(英文)WeaviateGraphQL方便,混合检索好用
数据量大、需要并发、中文环境Milvus性能好,过滤强,但运维复杂
已有PostgreSQLPGVector不用引入新组件,够用就行

有一个我选型时应该问自己但没问的问题:我的数据量会涨多快?

如果一开始就知道数据量会在三个月内从几千涨到几十万,我可能会直接上Milvus,省掉中间的迁移成本。但创业公司的特点就是”先活下来再考虑未来”,所以先用Chroma验证产品方向,这个决策本身也没错。

迁移的成本比想象中大#

最后说说迁移这件事。

从Chroma到Weaviate,我花了一天。从Weaviate到Milvus,花了三天。

不是技术难度大,是:

  1. 数据迁移要重新生成向量——不同数据库的向量存储格式不兼容
  2. 查询代码要全部重写——API差异不小
  3. 测试要重跑——每次迁移后都要重新验证检索质量

所以如果你还在选型阶段,我的建议是:别只看”哪个更好”,要看”哪个够你用多久”。选型不是找最优解,是找在当前约束下性价比最高的解。