Milvus 雲端可擴充向量資料庫的演進
在這篇文章中,我們將分享如何設計新的 Milvus 資料庫叢集架構的思考過程。
Milvus 向量資料庫的目標
當Milvus 向量資料庫的想法第一次出現在我們腦海時,我們希望建立一個資料基礎架構,可以幫助人們在他們的組織中加速採用 AI。
為了完成這個使命,我們為 Milvus 計畫設定了兩個關鍵目標。
易於使用
AI/ML 是一個新技術層出不窮的新興領域。大多數開發人員並不完全熟悉快速成長的 AI 技術與工具。開發人員已經消耗了他們大部分的精力來尋找、訓練和調整模型。他們很難再花額外的精力處理模型產生的大量嵌入向量。更不用說,處理大量的資料始終是一項極具挑戰性的任務。
因此,我們將「易用性」放在非常重要的位置,因為它可以大幅降低開發成本。
低運行成本
人工智慧在生產上的主要障礙之一,就是要證明投資回報的合理性。我們將有更多機會以較低的運行成本將我們的 AI 應用程式投入生產。而這將有利於提升潛在效益的幅度。
Milvus 2.0 的設計原則
在 Milvus 1.0 中,我們開始朝著這些目標邁進。但還遠遠不夠,尤其是在可擴展性和可用性方面。之後,我們開始了 Milvus 2.0 的開發,以改善這幾點。我們為這個新版本制定的原則包括
- 以高擴充性和可用性為目標
- 以成熟的雲端基礎架構與實務為基礎
- 在雲端盡量降低效能
換句話說,我們希望讓 Milvus 資料庫叢集成為雲原生。
資料庫叢集的演進
向量資料庫是資料庫的新品種,因為它可以處理新的資料類型 (向量)。但它仍與其他資料庫面臨相同的挑戰,並有一些自己的需求。在這篇文章的其餘部分,我將重點介紹我們從現有的資料庫叢集實作中所學到的,以及我們如何設計新的 Milvus 群組架構的思考過程。
如果您對 Milvus 群組元件的實作細節有興趣,請持續關注 Milvus 文件。我們將持續在 Milvus GitHub repo、Milvus 網站和 Milvus Blog 發佈技術文章。
理想的資料庫叢集
「目標小,失誤少」。
讓我們先列出理想資料庫叢集應具備的關鍵功能。
- 並發性和無單點故障:連結至不同群組成員的使用者可以同時擁有相同資料的讀/寫存取權。
- 一致性:不同的群組成員應該看到相同的資料。
- 可擴充性:我們可以隨時新增或移除群組成員。
老實說,所有這些功能都很難一起取得。在現代的資料庫群集實作中,人們必須犧牲其中一些功能。人們並不期望一個完美的資料庫叢集,只要它能符合使用者的使用情境即可。然而,萬物共享的資料庫叢集曾經非常接近理想的資料庫叢集。如果我們想要學習一些東西,應該從這裡開始。
資料庫叢集的主要考量
與其他現代實作相比,shared-everything 叢集有更悠久的歷史。Db2 數據共享群組和 Oracle RAC 是典型的萬物共享群集。許多人認為萬物共用意味著共用磁碟。其實遠遠不止如此。
萬物共用群集中只有一種資料庫會員。使用者可以連線到其中任何一個對稱成員來存取任何資料。什麼是「一切」需要共用才能運作?
群組中的事件順序
首先,群組的事件順序對於解決不同群組成員的並發存取所造成的潛在衝突非常重要。我們通常使用資料庫記錄序列號來表示事件順序。同時,記錄序列號通常是由時間戳產生。
因此,群組事件順序的需求等同於全局計時器的需求。如果我們能為群組提供一個原子鐘,那就太棒了。然而,Milvus 是一個開放源碼的軟體專案,這表示我們應該仰賴一般可用的資源。到目前為止,原子鐘對於大公司來說仍然是一種高級的選擇。
我們已經在 Milvus 2.0 資料庫叢集中實現了時間同步元件。您可以在附錄中找到連結。
全局鎖定
資料庫有鎖定機制來解決並發的存取衝突,無論是樂觀或悲觀的鎖定。同樣地,我們也需要全局鎖來解決不同群組成員間的同時存取衝突。
全局鎖定意味著不同的群組成員必須彼此交談來協商鎖定請求。有幾個重要因素會影響這個全局鎖協商過程的效率:
- 系統間連線的速度
- 需要參與協商過程的群組成員數量
- 群組衝突的頻率
典型的群組大小不超過 100。例如,Db2 DSG 是 32;Oracle RAC 是 100。這些群組成員會被放置在一個以光纖連接的機房內,以減少傳輸延遲。這就是為什麼有時稱為集中式群集。由於群組大小的限制,人們會選擇高階伺服器 (大型主機或迷你電腦,它們在 CPU、記憶體、I/O 通道等方面有更大的容量) 來組成共享一切的群組。
在現代雲端環境中,這種硬體預設已大幅改變。現在,雲端資料中心由高密度的機房組成,機房內滿是(成千上萬台)具備 TCP/IP 連線的商品 X86 伺服器。如果我們依賴這些 X86 伺服器來建立資料庫叢集,群組規模應該會增加到數百 (甚至數千) 台機器。而在某些業務情況下,我們會希望這數百台 X86 機器分散在不同的區域。因此實施全局鎖定可能不再值得,因為全局鎖定的效能不夠好。
在 Milvus 2.0 中,我們不打算實作全局鎖定功能。一方面,向量資料沒有更新。(所以我們不需要擔心在有分片排列的 Milvus 群組中,同一個資料會有多個寫入者衝突的問題。同時,我們可以使用 MVCC(多版本並發控制,一種避免鎖的並發控制方法)來解決讀寫衝突。
另一方面,向量資料處理比結構化資料處理消耗更多的記憶體。人們對向量資料庫的擴充性要求更高。
共享內存資料快取
我們可以簡單地將資料庫引擎分成兩部分:儲存引擎和運算引擎。儲存引擎負責兩項關鍵任務:
- 將資料寫入永久儲存區,以達到耐久性的目的。
- 將資料從永久儲存載入到記憶體內的資料快取記憶體 (AKA buffer pool);這是運算引擎存取資料的唯一地方。
在資料庫叢集的情況下,如果成員 A 更新了成員 B 的快取資料,該怎麼辦?成員 B 如何知道其內存資料已過期?經典的萬物共享叢集有緩衝區交叉失效機制來解決這個問題。如果我們在群組成員間維持強大的一致性,緩衝區交叉失效機制的運作方式與全局鎖定類似。如前所述,這在現代的雲端環境中並不實際。因此,我們決定降低 Milvus 雲端可擴充群組的一致性層級,以最終一致性的方式來處理。如此一來,Milvus 2.0 中的緩衝區交叉無效機制就可以成為異步過程。
共用儲存
當討論資料庫叢集時,共用儲存可能是人們首先會想到的。
在近年的雲端儲存演進中,儲存選項也發生了顯著的變化。儲存連接網路 (SAN) 曾經是 (現在仍然是) 共用群組的儲存基礎。但在雲端環境中,沒有 SAN。資料庫必須使用連接至雲端虛擬機器的本機磁碟。使用本機磁碟會帶來群組成員間資料一致性的挑戰。我們也必須擔心群組成員的高可用性。
之後,Snowflake 為使用雲端共用儲存空間 (S3 儲存空間) 的雲端資料庫樹立了一個很好的典範。它也啟發了 Milvus 2.0。如前所述,我們打算依賴成熟的雲基礎架構。但在使用雲端共用儲存空間之前,我們必須考慮幾件事情。
首先,S3 儲存便宜又可靠,但它的設計並不適用於資料庫情境的即時 R/W 存取。我們需要建立資料元件 (在 Milvus 2.0 中稱為資料節點) 來橋接本機記憶體/磁碟與 S3 儲存。我們可以學習一些範例 (像是 Alluxio、JuiceFS 等)。我們無法直接整合這些專案的原因是我們專注於不同的資料粒度。Alluxio 和 JuiceFS 是針對資料集或 POSIX 檔案設計的,而我們則專注於資料記錄 (向量) 層級。
當向量資料在 S3 儲存解決之後,元資料的答案就很簡單了:儲存在 ETCD 中。那麼,日誌資料呢?在經典的實作中,日誌儲存也是以 SAN 為基礎。一個資料庫群組成員的日誌檔案會在資料庫群組內共用,以達到故障復原的目的。因此,在進入雲端環境之前,這並不是問題。
在 Spanner 論文中,Google 說明了他們如何使用 Paxos 共識演算法實現全球分散式資料庫(群組)。您需要將資料庫叢集編程為狀態機的複製群組。重做日誌 (redo log) 通常是要在群組中複製的「狀態」。
透過共識演算法複製重做日誌是一種強大的工具,在某些商業情境中具有相當大的優勢。但對於 Milvus 向量資料庫,我們並不覺得有足夠的誘因去建立一個整體的狀態機複製群組。我們決定使用雲端訊息佇列/平台(Apache Pulsar、Apache Kafka 等)作為日誌儲存的替代性雲端共用儲存。透過將日誌儲存委託給訊息平台,我們獲得了以下好處。
- 該群組更偏向於事件驅動,這表示許多程序可以是異步的。它提高了可擴展性。
- 元件的耦合更為鬆散,因此更容易執行線上滾動升級。它提高了可用性和可操作性。
我們會在後面的章節再討論這個主題。
到目前為止,我們已經將資料庫叢集的關鍵考慮因素總結完畢。在我們跳到討論 Milvus 2.0 架構之前,讓我先說明我們如何在 Milvus 中管理向量。
資料管理與效能可預測性
Milvus 以集合的方式儲存向量。集合」是一個邏輯概念,相當於 SQL 資料庫中的「表」。一個「集合」可以有多個實體檔案來保存向量。一個實體檔案就是一個「段」。段 "是一個物理概念,就像 SQL 資料庫中的表空間檔案。資料量較少時,我們可以將所有資料儲存在單一區段/實體檔案中。但現在,我們不斷面臨大數據。當有多個區段/實體檔案時,我們應該如何將資料分散在不同的資料分區中呢?
雖然資料是第一位的,而不是索引,但我們必須以索引演算法偏好的方式儲存資料,才能在大多數情況下有效率地存取資料。在 SQL 資料庫中,一個經常使用的策略是依據分割關鍵值的範圍進行分割。人們通常會建立叢集索引來強制執行分割關鍵。總體而言,對 SQL 資料庫來說,這是一個不錯的方法。資料以良好的形態儲存,並針對 I/O(prefetch)進行最佳化。但仍有缺陷。
- 資料偏斜。某些分割區的資料可能比其他分割區多很多。實際資料的分佈並不像數值範圍那麼簡單。
- 存取熱點。某些資料分區可能會有較多的工作量。
想像一下,更多的工作負載會流向資料較多的分區。當這些情況發生時,我們需要重新平衡各分割區的資料。(這是 DBA 繁瑣的日常生活)。
向量的叢集索引
我們也可以為向量建立聚簇索引(倒列表索引)。但這與 SQL 資料庫的情況不同。在 SQL 資料庫中建立索引後,透過索引來存取資料是非常有效率的,只需要較少的計算和較少的 I/O 操作。但對於向量資料,即使有索引,也會有多得多的計算和 I/O 操作。所以前面提到的缺陷對向量資料庫叢集的影響會更大。此外,由於資料量和運算複雜度的關係,在不同區段間重新平衡向量的成本非常高。
在 Milvus 中,我們使用成長分割的策略。當我們將資料注入向量集合時,Milvus 會將新的向量追加到集合中最新的區段。一旦分段的大小足夠大 (臨界值是可設定的),Milvus 就會關閉該分段,並為關閉的分段建立索引。在此期間,會建立一個新的區段來儲存即將到來的資料。這個簡單的策略對向量處理來說更為平衡。
向量查詢是一個在向量集合中搜尋最相似候選項的過程。它是一個典型的 MapReduce 程序。舉例來說,我們想要從有十個區段的向量集合中搜尋前 20 個相似的結果。我們可以在每個分段上搜尋前 20 個,然後將 20 * 10 的結果合併為最後的 20 個結果。由於每個區段都有相同數量的向量和相似的索引,因此每個區段的處理時間幾乎相同。這讓我們擁有效能可預測的優勢,這在規劃資料庫叢集的規模時是非常重要的。
Milvus 2.0 的新範式
在 Milvus 1.0 中,我們實作了像大多數 SQL 資料庫一樣的讀寫分割分片群組。這是 Milvus 資料庫叢集擴充的一次很好的嘗試。但問題也相當明顯。
Milvus 資料庫 1.0
在 Milvus 1.0 中,R/W 節點必須完全照顧最新的分段,包括向量追加、在這個沒有索引的分段中搜尋、建立索引等。由於每個集合只有一個寫入者,如果資料持續串流到系統中,寫入者就會非常忙碌。R/W 節點與讀取器節點之間的資料分享效能也是一個問題。此外,我們必須依賴 NFS (不穩定) 或高級雲端儲存 (太昂貴) 來進行資料分享儲存。
這些現有的問題在 Milvus 1.0 架構中很難解決。因此,我們在 Milvus 2.0 設計中引入了新的範例來解決這些問題。
Milvus 架構
角色模型
有兩種模式可以編程並發計算系統。
- 共用記憶體,意味著並發控制 (鎖定) 和同步處理
- 演算法模型 (AKA 訊息傳遞) 表示訊息驅動和非同步處理
我們也可以在分散式資料庫叢集中應用這兩種模型。
如前所述,大多數高知名度的分散式資料庫都使用相同的方法:透過共識演算法複製重做記錄。這是使用共識演算法的同步處理,為重做記錄建立分散式共享記憶體。不同的公司和風險投資已經在這項技術上投資了數十億美元。在我們開始研發 Milvus 2.0 之前,我不想評論這個問題。很多人將這項技術視為實現分散式資料庫系統的唯一方法。這很煩人。如果我不說點什麼,人們可能會誤會我們在分散式資料庫設計上太魯莽了。
近年來,以共識演算法進行 Redo-log 複製是最被高估的資料庫技術。有兩個關鍵問題
- 認為重做記錄複製更好的假設是脆弱的。
- 廠商誤導了人們對共識演算法能力的期望。
假設我們有兩個資料庫節點,一個是來源節點,另一個是目標節點。在一開始時,它們擁有資料的精確副本。我們對源節點進行了一些變更作業 (I/U/D SQL 語句),而我們想要保持目標節點的更新。我們應該怎麼做?最簡單的方法是重播目標節點上的作業。但這不是最有效率的方法。
考慮到 I/U/D 語句的執行成本,我們可以將它分成執行準備和實體工作兩部分。執行準備部分包括 SQL 解析器、SQL 最佳化器等的工作。無論有多少資料記錄會受到影響,它都是固定的成本。實體工作部分的成本取決於有多少資料記錄會受到影響;它是浮動成本。重做記錄複製背後的理念是節省目標節點上的固定成本;我們只在目標節點上重放重做記錄 (實體工作)。
節省成本的百分比是重做記錄數量的倒數。如果一個作業只影響一筆記錄,我應該可以從重做記錄複製中看到顯著的節省。如果是 10,000 條記錄呢?那麼我們應該擔心網路的可靠性。傳送一個作業和傳送 10,000 個重做記錄,哪個更可靠?一萬條記錄呢?重做記錄複製在支付系統、元資料系統等場景中超級有用。在這些情境中,每個資料庫 I/U/D 作業只會影響少數記錄 (1 或 2)。但它很難與批次作業等 I/O 密集型工作負載搭配使用。
供應商總是宣稱共識演算法可以為資料庫叢集提供強大的一致性。但是人們只使用共識演算法來複製重做記錄。不同節點上的重做記錄是一致的,但這並不表示其他節點上的資料檢視也是一致的。我們必須將重做記錄合併到實際的資料表記錄中。因此,即使進行同步處理,我們仍只能在資料檢視上獲得最終一致性。
我們應該在適當的地方使用共識演算法來複製重做記錄。Milvus 2.0 中使用的元資料系統 (ETCD) 和訊息平台 (例如 Apache Pulsar) 已經實作了共識演算法。但正如我之前所說,「對於 Milvus 向量資料庫,我們並不覺得作為一個狀態機複製群組整體有足夠的誘因」。
在 Milvus 2.0 中,我們使用 actor 模型來組織 worker 節點。工人節點是孤獨的。它們只與訊息平台對話,取得指令並傳送結果。聽起來很無聊。
「我們的座右銘是什麼?」「無聊永遠是最好的。」--《殺人兇手保鏢》(2017)
演員模型是異步的。它適用於可擴充性和可用性。由於 Worker 節點之間互不相識,因此即使某些 Worker 節點加入或移除,也不會對其他 Worker 節點造成影響。
可用性與耐久性的分離
在 Milvus 2.0 中,我們做的是操作重播而不是日誌重播,因為在向量資料庫中,操作重播和日誌重播沒有太大的差別。我們沒有 Update 功能,也沒有 Insert with Select 功能。而且使用演員模型進行操作重播也更容易。
因此,多個 Worker 節點可能會根據其責任,從訊息平台執行相同的作業。我之前提到我們決定使用 S3 雲端儲存作為 Milvus 資料庫叢集的共用儲存層。S3 儲存非常可靠。那麼不同的工作節點是否有必要寫出相同的資料到共用儲存空間呢?
因此,我們為工作節點設計了三種角色。
- 查詢節點根據指派維護記憶體中的資料檢視。查詢節點的工作包括進行向量搜尋和保持記憶體中的資料更新。但它不需要寫入任何東西到 S3 儲存空間。它是該組中對記憶體最敏感的節點。
- 資料節點負責將新資料寫入 S3 儲存空間。資料節點不需要維護記憶體內的資料檢視,因此資料節點的硬體配置與查詢節點有很大的不同。
- 當段的大小達到臨界值時,索引節點會為資料節點關閉的段建立索引。這是該群組中最耗費 CPU 的工作。
這三種節點代表不同類型的工作負載。它們可以獨立擴充。我們稱之為可用性與耐久性的分離,這是從 Microsoft Socrates 雲端資料庫學習來的。
結束,也是開始
本文回顧了 Milvus 向量資料庫 2.0 的幾個設計決策。 讓我們在此快速總結這些觀點。
- 我們為 Milvus 叢集 2.0 選擇了最終的一致性。
- 我們已盡可能將成熟的雲端元件整合到 Milvus 2.0 中。我們已將 Milvus 2.0 引入的新元件控制在使用者的生產環境中。
- Milvus 2.0 遵循演員模型和可用性與耐久性的分離,在雲環境中很容易擴展。
到目前為止,我們已經形成了 Milvus 2.0 可雲端擴充資料庫的骨幹,但我們的工作積壓中還有許多來自 Milvus 社群的需求需要滿足。如果您也有相同的使命(「打造更多的開源基礎建設軟體,加速 AI 轉型」),歡迎加入 Milvus 社群。
Milvus 是 LF AI & Data 基金會的畢業專案。您不需要為 Milvus 簽署任何 CLA!
附錄
Milvus 設計文件
https://github.com/milvus-io/milvus/tree/master/docs/design_docs
C++ 中的 Raft 實作
如果您仍然對共識演算法感興趣,我建議您看看eBay 的開放原始碼專案 Gringofts。它是 Raft 共識演算法 (Paxos 家族的變體) 的 C++ 實作。我的朋友 Jacky 和 Elvis (我在 Morgan Stanley 的前同事) 為 eBay 線上付款系統建立了這個系統,而 eBay 正是這項技術最適合的應用場景之一。
- Milvus 向量資料庫的目標
- 資料庫叢集的演進
- 結束,也是開始
- 附錄
On This Page
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word