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-composeservices: 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万以内,需要混合检索(英文) | Weaviate | GraphQL方便,混合检索好用 |
| 数据量大、需要并发、中文环境 | Milvus | 性能好,过滤强,但运维复杂 |
| 已有PostgreSQL | PGVector | 不用引入新组件,够用就行 |
有一个我选型时应该问自己但没问的问题:我的数据量会涨多快?
如果一开始就知道数据量会在三个月内从几千涨到几十万,我可能会直接上Milvus,省掉中间的迁移成本。但创业公司的特点就是”先活下来再考虑未来”,所以先用Chroma验证产品方向,这个决策本身也没错。
迁移的成本比想象中大
最后说说迁移这件事。
从Chroma到Weaviate,我花了一天。从Weaviate到Milvus,花了三天。
不是技术难度大,是:
- 数据迁移要重新生成向量——不同数据库的向量存储格式不兼容
- 查询代码要全部重写——API差异不小
- 测试要重跑——每次迁移后都要重新验证检索质量
所以如果你还在选型阶段,我的建议是:别只看”哪个更好”,要看”哪个够你用多久”。选型不是找最优解,是找在当前约束下性价比最高的解。