Milvus數據管理:刪除的實現原理

本文將主要講述 Milvus 是怎麼實現刪除功能的。刪除是許多用戶期待已久的功能,這次終於在 Milvus 0.7.0 版本中發佈。區別於直接調用 FAISS 的 remove_ids 接口,爲了讓刪除更加高效,並能夠支持更多索引類型,我們做了全新的設計。

 

在 FAISS 中,刪除 ID 和它對應的向量需要遍歷所有數據以決定哪些向量需要刪除

https://github.com/facebookresearch/faiss/wiki/Special-operations-on-indexes#removing-elements-from-an-index,頻繁操作會極大地影響系統性能,更無法做到刪除和查詢併發執行。如果是已經落盤的數據,則需要把數據文件加載進內存進行刪除,再重新落盤,代價非常大。這個方案自然無法運用在生產環境。此外,FAISS 目前只在 IndexFlat, IndexIVFFlat, IDMap 這三種索引類型上支持刪除,而 Milvus 的目標是不僅讓 FAISS 的所有 CPU 與 GPU 索引支持刪除,在未來還能陸續擴展到其他對接的 ANNS 庫中。所以,我們必須自己設計刪除功能。

 

在上一篇文章 Milvus 如何實現數據動態更新與查詢 中,我們瞭解到數據從導入直至落盤的全部過程。在這裏我們先回顧一下其中的一些基本概念。Memory manager 管理所有 insert buffer,其中每個 MemTable 對應一個 Collection(在 Milvus 0.7.0 中 Table 被更名爲 Collection),插入到內存中的數據會被自動切分成多個 MemTableFile,在 flush(落盤)時每個 MemTableFile 會被序列化成一個原始文件。這個架構在設計刪除功能時依然保持。

 

我們將刪除 API 定義爲刪除一個 Collection 內所有與傳入的 Entity ID 對應的數據。設計刪除功能時,我們首先把刪除分爲兩個場景:第一種是刪除依然還在 insert buffer 中的數據,第二種是刪除已經落盤的數據。第一種場景比較直觀,我們可以通過 ID 找到對應 MemTableFile,然後在內存中直接將數據刪除(圖一)。因爲刪除不能與插入數據同時進行,並且因爲存在上篇文章中提及的落盤時將 MemTableFile 從 Mutable 轉爲 Immutable 狀態的機制,刪除操作只會在 Mutable buffer 中進行,所以刪除操作也不會與落盤操作發生衝突,從而保證了數據的一致性。

 

                                                                                                            (圖一)

 

第二種場景相對更加複雜,也更加常見,因爲大多數情況下數據停留在 insert buffer 中的時間都會很短,在短時間內就會落盤。將那些已經落盤的數據加載上來進行硬刪除的方案效率太低,所以我們採用了另一種更高效的方案:軟刪除;不真正刪除落盤的數據,而是另外存一份文件記錄被刪除的 ID。在進行需要讀取數據的操作,例如搜索時,過濾掉那些已記錄的被刪除 ID。

 

而涉及到具體實現,我們就需要考慮幾點問題。在 Milvus 中,數據只有落盤纔可見,或者說可以搜到。因此,刪除已經落盤的數據不需要在調用 delete API 時進行,而是將它放在下一次落盤的時候進行。能夠這樣做的原因是已經落盤的數據文件不會再有新增數據,所以軟刪除不會對已落盤的數據有任何影響。調用刪除 API 時,對於還在 insert buffer 未落盤的數據,直接刪除就可以;對於已經落盤的數據,則需要在內存中記錄被刪除數據的 ID。在落盤時,將被刪除的 ID 寫入 DEL 文件以用於記錄對應 Segment 裏哪些 Entity 被刪除。落盤完成後,這些修改纔可見。具體過程如圖二所示。在 0.7.0 版本之前,我們只有 auto-flush(自動落盤)的機制。每隔 1 秒,後臺線程會序列化 insert buffer 中的數據。在我們這次設計中,我們決定添加主動 flush 接口。這樣,在調用 delete 接口後,用戶可以選擇再調用 flush,保證新增的數據可見,被刪除的數據不會再被搜到。

 

 

                                                   

                                                                                              (圖二)

 

