2026年9月30日,Turbopuffer 工程师 Dan Harrison 宣布对轮缓冲器(turbopuffer)的存储架构进行重大调整。此次变更旨在重构文件与索引布局、写入压缩及查询方式,以全面提升文本、正则表达式及向量搜索的性能。

Turbopuffer v1 作为无服务器向量数据库推出时,核心策略是利用对象存储作为数据源以降低成本,并通过分层 NVMe SSD/内存缓存保障性能。这一权衡方案得到了 Cursor 和 Notion 等早期客户的验证。随后演进的 v2 版本增强了文本和正则搜索能力,并拓展至 Linear 同步引擎等非典型搜索场景。尽管查询引擎不断迭代以支持新用例,但其底层存储架构始终未变:近似最近邻(ANN)向量索引仍是所有其他索引和查询计划围绕的核心主索引。这种设计在 GROUP BY 和聚合操作等特定查询计划上存在局限。
鉴于矢量主导架构已接近其设计极限,团队决定转向新的主索引机制,将 ANN 降级为“仅仅是另一个”二级索引。以下回顾从 Turbopuffer v1 到当前状态的技术演变路径。
v1: 单一 ID 与向量结构
在 Turbopuffer 初版中,文件仅包含一个 ID 和一个向量。当时业界主流倾向于基于图形的向量索引,但团队发现层次聚类索引更适配对象存储特性。初期采用 SPANN 算法,后迁移至 SPFresh 以支持增量索引。向量被聚合成群,中心点再进一步聚合形成树状结构。
┌───────────────────┐
│ root centroid │
└───────────────────┘
╱ │ ╲
╱ │ ╲
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ leaf centroid │ │ leaf centroid │ │ leaf centroid │
└───────────────────┘ └───────────────────┘ └───────────────────┘
╱ ╲ ╱ ╲ ╱ ╲
╱ ╲ ╱ ╲ ╱ ╲
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ vector │ │ vector │ │ vector │ │ vector │ │ vector │ │ vector │
└────────┘ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘
┌───────────────┐
│ root centroid │
└───────────────┘
╱ │ ╲
╱ │ ╲
┌────────┐┌────────┐┌────────┐
│ leaf ││ leaf ││ leaf │
│centroid││centroid││centroid│
└────────┘└────────┘└────────┘
╱ ╲ ╱ ╲ ╱ ╲
┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐
│vec││vec││vec││vec││vec││vec│
└───┘└───┘└───┘└───┘└───┘└───┘
该结构在一个呈现为键值映射的存储层上实现。每个集群分配一个 ClusterId,集群内的向量分配密集的 LocalId。
K::Vector(C0L0) = vec![0.45, 0.32, ...]
K::Id(C0L0) = 7
K::Vector(C0L1) = vec![-0.28, 0.96, ...]
K::Id(C0L1) = 13
K::Vector(C1L4) = vec![0.64, -0.48, ...]
K::Id(C1L4) = C0
所有数据均通过 ClusterId 和 LocalId(如 C0L1)组合成的键进行寻址,即 ANN 地址。在此架构下,ANN 索引被视为主要索引。
v2: 属性过滤与全文搜索
从 v1 过渡到 v2 的标志是引入了两个新的查询计划:属性过滤和全文搜索。
属性过滤
为满足用户在向量搜索中添加属性过滤的需求,并提升过滤速度与召回率,团队将其建模为反向索引。
K::AttrIndex("family", "Alcidae") -> vec![C0L3, C1L2, C1L3, ...]
K::AttrIndex("genus", "Fratercula") -> vec![C0L3, C1L2, C1L9, ...]
对于投影操作(include_attributes),文档属性与 ID 及向量一同存储。
K::Vector(C0L0) = vec![0.45, 0.32, ...]
K::Id(C0L0) = 7
K::Attr(C0L0, "family") = "Alcidae"
K::Attr(C0L0, "genus") = "Fratercula"
全文搜索
BM25 全文搜索是另一项关键功能。类似属性搜索,全文搜索首先定位包含查询术语的文档(倒排列表)。FTS 索引还存储了 BM25 评分所需的元数据(词频、文档长度):
K::FTS("description", "Atlantic") -> vec![(C0L0, 2, 37), (C9L4, 1, 42), ...]
K::Attr(C0L0, "description") -> "A sharply dressed black-and-white seabird with a \
huge, multicolored bill, the Atlantic Puffin is often \
called the clown of the sea. It breeds in burrows on \
islands in the North Atlantic, and winters at sea."
此后,系统陆续推出了聚合、正则搜索、模糊匹配、稀疏向量搜索及属性排序等其他索引结构和查询引擎。
矢量主导索引面临的挑战
矢量主导架构之所以长期保留,是因为其在对象存储上的 ANN 搜索表现优异,支持单个索引容纳超过 1000 亿个向量。任何重大架构变动都可能导致 ANN 性能回退。然而,非向量查询形状在该架构下面临三大瓶颈:存储放大、写入放大以及有限的向量化。
存储放大
目前,Turbopuffer 将每个文件的全部内容存储在其 ANN 地址下。当文件仅含一个向量时,非向量数据只需存储一次。但在多向量表示场景(如文档嵌套或迟交互模型)中,必须为每个向量复制完整内容,导致显著的空间浪费。
写入放大
每当文档插入、更新或删除时,SPFresh 可能会重新平衡向量以维持集群质量,防止召回率下降。由于文档所有内容均绑定于向量 ANN 地址,这种重平衡会级联移动整个文档内容及引用它的反向索引(属性和 FTS)。单个向量的更新可能触发数百个属性及其索引的移动,严重制约索引吞吐量优化空间。
有限的向量化
现代查询引擎依赖向量化技术,通过对数据块执行紧凑循环来分摊固定开销、提升压缩效率并保持 CPU 流水线满载。例如,DuckDB 批次大小为 2,048 行,ClickHouse 可达约 65k,Lucene 倒排列表块包含 256 个文档。相比之下,Turbopuffer 的 ANN 索引最佳性能区间仅为 100–200 个文档的集群大小。即使某些查询计划偏好更大批次以保持 CPU 满载,仍受限于 ANN 主索引的小块尺寸。
此前实验显示,将全文搜索发帖列表划分在 ANN 集群边界之外,可使索引缩小 10 倍,查询速度提升 20 倍。这是因为发帖列表可独立存储并指向文档,而聚集和其他扫描需读取按组存储的文件块。只要 ANN 地址为主键,这些操作的块大小就被限制在集群规模内,无法利用更大的理想块尺寸。
去中心化主索引:Turbopuffer v3
解决上述问题的核心思路是摒弃 ANN 地址作为主键。这正是 Turbopuffer v3 的关键变革。本月早些时候,v3 实现了 100% CI 测试通过。当前阶段首要关注正确性,后续将致力于性能优化。团队计划在 v3 正式发布前的未来几周内公开基准测试结果,力求达到甚至超越现有性能平衡。
Turbopuffer 是一个快速搜索引擎,托管超过 1 万亿个文件,处理每秒超过 1000 万次写入,服务每秒超过 2.5 万个查询。





