對LevelDB的“升級版”存儲引擎RocksDB的調研成果


Google的leveldb是個非常優秀的存儲引擎。但還是有一些不盡人意的地方,比方leveldb不支持多線程合並。對key范圍查找的支持還非常easy,未做優化措施,等等。而Facebook的RocksDB是個更彪悍的引擎。實際上是在LevelDB之上做的改進。在使用方法上與LevelDB非常的相似,兩者的對照能夠參考以下的參考資料1。

這里之所以要調研rocksdb是由於rocksdb中增加了prefix bloomfilter的實現,可以支持對范圍查找的優化。對我眼下的項目非常有參考意義,以下是我調研和剖析rocksdb部分源代碼總結出的部分結果。


1. 對RocksDB中與Bloomfilter相關的調研結果

這一步主要參考rocksdb的官方博客和相關討論,總結得到下面信息:

(1)rocksdb支持在key的sub-part上設置Bloomfilter,這使得范圍查詢成為可能。

(2)將key分為prefix和suffix,配置了一個prefix_extractor 來指定key-prefix。並用此存儲每一個key-prefix的blooms,然后用指定了prefix的iterator來使用這些bloom bits避免查詢那些不包括所指定prefix的keys,從而實現了prefix過濾。

(3)Rocksdb實現了兩個Bloomfilter,一個是在讀block之前使用Bloomfilter過濾不包括key的blocks(與leveldb同樣),還有一個是在查詢memtable時動態生成一個bloomfilter實現內存中的key過濾(在block read之前)。

 

上面這些信息源主要來自下面幾個參考資料:


2. rocksdb中Get接口實現優化(與leveldb對照)

 以下簡單總結下rocksdb中Get接口實現過程中的一些優化技術,整體實現流程與leveldb一致,都是memtable —>immemtable—>sstable的過程。但實現細節有所不同,主要有以下幾點不同:

(1)memtable/ immemtable的Get實現(memtable.cc::Get)

Rocksdb在這個過程中增加了Bloomfilter機制,例如以下:

if (prefix_bloom_&&

     !prefix_bloom_->MayContain(prefix_extractor_->Transform(user_key))){

    // iter is null if prefix bloom says thekey does not exist

} else {

   // 查詢memtable

}

這個Bloomfilter是動態生成的(沒有持久化)且是prefix bloom。依據prefix進行過濾。


(2)sstable中的Get實現:level —>file -> block逐層搜索

a. 在level 0層,在找files之前加了預讀取功能(prefetch bloom filter data blockfor L0 files)

// Prefetch table data to avoidcache miss if possible

    if (level == 0) {

      for (int i = 0; i < num_files; ++i) {

        auto* r =files_[0][i]->fd.table_reader;

        if (r) {

          r->Prepare(ikey);

        }

      }

    }

採用的是prefix hashing技術(參考資料2)。

b.然后在各層找到可能的files(查找方式與leveldb同),並對files進行key range filteringfractional cascading技術優化level上的文件查找,但要滿足兩個條件:一是不僅僅有一個L0層。二是L0層必須有3個文件以上。即假設L0層少於3個文件。就不做key range filtering。由於這樣的情況下系統每次查詢的table數目已經非常少了,所以這時候key range filtering非常可能反而沒有直接查詢files高效。

key range filtering非常easy,就是看key在不在file的[smallest_key,largest_key]之間,而fractional cascading技術簡單說是利用上層key range filtering的比較信息作為下一層key range filtering的參考,以降低比較的次數,使得更快定位下一層的files,詳細看參考資料3。定位到file后。就要進行block的查詢了。rocksdb中(block_based_table_reader.cc)的block查找使用的Bloomfilter機制與leveldb一樣。

 

除此之外,rocksdb還有非常多與leveldb不一樣的地方。比方rocksdb中memtable的數據結構除了skiplist實現外還有linked list的實現,sstable的實現除了block table之外還有plain table;RocksDB支持多線程合並,支持在單個進程中啟用多個實例,除了主要的Put/Get/Delete接口外還添加了個Merger接口,等等……


3. rocksdb中prefix Bloomfilter的實現細節

研究rocksdb的源代碼后,以我自己理解的角度總結rocksdb實現prefixbloom的大致方法例如以下:

(1)首先rocksdb中持久化數據的存儲格式有兩種:BlockBasedTable格式和PlainTable格式。當中BlockBasedTable格式衍生自新版leveldb中的BlockTable格式,整體格式全然沒變。例如以下所看到的:

<beginning_of_file>

[datablock 1]

[datablock 2]

...

[datablock N]

[metablock 1: filter block]          

[metablock 2: stats block]           

...

[metablock K: future extended block] 

[metaindexblock]

[indexblock]

[Footer]                              

<end_of_file>

可是在實現上與leveldb有有所不同。比方紅色標出的filter block部分,leveldb的filter block部分能夠存儲全部key的bloomfilter。而rocksdb的filter block部分不僅能夠存儲全部key的bloomfilter。還能夠存儲全部key的prefix的bloomfilter。通過兩個參數whole_key_filtering_和prefix_extractor_來控制。當中whole_key_filtering_控制是否存儲整個key的bloomfilter,而prefix_extractor_控制是否存儲prefix的bloomfilter。

