ARIES リカバリアルゴリズム
クラッシュしても DB が必ず正しい状態に戻るのはなぜか。Analysis・Redo・Undo の3フェーズと LSN・CLR の仕掛けを原理から押さえれば、商用 DBMS の復旧機構が腑に落ちます。
- 1.ARIES は Analysis(解析)・Redo(再実行)・Undo(巻き戻し)の3フェーズでクラッシュリカバリを行う。Redo では未コミット分も含め履歴を完全再現してから、未コミット分だけを Undo する(Repeating History)。
- 2.各ログレコードに単調増加の LSN を振り、ページの pageLSN と比較して redo の二重適用を防ぐ。Dirty Page Table と Transaction Table を fuzzy checkpoint に記録し、Analysis の再構築起点にする。
- 3.Undo した操作も CLR(補償ログ)として記録し、undoNextLSN で次の取り消し位置を指す。これによりリカバリ中の再クラッシュでも同じ undo を二度行わず、復旧全体が冪等になる。
ARIES が解く問題
クラッシュ直後のディスクは、ページごとに「いつまでの変更が反映されているか」がバラバラです。あるページにはコミット前の更新まで書かれ、別のページにはコミット済みの更新がまだ書かれていない、という状態が普通に起こります。原因は実用 DBMS が No-Force / Steal を採るからです。No-Force はコミット時にデータページの書き戻しを強制しない(だから redo が要る)、Steal は未コミットの汚れたページもバッファ都合で書き戻してよい(だから undo が要る)。
ARIES(Algorithm for Recovery and Isolation Exploiting Semantics)は、この混沌としたディスクを WAL(先行書き込みログ) だけを手がかりに、決定的かつ冪等に修復するアルゴリズムです。設計原理は3本柱にまとめられます。
| 原理 | 内容 | 効果 |
|---|---|---|
| WAL | データページを書き戻す前に、その変更ログを先に永続化する | redo/undo の前提(壊れたページの修復素材を必ず残す) |
| Repeating History | Redo で未コミット分まで含めクラッシュ直前の状態を一旦完全再現する | リカバリ中の再クラッシュでも状態が一貫し再開が単純になる |
| Logging changes during Undo | Undo した操作も CLR として記録する | undo が冪等化し、二重取り消しが起こらない |
LSN とページの自己記述
各ログレコードには単調増加する LSN(Log Sequence Number)が振られます。LSN は実質的にログ上の書き込み位置(オフセット)であり、順序と位置を同時に表します。ARIES の正しさは、いくつかの LSN を要所に埋め込むことで成立しています。
- pageLSN: 各データページのヘッダに記録される「そのページに最後に適用された変更の LSN」。これがあるおかげで redo は冪等になります。あるログレコード(LSN =
L)を redo しようとするとき、対象ページのpageLSN >= Lなら「その変更はすでに反映済み」と判断してスキップできる。だからリカバリが途中でまた落ちて最初からやり直しても、同じ正しい結果に収束します。 - prevLSN: 同一トランザクションが直前に書いたログの LSN。各トランザクションのログがリンクリストとして逆順にたどれ、undo はこれを伝って自分の変更だけを巻き戻します。
- recLSN: あるページが「いつ汚れ始めたか」の最古の LSN(後述の Dirty Page Table が持つ)。redo の開始点を決めます。
ARIES の redo は「物理的(physical)」あるいは「論理を物理に写した(physiological)」やり直しです。ログには変更後のページ状態を再現するに足る情報が入っており、適用するかどうかは pageLSN と当該ログの LSN の単純比較だけで決まります。比較が pageLSN >= LSN ならスキップ。この一点で「適用済みを二度適用しない」が機械的に保証され、Redo フェーズ全体が何度繰り返しても同じ結果になります。
Dirty Page Table と Transaction Table
リカバリの再構築起点を与えるのが、平常時にメモリ上で維持される2つの表です。
- Dirty Page Table(DPT): バッファ上で変更され、まだディスクへ書き戻していないページの集合。各エントリは
(ページID, recLSN)を持つ。recLSNは「そのページを最初に汚した変更の LSN」で、redo の開始点を決める鍵になります。 - Transaction Table(TT): 実行中トランザクションの集合。各エントリは
(トランザクションID, 状態, lastLSN)を持ち、lastLSNはそのトランザクションが最後に書いたログの LSN。undo の出発点になります。
これらは fuzzy checkpoint(ファジーチェックポイント)の際にログへ書き出されます。ファジーとは「全ダーティページの書き戻し完了を待たずにチェックポイントを進める」方式で、平常時の I/O を止めません。チェックポイントは DPT と TT のスナップショットをログに記録するだけで、ページ書き戻しはバックグラウンドに任せます(バッファプールとページ置換 のバックグラウンド書き出しを参照)。
直感に反しますが、ARIES のチェックポイント自体はダーティページを強制 flush しません。記録するのは DPT と TT の中身だけ。だからチェックポイント時点で DPT に載っているページは「まだディスク未着地かもしれない」もので、Redo はそれらを recLSN から再適用して着地を保証します。チェックポイントを軽量に保ちつつ復旧範囲を有界化する、これが fuzzy checkpoint の狙いです。
3フェーズの全体像
リカバリは最後に完了したチェックポイントを足がかりに、Analysis → Redo → Undo の順で進みます。
| フェーズ | 走査方向 | やること | 保証するもの |
|---|---|---|---|
| Analysis(解析) | 前方(チェックポイント以降) | DPT と TT をクラッシュ直前の状態まで再構築し、Redo 開始点と Undo 対象を確定 | 復旧範囲の決定 |
| Redo(再実行) | 前方(redoLSN 以降) | DPT 上のページに対し、未コミット分も含め全変更を LSN 順に再適用 | 永続性(コミット済みの再現)と履歴の完全再現 |
| Undo(巻き戻し) | 後方 | クラッシュ時に未コミットだった loser を逆順に取り消し、CLR を記録 | 原子性(中途半端を残さない) |
Analysis フェーズ
最後のチェックポイントが記録した DPT と TT を初期値として読み込み、そこからログを前方へスキャンします。更新ログを見たら、そのページが DPT になければ追加し、トランザクションを TT に反映する。コミット/アボートの完了ログを見たら、そのトランザクションを TT から除く。スキャンを終えた時点で、TT に残っているのが loser(クラッシュ時に未コミットだったトランザクション=Undo 対象)、DPT に残っているのが「ディスク未着地かもしれないページ」です。Redo の開始点 redoLSN は、DPT 中の recLSN の最小値になります。それより前のページ変更はすでに全てディスクに着地しているからです。
Redo フェーズ
redoLSN からログを前方へスキャンし、再適用すべきか1件ずつ判定します。ここが Repeating History の核心で、loser の変更も含めて再適用します。判定は次の通りです。
各更新ログレコード(LSN = L, 対象ページ = P)について:
if P が DPT に無い: skip # そのページは既にディスクに着地済み
elif L < DPT[P].recLSN: skip # この変更より後から汚れ始めた → 着地済み
else:
P をバッファに読み込む
if P.pageLSN >= L: skip # 既に反映済み(redo の冪等判定)
else: L を再適用し P.pageLSN = L に更新
3段階の枝刈り(DPT 不在・recLSN 未満・pageLSN 以上)で、ディスク読み込みと再適用を必要最小限に抑えます。Redo を終えると、ディスクの論理状態はクラッシュが起きなかったかのように、直前の全履歴が反映された状態になります。
loser の変更を redo でわざわざ復元してから、直後の Undo で消すのは一見無駄に見えます。しかしこうすると「Undo が常に既知の完全な状態に対して走る」ため、Undo のロジックが単純で一貫します。途中でまた落ちても、再び Repeating History で同じ状態を作り直せばよい。部分的な状態の場合分けを排して冪等性を担保する、これが Repeating History の対価と見返りです。
Undo フェーズと CLR
Redo 完了後、TT に残った loser たちの変更を後方へ取り消します。各 loser の lastLSN を起点に、prevLSN のリンクをたどって自分の更新ログだけを逆順に巻き戻します。複数の loser がある場合は、全 loser の未処理 LSN のうち最大のものを毎回選んで進めると、効率よく一括処理できます。
ここで決定的に重要なのが CLR(Compensation Log Record、補償ログレコード)です。ある更新を取り消すたびに、「取り消したという事実」を CLR としてログに追記します。CLR は redo 可能(redo-only)で、undoNextLSN という特別なフィールドを持ちます。これは「次に取り消すべき LSN」、すなわち取り消した更新の prevLSN を指します。
CLR が無いと、Undo の途中でクラッシュした場合、再リカバリ時にどこまで取り消したか分からず、同じ更新を二度取り消してしまいます(二重 undo はデータを壊す)。ARIES では、再リカバリの Redo フェーズで CLR も再適用されるため取り消し効果が再現され、その CLR の undoNextLSN が「次はここから取り消せ」と教えてくれます。すでに取り消した範囲を CLR が飛ばすので、Undo は決して後戻りせず、リカバリ全体が完全に冪等になります。
ログ: ... U1(prev=-) U2(prev=U1) U3(prev=U2) [クラッシュ]
Undo: U3 を取り消し → CLR3(undoNext=U2 の LSN) を記録
U2 を取り消し → CLR2(undoNext=U1 の LSN) を記録
Undo 中に再クラッシュ
再Redo: CLR3, CLR2 を再適用(取り消し効果が復元される)
再Undo: 最後の CLR2 の undoNext=U1 から再開 → U1 だけ取り消す(U2,U3 は二度やらない)
並行制御との接点
ARIES の正しさは、適切なロックの上に成り立ちます。Undo が他トランザクションを巻き込まずに自分の変更だけを巻き戻せるのは、更新したレコードをコミットまで排他ロックで保持する(Strict 2PL 相当の)前提があるからです。これがあるからこそ、redo は physiological に、undo は論理的に整合します。詳しくは ロックと MVCC を参照してください。
なお ARIES はインデックス(B+Tree)の高並行な構造変更まで扱う ARIES/IM や ARIES/KVL などの拡張系を持ち、ページ単位の物理 undo と、操作単位の論理 undo を組み合わせて、構造が変わっても整合する復旧を実現します。
「なぜ Redo を Undo より先にやるのか」「なぜ未コミット分まで Redo するのか」「CLR は何のためにあるのか」が頻出です。答えは順に、Repeating History でクラッシュ直前の状態を完全再現してから loser を消すと Undo が単純かつ冪等になる/その完全再現のため/Undo を冪等化し再クラッシュ時の二重取り消しを防ぐため。pageLSN が redo の、undoNextLSN(CLR)が undo の冪等性を担う、と対で言えると強いです。
まとめ
ARIES は「WAL・Repeating History・Undo 中もログを取る」という3原理から、Analysis・Redo・Undo の3フェーズを導きます。Analysis が DPT・TT を再構築して範囲を決め、Redo が pageLSN を見て履歴を冪等に完全再現し、Undo が CLR と undoNextLSN を使って未コミット分だけを冪等に取り消す。これにより No-Force / Steal の自由を保ちながら、何度クラッシュしても同じ正しい状態に収束する復旧が成立します。
物理層で永続性を支える基盤は WAL の仕組み、ダーティページとチェックポイントの実体は バッファプールとページ置換、復旧が守ろうとする原子性・永続性の定義は トランザクションとは何か、任意時点復元まで含めた運用面は バックアップとリカバリ を合わせて読むと、リカバリの全体像が立体的に見えてきます。
データベース Article
ARIES リカバリアルゴリズムを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
ARIES
比較で見る軸
難易度: advanced / カテゴリ: データベース / タグ数: 5
導入後に効く点
各ログレコードに単調増加の LSN を振り、ページの pageLSN と比較して redo の二重適用を防ぐ。Dirty Page Table と Transaction Table を fuzzy checkpoint に記録し、Analysis の再構築起点にする。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- データベース
- タグ数
- 5
判断チェックリスト
- 自社の用途が「ARIES / クラッシュリカバリ」に近いか確認する。
- 強みである「ARIES は Analysis(解析)・Redo(再実行)・Undo(巻き戻し)の3フェーズでクラッシュリカバリを行う。Redo では未コミット分も含め履歴を完全再現してから、未コミット分だけを Undo する(Repeating History)。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。