OceanBaseデータベースのシェアードナッシング(Shared-Nothing、SN)モードのクラスタアーキテクチャでは、そのキャッシュ設計はOracleやMySQLとは大きく異なります。OceanBaseデータベースのストレージエンジンはLSM-Treeアーキテクチャに基づいており、すべての変更はMemTableにのみ書き込まれ、SSTableは読み取り専用であるため、OceanBaseデータベースのキャッシュは読み取り専用キャッシュとなり、ダーティページをフラッシュする関連ロジックが存在しません。これは従来のデータベースに比べて簡略化されています。しかし同時に、SSTableに格納されるデータに対してデータエンコードと圧縮が行われるため、格納するデータは可変長であり固定長ではなくなり、従来のデータベースと比べてキャッシュのメモリ管理ははるかに複雑になります。さらに、OceanBaseデータベースはマルチテナント向けの分散データベースシステムでもあるため、従来のデータベースと同様にキャッシュのメモリ淘汰を処理するだけでなく、キャッシュ内部でマルチテナントのメモリ分離も行う必要があります。
OracleやMySQLと同様に、OceanBaseデータベース内部にも多種多様なタイプのキャッシュが存在します。SSTableデータをキャッシュするBlock Cache(OracleやMySQLのbuffer cacheに類似)のほか、row cache(データ行をキャッシュするために使用)、log cache(redo logをキャッシュするために使用)、location cache(データコピーの場所をキャッシュするために使用)、schema cache(テーブルのSchema情報をキャッシュするために使用)、bloom filter cache(静的データのBloomfilterをキャッシュし、空検索を高速にフィルタリングするために使用)などがあります。OceanBaseデータベースは統一されたキャッシュフレームワークを設計しており、すべての異なるテナントの異なるタイプのキャッシュはこのフレームワークによって統一的に管理されます。異なるタイプのキャッシュには異なる優先順位が設定され、各キャッシュはそれぞれの優先順位とデータアクセスのヒートに応じて相互にスペースを競合します。異なるテナントについては、対応するテナントのメモリ使用量の上限と下限が設定され、各テナントのキャッシュはそれぞれのテナントのメモリ上下限およびサーバ全体のメモリ上限に応じて相互にスペースを競合します。
上図は、典型的なテーブル取得の例を通じて、OceanBaseデータベース内で現在クエリプロセスにサービスを提供している各種キャッシュを示しています。
BloomFilter Cache
OceanBaseデータベースのBloomFilterはマクロブロック上に構築され、ユーザーの実際の空検索率に応じて必要に応じて自動的に構築されます。あるマクロブロック上の空検索回数が特定のしきい値を超えると、BloomFilterが自動的に構築され、キャッシュに格納されます。
Row Cache
各SSTableに対して具体的なデータ行をキャッシュします。Get/MultiGetクエリを実行する際、対応するデータ行をRow Cacheに格納することで、次回同じ行をクエリする際に、二分探索による行の検索を繰り返すことを回避できます。
Block Index Cache
マイクロブロックのインデックスをキャッシュします。各SSTableはマクロブロックで構成されていますが、2MBの粒度はユーザークエリにとってはしばしば粗すぎるため、ユーザークエリの範囲に基づいてマクロブロック内で実際に必要なマイクロブロックを特定する必要があります。マイクロブロックインデックスは、各マクロブロック内のすべてのマイクロブロックの範囲を記述するものです。特定のマクロブロックのマイクロブロックにアクセスする必要がある場合、そのマクロブロックのマイクロブロックインデックスを事前にロードする必要があります。プレフィックス圧縮が行われているため、サイズは通常小さく、OceanBaseデータベース内部では高い優先順位が与えられているため、一般的にヒット率は高くなります。
Block Cache
OracleのBuffer Cacheに類似し、具体的なデータマイクロブロックをキャッシュします。各マイクロブロックは解凍後にBlock Cacheにロードされるため、各キャッシュのサイズは可変長になります。
Fuse Row Cache
LSM-Treeアーキテクチャでは、同一行の変更が異なるSSTableに存在する可能性があります。OceanBaseデータベースはストレージ使用量をさらに最適化するため、ユーザーの更新ごとに増分データのみを保存します。そのため、クエリ時には各SSTableのクエリ結果を融合する必要があります。ユーザーが新たな更新をトリガーしなくなると、この融合結果は以降のクエリに対して常に有効となります。そのため、OceanBaseデータベースは融合結果をキャッシュするFuse Row Cacheも提供しており、一部ユーザーのホットスポット行クエリをより大幅にサポートします。
OceanBaseデータベースは、上記のユーザークエリ関連のマルチレベルキャッシュに加え、さらに多くの異なるタイプのキャッシュもサポートしています。例えば:
Partition Location Cache:Partitionの位置情報をキャッシュし、クエリのルーティングを支援します。
Schema Cache:データテーブルのメタ情報をキャッシュし、実行計画の生成およびその後のクエリに使用されます。
Clog Cache:clogデータをキャッシュし、特定の状況下でのPaxosログのプルを高速化します。
......
これほど多様な種類のキャッシュをより汎用的にサポートするためには、可変長データの処理が必要です。OceanBaseデータベースの基盤となるキャッシュフレームワークは、メモリを複数の2MBサイズのメモリブロックに分割し、メモリの申請と解放はすべて2MB単位で行われます。可変長データは、2MBサイズのメモリブロック内に単純にパックされます。データの迅速な位置付けをサポートするため、ハッシュマップには対応するデータへのポインタが格納されており、全体の構造は以下の図のようになります。
キャッシュメモリは2MB単位で一括してフラッシュされます。OceanBaseデータベースは、各2MBメモリブロック上の各要素のアクセス頻度に基づいてスコアを計算し、アクセス頻度が高いメモリブロックほどスコアが高くなります。同時に、バックグラウンドスレッドが定期的にすべての2MBメモリブロックのスコアをソートし、スコアが低いメモリブロックをフラッシュします。全体のスコアは高くないものの内部にホットデータが存在する2MBメモリブロックについては、OceanBaseデータベースはそのホットデータを元の「コールドブロック」から「ホットブロック」へ移動させることで、ホットデータのフラッシュを回避します。データ構造上、OracleやMySQLのようなLRUチェーン表を維持していないため、データ読み取りにおいて、OceanBaseデータベースのキャッシュアクセスはほぼロックフリーです(HashMap上のBucketロックを除く)。これにより、ホットデータへの高並行アクセスに対してより適しています。フラッシュ時には、テナントのメモリ上限および下限を考慮し、各テナント内のキャッシュメモリ使用量を制御します。