隨著LLM技術應用及落地,數據庫需要提高向量分析以及AI支持能力,向量數據庫及向量檢索等能力“異軍突起”,迎來業界持續不斷關注。簡單來說,向量檢索技術以及向量數據庫能為 LLM 提供外置的記憶單元,通過提供與問題及歷史答案相關聯的內容,協助 LLM 返回更準確的答案。
不僅僅是LLM,向量檢索也早已在OLAP引擎中應用,用來提升非結構化數據的分析和檢索能力。ByteHouse是火山引擎推出的云原生數據倉庫,近期推出高性能向量檢索能力,本篇將結合ByteHouse團隊對向量數據庫行業和技術的前沿觀察,詳細解讀OLAP引擎如何建設高性能的向量檢索能力。
負載特征
向量檢索的目標是查找與給定向量最相似的 k 個結果,廣泛用于以圖搜圖、推薦系統等場景。近兩年,隨著大模型的普及,而基于向量檢索構建的大模型檢索增強功能,能夠顯著改善大模型的結果準確率低的問題,得到了廣泛的關注。因此,向量檢索相關技術,以及基于向量檢索的向量數據庫的概念逐漸流行起來,成為數據庫領域一個熱門話題。
實際使用場景中,向量檢索針對的數據集大小通常會在 million 甚至 billion 級別,而查詢延遲通常會要求在數毫秒到百毫秒內返回,因此,通常不會使用 brute force 的方式進行計算,而是會使用具有特殊結構的向量檢索索引的方式來計算,比較流行的向量索引算法有 HNSW、Faiss IVF 等。
這類基于向量索引的向量檢索負載大概具有以下幾個特點:
構建時間長,資源消耗大:索引的構建時間通常比較長,遠大于數據插入的時間,以常用的 gist1M 數據集為例不同類型的索引構建時間大概需要幾十秒甚至上百秒。此外,構建索引通常需要消耗較多的 CPU 及內存資源。因此,在實現向量檢索功能時,需要考慮如何高效管理索引構建任務需要的資源,保證構建速度的同時,也不會影響其他任務的進行。
內存計算:HNSW、Faiss IVF 類索引都需要將索引結構全部讀取到內存中,而索引結構通常會包含有所有向量數據的原始數據以及一些額外的結構相關數據,因此其大小通常會大于向量數據的總量,由于結構較大每次加載索引時間會比較長,對于查詢低延遲和高 QPS 的需求場景通常需要索引常駐內存。因此,向量檢索功能需要考慮如何支持內存計算,并考慮內存資源的高效管理。
融合查詢:用戶通常需要查詢相近向量的很多其他屬性信息,通常也需要結合一些標量過濾條件進行更符合預期的結果篩選。因此,向量檢索功能需要考慮如何降低從向量檢索到其他屬性讀取的額外開銷,同時考慮如何與過濾語句結合。
ByteHouse 當前已經有一整套 skip index 的實現。向量索引可以作為一種新型的 skip index 來引入使用。然而,原本的 skip index 體系并不能高效支持向量檢索相關計算,主要體現在以下幾點:
1 當前沒有針對 skip index 的 cache 機制,因此無法保證向量索引常駐內存
2 當前 skip index 只用于查詢計劃執行前做 mark level 的過濾,過濾的結果需要通過額外的距離計算才能獲取到 topK 的結果,而不是直接使用 skip index 計算的結果來獲取,計算上存在冗余。
3 skip index 只能按照 mark 粒度(mark * granule)來進行構建,對于數據量較大的 data part 而言,會存在多個 skip index,帶來更多的 IO 與計算的開銷。
考慮到以上幾點,我們認為現有的 skip index 架構不能支持高性能的向量檢索計算,因此,我們重新針對向量檢索場景設計了一套全新的架構方案。
整體架構
ByteHouse 的向量檢索功能整體的架構如下圖所示:
向量索引方面,我們接入了 hnswlib、faiss 兩個比較流行的檢索算法庫,支持 HNSW、IVF_PQ、IVF_PQ_FS 等多種常用索引。另外,考慮到向量檢索需要在內存中執行,還加入了向量索引緩存機制,確保查詢涉及的 data part 的索引能夠常駐內存,以實現低延遲的向量檢索。
另外,我們基于現有 skip index 邏輯,添加了對應索引的構建語句支持,指定每個 data part 只構建一個索引。
考慮到構建資源消耗較高,在索引構建流程上,針對此類索引,添加了構建資源(CPU)控制機制,并且針對內存使用較大場景(IVF 類型索引的 train 方法),提供了 on disk 的構建邏輯。
查詢執行方面,我們在查詢的各個層次針對向量檢索相關的查詢進行了 Pattern 識別與 Query 改寫,目前主要識別 order by L2Distance/cosineDistance + limit topK 相關查詢,并針對向量檢索的計算特點,實現了一個全新的 SelectWithSearch 算子來執行實際的向量檢索與其他屬性讀取操作。
新舊執行鏈路比較如下:
Skip Index Based Pipeline
New Pipeline
構建語句例子如下:
CREATE TABLE test_ann
(
`id` UInt64,
`label` String,
`vector` Array(Float32),
INDEX v1 vector TYPE HNSW('DIM=960, METRIC=COSINE')
)
ENGINE = MergeTree
ORDER BY id
查詢語句例子如下:
select
id,
label,
dist
from test_ann
prewhere label = '...'
order by cosineDistance(vector, [query_vector]) as dist
limit 100
優化
1 向量列讀取操作消除:
識別向量列是否只在向量檢索操作中需要,如果是,則在最終的讀盤操作中,去掉向量列,減少不必要的讀取操作
2 向量檢索計算前置:
默認執行流程中,我們會為每個 data part 創建一個 SelectWithSearch 算子,計算時會針對單個 part 執行向量檢索與其他屬性的讀取,由于讀取任務最小的讀取單元是一個 mark,這樣的執行計劃總的讀取行數最大可為 (part_num * mark_size * topK) 行。造成的結果是性能會隨 part 數量增多而不斷下降。為了優化多 part 場景的查詢性能,我們提出了一種向量檢索前置的優化思路,即在執行計劃實際執行之前,將所有 part 的向量檢索全部先進行計算,得到全局的 topk 個結果,再進行各個 part 的其他屬性讀取,這樣改造后,每次查詢要讀取的行數最高為 (mark_size * topK) ,實際場景測試中,latency 會有 2x 以上的提升。
3 Cache Preload
當前支持的向量索引需要加載到內存中以后才能進行高性能的向量檢索計算。在查詢執行到向量檢索相關操作時,如果發現待計算的 data part 對應向量索引未存在與 vector index cache 中,則需要首先調用 load 操作將 index load 到內存中,查詢則需要等待 load 結束后才能繼續執行。vector index load 操作會顯著增加查詢的執行實現,尤其是對于新寫入的 data part 或者 server 重新啟動的場景。針對這個問題,我們添加了 cache preload 的機制,在 data part 生成后,以及 server 啟動過程中,將 index load 自動到內存中,并且添加特定的 setting,支持 table level 以及 global 的 cache preload 配置。該機制為當前支持的 HaMergeTree、HaUniqueMergeTree 引擎都添加了支持。
性能評測
我們使用 VectorDBBench 對 ByteHouse 以及專用向量數據庫 Milvus 進行了評測。ByteHouse 同等 recall 情況下,QPS 好于 Milvus,同時在數據插入時間上,也是優于 Milvus。
未來規劃
當前已經實現了高性能向量數據庫的基本框架,但是,在資源消耗、性能以及易用性上,仍有很多需要探索和改善的方面。后續 ByteHouse 將繼續針對低資源消耗向量索引、查詢性能優化、易用性、大模型生態等方面進行探索。
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.