RIP,向量資料庫?——turbopuffer v3 把 ANN 從主索引降級為次級索引

譯註:turbopuffer 宣布儲存架構大改版 v3:不再以 ANN 向量位址當主鍵,向量索引降級為「其中一個次級索引」。以下是全文翻譯,以及 Hacker News 討論串的回饋整理。

全文翻譯

RIP, vector database

2026 年 9 月 30 日 • Dan Harrison(工程師)

原文:https://turbopuffer.com/blog/rip-vector-database

我們正在改變 turbopuffer 的儲存架構。新引擎非正式稱為「turbopuffer v3」,改變了文件與索引在 turbopuffer 中的佈局、寫入、壓實(compaction)與查詢方式。它將讓我們能支援更多查詢計畫,並在更大的規模上提供服務。

turbopuffer 以 serverless 向量資料庫之姿誕生,高度專精於以極低成本提供合理快速的向量搜尋。以物件儲存(object storage)作為 source of truth 換來了經濟性,分層的 NVMe SSD/記憶體快取換來了效能。這些特定權衡的價值最早被我們的客戶驗證,包括 Cursor 和 Notion。

隨著時間推進,turbopuffer 已演變成一個通用的搜尋資料庫(以及其他用途)。查詢引擎一路演化以支援這些查詢計畫,但儲存架構大致未變:ANN 向量索引過去是、現在仍是主索引(primary index),所有其他索引與查詢計畫都圍繞它運作。

我們已把主 ANN 索引推到了極限,但該往前走了。我們正在改用新的主索引,並讓 ANN 成為「其中一個」次級索引(secondary index)。我們覺得打開大門、讓大家一路追蹤這個過程應該會很有趣。

在這第一篇更新中,我們先交代為什麼要這麼做。跟我走一段從 tpuf v1 到今天的短旅程。

第一版:文件只有 ID 和向量

在 turbopuffer 第一版中,文件只由一個 ID 和一個向量組成。當時的主流看法是圖式(graph-based)向量索引,但層級式聚類索引與物件儲存更合得來。我們從 SPANN 開始,最終遷移到 SPFresh 以支援增量索引。向量被聚類成群組,群組的質心(centroid)再被聚類,重複此過程形成一顆單一根節點的樹。


                      ┌───────────────────┐
                      │   root centroid   │
                      └───────────────────┘
                     ╱          │          ╲
                    ╱           │           ╲
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│   leaf centroid   │ │   leaf centroid   │ │   leaf centroid   │
└───────────────────┘ └───────────────────┘ └───────────────────┘
      ╱       ╲             ╱       ╲             ╱       ╲
     ╱         ╲           ╱         ╲           ╱         ╲
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ vector │ │ vector │ │ vector │ │ vector │ │ vector │ │ vector │
└────────┘ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘

我們把它實作在一個呈現為鍵值對映(key-value map)、鍵有序且唯一的儲存層之上。每個聚類給一個 ClusterId,聚類內的向量給一個密集的 LocalId。


// leaf vectors
K::Vector(C0L0) = vec![0.45, 0.32, ...]
K::Id(C0L0) = 7
K::Vector(C0L1) = vec![-0.28, 0.96, ...]
K::Id(C0L1) = 13

// C0 的聚類質心本身在樹的下一層被聚類
K::Vector(C1L4) = vec![0.64, -0.48, ...]
K::Id(C1L4) = C0

如上所示,所有東西都以 ClusterId 和 LocalId(例如 C0L1)作鍵,兩者合稱 ANN 位址(ANN address)。這就是我們說「ANN 索引是主索引」的意思。

v2:屬性過濾與全文搜尋

兩種新查詢計畫標誌著 turbopuffer v1 → v2 的非正式轉變:屬性過濾與全文搜尋。

屬性過濾。 自然地,客戶希望能加入屬性值,並以此過濾向量搜尋。為了讓過濾既快又有高召回率,我們將其建模為倒排索引(inverted index),把屬性值映射到包含它的文件之 ANN 位址。


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 全文搜尋是另一個顯而易見且廣受需求的查詢計畫。與屬性搜尋類似,全文搜尋先找出包含查詢詞項的文件(通常稱為 postings)。對 FTS 索引而言,我們還放入 BM25 計分所需的(詞項數、文件長度)中繼資料:


