When a relational engine rolls back a transaction, the undo work itself is written to the transaction log as compensation log records. What problem do those records solve, and what happens if the server crashes again while a rollback is still in progress?
answer
- undo modifies pages, so undo must be logged
- CLR carries undoNext pointer
- CLRs are redo-only, never undone
- second crash resumes, never restarts rollback
- bounded work, guaranteed termination
basics
~20 sA compensation log record (CLR) logs each reversal as it is performed and carries a pointer to the next record still to be undone. That makes rollback restartable and never repeated: after a second crash, recovery reads the CLRs, sees which reversals are done, and resumes from the pointer. CLRs are redo-only and are never themselves undone.
solid answer
~50 sUndo changes pages, so undo must be logged like any other change; otherwise a crash mid-rollback would lose the reversals or, worse, cause them to be applied twice. A **compensation log record** describes the reversal of one earlier update. It carries: - the change it applied (so redo can reapply it, since it modified a page), - an **undoNext** pointer to the LSN of the next record of that transaction that still needs undoing. On a second crash, analysis sees the transaction is still a loser, redo reapplies the CLRs like any other change, and undo follows undoNext, resuming exactly where it stopped. Work already reversed is never reversed again, which matters because reversals are not idempotent: undoing a decrement twice would over-credit. CLRs are **redo-only**: they are never undone. That bounds the work, preventing an infinite regress of undoing the undo, and guarantees rollback terminates no matter how many crashes interrupt it.
code
text · 12 linesLSN 100 T update A (prevLSN -)
LSN 110 T update B (prevLSN 100)
LSN 120 T update C (prevLSN 110)
-- rollback starts --
LSN 200 CLR undo of 120, undoNext = 110
LSN 210 CLR undo of 110, undoNext = 100
<< crash >>
restart: T still a loser (no end record)
redo: reapply 200 and 210 if missing from pages
undo: last record 210 is a CLR -> jump to undoNext 100
undo 100, write CLR undoNext = null, write end recordgo deeper
Say that the undo work is itself logged so it is not lost, and that these records mark which reversals are already done.
Name the undoNext pointer and explain how a second crash resumes rather than repeats, and that reversals are not idempotent so once-only matters.
Add that CLRs are redo-only for termination, and connect the mechanism to normal ROLLBACK, deadlock victims and savepoints sharing the same code path.
Draw the operational conclusions: rollback costs log and I/O proportional to the work done, it cannot be short-circuited by restarting, and it lengthens recovery time, which argues for bounding transaction size in bulk workloads.
## Why undo has to be logged at all Rollback is not a metadata operation; it physically modifies pages: putting old values back, removing inserted tuples, reinserting deleted ones. Every page modification in a write-ahead-logged engine must be described in the log before the page can reach disk, otherwise recovery could neither reapply the modification nor even know it happened. So undo generates log records too. But what kind? Logging a reversal as an ordinary update would be a disaster: recovery would then consider that update part of the loser transaction and try to undo *it*, producing an endless alternation between doing and undoing. ARIES solves this with a distinct record type. ## What a compensation log record contains A CLR records that a specific earlier change has now been reversed. Two fields carry the weight: 1. **The redo information**: what the reversal did to the page, so redo can reapply it after a crash. Like any other change, the reversal may or may not have reached the data file. 2. **undoNext**: the LSN of the next log record belonging to the same transaction that still needs to be undone. This is copied from the prevLSN chain of the record that was just undone, effectively skipping over completed work. A CLR has **no undo information**, and that is the point. It is declared redo-only: recovery reapplies CLRs but never reverses them. ## The crash-during-rollback scenario, step by step Suppose transaction T made updates at LSNs 100, 110 and 120 and is being rolled back. - Undo reverses LSN 120 and writes CLR at LSN 200 with undoNext = 110. - Undo reverses LSN 110 and writes CLR at LSN 210 with undoNext = 100. - The server crashes before reversing LSN 100. On restart, analysis walks the log and finds T with no end record, so T is still a loser, and its last record is LSN 210. Redo repeats history, reapplying whatever is missing, including the two CLRs, so the pages reflect that 120 and 110 have been reversed. Undo then begins at T's last record, LSN 210, sees a CLR, and does not attempt any reversal for it; it simply follows undoNext to LSN 100, reverses that update, writes a final CLR, and writes T's end record. The result: each original update was reversed exactly once, across two crashes. ## Why exactly-once matters here Reversals are no more idempotent than the original changes. Undoing an insert removes a tuple; doing that twice can remove a different tuple that has since taken the slot. Undoing an update writes back a before-image; if a subsequent CLR-driven state has moved on, rewriting the old image can clobber correct data. Undoing a balance decrement adds the amount back; twice, and the account is wrong. The undoNext pointer chain is what enforces once-and-only-once. ## Termination and bounded work If CLRs could themselves be undone, a pathological loop is possible: crash, undo the undo, crash, undo that, forever, with the log growing each round. Making CLRs redo-only breaks the loop and gives a strict guarantee: the total undo work for a transaction is bounded by the number of its original updates, no matter how many times recovery is interrupted. Each crash strictly advances the rollback rather than restarting it. ## The same machinery serves ordinary rollback This is not recovery-only plumbing. A live ROLLBACK statement, a deadlock victim being aborted, a statement failing and rolling back to a savepoint: all walk the transaction's prevLSN chain backwards, apply reversals and write CLRs. Savepoints are simply a stopping point in that chain. Because normal aborts and crash recovery share the implementation, the recovery path is exercised constantly in production instead of only after failures. ## Operational consequences worth naming - **Rollback writes log.** A huge transaction that is aborted generates substantial additional log volume and I/O; rollback is not free and is not instantaneous. Aborting a bulk update can take as long as the update itself, occasionally longer. - **You cannot shorten rollback by killing the process.** Restarting only resumes the rollback from where the CLRs left off, and now with downtime added. - **Recovery time includes undo.** A crash that catches a long-running write transaction in flight extends the outage by the cost of reversing it, which is an argument for chunking bulk work into smaller transactions. ## One-line summary CLRs make rollback durable, resumable and non-repeating; being redo-only, they guarantee it terminates.
- Why are compensation log records never undone?To guarantee termination and bounded work. If a CLR could be undone, an interrupted rollback could undo its own undo, then undo that, growing the log without ever finishing. Declaring CLRs redo-only means each crash strictly advances the rollback, and the total undo work for a transaction can never exceed the number of updates it originally made.
- A ROLLBACK of a large bulk update is taking a long time. Can the operator speed it up by restarting the database?No, it makes things worse. The reversals already performed are recorded as CLRs, so recovery resumes the rollback from where it stopped rather than skipping it, and the remaining work still has to be done, now during a startup outage with the database unavailable. The right fix is preventative: split bulk work into smaller transactions so any single rollback is bounded.
- How do savepoints relate to this mechanism?A savepoint marks a position in the transaction's backward chain of log records. Rolling back to a savepoint walks that chain from the current end and applies reversals, writing CLRs exactly as a full rollback does, but stops when it reaches the marked position instead of continuing to the transaction's first record. The transaction then continues from there, still open.
saying these in an interview costs you the question
- Saying rollback is a cheap in-memory operation that writes no log
- Claiming a second crash during rollback restarts the rollback from the beginning
- Believing CLRs are undone like ordinary updates
- Thinking CLRs exist only for crash recovery and not for ordinary ROLLBACK or deadlock aborts
- Suggesting that killing or restarting the server cancels a long-running rollback