Nutanix 是一家做超融合的云計算廠商,實話,我之前對這家公司是一無所知,但在 2018 年 RocksDB meetup 上面,他們做了一個如何在 RocksDB 支持 coroutine read 以及 async write 的 talk 之后,我突然對這家廠商有了興趣。佩服他們對 RocksDB 有非常深的研究,順帶在 Scholar 上面查了查,然后又發現了 TRIAD: Creating synergies between memory, disk and log in log structured key-value stores 這篇 Paper,覺得有必要整理下他們公司對 RocksDB 的研究了。
需要注意,下面的東西只是根據 Nutanix 公開的 talk 和 paper 做的一些調研以及猜想,具體他們怎么做的,我其實是不清楚的。
Filter + Async I/O
對于 RocksDB 來說,它的讀寫 I/O 都是同步的,大家都知道,一般同步的東西,代碼寫起來是挺簡單,但性能其實并不是特別的高效。所以 RocksDB 的 team 一直想引入 Async I/O,也有了一些討論,也有了一些 PR,但無奈改動太大了。
Nutanix 采用了另一種方案來支持 Async I/O,也就是使用 coroutine,而且對 RocksDB core 幾乎代碼沒有改動。
原理也比較簡單,因為 RocksDB 提供了比較好的抽象,對于文件的操作,都是使用一個 Env 對外提供的,所以只需要實習一個自己的 Env,就能控制 RocksDB 的文件讀寫了。
Nutanix 實現了一個自己的應用線程池,類似于 Folly 的 Fibers 庫,然后實現了一個 Async I/O 的 thread pool,用來提交和處理 RocksDB 的 I/O 請求,然后這個 AIO pool 再去跟底層真正的 AIO 交互。
因為他們沒有透漏更多,我猜想 Nutanix 的流程應該是:
- 操作跑在一個單線程上面,基于 Fibers
- RocksDB 需要讀取某個文件的數據
- RocksDB 將請求發給 AIO thread pool
- 掛起當前的 coroutine
- AIO pool 發給底層的 AIO
- 等 I/O 處理結束在重新 resume 掛起的 coroutine 繼續處理
其實這個跟通常的 coroutine 方式差不多,Nutanix 在 talk 里面說到對于單個線程,吞吐能提升 8 倍,還是很猛的一個數字了。
Async Write
上面提到的主要是 Nutanix 對于 Async I/O 的優化,在寫入上面,他們也做了優化。
對于 LSM 這種數據結構來說,一次 Write,我們會先將數據 append 到 WAL 上面,然后在寫入 memtable。RocksDB 支持多線程寫,雖然它提供了 lock-free 的 memtable,但在 append WAL 仍然是不可能做到多線程并發的。所以 RocksDB 做了一些優化。一個是會選出一個 leader 線程,收集其他所有線程的寫入,做個 batch,批量寫入 WAL。另外就是引入了 pipeline 機制,一個線程先寫 WAL,然后寫 memtable,這時候另外的線程可以寫 WAL 了。
雖然有這些優化,但對于 write 來說,仍然可以認為是同步的,Nutanix 這里引入了 async write,其實原理很簡單,就是在 write 的時候帶上一個 callback,內部啟動了一個新的 leader 線程用來收集數據,batch 寫入,然后等寫入成功之后調用 callback。這里,Nutanix 額外提到使用了 direct I/O 來操作 WAL,這個還是比較有意思的,因為我以前一直以為對于 append 這種 I/O 操作,direct I/O 其實沒啥太大的作用,所以也不知道他們是如何實現的。
基于這個優化,Nutanix 說寫入提升了 3 到 4 倍,latency 減少了 2 倍,這個已經很猛了。
TRIAD
最后再來聊聊 TRIAD 這篇論文,這里來個小插曲,Facebook 的技術大佬 Mark 也提到了這篇 Paper,他說到之前竟然沒看到這篇文章(畢竟是 2017 年發布的),我猜想他其實之前也沒怎么關注 Nutanix,然后也是因為 RocksDB meetup 知道了,然后在 Google 出來的。。。
TRIAD 的原理還是非常簡單的,對于一些熱點頻繁更新的數據,在 Memtable flush 到 Level 0 的時候,并不會 flush 到 Level 0,而是重新寫回到 memtable,當然為了保證數據安全,會額外將這些數據寫入到一個 log 里面。
在 Memtable 里面,每個 key 會有額外的 4 字節空間來統計 key 的頻率,然后在 flush 的時候統計出最 hot 的 k 個 key。現在的算法比較簡單,只要大于平均頻率的 key 就是 hot key,這個算法其實在多數場景下面都是有效的。
對于 Level 0 和 Level 1 compaction,TRIAD 采用了 Hyperloglog 來計算兩層之間的重疊情況,如果如果有足夠的重疊了,就觸發 compaction,否則則是延遲觸發。計算重疊的公式為 UniqueKeys(file-1, file-2, ... file-n) / sum( Keys( file-i ) )
,其中 Keys( file-i )
表明是第 I 個 SST 的總的 key 的個數,而 UniqueKeys
則是估算的所有 SST 的唯一 key 的個數。
對于 LSM 來說,一個被刷到 Level 0 的 memtable,通常數據其實也存在 WAL 里面,所以 TRIAD 做了一些改進,在 flush 到 Level 0 的時候,只是將一個 index(CL-SSTable) 刷到了 Level 0,這樣通過 index 就能在 WAL 找到對應的數據了。然后在 Level 0 compacted 到 Level 1 的時候,WAL 才會被刪除。
關于 TRIAD,大家可以直接去看源碼。
總結
上面只是一些我自己的理解,直觀的感受就是 Nutanix 這家公司在 RocksDB 上面也做了很多東西,但網上能 Google 出來的東西挺少的。對于我們來說,這些優化如果 RocksDB 能引入那當然最好,如果不能,短期對我們意義不大,畢竟我們現在沒太多的人力去開發相關的東西,如果你對這塊感興趣,歡迎聯系我 tl@pingcap.com。