K::FTS("description", "Atlantic") -> vec![(C0L0, 2, 37), (C9L4, 1, 42), ...]
K::Attr(C0L0, "description") -> "A sharply dressed black-and-white seabird ..."

隨著時間,我們還推出了數種其他索引結構與查詢引擎:聚合(aggregations)、正規表示式搜尋、模糊比對、稀疏向量搜尋、屬性排序——全部建立在同一套以向量為主的儲存佈局之上。

以向量為主索引的問題

ANN 主索引直到今天大致維持原樣,原因只有一個:它在物件儲存上的 ANN 搜尋表現真的、真的很好。在這套架構之上,我們把向量搜尋推到單一索引 1000 億以上向量、以 1k+ QPS 提供 200 ms p99 讀取。在這裡做任何重大變更都可能引入 ANN 效能回歸。

然而,這個佈局讓我們在非向量查詢形狀上無法達到最先進水準,主要有三個面向:

儲存放大。 turbopuffer 目前把每份文件的完整內容放在其 ANN 位址之下。只有一個向量時,非向量資料與向量一起存放一次即可。但對文件的多向量表示(multi-vector representations),例如文件巢狀(document nesting)或延遲互動(late interaction),就必須為每個向量複製一份內容。這就是我們某些較遺憾的限制的來源。

寫入放大。 任何時候文件被插入、更新或刪除,SPFresh 可能會重新平衡向量以確保它們維持良好聚類(否則召回率會下降)。因為文件中的一切都以其向量的 ANN 位址作鍵存放,這次重新平衡會連動搬移完整的文件內容,以及引用它的所有倒排(屬性與 FTS)索引。只更新一個向量,就可能搬動數百個屬性及其索引。這種寫入放大嚴重到我們調整索引吞吐量的努力已開始出現邊際效益遞減。

向量化受限。 現代查詢引擎是向量化(vectorized)的:它們對數值區塊跑緊區塊跑緊湊迴圈,攤平每個區塊的固定成本、壓縮效果更好、讓 CPU 流水線保持滿載,並解鎖 SIMD。例如 DuckDB 以 2,048 列為一批,ClickHouse 最多約 65k,Lucene 的 posting 區塊是 128 份文件,而我們的 ANN 索引在約 100–200 份文件的群組上運作最佳。每個查詢計畫都有其最佳區塊大小,但今天它們全被 ANN 主索引綁架了。一個想要數千份文件的區塊來讓 CPU 飽和的計畫,仍然被困在 100–200。

我們已經記錄過這在 turbopuffer 中有多重要。第一版全文搜尋沿 ANN 聚類邊界分割 posting list,中位區塊只容納約 1.5 個 postings。FTS v2 把 postings 改造成約 256 的固定區塊後,索引縮小 10 倍、查詢最快加快 20 倍。Posting list 做得到,是因為它們單獨存放、指向文件,其佈局不必跟隨聚類。而聚合與其他掃描直接讀取文件本身,文件是一個聚類一個區塊存放——只要 ANN 位址仍是主鍵,它們的區塊大小就受聚類大小限制,即使它們想要更大。

RIP,主向量索引

這些問題的解法很簡單:不要以 ANN 位址作鍵。這正是 turbopuffer v3 所做的改變。可以想見,這不是個平凡的改動。

我們最近達成一個重大里程碑:turbopuffer v3 的 CI 100% 通過。但目前它相較生產版 turbopuffer 有顯著的效能回歸。這不令人意外!到目前為止,我們專注在基礎設計與正確性,還沒有開始調優。看著數字往下掉很有趣,所以我們想讓大家在效能打磨的第零天就進場。

我們會隨著基準測試的進展發布結果。沿途我們會深入新架構的細節,以及最終會讓 turbopuffer 在更多查詢計畫上更快的優化。