第二個問題是 Milvus 的原始文件和索引文件是兩個獨立的文件,在元數據中也是兩行獨立的記錄。當刪除某個 ID 時,我們需要找到 ID 對應的原始數據文件和索引文件,並且一起記錄。於是,我們引入 segment 概念,一個 segment 包含原始文件(原始文件又包括原始向量文件和 ID 文件),索引文件,和記錄被刪除 ID 的 DEL 文件。segment 作爲數據的基本讀寫和搜索單元,一個 Collection(圖三)由多個 segment 組成,在磁盤上則是一個 Collection 文件夾下有多個 segment 文件夾。因爲我們的元數據基於關係型數據庫(SQLite 和 MySQL),記錄 segment 內的關係非常簡單,刪除操作也不再需要對原始文件和索引文件做分別的處理。

 

                                                

                                                                                         (圖三)

 

第三個問題則是,在搜索時,具體如何過濾已經被刪除的數據。實際上,DEL 文件記錄的 ID 是其在 segment 內存儲數據的的 offset(偏移位)。由於已落盤的 segment 不會有新增的數據,所以 offset 也不會改變。DEL 文件在內存中的數據結構是一個 bitmap,其中的 active bit 代表被刪除的 offset。我們對 FAISS 也進行了相應的修改:在 FAISS 中進行搜索時,會過濾掉 active bit 對應的向量,不再參與距離計算(圖四)。在 FAISS 中具體的修改在此不做詳述。

 

                                   

                                                                                         (圖四)

 

最後一個問題屬於優化:對落盤的文件進行刪除時,需要首先找到被刪的 ID 具體存在於 Collection 中的哪一個 segment,再記錄它的 offset。最暴力的方法當然是將每一個 segment 中的 ID 都暴搜一遍。我們想到的優化方法是在每個 segment 內添加一個 bloom filter(布隆過濾器)。Bloom filter 是一種隨機數據結構,用於判斷一個元素是否存在於一個集合中。因此,我們可以只加載每個 segment 的 bloom filter,只有 bloom filter 判斷被刪的 ID 在當前 segment 中,我們纔在該 segment 內尋找其對應的 offset,否則我們就可以忽略此 segment(圖五)。我們選擇 bloom filter 的原因是因爲相比其他數據結構,例如哈希表,它用的空間更小,並且查詢效率非常高。雖然 bloom filter 有一定概率存在 false positive(錯誤判斷元素存在),但因爲這個概率可以自己調節,我們可以將需要搜索的 segment 降到理想數量。Bloom filter 也需要支持刪除,否則已被刪除的 Entity ID 依然會在 bloom filter 中找到,導致誤判率增加。因此,實現的時候,我們使用的是支持刪除的 counting bloom filter。在這篇文章,我們不會對 bloom filter 的詳細原理做太多介紹,感興趣的讀者可以參閱https://en.wikipedia.org/wiki/Bloom_filter。

 

                                                                                  (圖五)

 

對應刪除的實現,我們已經介紹的差不多了。大家已經知道,對於已經落盤的數據,我們採用的是軟刪除法。當被刪除的數據越來越多的時候,爲了清理這些被刪除的數據,我們需要另外一個功能:Compact,能夠將被軟刪除的數據真正刪掉。Compact 做的事情除了刪除數據外,如果 Segment 已經建了索引,也會將舊索引文件刪除並且在後臺重建索引。目前,用戶只能手動調用 Compact 接口,實現數據的整理。未來我們希望能引入檢查機制,例如當檢查到刪除的數據量超過一定閾值或者刪除後的數據分佈產生了一定變化,能夠自動 Compact。

 

至此,我們已經基本概括了關於刪除相關的功能和實現中的一些設計思想。當然,這部分還有很多的優化空間,我們也非常歡迎大家提出更多意見~

 

 

錯過上期文章了嗎?看這裏!

Milvus 如何實現數據動態更新與查詢

 

 

| 歡迎加入 Milvus 社區

 

github.com/milvus-io/milvus | 源碼

milvus.io | 官網

milvusio.slack.com | Slack 社區

zhihu.com/org/zilliz-11/columns | 知乎

zilliz.blog.csdn.net | CSDN 博客

space.bilibili.com/478166626 | Bilibili

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章