rocksdb學習筆記


rocksdb是在leveldb的基礎上優化而得,解決了leveldb的一些問題。

主要的優化點

1.增加了column family,這樣有利於多個不相關的數據集存儲在同一個db中,因為不同column family的數據是存儲在不同的sst和memtable中,所以一定程度上起到了隔離的作用。

2.采用了多線程同時進行compaction的方法,優化了compact的速度。

3.增加了merge operator,優化了modify的效率

4.將flush和compaction分開不同的線程池,能有效的加快flush,防止stall。

5.增加了對write ahead log(WAL)的特殊管理機制,這樣就能方便管理WAL文件,因為WAL是binlog文件。

 

下面從幾個小點來一窺rocksdb的微妙之處。

rocksdb的column family

column family的具體使用場景,官方並沒有給出非常好的說明,rocksdb支持跨column family的原子寫操作,說明官方認為column family的數據之間還是有一定的關系的,而column family的數據文件是分割開的,包括sst文件和memtable都是不會共用的,所以官方還是希望不同column family的數據進行隔離。所以我覺着一個應用場景就是不同的數據混跑在同一個db上,這個僅是個人感覺,歡迎大家討論。那么不同column family之間會怎樣互相干涉呢?

Q:多column family的flush是怎么進行線程分布的?

A:可以共用一個線程池,所以寫的多的會多占用線程。也可以分開配置線程池(指定不同的env),這樣就會不互相干擾。

 

Q:多column family的內存是怎么分配的?

A:內存主要有兩方面,第一方面是write buffer,第二方面是block cache。

write buffer是每個column family單獨享有的,block cache可以配置成column family級別,也可以配置成整個db的column family共享一個block cache,也可以配置成多個db共享一個block cache。

rocksdb的文件類型

主要有以下幾種類型sst文件,CURRENT文件,manifest文件,log文件,LOG文件和LOCK文件

sst文件存儲的是落地的數據,CURRENT文件存儲的是當前最新的是哪個manifest文件,manifest文件存儲的是Version的變化,log文件是rocksdb的write ahead log,就是在寫db之前寫的數據日志文件,LOG文件是一些日志信息,是供調試用的,LOCK是打開db鎖,只允許同時有一個進程打開db。

ColumnFamilyOptions

  這些option都是column family相關的,可以對不同的column family賦不同的值。

  inplace_update_support: 字面含義是是否支持在原位置更新,如果支持的話,那么原來的數據就被擦除了,所以snapshot和iterator保留當時的數據的邏輯就沒法實現了

  num_levels: 記錄的是version的level的數目,默認是7,即0~6

  target_file_size_base: level1的sst文件的大小,默認為2MB

  target_file_size_multiplier: level1以上的sst文件大小,乘數因子默認是1,即所有level的文件大小都是2MB

    level0的文件大小是由write_buffer_size決定的,level1的文件大小是由target_file_size_base決定的,level2及以上,size = target_file_size_base * (target_file_size_multiplier ^ (L - 1))

  max_bytes_for_level_base: level1的sst總的文件總和大小,默認是10MB

  max_bytes_for_level_multiplier: level2及以上的level的sst文件總和大小的乘數因子,默認是10,

    level0的sst文件總和大小是level0_stop_writes_trigger * write_buffer_size,因為level0的文件數目達到level0_stop_writes_trigger時候就會停止write。

    level1及以上的文件總和大小是max_bytes_for_level_base * (max_bytes_for_level_multiplier ^ (L - 1)),默認的level0是4MB * 24 = 96MB,level1是10MB,level2是100MB,level3是1G,level4是10G。。。

 

rocksdb原子操作的實現

  rocksdb的一個WriteBatch是原子操作,要么全部成功,要么全部失敗,具體的實現原理是在整個log的寫的過程中只會調用Write操作,最后會調用一次flush,所以如果中間發生機器crash,所有的都會失敗,否則所有的都會成功。

 

rocksdb寫和讀放大

  rocksdb的寫會寫WAL(Write Ahead Log),如果sync的話,會寫一次磁盤,然后會寫memtable

  寫rocksdb的時候有可能會卡住,詳見下面的rocksdb的寫stall

 

  rocksdb的讀,會首先讀memtable,如果memtable沒有找到的話,會讀下面level的數據,由於level0的多個sst會有交疊,所以每個sst都會通過filemeta判斷在不在最小和最大的范圍內,如果在就需要讀這個sst的文件內容,來查看,其他level的sst文件不會有數據交疊的情況,所以只會有一個文件可能含有這個數據。

  可以看出來讀放大還是比較嚴重的。rocksdb為了減少讀放大,增加了cache.

  讀cache

    rocksdb的讀cache分為兩部分:table cache和block cache。這兩個都是LRUCache

    block cache存儲的block,包括index block和filter block(通過options可以配置)

    table cache存儲的是table,是整個文件的meta信息和Foot信息。table_cache_size的消耗內存的大小是有Options里面的max_open_files決定的。

  bloomfilter:

    bloomfilter的增加並不能減少寫放大,因為bloomfilter是table范圍的或者block范圍的,而且bloomfilter是存儲在文件中的,那么必須把這些從文件里面讀出來后才能起到作用。

 