Hacker News 回饋整理

(297 分、79 則留言,討論串:https://news.ycombinator.com/item?id=49923466)

1. 最高讚的類比:這就是 Postgres vs MySQL/InnoDB 的老論戰

gopalv(首讚)指出,這與 Postgres/MySQL 如何建立索引是直接平行的:設計選擇從「Postgres 模式」變成「MySQL 模式」——差別在於 reindex 成本 vs lookup 成本。MySQL 索引指向穩定的 primary key,所以只要不改主鍵,就不用更新所有屬性索引;Postgres 索引指向每次更新都會變的 row-id。他並點名 Uber 2016 年從 Postgres 遷移到 MySQL 以避免索引放大的經典文章,說它就是這篇文章的鏡像。

blakeashleyjr 作了最精準的一句總結:「『更新一個向量就搬動數百個屬性及其索引』基本上就是 Uber 2016 寫入放大文章的搜尋版,解法也一樣:別把索引指向資料列所在的位置。」但他也提出技術追問:當多一次的 lookup 是 S3 GET 而不是 B-tree 跳躍時代價多大?cluster 是否保留向量副本讓搜尋本身仍本地化、只有結果抓取付出間接成本?否則冷資料的 p99 恐怕會變差。

反方意見:

  • malisper:這不是「Postgres 架構在好 schema 下更好」。次級索引指向主鍵才能做 undo logging,進而免去 vacuum(Postgres 最痛的部分),且主鍵索引多在快取中,額外成本比看起來小很多。
  • throwaway7783:undo log 讓 rollback 和 crash recovery 變慢——一樣是權衡。
  • tomnipotent:原意是 PG 的 tid 直接指向頁面與 slot,MySQL 還要多走一次 B-tree。
  • barrkel:MySQL(8 之前)本來就是針對主鍵點查優化的,行直接存在 clustered 的 PK 索引裡。
  • SigmundA:MSSQL 的 clustered index、Oracle 的 IOT 都讓你自行選擇;從 MSSQL 轉來 PG,最懷念的就是沒有真正的 clustered index。

2. 「新的主索引是什麼?」——官方親自回答

  • drewlanenga:多向量重複的問題合理,但新的主索引到底是什麼?
  • benesch(turbopuffer 團隊):一個自動產生的內部 ID:(segment ID, doc ID);使用者提供的主鍵(文件的 id 欄位)在儲存層降級為次級索引。
  • 有網友發現 blog 連結的 v3 dashboard 停在 9 月 7 日,benesch 回:一切正常,只是落後發表進度幾週——「忙著效能調優,抽不出來寫 dev log」。

3. 「你不是第一個這麼做的人」——同方向的既有方案

  • Tsarp:早就很喜歡 LanceDB——Lance 同樣把 ANN 當次級索引,row 待在 fragment 裡,向量索引從不搬移它們。
  • marekgalovic(TopK):我們很久以前就想通了「向量資料庫是錯誤的抽象」,從頭建了支援 dense/sparse 向量、延遲互動、詞法搜尋、regex、過濾、自訂計分的 serverless 搜尋引擎。
  • sreekanth850:企業檢索幾乎不需要純向量資料庫。他們在有原生向量支援的 SQL 資料庫(CratedB;也提到 ClickHouse、StarRocks)上建引擎,向量相似只是與全文搜尋、過濾、join 並列的一個查詢原語,ACL、文件版本、時間過濾都是普通 predicate——「多加一個向量資料庫就多一個活動部件,資料一更新要同步兩套系統才是最難做對的」。
  • real_faxenoff:試遍主流向量資料庫都不滿意,最後用編譯掉多用戶功能的 SQLite 加上完全獨立的 IVF 索引(GPU 上建,25 萬向量 4 秒)自己搞;目前只覺得 LanceDB 有希望但太重。
  • gk1:「向量資料庫一直比較關於檢索,而不是向量或資料儲存——只是這個詞黏得太牢,公司抱太久。」tveita 接:那樣你就是搜尋引擎,要和已內建向量功能的 Elasticsearch、Vespa 競爭價格、效能、功能,以及「誰能在網頁上多提幾次 AI」。

4. 爭議:這文章是不是 AI 寫的?

  • vhiremath4:「AI Slop. Will not read.」(引發一串反擊)
  • gravitronic:turbopuffer 創辦人是他做過工作裡最聰明的一群人,強烈懷疑是手寫;_peregrine_(turbopuffer 員工)確認:「我們還是手寫的。」
  • croemer 認為某句(”Object storage as the source of truth gave the economics…”)很 LLM;mediaman 反駁:那種句式人類寫了幾十年了,這就是用單一語病觸發判斷的問題。
  • nightfly:讀的時候也一直自問「是不是 AI 寫的」,但問題不在破折號,而是「可愛標題加囉嗦開場句」的句式。
  • 旁枝討論:FLeXMurphy 發現有人直接貼 LLM 輸出來留言,引出一長串「HN 是否正被 LLM/行銷機器人佔領」的辯論(shadow-ban 多年仍發帖的帳號、為累積 karma 而留言的機器人、lobste.rs 邀請制能否擋住慢速滲透等)。

5. 發散討論:要不要乾脆「殺死資料庫」?

  • childintime 提問:是否該用 LLM 優化的編譯版資料庫、直接用 Rust 寫死所需 API?tyre:不,還不是時候。cogman10:想不出好理由——SQLite、Parquet 等格式已經很好,自造格式的代價是失去所有既有的診斷工具。
  • 諷刺接力:dymk「該拿掉鎚子改用瑞士刀了嗎?」→ pessimizer「我們可以為每根釘子造一把新鎚子!」→ combobyte「何必一把鎚子用一輩子?付每月 100 美元,每次要敲釘子時重新造一把不是更好?」
  • eatonphil:他已在兩家公司看過、自己也在做這種「為特定用途手刻搜尋索引」的事,尤其在沒有資料遺失風險的場景。bijowo1676:「SQLite 已經存在了。」Ostatnigrosh:「除非你是 TigerBeetle,什麼都想手刻。」
  • ActorNightly:有人想過把資料庫本身做成神經網路嗎?thefxperson:過去 3-4 年這叫 generative retrieval——用 transformer 權重當索引,以受限解碼直接生成文件 ID。

6. 對效能宣稱的驗證要求

  • ironqcold:「我想在同樣規模看到 1k+ QPS 下的 p99。」(對 200 ms p99、1000 億向量的宣稱要求眼見為憑)
  • 加上前述 blakeashleyjr 對「S3 GET 間接成本會不會拖垮冷 p99」的質疑,是技術面最集中的兩個問號。

7. 離題但有趣的插曲

  • 文章剛發時頁面載不動(OutOfHere:「RIP.」),引出一串關於 10G 寬頻有沒有幫助、以及匿名 throwaway 帳號留言是不是行銷水軍的互相諷刺(有人貼 Web Archive 連結被質疑是 Archive.org 的水軍,回應:「我收了 Internet Archive 的大錢來推廣他們——這就是我們這種水軍的新套路」)。
  • DevKoala:「不久之後他們只會賣你 markdown。」
  • orliesaurus:「坐等 Qdrant CEO 出現。」

總體調性

技術討論的主軸是正面且認同問題診斷的——最多人共識是「這正是 Uber 2016/InnoDB 解過的經典題,turbopuffer 現在才走到這一步」;質疑集中在三處:新索引的具體設計(官方已回答)、S3 上多一次 indirection 的真實代價、效能宣稱待基準測試驗證。另有一條平行的「是不是 AI 寫的」文風爭論(作者已否認),以及大量離題的 LLM 留言機器人與「手刻資料庫」發散討論。


(轉譯自 Turbopuffer 2026-09-30 部落格文章〈RIP, vector database〉,原文:https://turbopuffer.com/blog/rip-vector-database 。網友回饋整理自 Hacker News 討論串:https://news.ycombinator.com/item?id=49923466 ,資料截至 2026-10-02。)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *