閒聊儲存引擎選型
這個話題很大,最近正好遇到這樣的問題,我想我對這個問題的理解經歷了一個過程,從這個過程中我也認識到其實如果你不深入去實踐,有些問題只能泛泛而談(就像我現在說的這樣)。我就講講我在一個文件索引專案中的心得。
記憶體 or 磁碟
一開始我們想可不可以把資料放在記憶體裡呢?這樣速度可以很快。
很快我們發現又幾個問題不好解決:
1. go,java語言的GC問題在大規模記憶體儲存場景下是一個大問題,因為我們多副本之間使用raft一致性複製協議,如果因為GC導致leader的心跳不能正常傳送會引起大規模leader選舉,系統幾乎不可用,隨著資料量越多,這個問題越明顯。筆者曾經一個專案就遇到過類似的問題,當時為了提升cache 的命中率,增大了cache size,結果full GC導致系統發生大規模通訊異常,頻繁發生選舉,系統幾乎不可用。
2. 因為資料在記憶體中,一旦程序重啟,資料恢復耗時很長,無論是通過網路快照還是本地快照,這個過程都很長,對於一個分片採用三副本備份的系統,意味著期間再發生一個副本異常那麼這個分片不可用了,並且這個過程越長髮生的概率就越高。
3. 因為我們是文件儲存系統,支援全文檢索,這樣不可避免的,儲存的資料會比文件自身的大小大不少,這樣原本就相對緊缺的記憶體資源就存在更大的消耗。
基於以上三個理由,其實還有第四個理由,那就沒有沒有一個高效的記憶體儲存引擎適合我們。最後記憶體方案放棄了。
國內的悟空索引是採用記憶體儲存,有興趣的同學可以看看。
BadgerDB or RocksDB
rocksdb是一個很成熟的KV儲存引擎,開源社群有比較好的針對go語言封裝的gorocksdb,採用CGO呼叫實現go對C/C++的呼叫。我們在使用bleve作為索引核心去學習和使用的時候就是採用這個引擎作為底層的儲存引擎,bleve的index driver底層可以是各種KV儲存引擎,bleve封裝了薄薄的一層介面,只要適配這些介面即可作為bleve的底層儲存。
badgerDB是一個針對大value優化的類似levelDB的KV儲存引擎,採用go語言編寫。
在實踐的過程我們發現相比於rocksdb,badgerDB並不合適,主要是因為在kv儲存模型下,bleve把文件以及倒排索引都打散成KV儲存了,基本上很少有大value,導致讀寫效能不理想(原因在於key,value的都寫都至少進過兩次磁碟IO)。
不過rocksdb也好不到哪裡去,我們在使用rocksdb的bleve(不是裸的引擎,是我們整個系統)與ES系統在相同的環境下測試,寫的效能不及ES,延時波動也不及ES那麼穩定。我們分析是因為兩者使用了不同的儲存引擎導致的(我們也使用過一些null store來測試除儲存引擎之外的系統元件的效能,排除了這些干擾)。
segment file or KV
經歷了以上這寫痛苦和曲折之後我開始分析這裡的核心邏輯,是什麼引起這樣的不同?
我想一句話就可以概括這個本質的區別,那就是:兩者看待document的角度不同。
segment file系統把document作為整體去看待,無論讀寫都是如此,segment file中不同的segment之間幾乎毫無瓜葛,segment 合併也很簡單。
KV儲存系統基本上分為兩大類,LSM架構和Btree架構。但是核心邏輯就是kv,全域性有序。因此一個文件包括它的倒排索引都是一個個KV,即document = kv group。全域性有序的好處是大大得,這一點無可爭議,作為一個KV儲存引擎,這個基本上也是必須要保證的,否則讀的效能就太差了(尤其是range scan)。但是作為document的儲存引擎,這種離散的KV group對document並不友好,因為它要滿足自身的一些核心功能而額外做很多事情,而這些並不是document追求的,那麼在相同的資源下,誰是更適合的就不言而喻了,實踐也證明了這一點。
不過這裡我也看到segment file有一個“死穴”:實時性(或者是筆者孤陋寡聞,也許已經有高效的解決之道了,不過可以肯定一點那就是這個問題一點兒也不簡單)。而KV系統可以做到實時性,(btree 架構天然寫的慢,這個不講),LSM架構就是優化了寫,並且保證mem table可實時讀寫。
segment file才剛剛開始,但是我覺得從戰略的角度講,這個抉擇是對的。
從這次的思考中筆者認為在儲存領域,如何看待你的儲存物件,從這角度出發去選擇或者開發儲存引擎也許才能找到更適合自己需求的那一個。