第一次看完 WiscKey 這篇論文,覺得寫得很接地氣,很實(shí)用,很通俗易懂。
這里簡(jiǎn)單記錄一下。
WiscKey 簡(jiǎn)介
WiscKey 的提出,主要是為了優(yōu)化 LSM-Tree 的寫放大問(wèn)題。此前已經(jīng)有不少論文討論過(guò)這個(gè)問(wèn)題,如 LSM-trie 和 PebblesDB,但是大部分優(yōu)化方法都不是很徹底——簡(jiǎn)單說(shuō)就是,優(yōu)化效果太差,或者不夠通用。WiscKey 提出的是一種比較通用、效果明顯且簡(jiǎn)單易懂的方法。
其實(shí) WiscKey 優(yōu)化寫放大的原理非常簡(jiǎn)單:在 LSM-Tree 上做 key 和 value 的分離存儲(chǔ)。
LSM-Tree 寫放大的根本原因是,compaction 時(shí)為了保證數(shù)據(jù)有序進(jìn)行大量數(shù)據(jù)(key 和 value)重寫。實(shí)際上,需要保持有序的只有 key,如果將 key 和 value 分開存儲(chǔ),compaction 重寫數(shù)據(jù)的時(shí)候,就只需要重寫 key(和 value 的位置,簡(jiǎn)稱 vpos)。這在 key size (簡(jiǎn)稱 ksize)遠(yuǎn)小于 value size (簡(jiǎn)稱 vsize)的場(chǎng)景(現(xiàn)實(shí)場(chǎng)景基本都是這樣)降低寫放大的效果十分明顯。
另一方面,當(dāng) vsize 比較小的時(shí)候,重寫 value 這部分的開銷就比較小,key-value 分離存儲(chǔ)帶來(lái)的好處就不足以抵消它帶來(lái)開銷。Key-value 分離存儲(chǔ)帶來(lái)的額外開銷 —— 讀寫一個(gè) key-value 需要操作不同文件。這在 range query 的場(chǎng)景下,會(huì)產(chǎn)生多次隨機(jī) I/O,影響比較明顯。如下圖所示。
對(duì)順序 range query:
- 當(dāng) vsize 比較小的時(shí)候,LevelDB 的性能和 WiscKey 很接近。因?yàn)槭琼樞?range query,WiscKey 的預(yù)讀緩存功能可以有效地提升性能。
- 當(dāng) vsize 逐漸變大,LevelDB 的性能急劇下降,WiscKey 的性能比較平穩(wěn),明顯優(yōu)于 LevelDB。因?yàn)?value 變大,導(dǎo)致 LevelDB 一個(gè) block 可以存儲(chǔ)的 kv pair 變少了,cache 效果變差。
對(duì)于隨機(jī) range query:
- 當(dāng) vsize 比較小的時(shí)候,LevelDB 的性能優(yōu)于 WiscKey。因?yàn)槭请S機(jī) range query,范圍應(yīng)該比較小,WiscKey 的預(yù)讀緩存功能沒(méi)能很好發(fā)揮作用。
- 當(dāng) vsize 逐漸變大,WiscKey 的性能很快超越了 LevelDB。
Key-value 分離存儲(chǔ)后,LSM-Tree 索引結(jié)構(gòu)上只存儲(chǔ) key 和 vpos,實(shí)際的 value 存儲(chǔ)在另一個(gè) append-only 的 log 文件中,稱為 vlog。總體看來(lái),WiscKey 很像一個(gè)用 LSM-Tree 做索引的 BitCask。
WiscKey 帶來(lái)的好處
- LSM-Tree Compaction 不需要重寫 value,大大減小寫放大。
- LSM-Tree 不存儲(chǔ) value,體積更小,一個(gè) block 能存更多的 key,有利于減少讀 LSM-Tree 的 I/O。
- LSM-Tree 的體積小,cache 效果應(yīng)該會(huì)更好。LSM-Tree 的上面幾層基本都可以 cache 在內(nèi)存中。
WiscKey 面臨的問(wèn)題和挑戰(zhàn)
雖然減小了寫放大,提升了 key 的密度,進(jìn)而優(yōu)化了 LSM-Tree 的 cache 效果。但是 key-value 分離存儲(chǔ)也給 WiscKey 帶來(lái)一些問(wèn)題和挑戰(zhàn)。下面簡(jiǎn)單分析一下。
當(dāng)發(fā)起一次 range query 的時(shí)候,最終會(huì)轉(zhuǎn)化成多次針對(duì) vlog 的隨機(jī)讀。這種場(chǎng)景下, WiscKey 會(huì)通過(guò)多個(gè)后臺(tái)線程對(duì)后面的多個(gè)數(shù)據(jù)進(jìn)行預(yù)讀,并緩存起來(lái),充分利用 SSD 內(nèi)部的并行性。預(yù)讀緩存對(duì)于范圍比較大的 range query 效果可能比較明顯,對(duì)于小范圍的隨機(jī) range query,可能發(fā)揮不了什么作用。
Key 和 value 分開存儲(chǔ)后,怎么保證 key 和 value 的一致性呢?針對(duì)這個(gè)問(wèn)題,WiscKey 采用的方案是,先寫 vlog,再寫 LSM-Tree 。如果寫 vlog 成功,寫 LSM-Tree 失敗,則寫失敗。此時(shí),vlog 上的值不會(huì)被暴露出去,只需后面進(jìn)行垃圾回收。這里如果要保證數(shù)據(jù)一致性和可靠性,則寫 vlog 后要刷盤,寫 LSM-Tree 后也要 WAL 刷盤。這里有兩次刷盤開銷,通過(guò) WAL 和 vlog 的合并,可以優(yōu)化成一次。
-
vlog 的垃圾回收。垃圾回收需要掃描數(shù)據(jù),根據(jù)數(shù)據(jù)結(jié)構(gòu),有兩個(gè)方向可以考慮:
- 掃 LSM-Tree。根據(jù) LSM-Tree 把 vlog 中有效的內(nèi)容都提取出來(lái)。這個(gè)方法不太適合在線服務(wù),需要一次性重寫整個(gè) vlog,開銷比較大。重寫 vlog 的同時(shí)外部依然會(huì)更新 LSM-Tree 和 vlog,數(shù)據(jù)一致性不太好處理。
- 掃 vlog。為了掃描 vlog,vlog 不能只存儲(chǔ) <value>,需要存儲(chǔ) <key, value>。根據(jù)掃出來(lái)的每一個(gè) <key, value>,通過(guò) LSM-Tree 驗(yàn)證其有效性。為了不影響在線服務(wù),WiscKey 每次取出 vlog 最舊的一部分?jǐn)?shù)據(jù)(幾 MB),通過(guò) LSM-Tree 進(jìn)行驗(yàn)證,過(guò)期則丟棄,有效則 append 到 vlog,并修改 LSM-Tree(類似執(zhí)行一次寫入操作),并刪除這部分回收驗(yàn)證過(guò)的數(shù)據(jù),通過(guò) fallocate() 釋放無(wú)效的文件磁盤空間。