rocksdb的版本管理

rocksdb的版本相關的數據結構有Version、VersionStorageInfo、VersionBuilder、VersionEdit、SuperVersion和VersionSet。

VersionEdit描述的是版本的變更,其主要操作為AddFile和DeleteFile,分別表示,在某個level上增加文件和刪除文件,都是版本變更的操作。

VersionBuilder是生成Version的工具,所以其有兩個主要函數:

void Apply(VersionEdit* edit);
void SaveTo(VersionStorageInfo* vstorage);

分別是應用某個VersionEdit和將現在的版本Saveto某個VersionStorageInfo

VersionStorageInfo是Version的信息的存儲結構,每一個Version的sst文件信息都保存在VersionStorageInfo。

Version是一個完整的版本。sst文件信息存儲在VersionStorageInfo。可以在這個版本上Get數據。

SuperVersion是db的一個完整的版本包含的所有信息,包含當前的Memtable,imm和一個Version。也就是Version包含的是sst數據信息,SuperVersion包含的是Version的數據和memtable中的數據。

VersionSet是整個db的版本管理,並維護着manifest文件。每個column family的版本單獨管理,在ColumnFamilyData這個數據結構里面有current Version。

 

rocksdb的Flush

Flush是指將memtable的數據導入到sst中,變成持久化存儲,就不怕數據丟失了。

1.首先在memtable的add的時候,會檢測是否memtable的大小達到了max write buffer,如果是就將should_flush_置為true,並會在WriteBatch的Handler里面調用CheckMemtableFull,將當前column family加入flush_scheduler。

2.在Write的時候,調用ScheduleFlushes,將需要flush的column family的memtable切換一個新的,同時將原來的memtable加入cfd的imm中,如果這個column family data的imm數量大於min_write_buffer_number_to_merge,並啟動一個新的線程調用BGWorkFlush

由於真正的Flush過程是在另一個線程完成的,所以這個地方並不會block寫過程

 

另外,如果total_log_size大於max_wal_log_size並且不是只有一個column family,也會觸發flush,因為flush能將memtable持久化到磁盤上,同時對應的wal就可以刪除了

rocksdb的compaction

rocksdb的compaction的觸發條件有兩類:

第一類是某一個level的數據太多

  1. VersionStorageInfo的compaction_score_的計算方法是level0的是當前文件數目/level0_file_num_compaction_trigger,其他層是該層當前文件大小總和/該層的配置的允許文件總和最大值
  2. 基於level的存儲的compaction總的來說,就是一次挑選某一個level的一個文件,然后將該文件和高level的多個相交文件merge,最后生成多個高level的文件。具體的細節是:每次會挑選compaction score最高的一個level,並在這個level中找到一個文件大小最大,並且上一個level的相交文件沒有在compaction的一個文件

第二類是seek太多

疑問:

如果option里面soft_rate_limit設置的為0.0和1之間,compaction並不會觸發,但是會觸發write delay,這是為什么?

rocksdb的寫stall

 在DBImpl也就是db的實例里面有一個WriteController,同時在ColumnFamilySet里面也有這個WriteController的指針,這個數據結構會控制db的寫stall行為。

在ColumnFamily進行SuperVersion變更的時候(增加新memtable,flush增加sst,compaction)都會查看需不需要stall Write,stall的條件是:

1)imm的數量大於等於option允許的最大數目

2)level0的文件的數量大於option允許的數目

如果沒有滿足上面兩項,但是compaction score比較大會delay寫

rocksdb的write ahead log(WAL)

  1. 每次寫操作,rocksdb會先寫write ahead log,然后才會寫db
  2. write ahead log可以配置到單獨的空間,並且可以配置WAL文件的單獨的刪除機制。這種原因是為了保存WAL文件,達到特殊的目的,比如,其他sst文件放在不可靠存儲里面,而WAL放到可靠存儲里面。

rocksdb的缺點

1.column family之間的隔離做的不是非常好,因為一個db只有一個WriteController,那么一旦一個db中的一個column family發生了阻塞,比如寫太快,那么就會阻塞其他的column family的寫。

2.多進程compaction和flush的效果我持懷疑態度,因為這兩個主要是磁盤操作,多進程並不會有很好的效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM