概述 compaction主要包括兩類:將記憶體中imutable 轉儲到磁碟上sst的過程稱之為flush或者minor compaction;磁碟上的sst文件從低層向高層轉儲的過程稱之為compaction或者是major compaction。對於myrocks來說,compaction過程都 ...
概述
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。
圖1
flush(minor-compaction)
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)
5.至此若幹個block已經寫入文件,併為每個block生成了indexblock記錄
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/
http://www.cnblogs.com/KevinT/p/3819134.html
http://luodw.cc/2015/11/04/leveldb-20/
https://askdba.alibaba-inc.com/libary/control/getArticle.do?articleId=72393