概述html
compaction主要包括兩類:將內存中imutable 轉儲到磁盤上sst的過程稱之爲flush或者minor compaction;磁盤上的sst文件從低層向高層轉儲的過程稱之爲compaction或者是major compaction。對於myrocks來講,compaction過程都由後臺線程觸發,對於minor compaction和major compaction分別對應一組線程,經過參數rocksdb_max_background_flushes和rocksdb_max_background_compactions能夠來控制。經過minor compaction,內存中的數據不斷地寫入的磁盤,保證有足夠的內存來應對新的寫入;而經過major compaction,多層之間的SST文件的重複數據和無用的數據能夠迅速減小,進而減小sst文件佔用的磁盤空間。對於讀而言,因爲須要訪問的sst文件變少了,也會有性能的提高。因爲compaction過程在後臺不斷地作,單位時間內compaction的內容很少,不會影響總體的性能,固然這個能夠根據實際的場景對參數進行調整,compaction的總體架構能夠參見圖1。瞭解了compaction的基本概念,下面會詳細介紹compaction的流程,主要包括兩部分flush(minor compaction),compaction(major compaction),對應的入口函數分別是BackgroundFlush和BackgroundCompaction。linux
圖1git
flush(minor-compaction)github
Rockdb中在內存的數據都是經過memtable存儲,主要包括兩種形式,active-memtable和immutable-memtable。active-memtable是當前正在提供寫操做的memtable,當active-memtable寫入超過閥值(經過參數wirte_buffer_size控制),會將這個memtable標記爲read-only,而後再建立一個新的memtable供新的寫入,這個read-only的memtable就是immutable-memtable。咱們所說的flush操做就是將imumutable-memtable 寫入到level0的過程。flush過程以column family爲單位進行,一個column family是一組sst文件的集合,在myrocks中一個表能夠是一個單獨的column family,也能夠多個表共用一個column family。每一個column family中可能包含一個或多個immutable-memtable,一個flush線程會抓取column family中全部的immutable-memtable進行merge,而後flush到level0。因爲一個線程在flush過程當中,新的寫入也源源不斷進來,進而產生新的immutable-memtable,其它flush線程能夠新起一個任務進行flush,所以在rocksdb體系下,active-memtable->immutable-memtable->sst文件轉換過程是流水做業,而且flush能夠併發執行,相對於levelDB,併發compaction的速度要快不少。經過參數max_write_buffer_number能夠控制memtable的總數量,若是寫入很是快,而compaction很慢,會致使memtable數量超過閥值,致使write stall的嚴重後果。另一個參數是min_write_buffer_number_to_merge,整個參數是控制至少幾個immutable纔會觸發flush,默認是1。flush的基本流程以下:算法
1.遍歷immutable-list,若是沒有其它線程flush,則加入隊列數據結構
2.經過迭代器逐一掃描key-value,將key-value寫入到data-block 架構
3.若是data block大小已經超過block_size(好比16k),或者已經key-value對是最後的一對,則觸發一次block-flush併發
4.根據壓縮算法對block進行壓縮,並生成對應的index block記錄(begin_key, last_key, offset)app
5.至此若干個block已經寫入文件,併爲每一個block生成了indexblock記錄ide
6.寫入index block,meta block,metaindex block以及footer信息到文件尾
7.將變化sst文件的元信息寫入manifest文件
flush實質是對memtable中的記錄進行一次有序遍歷,在這個過程當中會去掉一些冗餘的記錄,而後以block爲單位寫入sst文件,寫入文件時根據壓縮策略肯定是否對block進行壓縮。爲何會有冗餘記錄?這個主要是由於rocksdb中不管是insert,update仍是delete,全部的寫入操做都是以append的方式寫入memtable,好比前後對key=1的記錄執行三個操做insert(1),update(1),delete(1),在rocksdb中會產生3條不一樣記錄。(在innodb中,對於同一個key的操做都是原地更新,只有一條記錄)。實際上delete後這個記錄不該該存在了,因此在合併時,能夠幹掉這些冗餘的記錄,好比這裏的insert(1),update(1),這種合併使得flush到level0的sst已經比較緊湊。冗餘記錄主要有如下三種狀況:(user_key, op)表示對user_key的操做,好比put,delete等。
1.對於(user_key,put),(user_key,delete),則能夠將put刪掉
2.對於(user_key,single-delete),(user_key,put),single-delete保證put,delete成對出現,能夠同時將兩條記錄都刪掉。
3.對於(user_key,put1),(user_key,put2),(user_key,put3)能夠幹掉比較老的put
對於以上3種狀況,都要考慮snapshot,若是要刪除的key在某個snapshot可見,則不能刪除。注意第1種狀況,(user_key,delete)這條記錄是不能被刪除的,由於對用戶而言,這條記錄已經不存在了,但因爲rocksdb的LSM-tree存儲結構,這個user_key的記錄可能在level0,level1或者levelN,因此(user_key, delete)這條記錄要保留,直到進行最後一層的compaction操做時才能將它幹掉。第2種狀況,single-delete是一個特殊的delete操做,這個操做保證了put,delete必定是成對出現的,因此flush時,能夠將這兩條記錄同時幹掉。
compaction(major-compaction)
咱們一般所說的compaction就是major-compaction,sst文件從低level合併到高level的過程,這個過程與flush過程相似,也是經過迭代器將多個sst文件的key進行merge,遍歷key而後建立sst文件。flush的觸發條件是immutable memtable的數量是否超過了min_write_buffer_number_to_merge,而compaction的觸發條件是兩類:文件個數和文件大小。對於level0,觸發條件是sst文件個數,經過參數level0_file_num_compaction_trigger控制,score經過sst文件數目與level0_file_num_compaction_trigger的比值獲得。level1-levelN觸發條件是sst文件的大小,經過參數max_bytes_for_level_base和max_bytes_for_level_multiplier來控制每一層最大的容量,score是本層當前的總容量與能存放的最大容量的比值。rocksdb中經過一個任務隊列維護compaction任務流,經過判斷某個level是否知足compaction條件來加入隊列,而後從隊列中獲取任務來進行compact。compaction的主要流程以下:
1.首先找score最高的level,若是level的score>1,則選擇從這個level進行compaction
2.根據必定的策略,從level中選擇一個sst文件進行compact,對於level0,因爲sst文件之間(minkey,maxkey)有重疊,因此可能有多個。
3.從level中選出的文件,咱們能計算出(minkey,maxkey)
4.從level+1中選出與(minkey,maxkey)有重疊的sst文件
5.多個sst文件進行歸併排序,合併寫出到sst文件
6.根據壓縮策略,對寫出的sst文件進行壓縮
7.合併結束後,利用VersionEdit更新VersionSet,更新統計信息
上面的步驟基本介紹了compaction的流程,簡單來講就是選擇某個level的sst文件與level+1中存在重疊的sst文件進行合併,而後將合併後的文件寫入到level+1層的過程。經過判斷每一個level的score是否大於1,肯定level是否須要compact;對於level中sst文件的選擇,會有幾種策略,默認是選擇文件size較大,包含delete記錄較多的sst文件,這種文件儘快合併有利於縮小空間。關於選擇sst文件的策略能夠參考options.h中的CompactionPri的定義。每次會從level中選取一個sst文件與下層compact,但因爲level0中可能會有多個sst文件存在重疊的範圍,所以一次compaction可能有多個level0的sst文件參與。rocksdb後臺通常有多個線程執行compact任務,compaction線程不斷地從任務隊列中獲取任務,也會不斷地檢查每一個level是否須要compact,而後加入到隊列,所以總體來看,compact過程是併發的,但併發的基本原則是,多個併發任務不會有重疊的key。對於level0來講,因爲多個sst文件會存在重疊的key範圍,根據level0,level+1中參與compact的sst文件key範圍進行分區,劃分爲多個子任務進行compact,全部子任務併發執行,都執行完成後,整個compact過程結束。另外還有一個問題要說明的是,compact時並非都須要合併,若是level中的輸入sst文件與level+1中無重疊,則能夠直接將文件移到level+1中。
Universal Compaction
前面介紹的compaction類型是level compaction,在rocksdb中還有一類compaction,稱之爲Univeral Compaction。Univeral模式中,全部的sst文件均可能存在重疊的key範圍。對於R1,R2,R3,...,Rn,每一個R是一個sst文件,R1中包含了最新的數據,而Rn包含了最老的數據。合併的前提條件是sst文件數目大於level0_file_num_compaction_trigger,若是沒有達到這個閥值,則不會觸發合併。在知足前置條件的狀況下,按優先級順序觸發如下合併。
1.若是空間放大超過必定的比例,則全部sst進行一次compaction,所謂的full compaction,經過參數max_size_amplification_percent控制。
2.若是前size(R1)小於size(R2)在必定比例,默認1%,則與R1與R2一塊兒進行compaction,若是(R1+R2)*(100+ratio)%100<R3,則將R3也加入到compaction任務中,依次順序加入sst文件
3.若是第1和第2種狀況都沒有compaction,則強制選擇前N個文件進行合併。
相對於level compaction,Univeral compaction因爲每一次合併的文件較多,相對於level compaction的多層合併,寫放大較小,付出的代價是空間放大較大。除了前面介紹的level compaction和univeral compaction,rocksdb還支持一種FIFO的compaction。FIFO顧名思義就是先進先出,這種模式週期性地刪除舊數據。在FIFO模式下,全部文件都在level0,當sst文件總大小超過閥值max_table_files_size,則刪除最老的sst文件。整個compaction是LSM-tree數據結構的核心,也是rocksDB的核心,本文梳理了幾種compaction方式的基本流程,裏面還有不少的細節沒有涉及到,有興趣的同窗能夠在本文的基礎上仔細閱讀源碼,加深對compaction的理解。
附錄
相關文件:
rocksdb/db/flush_job.cc
include/rocksdb/universal_compaction.h
rocksdb/db/compaction_job.cc
db/compaction_picker.cc
rocksdb/table/block_based_table_builder.cc
相關接口:
FlushMemTableToOutputFile //flush memtable到level0
FlushJob::Run //flush memtable 任務
PickMemtablesToFlush //選擇能夠flush的immutable-memtable
WriteLevel0Table //刷sst文件到level0
BuildTable //實現建立sst文件
UniversalCompactionPicker::NeedsCompaction //是否須要compact
PickCompaction //須要進行compact的sst文件
PickCompactionUniversalReadAmp //選擇相鄰的sst文件進行合併
NeedsCompaction //判斷文件是否level是否須要compact
LevelCompactionPicker::PickCompaction // 獲取level中sst文件進行compact
LevelCompactionPicker::PickCompactionBySize
IsTrivialMove // 是否能夠移動更深的Level,沒有overlap的狀況下。
ShouldFormSubcompactions // 判斷是否能夠將compaction任務分片
CompactionJob::Prepare // 劃分子任務
CompactionJob::Run() // compaction的具體實現
BlockBasedTableBuilder::Finish //生成sst文件
參考文檔
http://rocksdb.org/blog/2016/01/29/compaction_pri.html
http://smalldatum.blogspot.com/2016/02/compaction-priority-in-rocksdb.html
http://rocksdb.org/blog/2016/01/29/compaction_pri.html
https://github.com/facebook/rocksdb/blob/v3.11/include/rocksdb/options.h#L366-L423
http://rocksdb.org/blog/2015/07/23/dynamic-level.html
https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide
http://alinuxer.sinaapp.com/?p=400
http://dirtysalt.github.io/leveldb.html
http://openinx.github.io/2014/08/17/leveldb-compaction/