Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Page journals: replay committed index changes

Last updated: 5 Oct 20269 min read
tutorial
IntermediateBy AITrove Editorial

A page-index update may touch several pages. An ordered redo journal can group proposed page contents by transaction ID and make replay apply a group only after its commit marker appears. This teaching model stores begin, page, and commit records in a list. Recovery starts from a checkpoint page map, accumulates pending page changes, and applies those changes when it reads a matching commit. A transaction ending without commit is ignored. The model deliberately keeps every record and page in memory; it does not perform a disk flush, validate a torn record, undo an uncommitted page already written to storage, or guarantee atomicity of a physical commit marker.

Operational case

A committed page change rewrites P-19 as rebalanced and introduces P-26 as a new leaf. A later proposed split for P-52 has no commit record, representing an interrupted operation. Replay returns only P-19 and P-26 from the committed group. It does not manufacture P-52. The order of records is part of the structure: a page record without its begin or a commit without its begin raises. In a real storage engine, log durability must precede publishing dirty index pages, and recovery policy must account for any uncommitted changes that reached disk. This small replay model assumes they did not.

Working Python program

python
class PageRedoJournal:
    def __init__(self, checkpoint_pages):
        self.checkpoint_pages = dict(checkpoint_pages)
        self.records = []
        self.next_transaction = 47

    def record(self, page_changes, commit=True):
        transaction = self.next_transaction
        self.next_transaction += 1
        self.records.append(("begin", transaction))
        for page_id, contents in page_changes.items():
            self.records.append(("page", transaction, page_id, contents))
        if commit:
            self.records.append(("commit", transaction))
        return transaction

    def recover(self):
        pages = dict(self.checkpoint_pages)
        pending = {}
        for record in self.records:
            action, transaction, *details = record
            if action == "begin":
                pending[transaction] = {}
            elif action == "page":
                if transaction not in pending:
                    raise ValueError("page record without begin")
                page_id, contents = details
                pending[transaction][page_id] = contents
            elif action == "commit":
                if transaction not in pending:
                    raise ValueError("commit record without begin")
                pages.update(pending.pop(transaction))
            else:
                raise ValueError("unknown journal record")
        return pages


journal = PageRedoJournal({"P-19": "original"})
journal.record({"P-19": "rebalanced", "P-26": "new leaf"})
journal.record({"P-52": "unfinished split"}, commit=False)
print(journal.recover())

Output

Output
{'P-19': 'rebalanced', 'P-26': 'new leaf'}

Time, space, and tradeoff

For R journal records and P checkpoint pages, replay scans O(R) records and copies O(P) pages; applying page updates costs O(U) for U committed update records, already bounded by R. The in-memory journal and pending transaction maps use O(R) additional space in the worst case, while the recovered page map uses O(P + U) entries. A production implementation needs durable write ordering, checksums or equivalent record validation, truncation or checkpointing, concurrency control, and an undo or no-steal policy consistent with its page-flush rules. This example establishes a replay invariant, not a database durability guarantee.

Common Mistakes

  • Do not apply an uncommitted page record during redo-only recovery.
  • Do not flush uncommitted data pages and assume this model can undo them.
  • Do not claim a Python list models durable ordered writes or torn-record detection.
  • Do not reuse transaction IDs in one untrimmed journal without a generation rule.

Connected lessons

Apply this contract in the reversible depot release project, then check the operations quiz.

Framed journals: detect a torn tail before replay examines the next boundary.

data structures
range-query-structures
Storage details