不学不知道,一学吓一跳。
嵌入式 DB 的设计哲学,已经出神入化到这等地步,真是让人赞叹!
Chroma 是一款嵌入式向量数据库。
这没什么。
但是,它也是一款可以支持 OLAP 的向量数据库,它还是一款可以支持 OLTP 的向量数据库,你信不?
Chroma 真是把设计模式思想运用到了极致!
这个设计模式叫:组合优于继承。
Chroma 是如何支持 OLAP 的呢?
py
chroma_client = chromadb.Client(
Settings(
persist_directory=persist_directory,
chroma_db_impl="duckdb+parquet",
)
)
创建 Chroma 时,可以通过 chroma_db_impl 来指定存储引擎,上面例子中选用了 duckdb,此外还可以选择 sqlite、pg 等。
选择 duckdb 之后如何使用 duckdb 呢?举两个具体场景:
-
小型知识库(几万条):
写入 :Chroma 把每条 embedding 和 metadata 写入 chromadb/ 下的 Parquet 文件(由 DuckDB 管理)。
查询:Chroma 直接把所有 embedding 载入内存或索引中,用 numpy/ANN 计算相似度,返回 top-k。 -
需要按元数据先过滤再检索的场景:
你将 Query 传给 Chroma 后,Chroma 内部先用 DuckDB 的 SQL 在磁盘上按 metadata(比如 source="wiki" 且 date>2024-01-01)筛出候选 id 列表,然后只把这些候选的 embedding 读入内存并计算相似度,减少计算量。最终将计算结果返回给用户。
可以看到,Chroma 的核心贡献在于向量搜索,至于存储,完全委托给了 duckdb 等嵌入式存储,他们各自发挥各自的优势,将能力组合,发挥出更大的价值,同时还留给用户足够的灵活性。
假如 Chroma 要自己写存储和计算引擎,那它将要做大量与核心竞争力无关的工作,同时还做不好。假设它做了个类似 duckdb 的引擎,那它就要放弃 sqlite 这样的引擎,反之亦然。
聪明的 Chroma,充分利用"嵌入式"的有利地形,把地利发挥到极致,实在妙妙妙!
例子:
https://github.com/daveshap/ChromaDB_Chatbot_Public/blob/main/chat.py
这是一个基于知识库的 RAG 智能客服机器人,它使用 Chroma 来存储和检索聊天内容。
update
上面这些内容,都成了 Chroma 的老黄历。
Chroma 通过上面的方式,快速发展,得到了社区认可,但是也遇到了很多问题,最终,他们在 2023 年切换成了自研的存储层。背后的考虑如下:结合官方代码、作者访谈、Issue 讨论和社区反馈整理,一份"面向工程师"的深度解读:
1. ChromaDB 去掉 chroma_db_impl 后,现在的后端是什么?
答:ChromaDB 已经不再暴露"可插拔存储后端",而是改为使用一个"官方自带的统一嵌入式存储栈",底层主要包含:
- 自研的 persistence manager(负责向量与 metadata 序列化、cache、文件布局)
- 一个轻量级的 embedded key-value store(非 DuckDB / 非 SQLite,不再使用第三方 DB 来存储向量主数据)
- 索引层仍是 hnswlib(或其 Rust 改写版)
可以理解为:
Chroma 从 "duckdb/sqlite + hnswlib"
转为 "自研 mini-DB + hnswlib"。
新的架构把数据、索引、metadata 都放到 Chroma 自己的目录结构中管理。
不是 DuckDB,也不是 SQLite,而是一个内置的、不可更换的 embedded 数据存储。
2. 为什么去掉 chroma_db_impl?核心原因是什么?
根据公开讨论、内部成员解释、commit 历史来看,主要有 5 条关键原因:
原因 1:DuckDB / SQLite 做主存储并不好用(频繁踩坑)
这些后端本来没有为"向量 + 大量 metadata"这样的 workload 设计。
真实问题包括:
- 文件膨胀
- VACUUM 必须手动执行
- 写锁导致高并发写入失败
- 随数据积累,启动加载极慢
- 索引文件与 DB 文件容易不同步(损坏风险)
这些问题大量出现在 GitHub Issue 中,使得官方不得不背锅。
Chroma 官方在 2023 下半年就开始认为:
"DuckDB/SQLite 不是长期解决方案,会继续制造不稳定性。"
原因 2:用户误以为可以任意切换后端 → 导致大量错误
chroma_db_impl="duckdb" 这个参数给用户传达一个错误印象:
- 可以像 Milvus/Qdrant 那样选择后端
- 可以安全扩容
- 可以依赖 DuckDB 的持久化能力
- 可以用第三方工具直接读写数据
但事实是:
Chroma 没有定义 storage API,一切都 tightly coupled。
所以切换 backend 常常造成:
- 数据不一致
- 索引文件污染
- 启动失败
- Query 结果不正确
官方最终选择:干脆不要让用户切任何后端。
原因 3:产品定位从"数据库"转变为"嵌入式向量库"
这是关键背景。
Chroma 在 2024 年中明确调整产品路线:
不再定位为:
一个可扩展、可切换后端的"向量数据库"
而是定位为:
用于 LLM/RAG 的 嵌入式向量存储引擎(类似本地 Faiss + metadata store)
这个定位意味着:
- 单机优先
- 开箱即用
- 最小运维
- 不承诺分布式 / 大规模
- 不承诺企业数据库特性(ACID、schema、存储引擎切换等)
既然目标不是"数据库",那么保留 chroma_db_impl 只会造成误解。
原因 4:统一后端方便未来 Rust Server + Python client 的深度优化
Chroma 官方版本的重心已经转向:
- Rust 服务
- 内置索引
- 内置存储
如果继续兼容多后端,Rust 这一部分会变得极难维护。
统一后端后:
- 数据布局可控
- Rust/Python 行为一致
- 性能优化空间巨大
- 更容易实现 zero-copy loading
- 避免 duckdb/sqlite 的 CAP 约束
在工程上,这是必须的取舍。
原因 5:太多用户在生产中踩坑,而官方被迫背锅
DuckDB 的元数据损坏、SQLite 锁冲突、查询不稳定,这些问题都不是 Chroma 的锅。
但用户不管,都会贴到 Chroma 头上。
为了降低维护成本、避免混乱、提高稳定性,官方选择统一后端。
3. 官方为什么强调"后端不可配置"?背后的战略判断
一句话:
Chroma 想做的是"RAG 工程的本地嵌入式向量库",不是"企业级向量数据库"。
这跟你熟悉的 Milvus/Qdrant 路线完全相反。
官方的判断逻辑:
- 90% RAG 用户的数据量 < 30 万向量
- 用户最想要的是"简单、无需运维、开箱即用"
- 多后端带来更多 Bug,而不是更多价值
- 单机场景可以做到极致的性能和稳定性
- 统一后端便于持续优化(存储布局 + 索引 + cache 一体化)
这是一条**专注于"轻量级向量存储"**的路线。
总结(你可以直接放到团队内部文档)
去掉 chroma_db_impl 的本质原因:
- DuckDB/SQLite 导致大量稳定性问题
- 用户被误导为"可切换后端的数据库"
- 无法兼容未来 Rust/Python 的统一架构
- 产品定位从"数据库"→"嵌入式向量存储"
- 官方想彻底控制存储层,提高可靠性和性能
现在的 ChromaDB 后端:
一个内置的、不可替换的、轻量级的 embedded 存储引擎(自研的 persistence manager + KVS + hnswlib)。