Rocksdb 的 BlobDB key-value 分离存储插件
前言
還是回到傳統的 LSM-tree 中,我們key-value 寫入時以append形態存放到一個data-block中,多個data-block+metablock 之類的數據組織成一個sst。當我們讀數據以及compaction的時候讀到key 之后則很方便得讀取到對應的value,一次I/O能夠將key-value完全從磁盤讀上來。但這種存儲方式在大value場景下引入了非常多的讀寫放大,讀寫性能都會非常差。
所以Wisckey提出了key-value分離,且業界也有了一些不錯的實現,包括Tikv/Titan, DGraph/badger等應用在工業界的實現案例。
但是,這一些對于使用rocksdb作為存儲生態的用戶來說還是不夠方便,titan雖然是以rocksdb的插件形態存在的,但是很多rocksdb支持的功能還不夠完善(checkpoint/backup, Trasaction 等需求量比較大的場景),所以雖然基本功能實現了,但還是沒有辦法得到更加廣泛的應用。
這個時候rocksdb 社區的BlobDB經過長久的開發和完善,在舊版本的基礎上實現了一個全新的BlobDB版本。
支持的特性
文章標題說的是分離存儲插件,其實本身并不是插件的形態了, 這個新版本已經完全融入到rocksdb之中,支持的特性:
- rocksdb的基本操作接口不需要任何變動,
DB::Put,DB::Get等仍然繼續使用(Merge不支持),開啟k/v分離,只需要打開enable_blob_files=true以及設置min_blob_size就可以了(簡直不要太方便,當然,titan沒有做成這樣也可能是因為這樣與rocksdb代碼耦合太多了, 不方便跟進社區的版本)。 - Recovery 異常重放
- 壓縮
- atomic flush
- compaction filter( 支持 用戶選擇讀取value,有一些用戶使用compaction filter 只想要key的操作,這個時候不需要將value再回傳了 )
- Checkpoints
- 備份
- 事務
- 文件級別的checksum
- sst-filem-manager(后續支持ingest會比較方便)
當然還有一些特性暫時不支持:
- Merge 接口寫入大value (這個特性的優先級后續會比較高,主要update場景的需求量還是很大)
- EventListener
- Secondary Instance (離線讀取場景)
- ingest blob file(這個場景需求也會大一些,高效的離線導入)
GC 實現
這里簡單描述一下BlobDB的key-value分離存儲實現。
- 分離場景其實比較簡單,在Flush/Compaction過程中判斷是否開啟key/value分離, 且判讀寫入的value是否是超過設置的
min_blob_size,超過則通過blob_file_builder 將key-value寫入到.blob文件中且對應的key+key-index則仍然存放在sst中,否則key-value都存放在sst中。 - 重點是GC的實現,當然可以通過設置
enable_blob_garbage_collection控制是否開啟關閉GC。GC的調度不同于Titan的傳統GC,通過EventLister 在Compaction完成之后觸發,而是類似于Titan的level merge GC ,在Compaction過程中如果開啟了GC,會將大value讀出來,寫入到新的blobfile,舊的blobfile 則會后續通過后臺清理線程集中清理。
這種方式的GC實現 因為沒有辦法避免大value的讀取,當數據量足夠大的時候,compaction調度的GC引入的磁盤大量的讀寫導致的長尾也是無法接受的,所以社區也只能提供了可開啟關閉的GC的參數來交給用戶控制。
關于BlobDB中的option都是可以通過SetOptions來運行時動態變更。
性能測試
從測試結果來看,大value下的BlobDB和 TitanDB 讀寫性能接近,對于Rocksdb生態的用戶來說BlobDB 在功能上的優勢還是更受歡迎的。
測試bench mark:
測試版本:master分支
測試工具:db_bench
硬件:64core cpu + 512G mem + 3T NVMe-SSD
關閉key/value分離 隨機寫入性能:
numactl --cpubind=0 --membind=0 ./db_bench \--benchmarks=fillrandom,stats \--num=30000000 \--threads=16 \--writes=1000000 \--db=./db-test \--wal_dir=./db-test \--duration=120 \--key_size=16 \--value_size=8192 \-max_background_compactions=16 \-max_background_flushes=7 \-subcompactions=8 \-compression_type=none \-enable_pipelined_write=true \
下面是隨機寫入場景中的磁盤I/O情況,其中磁盤I/O達到1G及以上的時間底層都是有compaction調度的,帶著大value 進行compaction,整體的讀寫代價還是很大的,可以看到磁盤偶爾會有超過2G的帶寬占用,這對于只有152M的用戶寫入來說實在是太大的放大了。
(關于磁盤I/O沒有讀流量,是因為內存比較大,db數據量比較小,大多數的數據還都會被緩存在操作系統page-cache,所以compaction過程中的讀基本都會命中page-cache)
作為對比,開始測試BlobDB,使用如下benchmark
numactl --cpubind=0 --membind=0 ./db_bench \--benchmarks=fillrandom,stats \--num=30000000 \--threads=16 \--writes=1000000 \--db=./db-test \--wal_dir=./db-test \--duration=120 \--key_size=16 \--value_size=8192 \-max_background_compactions=16 \-max_background_flushes=7 \-subcompactions=8 \-compression_type=none \-enable_pipelined_write=true \-enable_blob_files=true \-min_blob_size=4096 \-enable_blob_garbage_collection=true
很明顯,吞吐相比于使用rocksdb來存放大value 提升了倍。
再看看磁盤I/O情況,因為我們也開啟了GC,所以這個過程中會有GC的調度。從磁盤帶寬來看,整體的吞吐還是比較均勻的,即使compaction + GC 一起存在,并不會有像未開啟key/value分離那樣的巨量I/O出現,因為不必要的key/value的value讀取并不會被調度起來。
關于讀性能的測試,大家可以變更benchmark為--benchmarks=readrandom,指定--use_existing_db=1和--use_existing_keys=1來保證key的100%命中就好。當然,blobdb的使用在一批熱點key的集中update場景下還是需要注意GC帶來的帶寬占用(熱點update會讓GC調度的頻率更高,重復寫入的key多,blob文件中失效的key也會很多),如果想要保持穩定的latency,軟件層的優化就是限速了,或者動態開啟關閉GC(業務低峰開啟GC,高峰關閉GC)。
總結
以上是生活随笔為你收集整理的Rocksdb 的 BlobDB key-value 分离存储插件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三个字的微信网名女
- 下一篇: 我爱爱爱爱你是哪首歌啊?