ARIES Recovery Algorithm Overview
ARIES Principles and Correctness
Read aloud is unavailable in this browser.
The Theoretical Foundations
ARIES is not merely a collection of clever techniques—it's a principled framework built on rigorous theoretical foundations. These principles ensure that recovery is always correct, regardless of when or how the system crashes. Understanding these principles is essential for several reasons: they explain why ARIES works, they enable reasoning about correctness, and they guide implementation decisions.
This page examines the core principles that govern ARIES: Write-Ahead Logging (WAL), Repeat History, the Steal/No-Force buffer management policies, and the fundamental invariants that tie everything together.
By the end of this page, you will understand: (1) the Write-Ahead Logging protocol and why it's fundamental to durability, (2) the Repeat History paradigm and its benefits, (3) Steal/No-Force policies and how ARIES enables them, (4) key invariants that guarantee correctness, and (5) how these principles work together to create a robust recovery system.
Principle 1: Write-Ahead Logging (WAL)
Write-Ahead Logging (WAL) is the foundational principle of ARIES and virtually all log-based recovery systems. It states:
Before any modification to a database page can be written to stable storage, the log records describing that modification must first be written to stable storage.
This seemingly simple rule has profound implications. It ensures that the log always contains sufficient information to recover from any possible crash state.
Formal WAL Rules
WAL decomposes into two sub-rules:
1. Undo Rule (for Atomicity)
Before a modified page is flushed to disk, the undo portion of its log record must be on stable storage.
This ensures we can always undo uncommitted changes, even if they've been written to the database.
2. Redo Rule (for Durability)
Before a transaction commits, all of its log records (both undo and redo portions) must be on stable storage.
This ensures committed transactions can always be recovered, even if their data pages haven't been flushed.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
// WAL Enforcement in Practice class WALEnforcer { private logManager: LogManager; /** * Called before flushing any page to disk * Ensures the log is ahead of the data */ beforePageFlush(page: Page): void { // Get the page's LSN - the most recent log record that modified it const pageModificationLSN: LSN = page.pageLSN; // Get what's been flushed to the log const currentFlushedLSN: LSN = this.logManager.getFlushedLSN(); // WAL Rule: Log must be flushed past the page's LSN if (currentFlushedLSN < pageModificationLSN) { // Must flush log first! this.logManager.flushToLSN(pageModificationLSN); } // Now safe to write the page } /** * Called when transaction commits * Ensures commit is durable before returning */ onCommit(transactionId: TransactionID): void { // Write commit log record const commitLSN = this.logManager.writeCommit(transactionId); // WAL Rule: Commit record must be on stable storage // before we tell the application "committed" this.logManager.flushToLSN(commitLSN); // Only now is commit confirmed }} // Why WAL matters - failure scenarios: // Scenario 1: Page flushed without WAL// - Page with uncommitted changes written to disk// - Crash before log flushed// - No undo information exists!// - Database is corrupt (uncommitted data persists) // Scenario 2: Commit without WAL // - Tell application "committed"// - Crash before log flushed// - Committed transaction's changes might be undone// - Durability violated! // WAL prevents both scenarios by enforcing log-before-data orderingWhy WAL Works
WAL's power comes from a simple insight: the log is the authoritative record of what should be in the database. If the log says a transaction committed, it committed—even if data pages don't reflect it yet (redo will fix that). If the log says a change was made but no commit appears, the change should be undone—the undo information in the log enables this.
The FlushedLSN Invariant
WAL is enforced through the FlushedLSN invariant:
For any page P on stable storage: FlushedLSN >= P.PageLSN
This invariant states that any log record referenced by a page on disk must already be on stable storage. The database system enforces this by checking before every page flush.
Violating WAL, even once, can lead to irrecoverable data corruption. A page on disk without its log record means no undo information if we need to roll back, and potentially no redo information if later modifications depend on it. Every database system treats WAL as an absolute law, never an optimization to skip.
Principle 2: Repeat History During Redo
The Repeat History paradigm is ARIES's distinctive approach to redo. Rather than selectively redoing only committed transactions, ARIES redoes everything—including operations from transactions that will be undone immediately after.
During redo, bring the database to exactly the state it was in at the moment of crash, then roll back uncommitted transactions.
This approach might seem wasteful: why redo work we'll undo? The answer lies in correctness and simplicity.
Why Repeat History?
1. Page-Level Consistency
Database pages can contain interlocking structures—a row might span multiple slots, indexes might reference data pages. If we selectively skip some operations, the page might be in an inconsistent internal state. Repeating history ensures all page-level structures are intact before we attempt logical undo.
2. CLR Handling
If the system crashed during a previous recovery (crash-during-recovery), CLRs in the log record partial undo progress. Repeat history applies these CLRs during redo, restoring the undo progress before continuing.
3. Simplicity
The redo algorithm doesn't need to track transaction status. It just replays operations. The separation between redo (repeat history) and undo (roll back losers) simplifies both phases.
- •Only redo committed transactions
- •Must track transaction status during redo
- •Page might be inconsistent if partial
- •CLR handling is complex
- •Different code paths for different txn states
- •Redo ALL operations, committed or not
- •Redo ignores transaction status
- •Pages fully restored to crash state
- •CLRs naturally handled (just redo them)
- •Single code path: replay everything
Repeat History Example
Consider this crash scenario:
- T1: Committed (COMMIT record exists)
- T2: Active (no COMMIT)
- T3: Was being rolled back (has CLRs)
Selective Redo Approach (Complex):
- During redo, check each record's transaction status
- Redo T1's operations (committed)
- Skip T2's operations (uncommitted)
- Partially redo T3's operations and CLRs (??)
- Complex logic for interleaved operations on same page
Repeat History Approach (ARIES):
- Redo ALL operations: T1, T2, T3, and CLRs
- Database now at exact crash-time state
- Undo T2's operations (was active)
- Continue T3's undo from where it left off (CLRs already applied)
- Clean, separated phases
Repeat History creates a clean separation: Redo's job is to restore crash-time state. Undo's job is to clean up uncommitted work. Neither phase needs to understand the other's concerns. This separation makes the algorithm easier to understand, implement, and verify.
Principle 3: Steal/No-Force Buffer Management
Buffer management policies determine when dirty pages are written to disk. These policies deeply affect both performance and recovery requirements. ARIES is designed to support the most flexible policies: Steal and No-Force.
The Buffer Policy Matrix
Two independent decisions define buffer policy:
- Steal vs. No-Steal: Can we flush dirty pages from uncommitted transactions?
- Force vs. No-Force: Must we flush all dirty pages at commit time?
| Policy | Steal (Uncommitted flushes OK) | No-Steal (Only commit flushes) |
|---|---|---|
| Force (Flush at commit) | Needs Undo + Redo | Needs Undo only, fastest recovery |
| No-Force (No flush at commit) | Needs Undo + Redo | Needs Redo only |
Why Steal?
Without steal, the buffer pool is constrained:
- A long-running transaction might dirty many pages
- Those pages cannot be flushed until commit
- Other transactions needing buffer space must wait
- System can run out of buffers
Steal allows the buffer manager to flush any page when it needs space, even from uncommitted transactions. This requires undo capability (the flushed change might need to be reversed).
Why No-Force?
Without no-force, every commit requires flushing potentially many pages:
- Transaction modifies 1000 pages
- Commit requires 1000 random writes
- Commit latency becomes enormous
- I/O bandwidth consumed by redundant writes (page might be modified again soon)
No-force allows commit to complete with just a log flush (sequential, fast). Modified pages are written later, possibly after multiple modifications (fewer total writes). This requires redo capability (committed changes might not be on disk).
Steal + No-Force: Maximum Flexibility, Maximum Requirements
ARIES is designed for the steal + no-force combination:
- Steal requires undo (uncommitted changes might be on disk)
- No-Force requires redo (committed changes might not be on disk)
- ARIES provides both through its comprehensive logging
This combination offers optimal performance: the buffer manager has complete freedom, and commits are fast (log flush only).
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
// Demonstrating Steal/No-Force behavior class BufferManager { private bufferPool: Map<PageID, Page> = new Map(); private poolSize: number = 1000; // Maximum pages in memory /** * STEAL Policy: * When buffer pool is full, we can evict ANY dirty page. * Even from uncommitted transactions! */ evictPage(pageId: PageID): void { const page = this.bufferPool.get(pageId); if (page.isDirty) { // WAL: Must flush log before page this.walEnforcer.beforePageFlush(page); // Write dirty page to disk // This is STEAL - page might be from uncommitted txn this.storage.writePage(pageId, page); } // Free the buffer slot this.bufferPool.delete(pageId); } /** * NO-FORCE Policy: * When transaction commits, we do NOT force dirty pages. * Only the log is forced. */ onTransactionCommit(txnId: TransactionID): void { // Write and force COMMIT log record const commitLSN = this.logManager.writeCommit(txnId); this.logManager.flushToLSN(commitLSN); // That's it! No page flushing required. // Pages will be flushed later by: // - Background checkpoint process // - Buffer replacement when slots needed // - System shutdown }} // Normal operation timeline:// 1. T1 modifies Page A → log written, PageA dirty in buffer// 2. Buffer pool full, Page A evicted (STEAL)// 3. T1 modifies Page B → log written, PageB dirty in buffer// 4. T1 commits → ONLY log flushed (NO-FORCE)// 5. Page B still dirty in buffer, written later//// If crash after step 4:// - T1 is committed (COMMIT record in log)// - Page A is on disk (was evicted)// - Page B is lost (was in buffer)// - Recovery: Redo PageB's change from log (no-force redo)// - No undo needed for T1 (committed)Steal/No-Force gives maximum buffer management flexibility and minimal commit latency. ARIES's sophisticated logging (undo information for steal, redo information for no-force) is the price of this flexibility. The result is a system optimized for high throughput: commits are fast, buffer pool is flexible, and recovery handles any resulting state.
Key Recovery Invariants
ARIES's correctness rests on several invariants—properties that must always hold. These invariants can be used to reason about correctness and guide implementation.
Invariant 1: WAL Ordering
For any page P on stable storage, the log is flushed to at least P.PageLSN.
This is the WAL rule expressed as an invariant. It ensures undo information is always available for any change that might be on disk.
Invariant 2: Commit Durability
A transaction is only confirmed committed if its COMMIT record is on stable storage.
This ensures the commit decision survives crashes. Combined with redo, it guarantees durability.
Invariant 3: CLR Redo-Only
Compensation Log Records are never undone; they are only redone.
This invariant is central to crash-during-recovery handling. CLRs record undo progress; undoing them would mean redoing original operations.
Invariant 4: UndoNextLSN Correctness
A CLR's UndoNextLSN equals the undone record's PrevLSN.
This ensures the undo chain skips over already-undone work correctly.
| Invariant | Formal Statement | Consequence if Violated |
|---|---|---|
| WAL Ordering | ∀ page P on disk: FlushedLSN ≥ P.PageLSN | Undo impossible → corruption |
| Commit Durability | COMMIT on stable storage before return | Committed txn might vanish |
| CLR Redo-Only | CLRs never have UndoInfo processed | Undo-of-undo = redo original |
| UndoNextLSN Skip | CLR.UndoNextLSN = UndoneRecord.PrevLSN | Re-undo of already-undone work |
| PageLSN Currency | PageLSN ≥ max(applied record LSNs) | Missed redo detection |
Invariant 5: Log Monotonicity
LSNs are assigned in strictly increasing order; the log is append-only.
No record can be inserted in the middle of the log. This ensures LSN comparison correctly reflects temporal ordering.
Invariant 6: Redo Idempotence
Applying an operation multiple times produces the same result as applying it once.
This allows recovery to safely redo operations without tracking whether they've been applied. PageLSN optimization skips redundant redo, but correctness doesn't depend on it.
Invariant 7: Transitive Closure Completion
After undo completes, no effects of uncommitted transactions remain in the database.
This is the atomicity guarantee: all-or-nothing. Either the transaction's COMMIT record exists (all effects persist) or it doesn't (no effects persist).
These invariants aren't just guidelines—they enable formal proofs of correctness. The ARIES papers demonstrate that if these invariants hold, recovery will always produce a consistent database. Implementation bugs that violate these invariants can cause subtle, hard-to-diagnose corruption.
Exploiting Operation Semantics
The 'S' in ARIES stands for Semantics. Unlike purely mechanical recovery approaches, ARIES exploits knowledge about what operations mean to optimize logging and recovery.
Semantic Knowledge Examples
-
Operation Inverses
ARIES knows that increment and decrement are inverses:
- Redo: Add 10 to balance
- Undo: Subtract 10 from balance
This is more compact than storing before/after images.
-
Compensation Understanding
ARIES knows that undoing an undo would mean redoing the original:
- Original: Insert row
- Undo: Delete row (CLR)
- Undo-of-undo: Insert row again (WRONG!)
Hence CLRs are redo-only.
-
Page Structure Awareness
Physiological logging knows page structure:
- "Set slot 5's column 3 to value V"
- More compact than full page image
- Deterministic within the page context
12345678910111213141516171819202122232425262728293031323334353637383940
// How ARIES exploits operation semantics // Example 1: Numeric operation semantics// Physical approach: Store before (500) and after (510) values = 8 bytes// Semantic approach: Store "ADD 10" = 2 bytes + identifier const semanticUpdate = { operation: "INCREMENT", column: "balance", amount: 10, // Undo is implicitly DECREMENT by 10}; // Example 2: String operation semantics// Physical: Store entire before and after string// Semantic: Store the operation const stringUpdate = { operation: "APPEND", column: "notes", value: " - Updated on 2024-03-15", // Undo would truncate the appended portion}; // Example 3: Complex operation semantics// Splitting a B-tree node const btreeOperation = { operation: "SPLIT_NODE", nodeId: "N123", splitKey: 500, newNodeId: "N124", // Represents complex structural change // Undo: merge nodes back together}; // The semantic approach enables:// 1. Smaller log records (operation vs. full state)// 2. Compounding (multiple operations might simplify)// 3. Logical understanding of inverse operationsSemantic Benefits
-
Smaller Logs: Storing "increment by 10" is smaller than storing before/after images for every row.
-
Operation Compression: Some systems can combine operations (set X to 5, then set X to 10 → just set X to 10).
-
Nested Top Actions: ARIES supports operations that must not be undone even if the containing transaction aborts (e.g., allocating a new page for a split).
-
Logical Undo: For some operations, the inverse isn't byte-level reversal but a semantic inverse (delete the row we inserted, even if its physical location changed).
ARIES supports 'nested top actions'—sub-operations that commit independently within a transaction. Example: allocating a new page during a B-tree split. Even if the transaction aborts, the page allocation might need to persist (otherwise the page is leaked). ARIES logs these with special handling to prevent their undo.
Recovery Correctness: A Proof Sketch
How do we know ARIES recovery is correct? The full formal proof is beyond our scope, but we can sketch the key arguments.
What We Must Prove
- Durability: All effects of committed transactions are present after recovery.
- Atomicity: No effects of uncommitted transactions are present after recovery.
- Consistency: Database is in a valid state after recovery.
- Termination: Recovery always completes in finite time.
Durability Proof Sketch
Claim: After recovery, all committed transaction effects are in the database.
Argument:
- A transaction is committed iff its COMMIT record is on stable storage (by definition).
- For a committed transaction T, WAL ensures all of T's log records are on stable storage before COMMIT.
- Analysis phase finds T in the TT (or adds it when scanning).
- Redo phase replays ALL log records, including T's.
- T has COMMIT record, so T is not in the loser set.
- Undo phase does not undo T's effects.
- Therefore, T's effects remain in the database. ∎
Atomicity Proof Sketch
Claim: After recovery, no uncommitted transaction effects persist.
Argument:
- A transaction is uncommitted iff it lacks a COMMIT record on stable storage.
- Analysis identifies such transactions (state = UNDO).
- Redo restores crash-time state, including uncommitted changes.
- Undo processes all loser transactions.
- For each loser T, undo traverses T's log chain via PrevLSN.
- Each UPDATE is undone; each CLR is followed via UndoNextLSN.
- After processing T's entire chain (PrevLSN = null), all T's effects are reversed.
- Therefore, no uncommitted effects remain. ∎
Crash-During-Recovery Correctness
Claim: Recovery is correct even if crashes occur during recovery.
Argument:
- Redo is idempotent—repeating it produces the same state.
- During undo, we write CLRs for each undo action.
- CLRs contain UndoNextLSN pointing past the undone record.
- If crash occurs during undo: a. Subsequent analysis finds CLRs in the log. b. Redo replays CLRs, restoring undo progress. c. Undo continues from UndoNextLSN, not redoing completed undo.
- Each recovery attempt makes progress (undoes at least one operation before crash, or completes).
- Total undo work is bounded; eventually undo completes.
- Therefore, recovery succeeds even with nested crashes. ∎
The original ARIES papers include detailed proofs. Modern research has also applied formal verification techniques (like TLA+) to ARIES-style algorithms, confirming their correctness under precisely specified assumptions. This rigor is why ARIES is trusted for critical systems.
Design Trade-offs and Alternatives
ARIES makes specific design choices. Understanding the alternatives illuminates why these choices were made.
Trade-off 1: Log Size vs. Recovery Speed
- More logging (detailed redo/undo info) → faster, simpler recovery
- Less logging (compact records) → slower recovery, re-computation needed
ARIES chooses comprehensive logging for predictable recovery.
Trade-off 2: Checkpoint Overhead vs. Recovery Bound
- Frequent checkpoints → bounded recovery time, but checkpoint overhead
- Rare checkpoints → less overhead, but longer recovery
ARIES supports tunable checkpoint frequency to balance these.
Trade-off 3: Repeat History vs. Selective Redo
- Repeat history → simpler algorithm, some redundant redo
- Selective redo → less work if many transactions uncommitted, complex logic
ARIES chooses repeat history for simplicity and correctness.
Trade-off 4: Physical vs. Logical Logging
- Physical → larger logs, simpler redo, page-level locking only
- Logical → smaller logs, complex redo, concurrency issues
- Physiological → balanced, supports fine-grained locking
ARIES chooses physiological as the best balance.
| Aspect | ARIES Approach | Alternative | Why ARIES Chose This |
|---|---|---|---|
| Redo scope | Repeat all history | Commit-time redo | Simplicity, CLR handling |
| Undo logging | CLRs with UndoNextLSN | Just rewind | Crash-during-recovery |
| Buffer policy | Steal/No-Force | No-Steal/Force | Performance flexibility |
| Checkpoint | Fuzzy | Quiescent | No service interruption |
| Logging | Physiological | Physical or Logical | Balance of compactness and determinism |
Modern Variations
While ARIES principles remain standard, modern systems sometimes vary:
- In-memory databases may use different logging (less redo emphasis)
- Distributed databases extend ARIES with distributed recovery protocols
- SSD-optimized systems may change I/O patterns but keep ARIES logic
- Append-only stores (like CockroachDB) use MVCC heavily, altering undo needs
But even these systems typically maintain ARIES's core invariants and three-phase structure.
ARIES was designed in the late 1980s, yet its principles remain dominant. This longevity stems from its principled foundation—the design isn't ad-hoc but built on proven concepts. Modern systems may adapt details, but the fundamental approach (WAL, repeat history, CLRs) persists.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.