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