RocksDBの概要
ロックスDBは、キーと値の保存と読み取り/書き込み関数を提供する LSM ツリーstorageエンジンです。Facebook によって開発され、LevelDB に基づいています。ユーザーが書き込んだキーと値のペアは、まず Write Ahead Log (WAL) に挿入され、次にメモリ内の SkipList ( MemTableと呼ばれるデータ構造) に書き込まれます。LSM ツリー エンジンは、ランダムな変更 (挿入) を WAL ファイルへの順次書き込みに変換するため、B ツリー エンジンよりも書き込みスループットが優れています。
メモリ内のデータが一定のサイズに達すると、RocksDB はコンテンツをディスク内の Sorted String Table (SST) ファイルにフラッシュします。SST ファイルは複数のレベル (デフォルトでは最大 6 レベル) に編成されています。レベルの合計サイズがしきい値に達すると、RocksDB は SST ファイルの一部を選択し、次のレベルにマージします。後続の各レベルは前のレベルの 10 倍の大きさになるため、データの 90% が最後のレイヤーに保存されます。
RocksDB では、複数のカラムファミリ (CF) を作成できます。CF には独自の SkipList ファイルと SST ファイルがあり、同じ WAL ファイルを共有します。このように、アプリケーションの特性に応じて、異なる CF に異なる設定を適用できます。同時に WAL への書き込み回数が増えることはありません。
TiKVアーキテクチャ
TiKV のアーキテクチャは次のようになります。
TiKV のstorageエンジンとして、RocksDB はRaftログとユーザー データの保存に使用されます。TiKV ノードのすべてのデータは 2 つの RocksDB インスタンスを共有します。1 つはRaftログ用 (多くの場合 raftdb と呼ばれます)、もう 1 つはユーザー データと MVCC メタデータ用 (多くの場合 kvdb と呼ばれます) です。kvdb には、raft、lock、default、write の 4 つの CF があります。
- raft CF: 各リージョンのメタデータを保存します。占有するスペースはごくわずかで、ユーザーが気にする必要はありません。
- ロック CF:悲観的トランザクションの悲観的ロックと分散トランザクションの事前書き込みロックを保存します。トランザクションがコミットされた後、ロック CF 内の対応するデータはすぐに削除されます。したがって、ロック CF 内のデータのサイズは通常非常に小さくなります (1 GB 未満)。ロック CF 内のデータが大幅に増加すると、コミットを待機しているトランザクションが多数あり、システムがバグまたは障害に遭遇していることを意味します。
- 書き込み CF: ユーザーが実際に書き込んだデータと MVCC メタデータ (データが属するトランザクションの開始タイムスタンプとコミット タイムスタンプ) を保存します。ユーザーがデータ行を書き込むと、データ長が 255 バイト未満であれば書き込み CF に保存されます。それ以外の場合は、デフォルトの CF に保存されます。TiDB では、非一意インデックスに保存された値は空であり、一意インデックスに保存された値はプライマリ キー インデックスであるため、セカンダリ インデックスは書き込み CF のスペースのみを占有します。
- デフォルト CF: 255 バイトを超えるデータを保存します。
RocksDBメモリ使用量
読み取りパフォーマンスを向上させ、ディスクへの読み取り操作を減らすために、RocksDB はディスクに保存されているファイルを特定のサイズ (デフォルトは 64 KB) に基づいてブロックに分割します。ブロックを読み取るときは、まずメモリ内の BlockCache にデータがすでに存在するかどうかを確認します。存在する場合は、ディスクにアクセスせずにメモリから直接データを読み取ることができます。
BlockCache は、LRU アルゴリズムに従って、最も最近使用されていないデータを破棄します。デフォルトでは、TiKV はシステムメモリの 45% を BlockCache に割り当てます。ユーザーは、 storage.block-cache.capacity
構成を適切な値に自分で変更することもできます。ただし、システムメモリ全体の 60% を超えることは推奨されません。
RocksDB に書き込まれるデータは、まずMemTableに書き込まれます。MemTable のサイズが 128 MB を超えると、新しいMemTableに切り替わります。TiKV には 2 つのMemTableインスタンスがあり、合計 4 つの CF があります。各 CF の 1 つのMemTableのサイズ制限は 128 MB です。同時に存在できる MemTable は最大 5 つで、それ以外の場合はフォアグラウンド書き込みがブロックされます。この部分が占有するメモリは最大 2.5 GB (4 x 5 x 128 MB) です。メモリの消費量が少なくなるため、この制限を変更することはお勧めしません。
RocksDB のスペース使用量
- マルチバージョン: RocksDB は LSM ツリー構造のキー値storageエンジンであるため、 MemTableのデータは最初に L0 にフラッシュされます。ファイルは生成された順序で配置されるため、L0 の SST の範囲が重複している可能性があります。その結果、同じキーが L0 で複数のバージョンを持つ可能性があります。ファイルが L0 から L1 にマージされると、特定のサイズ (デフォルトは 8 MB) の複数のファイルに分割されます。同じレベルの各ファイルのキー範囲は互いに重複しないため、L1 およびそれ以降のレベルでは各キーに対して 1 つのバージョンのみがあります。
- スペース増幅: 各レベルのファイルの合計サイズは前のレベルの x (デフォルトは 10) 倍なので、データの 90% が最後のレベルに保存されます。これは、RocksDB のスペース増幅が 1.11 を超えないことも意味します (L0 はデータが少ないため無視できます)。
- TiKV のスペース拡張: TiKV には独自の MVCC 戦略があります。ユーザーがキーを書き込むと、RocksDB に書き込まれる実際のデータはキー + commit_ts になります。つまり、更新と削除によって新しいキーも RocksDB に書き込まれます。TiKV は一定間隔でデータの古いバージョンを削除します (RocksDB の削除インターフェイスを介して)。そのため、ユーザーが TiKV に保存したデータの実際のスペースは、過去 10 分間に書き込まれたデータを加えた 1.11 に拡大していると考えられます (TiKV が古いデータを速やかにクリーンアップすると仮定)。
RocksDB のバックグラウンド スレッドと圧縮
RocksDB では、 MemTable をSST ファイルに変換したり、さまざまなレベルで SST ファイルをマージしたりする操作は、バックグラウンド スレッド プールで実行されます。バックグラウンド スレッド プールのデフォルト サイズは 8 です。マシンの CPU 数が 8 以下の場合、バックグラウンド スレッド プールのデフォルト サイズは、CPU 数から 1 を引いた数になります。
一般的に、ユーザーはこの設定を変更する必要はありません。ユーザーがマシンに複数の TiKV インスタンスを展開する場合、またはマシンの読み取り負荷が比較的高く、書き込み負荷が低い場合は、 rocksdb/max-background-jobs
を 3 または 4 に適宜調整できます。
書き込み停止
RocksDB の L0 は他のレベルとは異なります。L0 の SST は生成順に並べられます。SST 間のキー範囲は重複できます。したがって、クエリを実行するときは、L0 の各 SST を順番にクエリする必要があります。クエリのパフォーマンスに影響を与えないように、L0 にファイルが多すぎる場合は、WriteStall がトリガーされて書き込みがブロックされます。
書き込み遅延が突然急増した場合は、まず Grafana RocksDB KV パネルでWriteStall Reasonメトリックを確認します。L0 ファイルが多すぎるために WriteStall が発生している場合は、次の構成を 64 に調整できます。
rocksdb.defaultcf.level0-slowdown-writes-trigger
rocksdb.writecf.level0-slowdown-writes-trigger
rocksdb.lockcf.level0-slowdown-writes-trigger
rocksdb.defaultcf.level0-stop-writes-trigger
rocksdb.writecf.level0-stop-writes-trigger
rocksdb.lockcf.level0-stop-writes-trigger