从 Chroma 换成 Qdrant,我踩了 100 万向量的坑
版权声明
我们非常重视原创文章,为尊重知识产权并避免潜在的版权问题,我们在此提供文章的摘要供您初步了解。如果您想要查阅更为详尽的内容,访问作者的公众号页面获取完整文章。
TL;DR
选向量库不是在比功能,是把你的场景参数(数据量、查询复杂度、运维条件)套进决策框架 Chroma 是嵌入式帮手(零运维、<100 万向量最佳),Qdrant 是独立引擎(生产级、过滤不伤召回率) 两段可运行 Python 代码已给出——今天就能在自己的数据上跑起来
你打开三篇向量数据库对比文章,关掉三篇。
不是写得太差——它们列的功能都对。但看完你依然不知道自己的项目该用 Chroma 还是 Qdrant。
问题出在问法上。"Chroma 和 Qdrant 哪个更好"——这是错的。
选型不是在比产品,是在比场景。
你的数据量多大?查询条件是简单标签还是复杂组合?有人帮你管服务器吗?
换个说法你就懂了:你不会问"轿车和货车哪个更好"——你问"我每天送两个孩子上学顺路买菜,选哪个"。
01选型决策矩阵
我自己最早做 RAG 原型的时候,无脑选了 Chroma——就图个 pip install 省事。后来一个项目数据量涨到 200 万条,检索从 50ms 漂到 800ms,排查了半天才发现不是 embedding 模型的问题,是 Chroma 在数据量过阈值之后合并层扛不住了。换成 Qdrant,延迟回到 40ms 以内。所以上面那个 100 万的阈值不是我从哪抄的——是真的交过学费。
还是不确定?往下看。
02同一个操作,两种活法
装包:
pip install chromadb qdrant-client
Chroma 版:跟你的 Python 脚本住在一起
import chromadb # 不需要启动任何独立服务——Chroma 活在你的 Python 进程里 client = chromadb.Client() # 建集合,384 维向量 collection = client.create_collection( name="my_docs", metadata={"hnsw:space": "cosine"} # 余弦相似度 ) # 塞数据——文本和向量一起存 collection.add( documents=[ "Python 的 asyncio 在 3.11 后性能提升明显", "Qdrant 用 Rust 写的,GIL 管不着它", "Chroma 默认用 HNSW 做索引", ], metadatas=[ {"topic": "python", "year": 2024}, {"topic": "vector-db", "year": 2025}, {"topic": "chroma", "year": 2024}, ], ids=["doc_1", "doc_2", "doc_3"], ) # 语义搜索 results = collection.query( query_texts=["向量检索怎么加速?"], n_results=2, ) for i, doc_id in enumerate(results["ids"][0]): doc = results["documents"][0][i] distance = results["distances"][0][i] print(f" [{doc_id}] {doc} (距离: {distance:.3f})")
没启动任何服务,没配任何网络,没写任何 docker-compose。这就是嵌入式数据库——它就是你程序里 import 的一个库,像 SQLite 一样活在你的进程里。
Qdrant 版:独立生活,专人专事
from qdrant_client import QdrantClient from qdrant_client.models import Distance, VectorParams, PointStruct import numpy as np # 先启动 Qdrant 服务(docker 或 cloud) # docker run -p 6333:6333 qdrant/qdrant client = QdrantClient(host="localhost", port=6333) # 建集合时就要声明向量维度和距离算法 client.create_collection( collection_name="my_docs", vectors_config=VectorParams(size=384, distance=Distance.COSINE), ) # 插入数据——每条是一个 Point client.upsert( collection_name="my_docs", points=[ PointStruct( id=1, vector=np.random.rand(384).tolist(), # 真实项目换成 embedding 输出 payload={"topic": "python", "year": 2024, "text": "Python 的 asyncio 在 3.11 后性能提升明显"}, ), PointStruct( id=2, vector=np.random.rand(384).tolist(), payload={"topic": "vector-db", "year": 2025, "text": "Qdrant 用 Rust 写的,GIL 管不着它"}, ), PointStruct( id=3, vector=np.random.rand(384).tolist(), payload={"topic": "chroma", "year": 2024, "text": "Chroma 默认用 HNSW 做索引"}, ), ], ) # 语义搜索——写法一样简单,但背后是独立查询引擎 results = client.search( collection_name="my_docs", query_vector=np.random.rand(384).tolist(), limit=2, ) for hit in results: print(f" [{hit.id}] {hit.payload['text']} (score: {hit.score:.3f})")
表面看,两段代码做的事差不多。背后是完全不同的故事。
Chroma 在你的 Python 进程里开了一个 Rust 运行时(Tokio),把计算密集的向量距离扔给 Rust Worker 线程,绕过 Python GIL。数据在进程内零拷贝传递。
Qdrant 是一个独立服务进程。你的 QdrantClient 通过 HTTP/gRPC 跟它说话。它自己管内存、管磁盘、管索引——你的 Python 程序挂了,Qdrant 还在跑,数据不丢。
Chroma 是住在你家客房的帮手。Qdrant 是隔壁开了间办公室的团队。
这个区别不神秘——你用过 SQLite 和 PostgreSQL 就秒懂。
03同样跑查询,里面在做什么?
当你执行 .query() 或 .search() 时,两个库的内部动作完全不同。不一定每天用得到,但它决定了你的查询在什么条件下会变慢。
Chroma:推模式
Chroma 把查询计划拆成微小数据切片(Morsels),推给多个 Worker 并行算。同时做两件事:
已建索引的数据 → HNSW 图遍历 还没建索引的新数据 → 暴力扫描
两边结果在位图合并层汇合后返回。
好处:刚插入的数据立刻能被搜到——不用等索引构建。做 Agent 记忆、频繁增删数据的场景下,体验很好。
代价:合并层的开销依赖 Python HTTP 客户端的封包效率。数据量超过 100 万向量 时,这个层的压力开始压不住。
Qdrant:分段隔离
Qdrant 把数据拆成多个独立"段"(Segment)。每个段有自己的向量存储、元数据索引和 HNSW 图。写入先记 WAL(预写日志),再应用到对应段——断电了 WAL 帮你还原。
段分两种:可追加的(新数据往里写)和不可追加的(后台默默合并、重建索引)。前台查询和后台合段是隔离的——你不会因为索引重建而感觉查询变卡。
用一个类比来记:
Chroma 像开放式厨房——厨师同时切菜、炒菜、装盘,顾客看到全过程。菜出了直接端走。效率高,但客人一多就乱。
Qdrant 像分区仓库——进货区、存储区、出货区物理隔离。入库有登记簿(WAL),出库不影响理货。吞吐量大,但前期基建投入多。
我踩过一个经典的坑:Flask 服务里跑了 Chroma 做知识库检索,读写都在同一条线上。高峰期用户一多,GIL 把向量计算和 HTTP 响应绑在一起,整条链路都在等。后来拆成"Qdrant 独立服务 + Python 只做业务逻辑",才消停了。根因不是 Chroma 不行,是我没做好读写隔离——但 Chroma 的嵌入式设计确实让这种错误更容易犯。
04什么时候会翻车
两个库都有明确的退化边界。知道这些数字,比背一百行功能对比有用。
Chroma 的软肋:100 万向量
基准测试显示,Chroma 在这个量级附近开始出现明显性能退化。不是突然挂——延迟开始不稳定,有时候 20ms 返回,有时候 200ms。
原因在架构层:Python 和 Rust 之间的跨语言通信在大数据量时成瓶颈,元数据连接的内存膨胀也会触发 GC 抖动。
但 100 万向量 是什么概念?按每条文本 500 token、embedding 维度 768 来算,大约是 10 万篇中等长度文章。大多数个人项目和中小团队,离这个天花板还远。
Qdrant 的"大材小用"
Qdrant 在 5000 万向量 规模下依然稳定在 ~41 QPS(99% 召回率),靠三件套:Rust SIMD 指令集 + 分段隔离 + TurboQuant 量化压缩。
但如果你只有 5 万个向量,Qdrant 的优势你完全感受不到。反倒多了一个要维护的 Docker 容器。
菜市场买菜开了辆重卡——不是车不好,是场景不对。
查询过滤:最容易被忽略的杀手
假设你存了 50 万条文档,每条 5 个标签。你做语义搜索时还要过滤掉 2023 年之前的、只保留某些话题。
Chroma 的做法是先搜再过滤,或者先过滤再搜。前者精度可能不够(搜出来的被过滤掉后不够数),后者在条件复杂时性能衰减。
Qdrant 的 Filterable HNSW 直接在 HNSW 图遍历时应用位图掩码——语义搜索和条件过滤同时完成,互不干扰。
你的 WHERE 子句越复杂,越该考虑 Qdrant。
05所以你该怎么选
回到场景-需求框架。四个问题:
数据量:一年内向量存量会超过 100 万吗? 查询复杂度:过滤条件是简单标签还是多字段组合? 运维条件:你愿意维护一个独立服务吗? 预算:大规模场景下,RAM 成本敏感吗?
把你的答案套进去:
数据量 < 100 万 且 过滤复杂 → Qdrant
数据量 > 100 万 → Qdrant(几乎必选)
预算极紧 + 数据量超大 → Chroma(S3,250 倍成本差)
做 Agent / 上下文管理 → Chroma(原生支持更好)
我现在自己的做法:新项目一律 Chroma 起步,快速验证想法。等到数据量和查询复杂度真的摸到阈值了,再迁 Qdrant——到那时候,迁移的理由是数据告诉我的,不是别人推荐的。这个思路也送给你:别提前优化,但也别不知道边界在哪。
代码都给你了,今天就能跑。两个库都装上,拿你自己的数据试一圈,比看完十篇对比文章更清楚自己该选谁。
数据STUDIO
点击领取《Python学习手册》,后台回复「福利」获取。『数据STUDIO』专注于数据科学原创文章分享,内容以 Python 为核心语言,涵盖机器学习、数据分析、可视化、MySQL等领域干货知识总结及实战项目。
还在用多套工具管项目?
一个平台搞定产品、项目、质量与效能,告别整合之苦,实现全流程闭环。
白皮书上线