コンテンツにスキップ

VersionLog のアーキテクチャ

このページは、VersionLog の内部構造を理解したい場合に読むページです。設定と API の使用例は VersionLog の使い方 に分けています。本番運用の監視と保守コマンドは VersionLog の運用 を参照してください。

VersionLog が有効な場合、各エンジン変更は現在値の保存経路と同じ流れの中で記録されます。

  1. 新しいエンジンシーケンス番号を確保する。
  2. WAL が有効なら WAL に追加する。
  3. VersionLog エントリを追加する。
  4. MemTable に変更を反映する。
  5. COMMIT_ORDER の可視性用に VersionLog のコミット済み範囲を進める。
  6. クラスターモードが有効ならレプリケーション実行層へ送る。

COMMIT_ORDERhistory()getAt() は、対応するエンジン変更より先に VersionLog エントリを見せません。APPLIED は低遅延のために、追加済み履歴をより早く見せるオプションです。

.akvlog セグメントは 32 バイトの AKV5 v1 ヘッダーから始まります。エントリは詰め込まれたヘッダー、生キーのバイト列、保存値のバイト列、末尾 CRC32C を持ちます。

フィールド意味
seqエンジンのシーケンス番号です。
sourceNodeIdローカルノード、レプリカ元ノード、またはロールバック用の番兵値です。
timestampNs追記時刻です。
flags圧縮、ロールバック、retention-base などの保存フラグです。
keyFp64インデックス用のキーフィンガープリントです。
key生キーのバイト列です。
value生値、圧縮値ペイロード、または Blob 参照のバイト列です。
crc32cエントリのチェックサムです。

現在のエントリサイズ上限は 32 MiB です。uint16_t に収まらないキー、uint32_t に収まらない値はシリアライザーで拒否されます。

codec = ZSTD の場合、VersionLog は各値の圧縮を試みます。保存値は 4 バイトの非圧縮サイズと Zstd ペイロードから始まります。圧縮ペイロードと接頭辞の合計が生値より小さくない場合、そのエントリは非圧縮で保存されます。

公開される VersionEntry の値は常に展開済みバイト列です。内部の圧縮フラグは呼び出し元に返す前に取り除かれます。

基準 logPath がセグメント 0 です。後続セグメントは拡張子の前に -seg-N を挿入します。

history.akvlog
history-seg-1.akvlog
history-seg-2.akvlog

各セグメントは派生 .akvidx サイドカーを持つことがあります。

history.akvidx
history-seg-1.akvidx

サイドカーは Bloom filter と、キーフィンガープリントから (seq, VLog byte offset) レコードへのソート済み対応表を保存します。history() は Bloom filter で対象外セグメントを飛ばし、該当キーのオフセットだけを読みます。getAt() はキーごとのシーケンスを二分探索し、選ばれたレコードを直接読みます。

.akvlog セグメントが正本です。.akvidx サイドカーが欠損、古い、壊れている、または書けない場合は、安全にセグメント走査へフォールバックします。復旧、ローテーション、正常終了、retention-base 作成、保守ツールはサイドカーの再生成を試みます。

アクティブインデックスのメモリ

Section titled “アクティブインデックスのメモリ”

VersionLog は全履歴値をメモリに保持しません。閉じたセグメントはコンパクトなセグメントメタデータとサイドカーインデックスで表現します。変更中のアクティブセグメントはキーからオフセットへのメタデータだけを持ち、segmentBytes で上限が付きます。

segmentBytes = 0 の単一ファイルモードではアクティブメタデータも無効化され、新しく追加されたログの読み取りはファイル走査にフォールバックします。これにより、履歴を無制限にメモリへ積み上げません。

まだディスクに到達していない非同期書き込みは、設定された可視性境界で読めるように小さな常駐オーバーレイとして保持されます。

PARALLEL 書き込み受付は、安定したキーフィンガープリントにより各キーをレーンに割り当てます。各レーンはアクティブセグメント、キュー、ワーカー、アクティブインデックス、永続化済み末尾を所有します。異なるレーンは並列に永続化でき、同じキーは同じレーンを通るため順序を保ちます。

PARALLEL モードは各アクティブレーンセグメントの隣に .akvtail を書きます。tail は連続して永続化済みのバイト長を記録します。復旧処理はその接頭範囲だけを走査し、それ以降のバイト列を無視するため、中断されたレーン追記が履歴として見えることはありません。

復旧処理はセグメントファイルを発見し、ファイルヘッダー、各エントリ長、CRC32C を検証し、セグメントメタデータを再構築し、サイドカーを作り直し、最新セグメントのアクティブインデックスを復元します。

EAGER 復旧は open が返る前に完了します。BACKGROUND 復旧はアクティブファイルを開いた後に返り、その後の VersionLog と AkkEngine 操作は復旧バリアを待ってからエンジン状態を観測または変更します。

破損は受け入れません。復旧エラーには VersionLog: の文脈が含まれ、可能な場合はファイルパス、エントリオフセット、シーケンス番号も含まれます。

保持処理はセグメント単位で、デフォルトでは無効です。retentionDaysretentionMinCommitSeq は OR 条件です。閉じたセグメントは、経過時間の境界に達するか、最大シーケンスがシーケンス境界より小さい場合に削除候補になります。

期限切れセグメントを削除する前に、VersionLog は保持境界時点の最新値が消えてしまうキーについて合成 VLOG_FLAG_RETENTION_BASE エントリを書きます。これにより、保持範囲以降の getAt()history()、ロールバックに必要な開始状態を残します。

ベース境界より前の問い合わせは利用できません。保持処理はアクティブセグメントを削除せず、現在のコミット済み範囲を越えるセグメントも保持します。

VersionLog が有効な間、Blob GC は拒否されます。過去の履歴エントリが古い Blob 値を参照している可能性があり、それを削除すると過去時点読み取りが壊れるためです。VersionLog の保持処理は履歴セグメントを減らしますが、それだけでは Blob が未参照になったとは判断できません。

ロールバック操作では sourceNodeId = ROLLBACK_NODE を使い、VLOG_FLAG_ROLLBACK を立てます。統計情報では vlog.rollbackEntries から確認できます。

ロールバックは新しいレコードを書いて状態を戻します。対象シーケンス時点でキーに値がなければ tombstone を書き、値があればその値を現在の状態として再書き込みします。

現在のスモークテストでは、主に次を確認しています。

テスト内容確認している挙動
書き込み受付と可視性COMMIT_ORDERAPPLIED、直列受付、並列受付、バックグラウンド復旧。
セグメントインデックスのフォールバック.akvidx の欠損や破損で、正本であるセグメント走査にフォールバックすること。
復旧時の破損検出CRC 破損やコミット済みエントリの切り詰めで復旧が失敗すること。
並列レーンの永続化済み末尾.akvtail の永続化済み範囲以降を復旧処理が無視すること。
並行読み書き並列書き込み側/読み取り側でソート済み履歴と値が保たれること。
保守ツールvalidaterebuild-indexes が派生サイドカーを再生成し、存在しないログを拒否すること。