コンテンツにスキップ

VersionLog の使い方

このページは、VersionLog を有効にして、履歴取得、過去時点読み取り、ロールバック API を使いたい場合に読むページです。保存形式、復旧、サイドカー、保持処理、運用上の制限は VersionLog のアーキテクチャVersionLog の運用 に分けています。

バージョン履歴はデフォルトでは無効です。エンジンを開く前に有効化します。

akkaradb::engine::AkkEngineOptions opts;
opts.paths.dataDir = "data/history";
opts.components.versionLogEnabled = true;
opts.vlog.syncMode = akkaradb::engine::vlog::VLogSyncMode::BATCHED_SYNC;
auto db = akkaradb::engine::AkkEngine::open(std::move(opts));

paths.dataDir を指定すると、paths.versionLogPath<dataDir>/history.akvlog から派生します。opts.vlog.logPath を直接指定した場合は、そのパスが優先されます。

VersionLog と WAL は別コンポーネントです。最新値の経路が永続化済みでも、すべての履歴エントリが VersionLog 側の同期境界を越えているとは限りません。

モード挙動主な用途
SYNC各 VersionLog エントリを書き込み、永続化同期してから返ります。履歴の永続性を最優先する場合。書き込みスループットは落ちます。
ASYNC読み取り可能な履歴は即時更新し、ファイルへの書き込みはバックグラウンドワーカーで行います。小さな履歴永続化ウィンドウを許容してスループットを優先する場合。
BATCHED_SYNCバックグラウンドワーカーでまとめ、バッチ単位で永続化同期します。1 書き込み 1 同期を避けつつ、履歴の永続性も重視する場合。

groupNgroupMicrosgroupBytes は非同期/バッチフラッシュのまとめ条件です。asyncMaxPendingBytes は未処理の履歴ログバイト数を制限し、必要に応じて書き込み側にバックプレッシャーをかけます。

直近の履歴を明示的なチェックポイントとして永続化境界まで進めたい場合は、AkkEngine::forceSync() を呼びます。

VersionLog の圧縮は opt-in です。

opts.vlog.codec = akkaradb::engine::vlog::VLogCodec::ZSTD;
opts.vlog.zstdCompressionLevel = 1;

圧縮レコードは内部に元サイズを保持し、VersionEntry として返す前に展開されます。圧縮後ペイロードとサイズ prefix の合計が生値より小さい場合だけ圧縮保存されます。

VersionLog はデフォルトで segmentBytes = 64 MiB のセグメント化された保存形式を使います。古い履歴を閉じたセグメントから削除したい場合は保持境界を設定します。

opts.vlog.segmentBytes = 64ULL * 1024ULL * 1024ULL;
opts.vlog.retentionDays = 30;
opts.vlog.retentionMinCommitSeq = 0;

retentionDaysretentionMinCommitSeq は OR 条件です。どちらかの境界に達した閉じたセグメントが削除候補になります。削除前に、境界時点の状態が消えてしまうキーについて合成 retention-base エントリを書くため、保持された範囲以降の getAt()history()、ロールバックは必要な開始状態を持てます。

ベース境界より前の問い合わせは利用できません。history() の最初のエントリが VLOG_FLAG_RETENTION_BASE 付きの合成エントリになることがあります。

書き込みの多いローカルワークロードでは、PARALLEL 書き込み受付により、独立したレーンワーカーで VersionLog レコードを永続化できます。

opts.vlog.syncMode = akkaradb::engine::vlog::VLogSyncMode::ASYNC;
opts.vlog.writeAdmission = akkaradb::engine::vlog::VLogWriteAdmissionMode::PARALLEL;
opts.vlog.parallelWriteLanes = 4;
opts.vlog.parallelPendingLimitScope =
akkaradb::engine::vlog::VLogParallelPendingLimitScope::GLOBAL;

PARALLELsyncMode = ASYNC を要求します。同じキーの書き込みは安定した key fingerprint により同じレーンに入り、順序を保ちます。GLOBALasyncMaxPendingBytes を全レーン合計に適用し、PER_LANE はレーンごとに適用します。

成功した書き込みごとに、対象キーの VersionEntry が追加されます。

db->put(bytes("profile:1"), bytes("v1"));
db->put(bytes("profile:1"), bytes("v2"));
db->remove(bytes("profile:1"));
auto history = db->history(bytes("profile:1"));
for (const auto& entry : history) {
const uint64_t seq = entry.seq;
const uint64_t sourceNodeId = entry.sourceNodeId;
const uint64_t timestampNs = entry.timestampNs;
const uint8_t flags = entry.flags;
}

history(key) は、シーケンス番号順の履歴を返します。VersionLog が無効な場合、またはそのキーに履歴がない場合は、空の vector を返します。

フィールド意味
seq変更ごとに単調増加するエンジンシーケンス番号です。getAt() やロールバック API の入力に使います。
sourceNodeId変更の発生元です。ローカル書き込み、replica 適用、ロールバック書き込みを区別できます。
timestampNsエンジンが記録した追加時刻です。診断には使えますが、履歴の順序は seq が基準です。
flags保存フラグです。ロールバックエントリと retention-base エントリもここで判別できます。
value公開される値のバイト列です。圧縮レコードは返却前に展開されます。

安定した履歴カーソルとして扱うべきなのは seq です。壁時計時刻はメタデータで、getAt() の解決には使われません。

getAt(key, seq) は、seq 以下で最後に記録された履歴値を返します。

db->put(bytes("profile:1"), bytes("v1"));
auto h1 = db->history(bytes("profile:1"));
db->put(bytes("profile:1"), bytes("v2"));
auto previous = db->getAt(bytes("profile:1"), h1.front().seq);
if (previous) {
// previous == "v1"
}

該当する履歴エントリが tombstone の場合、getAt()std::nullopt を返します。VersionLog が無効な場合も std::nullopt です。

APIVersionLog 無効キーがない、または見える値がないclose 後
history(key)空の vector空の vectorthrow
getAt(key, seq)std::nulloptstd::nulloptthrow
rollbackKey(key, seq)throw古い値がなければ tombstone を書くthrow
rollbackTo(seq)throwすでに対象シーケンス以前の key は対象外throw

ロールバック API は書き込み API です。新しいシーケンス番号を確保し、WAL と VersionLog にレコードを追加し、MemTable を更新し、Cluster が有効ならレプリケーション実行層にも送ります。

rollbackKey(key, targetSeq) は、指定キーを targetSeq 時点の状態へ戻す新しいレコードを書き込みます。

auto history = db->history(bytes("profile:1"));
if (!history.empty()) {
db->rollbackKey(bytes("profile:1"), history.front().seq);
}

ロールバックは履歴ファイルの破壊的な書き換えではありません。新しいエンジン変更を追加し、その VersionLog エントリにロールバックフラグを付け、過去の履歴は残します。

変更されたキーをまとめて戻す

Section titled “変更されたキーをまとめて戻す”

rollbackTo(targetSeq) は VersionLog の履歴を走査し、最新バージョンが targetSeq より新しいキーをすべて戻します。

db->rollbackTo(checkpointSeq);

この操作は慎重に扱います。対象キーごとに新しい書き込みが発生し、クラスターモードではそれらのロールバック書き込みがレプリケーション実行層にも送られます。

AkkEngine::forceSync() は、WAL と VersionLog が有効な場合に両方を同期します。

db->forceSync();
db->close();

close() は VersionLog ワーカーを止め、ファイルをフラッシュし、必要なら派生サイドカーを書き、非同期書き込みで発生していたエラーがあれば表面化させます。