本文缺少實際的實踐經(jīng)驗。全部來自在網(wǎng)上的“道聽途說”和自己的“胡思亂想”。
寫放大、讀放大、空間放大
基于 LSM-Tree 的存儲系統(tǒng)越來越常見了,如 RocksDB、LevelDB。LSM-Tree 能將離散的隨機寫請求都轉(zhuǎn)換成批量的順序寫請求(WAL + Compaction),以此提高寫性能。但也帶來了一些問題:
- 讀放大(Read Amplification)。LSM-Tree 的讀操作需要從新到舊(從上到下)一層一層查找,直到找到想要的數(shù)據(jù)。這個過程可能需要不止一次 I/O。特別是 range query 的情況,影響很明顯。
- 空間放大(Space Amplification)。因為所有的寫入都是順序?qū)懀╝ppend-only)的,不是 in-place update ,所以過期數(shù)據(jù)不會馬上被清理掉。
RocksDB 和 LevelDB 通過后臺的 compaction 來減少讀放大(減少 SST 文件數(shù)量)和空間放大(清理過期數(shù)據(jù)),但也因此帶來了寫放大(Write Amplification)的問題。
- 寫放大。實際寫入 HDD/SSD 的數(shù)據(jù)大小和程序要求寫入數(shù)據(jù)大小之比。正常情況下,HDD/SSD 觀察到的寫入數(shù)據(jù)多于上層程序?qū)懭氲臄?shù)據(jù)。
在 HDD 作為主流存儲的時代,RocksDB 的 compaction 帶來的寫放大問題并沒有非常明顯。這是因為:
- HDD 順序讀寫性能遠(yuǎn)遠(yuǎn)優(yōu)于隨機讀寫性能,足以抵消寫放大帶來的開銷。
- HDD 的寫入量基本不影響其使用壽命。
現(xiàn)在 SSD 逐漸成為主流存儲,compaction 帶來的寫放大問題顯得越來越嚴(yán)重:
- SSD 順序讀寫性能比隨機讀寫性能好一些,但是差距并沒有 HDD 那么大。所以,順序?qū)懴啾入S機寫帶來的好處,能不能抵消寫放大帶來的開銷,這是個問題。
- SSD 的使用壽命和其寫入量有關(guān),寫放大太嚴(yán)重會大大縮短 SSD 的使用壽命。因為 SSD 不支持覆蓋寫,必須先擦除(erase)再寫入。而每個 SSD block(block 是 SSD 擦除操作的基本單位) 的平均擦除次數(shù)是有限的。
所以,在 SSD 上,LSM-Tree 的寫放大是一個非常值得關(guān)注的問題。而寫放大、讀放大、空間放大,三者就像 CAP 定理一樣,需要做好權(quán)衡和取舍。
RockDB 寫放大簡單分析
說明:RocksDB 支持多種 Compaction。下面分析的是 Level Style Compaction。
RocksDB 的寫放大分析:
- +1 - redo log 的寫入
- +1 - Immutable Memtable 寫入到 L0 文件
- +2 - L0 和 L1 compaction(L0 SST 文件的 key 范圍是重疊的,出于性能考慮,一般盡量保持 L0 和 L1 的數(shù)據(jù)大小是一樣的,每次拿全量 L0 的數(shù)據(jù)和全量 L1 的數(shù)據(jù)進行 compaction)
- +11 - Ln-1 和 Ln 合并的寫入(n >= 2,默認(rèn)情況下,Ln 的數(shù)據(jù)大小是 Ln-1 的 10 倍,見 max_bytes_for_level_multiplier )。
所以,總的寫放大是 4 + 11 * (n-1) = 11 * n - 7 倍。關(guān)鍵是 n 的取值。
假設(shè) max_bytes_for_level_multiplier 取默認(rèn)值 10,則 n 的取值受 L1 的大小和 LSM-Tree 的大小影響。
L1 的大小由 max_bytes_for_level_base 決定,默認(rèn)是 256 MB。
默認(rèn)情況下 L0 的大小和 L1 一樣大,也是 256 MB。不過 L0 比較特殊,當(dāng) L0 的 SST 文件數(shù)量達到 level0_file_num_compaction_trigger 時,觸發(fā) L0 -> L1 的 comapction。所以 L0 的最大大小為 write_buffer_size * min_write_buffer_number_to_merge * level0_file_num_compaction_trigger。
- write_buffer_size 默認(rèn) 64 MB。
- min_write_buffer_number_to_merge 默認(rèn) 1。
-
level0_file_num_compaction_trigger 默認(rèn) 4。
所以 L0 默認(rèn)最大為 64 MB * 1 * 4 = 256 MB。
因此,RocksDB 每一層的默認(rèn)大小為 :
L0 - 256 MB
L1 - 256 MB
L2 - 2.5 GB
L3 - 25 GB
L4 - 250 GB
L5 - 2500 GB
用戶可以根據(jù)自己的場景需求調(diào)整上面的各個參數(shù)。