Scenario Setup:

  • Pages: P, P
  • Transactions: T, T, T
  • Initial State: Both P and P on disk have some initial data. Their PageLSN on disk is . The buffer pool is initially empty/clean.

Simulated Log Sequence (Normal Operation + Crash):

Assume the following log records are written sequentially to stable storage. We also note the state of the relevant data structures (in memory) as things progress before the crash.

  1. LSN : Update(T1, P1, PrevLSN=0, Redo=Op1, Undo=UndoOp1)
    • Effect: T1 modifies P1 in the buffer pool.
    • TxnTable: { T1: {Status: Running, LastLSN: $10$} }
    • DPT: { P1: {RecLSN: $10$} } (P1 becomes dirty)
  2. LSN : Update(T2, P2, PrevLSN=0, Redo=Op2, Undo=UndoOp2)
    • Effect: T2 modifies P2 in the buffer pool.
    • TxnTable: { T1: {Status: Running, LastLSN: $10$}, T2: {Status: Running, LastLSN: $20$} }
    • DPT: { P1: {RecLSN: $10$}, P2: {RecLSN: $20$} } (P2 becomes dirty)
  3. LSN : Update(T1, P2, PrevLSN=10, Redo=Op3, Undo=UndoOp3)
    • Effect: T1 modifies P2 in the buffer pool.
    • TxnTable: { T1: {Status: Running, LastLSN: $30$}, T2: {Status: Running, LastLSN: $20$} }
    • DPT: { P1: {RecLSN: $10$}, P2: {RecLSN: $20$} } (P2 already dirty, RecLSN doesn’t change)
  4. LSN : BeginCheckpoint
  5. LSN : EndCheckpoint( ActiveTxn=[T1(LastLSN=$30$), T2(LastLSN=$20$)], DirtyPages=[P1(RecLSN=$10$), P2(RecLSN=$20$)] )
    • Effect: Checkpoint written, recording the current TxnTable and DPT state.
  6. LSN : Update(T3, P1, PrevLSN=0, Redo=Op4, Undo=UndoOp4)
    • Effect: T3 starts and modifies P1 in the buffer pool.
    • TxnTable: { T1: {Status: Running, LastLSN: $30$}, T2: {Status: Running, LastLSN: $20$}, T3: {Status: Running, LastLSN: $60$} }
    • DPT: { P1: {RecLSN: $10$}, P2: {RecLSN: $20$} } (P1 already dirty, RecLSN doesn’t change)
  7. LSN : Commit(T2)
    • Effect: T2 successfully commits. Its log records (LSN , ) are stable.
    • TxnTable: { T1: {Status: Running, LastLSN: $30$}, T2: {Status: Committed, LastLSN: $20$}, T3: {Status: Running, LastLSN: $60$} }
  8. LSN : Update(T1, P1, PrevLSN=30, Redo=Op5, Undo=UndoOp5)
    • Effect: T1 modifies P1 again.
    • TxnTable: { T1: {Status: Running, LastLSN: $80$}, T2: {Status: Committed, LastLSN: $20$}, T3: {Status: Running, LastLSN: $60$} }
    • DPT: { P1: {RecLSN: $10$}, P2: {RecLSN: $20$} }
  9. CRASH! Assume the system crashes now. All log records up to LSN are safe on disk. Assume no pages were flushed to disk after the checkpoint (so P and P on disk still have PageLSN = 0).

Recovery Process:

Phase 1: Analysis

  1. Find Checkpoint: Locate the last complete checkpoint (LSN - ).
  2. Load State: Initialize the TxnTable and DPT from the EndCheckpoint record (LSN ):
    • TxnTable: { T1: {Status: Running, LastLSN: $30$}, T2: {Status: Running, LastLSN: $20$} }
    • DPT: { P1: {RecLSN: $10$}, P2: {RecLSN: $20$} }
  3. Scan Forward from Checkpoint (LSN ):
    • LSN : Update(T3, P1, ...)
      • Add T3 to TxnTable: { ..., T3: {Status: Running, LastLSN: $60$} }
      • P1 already in DPT, no change to DPT.
    • LSN : Commit(T2)
      • Update T2 status in TxnTable: { ..., T2: {Status: Committed, LastLSN: $20$}, ... }
    • LSN : Update(T1, P1, ...)
      • Update T1 LastLSN in TxnTable: { T1: {Status: Running, LastLSN: $80$}, ... }
      • P1 already in DPT, no change to DPT.
  4. End of Log: Scan finishes.
  5. Analysis Outcome:
    • Final TxnTable: { T1: {Status: Running, LastLSN: $80$}, T2: {Status: Committed, LastLSN: $20$}, T3: {Status: Running, LastLSN: $60$} }
    • Final DPT: { P1: {RecLSN: $10$}, P2: {RecLSN: $20$} }
    • Loser Transactions: T1, T3 (Status is ‘Running’).
    • redoLSN: min(RecLSN values in DPT) = min() = .

