まず、役割
LevelDB SSTがそれぞれの層をどのファイルを知ることができますどのように、迅速にデータのSSTファイルを検索する方法に存在する、後で再起動する前の状態に復元された、およびので、これらの重要な問題は、メタ情報管理モジュールに依存する必要がありますか。情報と以下のように簡単に要約を維持する上での役割:
- 圧縮は、必要なときに圧縮プロセスが起動できるように、関連する情報が記録されます。
- メンテナンスSSTファイルのインデックス情報と全体LevelDBの読み取りのための階層情報は、圧縮データは、構造的なサポートを提供し、書きます。
- ライブラリ全体を作るか、機械のダウンタイムを再起動し、メタ情報のデータの永続性を担うプロセスから正しい状態に戻すことができます。
- LogNumber、配列を記録次状態情報SSTファイル番号。
- このような内部または外部のユーザーLeveldbバージョンのメタ情報を保持するようにして、スナップショットモードのファイルやデータを使用することができます。
第二に、データ構造
1.ファイルのメタデータ
注文したデータキーの値を保持し、多くのSSTable leveldbは、ありますが、異なるSSTableファイル間のキー値の間隔は、(レベル0を除く)重複していません。各ファイルを記述するFileMetaData .sst情報は、それが大幅に検索操作の効率を向上させる最大とキーSSTableの最小値、すなわち間隔キーの値を変更し、記録します。以下のデータ構造:
1 構造体FileMetaData { 2 INTの参考文献; // 参照の数 3。 int型 allowed_seeks; // 最大許容数検索 4。 uint64_tを数; // .sstファイル番号 5。 uint64_tをのFILE_SIZE; // ファイルサイズ(バイト単位) 。6 InternalKey最小; // 最小内部を提供する主要表 7。 InternalKey最大級; // 、内部キーテーブルで提供最大 8。 };
2.バージョン
バージョンleveldbはバージョンによって記録され、バージョンは実際にSSTableコレクションのシリーズです、SSTableファイルは別のレベルと異なるレベルに格納された複数のSSTableファイルを有することができます。バージョンFileMetaDataは、すべてのレベルのすべてのファイルを保存して、次と前のポインタによってリンクされたリストを双方向循環リストを形成し、一般的に現在のバージョンとして知られている最新バージョンの末尾の要素です。データ構造のバージョン:
1 クラスバージョン{ 2 プライベート: 3 VersionSet * vset_; // 所属このバージョンにVersionSet 。4 版* NEXT_; // NEXT_、PREVは、二重リンクリストを形成 5 版* prev_と、 6。 int型refs_と、 7。 8。 STD ::ベクトル<FileMetaData *> files_ [設定:: kNumLevels]; // FileMetaDataのすべてのレベルにあるすべてのファイルを記録した二次元配列、 9 10 // 下の取得FileMetaDataが一定回数を見つけたとき(ファイルのファイルを待っているのコンパクトを追求する統計の結果に基づいて、マージ操作を実行する必要があります) 11。 FileMetaData *の、file_to_compact_ れる12 INT file_to_compact_level_; // ファイルレベル属するマージする必要が 13である 14 ダブル compaction_score_; // スコア> = 1、また必要マージされる場合に 15 のint compaction_level_が; // レベル合わせする必要が 16 }
属し、NEXT_ prev_はfile_to_compact_、二環式およびチェーンを形成VersionSet版vset_点がマージ操作を実行するために待機しているファイルを検索するポイントの最大数に達している、files_は、二次元アレイ、files_ [レベル]は[I]これは、ファイルSSTable層のI番目のレベルを示します。
バージョンは、各レベルの現在のバージョンはSSTableファイルに保存され、同様にマージされたファイルとレベルを待ち、そしてファイルをマージする必要があるかどうかを決定するための機能を提供し、関連するレベルの情報です。
3. VersionEdit
VersionEditは、最新のバージョンを入手することができ、元のバージョンVersionEditのシリーズを追加することによって、バージョンの変化を示すために使用されます。
。1 クラスVersionEdit { 2 公共: 3。 無効 ADDFILE(int型 FILE_SIZE、uint64_tをレベル、uint64_tをファイル、constの InternalKey&smallestconst InternalKey&最大); 4。 無効 DeleteFileを(int型のレベル、uint64_tをファイル); 5。 プライベート: 6。 友達クラスVersionSet; 7。 // によって間違ったファイルの削除の繰り返しにつながらない非繰り返し、保証することができ、設定された変数を使用して、削除するファイルを記録するように設定 8 のtypedefのstd :: 設定を <はstd ::ペア< int型、uint64_tを>> DeletedFileSet、 9 10 std :: 文字列comparator_。 11 uint64_tをlog_number_。 12 uint64_tをのprev_log_number_。 13 uint64_tをのnext_file_number_。 14 SequenceNumberはのlast_sequence_。 15 ブールhas_comparator_。 16 ブールhas_log_number_。 17 ブールhas_prev_log_number_。 18 ブールhas_next_file_number_。 19 ブールhas_last_sequence_。 20 21 のstd ::ベクトル<スタンダード::ペア< 整数、InternalKey>> compact_pointers_。 22 DeletedFileSetのdeleted_files_。 23 std ::ベクトル<はstd ::ペア< int型、FileMetaData>> new_files_。 24 }
以上のことから、VersionEdit)がADDFILE()とDeleteFileを(によって達成、現在のバージョン2つの配列new_files_とdeleted_files_用のファイルを追加したり削除したりする2つの操作を保持します。
その後VersionSet :: LogAndApply(VersionEdit *編集)VersionEditを達成するためには、新しいバージョンを生成するために、バージョンに適用されます。
4. VersionSet
第三に、達成するために
LeveDB 版メタ情報のバージョンを表すバージョンは、主に二次元アレイFileMetaDataポインタを含む、層状SSTは、すべてのファイル情報を記録します。FileMetaDataの異なるバージョンを参照する参照カウントの数を記録し、ファイルサイズ、ファイル数、最大値と最小値、参照カウントを含むメタ情報ファイルを維持するために使用されるデータ構造は、ファイル内で参照されないことを保証することです削除されました。また、バージョンは、ステータス情報は、圧縮関連の情報が更新または要求の圧縮プロセスを読み書きされるトリガー記録します。Paoding解LevelDBの概要プロセス記述圧縮古いファイルを新しいファイルにつながると削除されますCompactMemTableとBackgroundCompaction過程で知ることができます。たびに、この時間は、生成され、VersionSetリストヘッダを挿入し、対応する新バージョンが存在します。
VersionSetは、メタ情報の時間を記録し、バージョン年代順を生産している二重リンクリストのバージョン構成であり、各バージョンの参照カウントを維持しながら、リストのヘッドは、最新バージョンの現在を指して、ではありませんバージョンで引用LevelDBは、安定したスナップショットビューのファイルにアクセスできるように、ファイルに対応するため、この方法では、SSTを保持している、削除します。VersionSetは、レコードのバージョン二重にリンクされたリストに加えてこのようなLogNumber、シーケンス、次のファイル番号SSTステータス情報などの一部を、となります。
以上の説明からわかるように、隣接するバージョンとの違いは、単にいくつかのファイルが他のファイルが削除され、削除されます。これは、バージョンの方法によって生成された新しいバージョンを取得することができます古いバージョンのアプリケーションファイルに変更されます。でLevelDB VersionEdit隣接差分バージョンでこれを表現します。
データの損失にプロセスまたはマシンのダウンタイムの崩壊を避けるために、LevelDBは、メタ情報のデータを必要とする、それが作業を前提とすることで、ディスクに永続化マニフェストファイルを。新しいバージョンの農産物がマニフェストを更新する必要がある時はいつでもそれは、マニフェストファイルのレコードがVersionEdit値のセット、マニフェストで増分コンテンツであることを、VersionEdit内容に正確に対応し、自然に新しいデータで見つかった、見ることができます次のように読み出しブロックと呼ばれます。
Manifest Block := N * Item
Item := [kComparator] comparator
or [kLogNumber] 64位log_number
or [kPrevLogNumber] 64位pre_log_number
or [kNextFileNumber] 64位next_file_number_ or [kLastSequence] 64位last_sequence_ or [kCompactPointer] 32位level + 变长的key or [kDeletedFile] 32位level + 64位文件号 or [kNewFile] 32位level + 64位 文件号 + 64位文件长度 + smallest key + largest key
可以看出恢复元信息的过程也变成了依次应用VersionEdit的过程,这个过程中有大量的中间Version产生,但这些并不是我们所需要的。LevelDB引入VersionSet::Builder来避免这种中间变量,方法是先将所有的VersoinEdit内容整理到VersionBuilder中,然后一次应用产生最终的Version,这种实现上的优化如下图所示:
在这一节中,我们依次看到了LevelDB版本控制中比较重要的几个角色:Version、FileMetaData、VersionSet、VersionEdit、Manifest和Version::Builder。同时了解了他们各自的作用
功能点
版本控制中维护的各种元信息,为LevelDB的各个工作流程中提供了必不可少的支持:
1,Get
我们已经知道,LevelDB尝试获取某个Key的值时会依次尝试从Memtable,Immutable,SST文件中读取。一旦需要从SST文件中读取,就需要解决从大量文件中快速定位文件的问题。正是由于Version中记录了当前每个文件的最大最小值,使得这个问题变成比较Key值与文件的Key Range的过程。
我们已经知道,LevelDB的写操作会直接写入Memtable并通过异步的Compaction过程写入到不同层次的SST文件中,因此,上层文件拥有较新的数据,利用这个特征,LevelDB的Get接口会由上至下的依次从每一层中尝试查找,一旦查找成功,便可以忽略下层的相同Key的记录。
Level0层比较特殊,文件之间相互重叠无序,需要由新到旧的尝试从每个文件中查找。其他Level,由于SST文件本身有序排列,因此可以利用二分查找快速定位Key所在文件。找到Key值所在文件后,再用庖丁解LevelDB之数据存储中介绍的格式读取文件中内容。
2,Compaction触发时机
我们已经知道,LevelDB中会有后台线程来执行Compaction的操作,将上层文件与下层文件归并生成新的下层文件。Version中记录的各层的文件信息来帮助决定进行Compaction的时机:
- 容量触发Compaction:每个Version在其生成的时候会初始化两个值compaction_level_、compaction_score_,记录了当前Version最需要进行Compaction的Level,以及其需要进行Compaction的紧迫程度,score大于1被认为是需要马上执行的。我们知道每次文件信息的改变都会生成新的Version,所以每个Version对应的这两个值初始化后不会再改变。level0层compaction_score_与文件数相关,其他level的则与当前层的文件总大小相关。这种区分的必要性也是显而易见的:每次Get操作都需要从level0层的每个文件中尝试查找,因此控制level0的文件数是很有必要的。同时Version中会记录每层上次Compaction结束后的最大Key值compact_pointer_,下一次触发自动Compaction会从这个Key开始。容量触发的优先级高于下面将要提到的Seek触发。
- Seek触发Compaction:Version中会记录file_to_compact_和file_to_compact_level_,这两个值会在Get操作每次尝试从文件中查找时更新。LevelDB认为每次查找同样会消耗IO,这个消耗在达到一定数量可以抵消一次Compaction操作消耗的IO,所以对Seek较多的文件应该主动触发一次Compaction。但在引入布隆过滤器后,这种查找消耗的IO就会变得微不足道了,因此由Seek触发的Compaction其实也就变得没有必要了。
- 手动Compaction:LevelDB提供了外部接口CompactRange,用户可以指定触发某个Key Range的Compaction,LevelDB默认手动Compaction的优先级高于两种自动触发。
3,构造Compaction:
达到触发条件进行Compaction操作时,会首先通过Version来构造所有本次Compaction所需要的信息,记录在Compaction对象中,包括发生Compaction的level,所有参与的level和level+1层的文件信息,level+2层的文件信息等。 下面针对自动触发Compaction的情况介绍,手动Compaction的过程大体类似,这个过程叫做PickCompaction。
- 获得要Compaction的一个文件加入input_[0],容量触发时这个文件由compaction_level_加compact_pointer_确定,否则由file_to_compact_level_和file_to_compact_确定。对于level0,由于其文件相互重合,需要将所有与当前Compaction文件重合的文件全部加入input_[0]。
- 获得所有与level[0]有Key Range重合的level+1层文件加入input_[1],可以看出所有input_[1]文件的Key Range可能大于level[0],为了减少LevelDB整体Compaction次数,LevelDB会在不增加input_[1]文件数的前提下尝试增加level[0]文件数来扩大level层文件的Key Range。
- 获得所有与当前Key Range重合的level+2层文件加入input_[2],这里记录level+2层的文件信息是为了Compaction生成新的level+1层文件时,保证新文件不会与level+2中太多的文件有Key Range的重合,从而导致以后该文件的Compaction有太大的Merge开销,这个信息会在生成新文件的过程中不断检查。
- 生成归并Iterator,接下来就是用上面收集的信息生成归并Iterator,之后遍历这个Iterator生成新的文件,Iterator相关的内容会在之后一篇博客详细介绍。
4,Version持久化:
Compaction过程会造成文件的增加和删除,这就需要生成新的Version,上面提到的Compaction对象包含本次Compaction所对应的VersionEdit,Compaction结束后这个VersionEdit会被用来构造新的VersionSet中的Version。同时为了数据安全,这个VersionEdit会被Append写入到Manifest中。在库重启时,会首先尝试从Manifest中恢复出当前的元信息状态,过程如下:
- 依次读取Manifest文件中的每一个Block, 将从文件中读出的Record反序列化为VersionEdit;
- 将每一个的VersionEdit Apply到VersionSet::Builder中,之后从VersionSet::Builder的信息中生成Version;
- 计算compaction_level_、compaction_score_;
- 将新生成的Version挂到VersionSet中,并初始化VersionSet的manifest_file_number_, next_file_number_,last_sequence_,log_number_,prev_log_number_ 信息;