不学不知道,一学真惊人。
嵌入式数据库的设计理念,竟能达到如此精妙的境界,令人由衷钦佩!
Chroma 是一个专为嵌入式场景打造的向量数据库。这本身并不稀奇。但你是否知道,它同时具备支持 OLAP 与 OLTP 工作负载的能力?这一点,恐怕很多人难以置信。
Chroma 在架构设计上,将“组合优于继承”的设计模式发挥得淋漓尽致。
那么,它是如何实现对 OLAP 的支持的呢?来看一段典型代码:
chroma_client = chromadb.Client(
Settings(
persist_directory=persist_directory,
chroma_db_impl="duckdb+parquet",
)
)
在初始化 Chroma 实例时,用户可以通过配置参数指定底层存储引擎。如上所示,此处选择了 duckdb+parquet 方案。此外,系统也曾支持 sqlite、pg 等多种后端选项。
chroma_db_impl
以 DuckDB 为例,具体应用场景如下:
场景一:小型知识库(数据量约几万条)
- 写入过程:每条 embedding 向量及其 metadata 元数据会被持久化到本地
chromadb/目录下的 Parquet 文件中,这些文件由 DuckDB 负责管理。 - 查询流程:Chroma 将所有 embedding 加载进内存或索引结构,利用 numpy 或近似最近邻(ANN)算法计算相似度,并返回 top-k 最匹配的结果。
场景二:需按元数据过滤后再检索
当用户发起带条件的查询请求时,Chroma 首先会将 Query 中的过滤条件转换为 SQL 语句,交由 DuckDB 在磁盘层面执行。例如,筛选满足 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 来完成聊天历史的存储与检索任务。
更新:Chroma 的架构演进
然而,上述内容如今已成为 Chroma 的“历史篇章”。
尽管早期通过插件化存储后端快速迭代并赢得社区认可,但随之而来的问题也日益凸显。最终,在 2023 年,Chroma 团队决定摒弃原有的可插拔架构,全面转向自研存储层。
以下内容结合官方源码、作者访谈、GitHub Issue 讨论及社区反馈整理而成,是一份面向工程师的深度解析。
1. 去除可插拔后端后,Chroma 当前的后端架构是什么?
如今,Chroma 不再对外暴露“可更换的存储后端”选项,转而采用一套统一的、官方内置的嵌入式存储栈。其底层主要包括:
- 自研的 persistence manager:负责向量与元数据的序列化、缓存机制以及文件组织布局;
- 一个轻量级的嵌入式键值存储(key-value store),不再依赖 DuckDB 或 SQLite 作为主数据存储;
- 索引层继续沿用 hnswlib,或其 Rust 重写版本以提升性能与稳定性。
简而言之,Chroma 的架构已从原先的 “duckdb/sqlite + hnswlib” 演进为 “自研 mini-DB + hnswlib”。
新架构下,所有的数据、索引和 metadata 都统一纳入 Chroma 自身定义的目录结构中进行集中管理。
这意味着,当前使用的是一种内置且不可替换的嵌入式数据存储引擎,既非 DuckDB,也非 SQLite。
chroma_db_impl
2. 为何要移除可插拔存储后端?背后的五大核心原因
根据公开讨论、团队成员说明及提交记录分析,主要有以下五点关键考量:
原因一:DuckDB / SQLite 并不适合作为主存储引擎(频繁遇到问题)
这些通用数据库并未针对“向量 + 大量元数据”的工作负载进行优化,实际使用中暴露出诸多痛点:
- Parquet 文件易出现膨胀现象,占用空间远超预期;
- 必须手动执行
VACUUM才能回收空间,运维成本高; - 写操作存在锁竞争,高并发写入时常失败;
- 随着数据增长,服务启动时加载时间急剧变长;
- 索引文件与数据库文件容易不同步,增加损坏风险。
这些问题在 GitHub 的 Issue 区频繁出现,导致用户抱怨不断,迫使官方被动“背锅”。
自 2023 年下半年起,Chroma 团队内部已达成共识:“DuckDB / SQLite 并非长期可行方案,将持续带来系统不稳定性。”
原因二:用户误解“可切换后端”意味着自由扩展与高可用
由于曾经提供 chroma_db_impl 参数供用户选择后端,造成一种错觉:
- 误以为可以像 Milvus 或 Qdrant 那样灵活选择分布式存储;
- 认为可通过更换后端实现安全扩容或集群部署。
这种认知偏差导致许多用户在生产环境中做出错误决策,进而引发故障。
chroma_db_impl="duckdb"Chroma 官方决定不再支持可配置的后端存储,这一变化背后有多重技术和战略考量。以下是核心原因的重新组织与表述:
**产品定位的根本转变:从“向量数据库”到“嵌入式向量存储引擎”**
在 2024 年中,Chroma 明确调整了其产品方向。它不再试图成为一个可扩展、支持多种存储后端的通用“向量数据库”,而是聚焦于成为专为 LLM 和 RAG 场景设计的嵌入式向量存储解决方案——类似于本地集成的 Faiss 加上元数据存储的能力。
这一新定位决定了以下设计原则:
- 优先服务于单机环境
- 强调开箱即用,无需复杂配置
- 最小化运维负担
- 不承诺分布式能力或大规模部署支持
- 放弃企业级数据库特性(如 ACID、schema 管理、存储引擎切换等)
既然目标不再是传统意义上的“数据库”,保留多后端接口只会引发用户误解,让人误以为具备更高的可移植性和灵活性,从而偏离实际使用场景。
chroma_db_impl
**技术架构演进需求:统一后端以支持 Rust 核心 + Python 客户端的深度优化**
目前 Chroma 的开发重心已转向基于 Rust 构建的服务核心,包含内置索引与存储模块。若继续兼容多个外部后端(如 DuckDB、SQLite),将极大增加 Rust 层的维护复杂度。
采用统一的内置后端带来诸多优势:
- 完全掌控数据布局,提升一致性
- 确保 Rust 与 Python 两端行为一致
- 释放更大的性能优化空间
- 更易实现 zero-copy 数据加载
- 规避 DuckDB 或 SQLite 在 CAP 理论下的固有限制
这是一项必要的工程取舍,旨在构建一个更高效、可控的技术栈。
**现实问题驱动:多后端导致大量生产环境故障,官方被迫承担声誉风险**
尽管诸如 DuckDB 元数据损坏、SQLite 锁竞争、查询不稳定等问题本质上并非 Chroma 所致,但用户往往将其归咎于 Chroma 本身。
这种责任错位带来了严重的后果:
- 官方团队需花费大量精力排查非自身引起的故障
- 社区反馈混乱,影响产品迭代方向
- 整体系统稳定性感知下降
为了降低维护成本、减少误用、提升用户体验的一致性,官方最终选择彻底统一存储后端。
**为什么 Chroma 坚持“后端不可配置”?背后的深层战略判断**
一句话总结:Chroma 的目标是成为 RAG 工程中的“本地嵌入式向量库”,而非对标 Milvus 或 Qdrant 那样的企业级向量数据库。
这一路线的核心逻辑包括:
- 90% 的 RAG 应用场景涉及的向量数量少于 30 万
- 绝大多数用户最关心的是简单性、免运维和快速启动
- 支持多后端带来的 bug 远多于实际价值
- 在单机环境下,可以做到极致的性能与稳定性
- 统一后端有利于持续进行存储、索引与缓存的一体化优化
这是一条明确聚焦于“轻量级、高可靠、本地化”的向量存储路径。
**总结:当前 ChromaDB 存储架构的本质**
移除外部后端依赖的根本原因在于:
- DuckDB 与 SQLite 引发了大量运行时稳定性问题
- 用户被误导认为它是可切换后端的数据库系统
- 无法适配未来 Rust 与 Python 协同优化的统一架构
- 产品定位已从“数据库”转向“嵌入式向量存储”
- 官方需要完全掌控存储层,以提升可靠性与性能表现
如今的 ChromaDB 使用的是一个内置的、不可替换的轻量级嵌入式存储引擎,由自研的持久化管理器(persistence manager)、键值存储(KVS)以及 hnswlib 索引构成。
chroma_db_impl

雷达卡


京公网安备 11010802022788号