假設想要存儲prefixbloomfilter。就須要事先將prefix長度信息存入prefix_extractor_中,以便filterblock building過程中能依據長度信息抽取出key的prefix然后生成prefixbloomfilter,並有個PrefixMayMatch()函數用來過濾prefix(leveldb中僅僅有KeyMayMatch())。

注:除了filter block實現不同之外。以下的iindexblock實現也不同,rocksdb中增加了prefixindex block的實現。prefixindex block會為datablock中每一個key的prefix部分保存一條索引記錄,以方便通過prefix進行查找。

(2)在filter block building完畢后就能夠進行prefix scan了,例如以下:

    autoiter = DB::NewIterator(ReadOptions());
    for (iter.Seek(prefix); iter.Valid()&& iter.key().startswith(prefix); iter.Next()) {
       //do something
    }

詳細實現通過封裝的iter內部的多個不同類型Iterator的Seek方法,當中使用到prefixbloomfilter的Iterator是sstable的TwoLevelIterator(即過濾的是磁盤IO),Two_level_iterator中的Seek方法在讀磁盤IO之前先進行了一次prefixfilter。例如以下(two_level_iterator.cc:: Seek):

 if (state_->check_prefix_may_match &&
     !state_->PrefixMayMatch(target)) {
   SetSecondLevelIterator(nullptr);
    return;
  }

這里PrefixMayMatch函數的詳細實現分為下面幾個步驟(block_based_table_reader.cc:: PrefixMayMatch):

a. 首先依據prefix_extractor信息抽取出key的prefix部分

b. 然后構造prefix的Index Iterator以依據索引信息查找該prefix是否可能在這個file里(此時還沒開始真正的block讀,即此時沒有磁盤IO操作)

c. 假設不可能在file里則返回false。假設有可能在,則進一步檢查下當前Iterator所指向的完整key的prefix是否是要查找的prefix(由於index僅僅能確定范圍,不能精確確定prefix一定存在),若是則返回true。否則就獲取filterblock里的bloomfilter,通過prefixbloomfilter的PrefixMayMatch進行過濾,假設過濾不了才開始真正的block磁盤查找。


上面的流程簡單講述了怎樣實現prefix scan,以下舉個簡單的樣例(來自db_test.cc):

使用以下的幾組prefixranges 生成11個sst文件:

GROUP 0:[0,10]                             (level 1)

GROUP 1:[1,2], [2,3], [3,4], [4,5], [5, 6] (level 0)

GROUP 2:[0,6], [0,7], [0,8], [0,9], [0,10] (level 0)

這11個prefix ranges相應的key ranges分別為:

GROUP 0: [00______:start, 10______:end]

GROUP 1: [01______:start, 02______:end], [02______:start, 03______:end],

         [03______:start, 04______:end], [04______:start, 05______:end],

         [05______:start,06______:end]

GROUP 2: [00______:start, 06______:end], [00______:start,07______:end],

         [00______:start,08______:end], [00______:start, 09______:end],

         [00______:start,10______:end]

當中prefix長度為8,此時假設要通過prefix“03______:”查找 這11個sst文件,先前的API(比方leveldb中)須要11次隨機IO才干找到。而用rocksdb中新的API及prefixfilter選項的啟用,我們僅僅須要2次隨機IO就可以,由於僅僅有兩個文件包括該prefix。


4.  RocksDB中關於get_range接口

 rocksdb中盡管實現了prefix Bloomfilter,可是並未提供get_range接口。官方文檔中說支持Bloomfilter范圍查詢指的應該是rocksdb已經實現了prefix Bloomfilter,那么用戶能夠利用這個實現范圍查找的過濾機制,但接口須要用戶自己實現。RocksDB對原來LevelDB中sst文件預留下來的MetaBlock進行了詳細利用,當中Prefixes信息存在metablock里(Block_based_table_builder.cc)。

因此我們能夠借鑒prefixBloomfilter的原理實現我們自己的范圍Bloomfilter。


5. leveldb中范圍Bloomfilter實現的初步思路

首先get_range對外的接口是這樣:

int get_range(int area, const data_entry &pkey, const data_entry &start_key,   

const data_entry &end_key, int offset, int limit, vector<data_entry*> 

&values,short type=CMD_RANGE_ALL);

當中pkey就是prefix key。因此我們依據對pkey實現bloomfilter來實現范圍bloomfilter的過濾。

基本實現思路例如以下:

(1)對data block里的每一個key抽取出合適的prefix

(2)對prefix key實現bloomfilter(與key實現一樣),並加入到filter block里,這里能夠與整個key的bloomfilter放在一起。也能夠分開放,通過index block控制索引

(3)在get_range實現過程中,首先獲取prefix bloomfilter,然后對pkey進行prefixfilter,過濾掉prefix不匹配的file或block。這樣就實現了范圍bloomfilter。


6. 參考資料

1. RocksDB介紹:一個比LevelDB更彪悍的引擎

2.Prefix hashing in RocksDB -Speeding up queries for special workloads

3.使用fractional cascading優化level上的文件查找

4.TheStory of RocksDB


免責聲明!

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



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