Phase 2: Redo (“Repeating History”)

  1. Start Scan: Begin scanning the log forward from `redoLSN = .
  2. Process Log Records:
    • LSN : Update(T1, P1, ...)
      • Is P in DPT? Yes. Is RecLSN(P)= LSN ? Yes.
      • Fetch P (Disk PageLSN=). Is PageLSN() < LSN()? Yes.
      • Redo Op1 on P (in buffer). Set P’s buffer PageLSN = .
    • LSN : Update(T2, P2, ...)
      • Is P in DPT? Yes. Is RecLSN(P)= LSN ? Yes.
      • Fetch P (Disk PageLSN=). Is PageLSN() < LSN()? Yes.
      • Redo Op2 on P (in buffer). Set P’s buffer PageLSN = .
    • LSN : Update(T1, P2, ...)
      • Is P in DPT? Yes. Is RecLSN(P)= LSN ? Yes.
      • Fetch P (Buffer PageLSN=). Is PageLSN() < LSN()? Yes.
      • Redo Op3 on P (in buffer). Set P’s buffer PageLSN = .
    • LSN : BeginCheckpoint - Skip.
    • LSN : EndCheckpoint - Skip.
    • LSN : Update(T3, P1, ...)
      • Is P in DPT? Yes. Is RecLSN(P)= LSN ? Yes.
      • Fetch P (Buffer PageLSN=). Is PageLSN() < LSN()? Yes.
      • Redo Op4 on P (in buffer). Set P’s buffer PageLSN = .
    • LSN : Commit(T2) - Skip (no data modification).
    • LSN : Update(T1, P1, ...)
      • Is P in DPT? Yes. Is RecLSN(P)= LSN ? Yes.
      • Fetch P (Buffer PageLSN=). Is PageLSN() < LSN()? Yes.
      • Redo Op5 on P (in buffer). Set P’s buffer PageLSN = .
  3. End of Log: Redo phase finishes.
  4. Redo Outcome: The buffer pool now reflects the exact state at the moment of crash.
    • P contains effects of Op1 (LSN ), Op4 (LSN ), Op5 (LSN ). Buffer PageLSN = .
    • P contains effects of Op2 (LSN ), Op3 (LSN ). Buffer PageLSN = .

Phase 3: Undo

  1. Identify Losers: From Analysis, losers are T1 and T3.
  2. Initialize ToUndo: Get LastLSN for each loser: ToUndo = { (from T1), (from T3) }.
  3. Scan Backward: Start processing from max() = .
    • Process LSN : Update(T1, P1, PrevLSN=30, ...)
      • Is LSN in ToUndo? Yes.
      • Write CLR: Append CLR(T1, P1, UndoNextLSN=30, Redo=None, Undo=UndoOp5) to log. Let’s say this gets LSN . (Log LSN must be stable).
      • Apply Undo: Perform UndoOp5 on P in the buffer.
      • Set PageLSN: Set P’s buffer PageLSN = (the LSN of the CLR).
      • Update ToUndo: Remove . Add UndoNextLSN () to ToUndo.
      • ToUndo = { }. Next LSN = max() = .
    • Process LSN : Commit(T2) - Skip (not in ToUndo).
    • Process LSN : Update(T3, P1, PrevLSN=0, ...)
      • Is LSN in ToUndo? Yes.
      • Write CLR: Append CLR(T3, P1, UndoNextLSN=0, Redo=None, Undo=UndoOp4) to log. Let’s say this gets LSN . (Log LSN must be stable).
      • Apply Undo: Perform UndoOp4 on P in the buffer.
      • Set PageLSN: Set P’s buffer PageLSN = .
      • Update ToUndo: Remove . Add UndoNextLSN () (i.e., add nothing).
      • ToUndo = { }. Next LSN = .
    • Process LSN : EndCheckpoint - Skip.
    • Process LSN : BeginCheckpoint - Skip.
    • Process LSN : Update(T1, P2, PrevLSN=10, ...)
      • Is LSN in ToUndo? Yes.
      • Write CLR: Append CLR(T1, P2, UndoNextLSN=10, Redo=None, Undo=UndoOp3) to log. Let’s say this gets LSN . (Log LSN must be stable).
      • Apply Undo: Perform UndoOp3 on P in the buffer.
      • Set PageLSN: Set P’s buffer PageLSN = .
      • Update ToUndo: Remove . Add UndoNextLSN () to ToUndo.
      • ToUndo = { }. Next LSN = .
    • Process LSN : Update(T2, P2, ...) - Skip (T2 is not a loser).
    • Process LSN : Update(T1, P1, PrevLSN=0, ...)
      • Is LSN in ToUndo? Yes.
      • Write CLR: Append CLR(T1, P1, UndoNextLSN=0, Redo=None, Undo=UndoOp1) to log. Let’s say this gets LSN . (Log LSN must be stable).
      • Apply Undo: Perform UndoOp1 on P in the buffer.
      • Set PageLSN: Set P’s buffer PageLSN = .
      • Update ToUndo: Remove . Add UndoNextLSN ().
      • ToUndo = {}.
  4. ToUndo is Empty: The main backward scan is complete.
  5. Write Abort Records: Append Abort(T1) and Abort(T3) log records (e.g., LSN and LSN ).
  6. Undo Outcome:
    • All effects of loser transactions T1 and T3 have been undone.
    • P is back to its initial state (UndoOp4 undid Op4, UndoOp5 undid Op5, UndoOp1 undid Op1). Its PageLSN is .
    • P now only contains the effects of the committed transaction T2 (Op2 from LSN ). UndoOp3 undid Op3 (from LSN ). Its PageLSN is .
    • The database is now consistent, reflecting only the committed work of T2.

Recovery is complete. The database can now accept new transactions. Note how CLRs ensured that undo operations were logged and wouldn’t be re-attempted incorrectly if a crash happened during the Undo phase itself.