之前再看RocksDB Version管理部分代碼,查找網上資料發現關於RocksDB Version管理的內容大部分就是對LevelDB RocksDB version管理的摘抄。對於VersionEdit、VersionBuilder兩者基本類似,但是RocksDB中由於新增ColumnFamily的概念,導致兩者在Version管理中存在些許的差別,而網上資料對於Version管理中ColumnFamily的部分介紹較少,結合之前代碼時的記錄,將自己的總結分享出來。
Version管理用於維護當前RocksDB中文件列表,包括磁盤中SSTable文件和內存中的Memtable和Immutable Memtable文件。RocksDB中有Column Family的概念,可以將一個Column Family理解為獨立的LSM-Tree。Column Family會維護自己的文件列表,內存中MemTable、Immutable Memtable和磁盤中按層排列的SSTable。不同的Column Family之間不共享Memtable和SSTable。每個Column Family的數據信息由ColumnFamilyData維護。RocksDB中可能會存在多個Column Family,多個Column Family由ColumnFamilySet管理。
每個Column Family中隨着外部的持續寫入,不斷發生flush和compaction操作。每次flush和compaction都會導致磁盤SSTable文件的變化,RocksDB通過Version來記錄不同時刻的磁盤SSTable文件狀態。多個Version以雙向鏈表的方式組織。具體的Version管理示意圖如圖1所示:
圖1 RocksDB Version管理示意圖
RocksDB中通過VersionSet進行Version管理。VersionSet中以雙向循環鏈表的方式組織所有ColumnFamilyData。如果單純以循環雙向鏈表的方式維護所有的Column Family,查找時的時間復雜度為O(N)。為了加快查找速度,ColumnFamilySet中保存兩個unordered_map,std::unordered_map<std::string, uint32_t> column_families_; std::unordered_map<uint32_t, ColumnFamilyData*> column_family_data_;第一個保存column family的name到唯一標識uint32_t(每個Column Family有一個唯一的標識)的映射,第二個保存uint32_t到具體ColumnFamilyData*的映射。可以根據Column Family name在O(1)時間內找出對應的ColumnFamilyData。
每個Column Family中的數據包括內存和磁盤的數據。ColumnFamilyData中包含指向Version head(dummy_version_)和最新Version(current_)的指針。SuperVersion用於記錄Column Family相關的所有文件,內存中的Memtable、Immutable Memtable(以Vector數組的形式組織),和磁盤中的SSTable。磁盤中的SSTable用Version維護,所有Version按照產生的先后順序插入到Version雙向鏈表中,最先的位於最前,通過current_指向最新的Version。Version和SSTable都是以引用計數的方式維護。下面的內容是RocksDB如何維護live SSTable的方式,可以概念上大體體會Version管理的內容。
RocksDB除了WAL日志文件外,還會包含文件系統中的SSTable文件列表。每次compaction之后,compaction會生成新的輸出文件添加到列表中,同時也會從列表中刪除輸入文件。需要注意的是,輸入文件並不能立即刪除,因為此時可能一些Get操作或者外部的迭代器正在使用該文件,需要保存文件至這些操作的完成或者迭代器的釋放。
Version用於管理LSM-Tree中的文件列表。在每次compaction結束或者memtable被flush到硬盤,新的version被創建用來記錄發生的變化。隨着不斷的寫入、compaction的進行,RocksDB中會存在多個version,但在任何時刻只有一個當前的version版本,即current version。新的Get查詢操作或者迭代器在其整個查詢過程和迭代器的生命周期內都會使用current version。“過時”(沒有被任何Get或者iterator迭代器使用)的version需要清除。被任何version都未使用的文件會被刪除。具體的示例如下:
最初開始時version v1有三個文件:
v1={f1, f2, f3} (current)
files on the disk: f1, f2, f3
然后在v1上創建了一個iterator迭代器
v1={f1, f2, f3} (current, used by iterator1)
files on the disk: f1, f2, f3
然后內存中memtable flush,增加了一個文件f4,一個新的version被創建:
V2={f1, f2, f3, f4} (current)
V1={f1, f2, f3} (used by iterator1)
Files on the disk: f1, f2, f3, f4
然后發生了一次compaction,f2, f3和f4合並為一個新的文件f5,新的version被創建:
V3={f1, f5} (current)
V2={f1, f2, f3, f4}
V1={f1, f2, f3} (used by iterator1)
Files on the disk: f1, f2, f3, f4, f5
此時,v2既不是最新的version也沒有被任何iterator或者get使用,所以v2可以被刪除,f4文件也會被刪除(沒有任何version引用)。但v1不能被刪除,因為v1仍然被iterator1使用。
V3={f1, f5} (current)
V1={f1, f2, f3} (used by iterator1)
Files on the disk: f1, f2, f3, f5
現在,如果iterator1被銷毀:
V3={f1, f5}(current)
V1={f1, f2, f3}
Files on the disk: f1, f2, f3, f5
此時,v1既沒有被引用也不是最新的,可以被刪除,同時文件f2, f3也會被刪除:
V3={f1, f5} (current)
Files on the disk: f1, f5
實現的上述操作的方式就是引用計數。SSTable文件和version都有一個reference count。當創建一個version時,該version使用的所有SSTable文件的reference count加一。同理,刪除一個version時,該version使用的所有SSTable文件的reference count減一。如果文件的reference count變為0,則該文件可以被刪除。
類似的,每個version也都有一個reference count。創建version時,它是最新的版本,因此它的reference count為1。如果該version不是最新的version,則其reference count減一。當該version被get或者iterator使用時,加一;get或者iterator完成時,減一。當version的reference count為0時,該version可以被刪除。
RocksDB Version管理部分由Version/VersionEdit/VersionSet完成,Version管理的示例圖如圖1所示。VersionEdit用於記錄兩個相鄰Version之間的差異,比如新增的文件、刪除的文件等等。對Version N施加VersionEdit可以得到Version N+1,如圖2所示。、
圖2 VersionEdit與Version關系示意圖
為了避免進程崩潰或機器宕機導致的數據丟失,RocksDB需要將元數據信息持久化到磁盤,對應的文件就是Manifest,每當有新的Version產生時都需要更新Manifest。新增數據正好對應VersionEdit內容,也就是說Manifest文件記錄的是一組VersionEdit值。圖3最上面的流程顯示了恢復元信息的過程,即一次次應用VersionEdit的過程,這個過程會產生大量的臨時Version,過於耗費資源。RocksDB引入VersionBuilder來避免產生中間Version變量,具體方式是先將所有的VersionEdit內容整理到VersionBuilder中,然后一次應用產生最終的Version。
圖3 RocksDB VersionBuilder示意圖
上述就是RocksDB Version管理的大致內容,清楚了實現思路之后,之后對具體每部分代碼進行分析時就會輕松很多。
參考資料: