Intermediate Examples
Examples 29-56 move from a single page's bytes to a whole storage engine's moving parts: write-ahead logging and the redo/undo split that makes crash recovery well-defined (LSN ordering, pageLSN-guided redo, checkpoints bounding replay), MVCC's row-version chains and the snapshot-visibility rule that lets readers proceed without locking anything, LRU-K and hit-ratio measurement for the buffer pool, a B-tree's bulk load, internal split propagation, and delete-time rebalancing, the clustered-vs-heap storage distinction, LSM leveled compaction plus the write/read/space amplification triangle a real engine has to balance, Bloom filters closing the read-amplification gap, and finally the row-store-vs-column-store physical layout choice measured directly in bytes touched. Every example.py below is a complete, self-contained, fully type-annotated Python file colocated under learning/code/ex-NN-slug/, verified two ways: python3 example.py prints its own expected output inline via # => comments and was actually run to capture the Output block, and a colocated test_example.py asserts the same behavior under pytest, independently checked with pyright --strict at zero errors.
Example 29: Append-Only Log Replay
ex-29 · exercises co-16
A write-ahead log is an append-only sequence of records that can be replayed in order. This example appends three records to an in-memory log and replays them, confirming the replay order matches the append order exactly.
learning/code/ex-29-append-only-log-replay/example.py
"""Example 29: Append-Only Log Replay."""
from dataclasses import dataclass # => a plain, typed record for one log entry
@dataclass
class LogRecord: # => the smallest unit a WAL ever stores -- just enough to replay one change
seq: int # => this record's position in append order
action: str # => a human-readable description of what happened
# A write-ahead log is APPEND-ONLY (co-16) -- records are never edited or
# reordered once written, so replaying them in the SAME order they were
# appended in is always well-defined, with no ambiguity about what happened
# first.
log: list[LogRecord] = [] # => models a WAL file as an in-memory list, in append order
def append(action: str) -> LogRecord: # => the only way a record enters the log
record = LogRecord(seq=len(log), action=action) # => seq is just "how many records came before"
log.append(record) # => append-only: always added at the END, never inserted elsewhere
return record
def replay() -> list[str]: # => walks the log front-to-back, in append order
return [record.action for record in log] # => list order == log order, by construction
append("insert row-1") # => record 0
append("insert row-2") # => record 1
append("delete row-1") # => record 2
actions = replay()
print(actions) # => Output: ['insert row-1', 'insert row-2', 'delete row-1']
assert actions == ["insert row-1", "insert row-2", "delete row-1"] # => replay order == append order
assert [r.seq for r in log] == [0, 1, 2] # => sequence numbers are strictly increasing, gap-free
print("ex-29 OK") # => Output: ex-29 OKRun: python3 example.py
Output:
['insert row-1', 'insert row-2', 'delete row-1']
ex-29 OKlearning/code/ex-29-append-only-log-replay/test_example.py
"""Example 29: pytest verification for Append-Only Log Replay."""
from example import append, log, replay
def test_replay_matches_append_order() -> None:
log.clear()
append("a")
append("b")
append("c")
assert replay() == ["a", "b", "c"]
def test_sequence_numbers_are_gap_free() -> None:
log.clear()
append("x")
append("y")
assert [r.seq for r in log] == [0, 1] # => no gaps, no reordering, ever
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Appending never inserts, edits, or reorders -- a log's replay order is always identical to its append order, which is the single property recovery (ex-33, ex-64) depends on.
Why it matters: Every WAL example for the rest of this course builds on this one guarantee: records go in at the end, and replay walks them back out in the same order, front to back. A real WAL lives in a file with fsync calls for durability (ex-60), but the append-only, order-preserving contract modeled here is exactly what makes replaying a crashed log a well-defined operation instead of a guess.
Example 30: WAL-Before-Page Ordering Guard
ex-30 · exercises co-16
The write-ahead-log rule requires a log record to reach stable storage before the page it describes is written. This example enforces that ordering explicitly and confirms flushing an undurable page raises instead of silently violating the rule.
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph LR
A["log record<br/>written"]:::blue
B["log fsync'd<br/>to disk"]:::orange
C["page flush<br/>now allowed"]:::teal
A --> B --> C
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-30-wal-before-page-guard/example.py
"""Example 30: WAL-Before-Page Ordering Guard."""
# The WAL rule: a log record must be DURABLE before the page it describes is
# flushed (co-16) -- reversing that order is exactly what makes a crash
# unrecoverable, since the page's change would exist with no log record to
# redo it from.
class WalOrderingError(Exception): # => raised when a flush is attempted out of order
"""Raised when a page flush is attempted before its log record is durable.""" # => documents intent
durable_lsns: set[int] = set() # => LSNs that have actually reached stable storage
def make_durable(lsn: int) -> None: # => simulates fsync-ing the log up through this LSN
durable_lsns.add(lsn)
def flush_page(page_lsn: int) -> None: # => the guarded page-write path
if page_lsn not in durable_lsns: # => the guard: check BEFORE writing the page
raise WalOrderingError(f"log record {page_lsn} is not yet durable -- refusing to flush the page")
print(f"page flushed (log record {page_lsn} was already durable)") # => Output: page flushed line
raised = False # => flips to True only if the guard actually fires below
try:
flush_page(page_lsn=5) # => LSN 5's log record was never made durable
except WalOrderingError: # => the exact exception the guard raises
raised = True # => confirms the guard fired instead of flushing out of order
assert raised # => the page write was refused
make_durable(5) # => NOW the log record is durable
flush_page(page_lsn=5) # => this time the guard passes -- Output: page flushed (log record 5 was already durable)
assert 5 in durable_lsns # => the log record really was made durable before this second flush
print("ex-30 OK") # => Output: ex-30 OKRun: python3 example.py
Output:
page flushed (log record 5 was already durable)
ex-30 OKlearning/code/ex-30-wal-before-page-guard/test_example.py
"""Example 30: pytest verification for the WAL-Before-Page Ordering Guard."""
import pytest
from example import WalOrderingError, durable_lsns, flush_page, make_durable
def test_flush_before_durable_raises() -> None:
durable_lsns.clear()
with pytest.raises(WalOrderingError):
flush_page(page_lsn=1)
def test_flush_after_durable_succeeds() -> None:
durable_lsns.clear()
make_durable(2)
flush_page(page_lsn=2) # => no exception -- the guard passed
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: The guard checks is_log_durable(page_lsn) before any page flush is allowed -- flushing a page whose log record has not reached stable storage yet is refused outright, not merely discouraged.
Why it matters: This ordering, not the log's mere existence, is the entire WAL guarantee: if the page could reach disk before its log record, a crash between those two writes would leave a change with no way to redo it after restart. Every recovery example later in this course (ex-33, ex-64 through ex-67) assumes this exact ordering was respected on the way in -- recovery cannot invent a guarantee the write path did not actually uphold.
Example 31: Monotonic LSN Assignment
ex-31 · exercises co-17
Every log record gets a strictly increasing log sequence number that totally orders the log. This example assigns LSNs to several records and confirms each new one is strictly greater than the last.
learning/code/ex-31-lsn-monotonic-assign/example.py
"""Example 31: Monotonic LSN Assignment."""
# LSNs (Log Sequence Numbers) totally order every WAL record (co-17).
class LsnGenerator: # => a strictly increasing counter -- the WAL's own clock
def __init__(self) -> None: # => starts before any LSN has been issued
self._next: int = 1 # => LSN 0 is reserved to mean "nothing written yet"
def next_lsn(self) -> int: # => issues one new LSN, guaranteed greater than every prior one
lsn = self._next # => capture the value about to be issued
self._next += 1 # => strictly increasing: no reuse, no gaps introduced by this generator
return lsn # => hand back this call's freshly issued LSN
gen = LsnGenerator() # => a fresh generator for this example
lsns = [gen.next_lsn() for _ in range(5)] # => issue five LSNs in a row
print(lsns) # => Output: [1, 2, 3, 4, 5]
for i in range(1, len(lsns)): # => walk every consecutive pair
assert lsns[i] > lsns[i - 1] # => each new LSN is STRICTLY greater than the previous one
print("ex-31 OK") # => Output: ex-31 OKRun: python3 example.py
Output:
[1, 2, 3, 4, 5]
ex-31 OKlearning/code/ex-31-lsn-monotonic-assign/test_example.py
"""Example 31: pytest verification for Monotonic LSN Assignment."""
from example import LsnGenerator
def test_lsns_strictly_increase() -> None:
gen = LsnGenerator()
a = gen.next_lsn()
b = gen.next_lsn()
c = gen.next_lsn()
assert a < b < c
def test_two_generators_are_independent() -> None:
gen1 = LsnGenerator()
gen2 = LsnGenerator()
assert gen1.next_lsn() == gen2.next_lsn() == 1 # => each generator starts its own count at 1
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: An LSN generator is nothing more than a counter that only ever increases -- but that simplicity is exactly what lets recovery compare two LSNs and know, unambiguously, which event happened first.
Why it matters: LSNs are the WAL's clock: ex-32's pageLSN comparison, ex-35's checkpoint bound, and ex-64's ARIES analysis phase all depend on being able to order log records and page states correctly using nothing but integer comparison. A monotonic counter is the simplest possible mechanism that provides that total order, which is exactly why real WAL implementations use one.
Example 32: Skip Already-Applied Records via pageLSN
ex-32 · exercises co-17
A page's own pageLSN records the LSN of the last log record already applied to it, which is what lets redo skip work that already happened. This example compares record LSNs against a page's pageLSN and confirms an already-applied record is skipped.
learning/code/ex-32-pagelsn-skip-applied/example.py
"""Example 32: Skip Already-Applied Records via pageLSN."""
from dataclasses import dataclass # => a plain, typed record for one log entry
@dataclass # => a plain, typed record -- no custom __init__ needed
class LogRecord: # => a WAL record that changed exactly one page
lsn: int # => this record's own log sequence number
page_id: int # => which page it changed
new_value: str # => the value it wrote
def redo(log: list[LogRecord], page_lsns: dict[int, int]) -> dict[int, str]: # => returns applied values
applied: dict[int, str] = {} # => page_id -> value, only for records actually re-applied
for record in log: # => walk the log in LSN order -- ARIES calls this "repeating history"
current_page_lsn = page_lsns.get(record.page_id, 0) # => 0 means "page never touched before crash"
if record.lsn > current_page_lsn: # => the redo rule: only re-apply what is NEWER than the page
applied[record.page_id] = record.new_value # => genuinely re-applied
page_lsns[record.page_id] = record.lsn # => the page's pageLSN now reflects this record too
# => record.lsn <= current_page_lsn means this change was ALREADY on the page before the crash
return applied # => the caller can see exactly which pages were genuinely re-applied
log = [ # => three records against the same page, only one of which still needs redo
LogRecord(lsn=1, page_id=10, new_value="v1"), # => already reflected on page 10 before the crash
LogRecord(lsn=2, page_id=10, new_value="v2"), # => also already reflected (page_lsns[10] starts at 2)
LogRecord(lsn=3, page_id=10, new_value="v3"), # => NOT yet reflected -- this one needs redo
] # => end of the log fixture
page_lsns = {10: 2} # => page 10's pageLSN says "everything up to LSN 2 is already applied"
applied = redo(log, page_lsns) # => run redo over the whole log
print(applied) # => Output: {10: 'v3'}
assert applied == {10: "v3"} # => only the record whose LSN exceeded the page's pageLSN was re-applied
assert page_lsns[10] == 3 # => the page's pageLSN now reflects the redo that actually happened
print("ex-32 OK") # => Output: ex-32 OKRun: python3 example.py
Output:
{10: 'v3'}
ex-32 OKlearning/code/ex-32-pagelsn-skip-applied/test_example.py
"""Example 32: pytest verification for Skip Already-Applied Records via pageLSN."""
from example import LogRecord, redo
def test_records_at_or_below_pagelsn_are_skipped() -> None:
log = [LogRecord(lsn=1, page_id=1, new_value="a"), LogRecord(lsn=2, page_id=1, new_value="b")]
page_lsns = {1: 2}
assert redo(log, page_lsns) == {} # => both records were already reflected on the page
def test_records_above_pagelsn_are_reapplied() -> None:
log = [LogRecord(lsn=1, page_id=1, new_value="a"), LogRecord(lsn=2, page_id=1, new_value="b")]
page_lsns = {1: 0}
assert redo(log, page_lsns) == {1: "b"} # => the LAST record wins -- page ends at its final value
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Redo only re-applies a log record whose LSN is GREATER than the page's current pageLSN -- anything less than or equal to it was already reflected in the page before the crash and must not be re-applied.
Why it matters: Without this check, ARIES-style redo (ex-65) would blindly reapply every logged change on every recovery, which is both wasteful and, for non-idempotent operations, actively wrong. The pageLSN comparison is what makes redo idempotent: replaying the same log against a page that already reflects some of those changes produces the correct final state regardless of how far the page's changes had already progressed before the crash.
Example 33: WAL Redo Replays Committed Writes
ex-33 · exercises co-19
After a simulated crash, replaying the log must restore every committed write, since a committed transaction's changes are guaranteed durable. This example discards in-memory state, replays the log, and confirms committed writes reappear.
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph LR
A["crash<br/>state lost"]:::blue
B["replay log<br/>start to end"]:::orange
C["committed writes<br/>restored"]:::teal
A --> B --> C
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-33-wal-redo-committed/example.py
"""Example 33: WAL Redo Replays Committed Writes."""
# ARIES-style redo replays EVERY logged write (co-19) -- undo removes the losers next.
from dataclasses import dataclass # => a plain, typed record for one log entry
@dataclass # => a plain, typed record -- no custom __init__ needed
class LogRecord: # => a WAL record for one key's write, tagged with its owning transaction
txn_id: int # => which transaction wrote this
key: str # => the key that was written
value: str # => the value it was set to
log: list[LogRecord] = [ # => one committed write, one that never committed
LogRecord(txn_id=1, key="a", value="committed-a"), # => txn 1 later commits
LogRecord(txn_id=2, key="b", value="uncommitted-b"), # => txn 2 never commits (crashes mid-flight)
] # => end of the log fixture
committed_txns: set[int] = {1} # => only txn 1 reached COMMIT before the simulated crash
def redo_all(log: list[LogRecord]) -> dict[str, str]: # => replays EVERY record, commit status ignored
state: dict[str, str] = {} # => simulates the page state, wiped clean by the "crash"
for record in log: # => redo does not check committed_txns at all -- that is undo's job (ex-34)
state[record.key] = record.value # => re-apply, regardless of whether the writer ever committed
return state # => the fully redone state, including transactions that never actually committed
state = redo_all(log) # => in-memory state was discarded ("crash") -- this rebuilds it from the log
print(state) # => Output: {'a': 'committed-a', 'b': 'uncommitted-b'}
assert state["a"] == "committed-a" # => the committed write survived the crash, via redo
assert 1 in committed_txns # => confirms txn 1 really did commit before the crash
print("ex-33 OK") # => Output: ex-33 OKRun: python3 example.py
Output:
{'a': 'committed-a', 'b': 'uncommitted-b'}
ex-33 OKlearning/code/ex-33-wal-redo-committed/test_example.py
"""Example 33: pytest verification for WAL Redo Replaying Committed Writes."""
from example import LogRecord, redo_all
def test_committed_writes_survive_redo() -> None:
log = [LogRecord(txn_id=1, key="k", value="v")]
state = redo_all(log)
assert state["k"] == "v"
def test_redo_replays_every_record_regardless_of_commit_status() -> None:
log = [LogRecord(txn_id=1, key="k", value="v1"), LogRecord(txn_id=2, key="k", value="v2")]
state = redo_all(log)
assert state["k"] == "v2" # => the LAST write in the log wins, whoever wrote it
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Redo scans the WHOLE log and re-applies every write it finds, regardless of commit status at this stage -- committed writes surviving the crash is a direct consequence of redo replaying the log at all, not of redo specifically singling out committed transactions.
Why it matters: This is durability's actual mechanism, not just its promise: COMMIT returning success means the transaction's log records are durable, and durable log records are what redo can always replay after any crash. Ex-34 shows the other half of the story -- an UNcommitted write also gets redone at this stage (ARIES redoes everything first, ex-65), and only undo, run afterward, rolls it back out.
Example 34: WAL Undo Rolls Back Uncommitted Writes
ex-34 · exercises co-19
After redo has replayed everything, undo rolls back the writes of transactions that never committed. This example undoes an uncommitted transaction's write using its logged before-image and confirms the original value is restored.
learning/code/ex-34-wal-undo-uncommitted/example.py
"""Example 34: WAL Undo Rolls Back Uncommitted Writes."""
# Undo removes exactly what redo just replayed for transactions that never committed (co-19).
from dataclasses import dataclass # => a plain, typed record for one log entry
@dataclass # => a plain, typed record -- no custom __init__ needed
class LogRecord: # => carries BOTH the old and new value -- undo needs the old one
txn_id: int # => which transaction wrote this
key: str # => the key that was written
before: str # => the value that existed BEFORE this write -- undo's restoration target
after: str # => the value this write set -- what redo would apply
log: list[LogRecord] = [ # => one committed write, one that never committed
LogRecord(txn_id=1, key="a", before="orig-a", after="committed-a"), # => txn 1 commits
LogRecord(txn_id=2, key="b", before="orig-b", after="uncommitted-b"), # => txn 2 never commits
] # => end of the log fixture
committed_txns: set[int] = {1} # => only txn 1 reached COMMIT before the crash
state: dict[str, str] = {record.key: record.after for record in log} # => redo already ran (ex-33's step)
print(state) # => Output: {'a': 'committed-a', 'b': 'uncommitted-b'}
def undo_losers(log: list[LogRecord], state: dict[str, str], committed: set[int]) -> None: # => rolls back losers
for record in reversed(log): # => undo walks the log BACKWARD -- most recent uncommitted change first
if record.txn_id not in committed: # => a "loser" transaction: never reached COMMIT
state[record.key] = record.before # => restore the value that existed before this write
undo_losers(log, state, committed_txns) # => run the repair pass over the redone state
print(state) # => Output: {'a': 'committed-a', 'b': 'orig-b'}
assert state["a"] == "committed-a" # => the committed write was untouched by undo
assert state["b"] == "orig-b" # => the uncommitted write was rolled back to its before-image
print("ex-34 OK") # => Output: ex-34 OKRun: python3 example.py
Output:
{'a': 'committed-a', 'b': 'uncommitted-b'}
{'a': 'committed-a', 'b': 'orig-b'}
ex-34 OKlearning/code/ex-34-wal-undo-uncommitted/test_example.py
"""Example 34: pytest verification for WAL Undo Rolling Back Uncommitted Writes."""
from example import LogRecord, undo_losers
def test_uncommitted_write_is_rolled_back() -> None:
log = [LogRecord(txn_id=1, key="k", before="old", after="new")]
state = {"k": "new"}
undo_losers(log, state, committed=set())
assert state["k"] == "old"
def test_committed_write_is_left_alone() -> None:
log = [LogRecord(txn_id=1, key="k", before="old", after="new")]
state = {"k": "new"}
undo_losers(log, state, committed={1})
assert state["k"] == "new" # => committed -- undo must never touch it
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Undo needs the log record's BEFORE-image (the value that existed prior to the write), not just its after-image -- rolling back means restoring what was there, and that information only exists if the log captured it at write time.
Why it matters: This is exactly why the steal/no-force buffer policy needs both redo AND undo, not just one: a dirty page can reach disk before its transaction commits (steal), so redo must be able to replay it forward, and a committed transaction's page might not reach disk before a crash (no-force), so redo must replay that too -- which means undo is the only mechanism left to remove an uncommitted transaction's changes after the fact.
Example 35: A Checkpoint Bounds Redo Replay
ex-35 · exercises co-20
A checkpoint record marks a point after which recovery only needs to scan forward, bounding how far back the log must be read. This example writes a checkpoint, appends more records, and confirms redo scans only from the checkpoint onward.
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph LR
A["log start<br/>skipped"]:::blue
B["checkpoint<br/>record"]:::orange
C["redo scans<br/>from here"]:::teal
A -.->|not read| B --> C
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-35-checkpoint-bounds-replay/example.py
"""Example 35: A Checkpoint Bounds Redo Replay."""
# A checkpoint (co-20) is the only thing that bounds how far back redo must scan.
from dataclasses import dataclass # => a plain, typed record for one log entry
@dataclass # => a plain, typed record -- no custom __init__ needed
class LogRecord: # => either a normal write or a checkpoint marker
lsn: int # => this record's own log sequence number
kind: str # => "write" or "checkpoint"
log: list[LogRecord] = [ # => two old records, a checkpoint, then two fresh records
LogRecord(lsn=1, kind="write"), # => old history -- already durable before the checkpoint
LogRecord(lsn=2, kind="write"), # => old history -- already durable before the checkpoint
LogRecord(lsn=3, kind="checkpoint"), # => the most recent checkpoint marker
LogRecord(lsn=4, kind="write"), # => needs redo -- happened AFTER the checkpoint
LogRecord(lsn=5, kind="write"), # => needs redo -- happened AFTER the checkpoint
] # => end of the log fixture
def last_checkpoint_lsn(log: list[LogRecord]) -> int: # => finds the MOST RECENT checkpoint's LSN
checkpoints = [r.lsn for r in log if r.kind == "checkpoint"] # => every checkpoint marker's LSN
return checkpoints[-1] if checkpoints else 0 # => 0 means "no checkpoint yet -- scan from the start"
def redo_scan_range(log: list[LogRecord]) -> list[int]: # => the LSNs redo actually has to look at
start = last_checkpoint_lsn(log) # => everything up to and including this LSN is already durable
return [r.lsn for r in log if r.lsn > start] # => only records strictly AFTER the checkpoint
scanned = redo_scan_range(log) # => the bounded set of LSNs redo must actually replay
print(scanned) # => Output: [4, 5]
assert scanned == [4, 5] # => redo skips records 1, 2, and the checkpoint itself -- only 4 and 5 remain
print("ex-35 OK") # => Output: ex-35 OKRun: python3 example.py
Output:
[4, 5]
ex-35 OKlearning/code/ex-35-checkpoint-bounds-replay/test_example.py
"""Example 35: pytest verification for a Checkpoint Bounding Redo Replay."""
from example import LogRecord, redo_scan_range
def test_scan_starts_strictly_after_the_checkpoint() -> None:
log = [LogRecord(1, "write"), LogRecord(2, "checkpoint"), LogRecord(3, "write")]
assert redo_scan_range(log) == [3]
def test_no_checkpoint_means_scan_the_whole_log() -> None:
log = [LogRecord(1, "write"), LogRecord(2, "write")]
assert redo_scan_range(log) == [1, 2] # => nothing to skip -- no checkpoint exists yet
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Recovery does not need to scan the entire log from the beginning of time -- it only needs to start from the most recent checkpoint, because everything before it is guaranteed already durable on the pages themselves.
Why it matters: Without checkpoints, a long-running database's recovery time after a crash would grow unboundedly with the log's total history, since redo (ex-33) would have to replay the entire log from record one every single time. A periodic checkpoint is what keeps recovery time bounded by "how much happened since the last checkpoint" rather than "how much has ever happened".
Example 36: Row Versions Tagged with xmin/xmax
ex-36 · exercises co-21
Under MVCC, each row version carries the transaction id that created it (xmin) and, once superseded, the transaction id that deleted it (xmax). This example updates a row and confirms the old version's xmax and the new version's xmin are both set correctly.
learning/code/ex-36-row-version-xmin-xmax/example.py
"""Example 36: Row Versions Tagged with xmin/xmax."""
# xmin/xmax (co-21) are the two columns MVCC tags every row version with.
from dataclasses import dataclass # => a plain, typed record for one MVCC row version
@dataclass # => a plain, typed record -- no custom __init__ needed
class RowVersion: # => one version of one row -- MVCC never edits a version in place
value: str # => this version's actual data
xmin: int # => the transaction id that CREATED this version
xmax: int | None = None # => the transaction id that DELETED (superseded) it, or None if still live
def update(versions: list[RowVersion], new_value: str, txn_id: int) -> RowVersion: # => co-21: append, never edit
current = versions[-1] # => the most recent (currently live) version
current.xmax = txn_id # => the OLD version is now superseded, tagged with the updating txn's id
new_version = RowVersion(value=new_value, xmin=txn_id) # => a BRAND NEW version, not an edit in place
versions.append(new_version) # => the row's version chain grows -- nothing was overwritten
return new_version # => hand back the freshly created version
versions: list[RowVersion] = [RowVersion(value="original", xmin=1)] # => created by txn 1
update(versions, new_value="updated", txn_id=2) # => txn 2 updates the row
print(versions[0]) # => Output: RowVersion(value='original', xmin=1, xmax=2)
print(versions[1]) # => Output: RowVersion(value='updated', xmin=2, xmax=None)
assert versions[0].xmax == 2 # => the OLD version is tagged as superseded by txn 2
assert versions[1].xmin == 2 # => the NEW version is tagged as created by txn 2
assert versions[1].xmax is None # => the new version is still live -- nothing has superseded IT yet
print("ex-36 OK") # => Output: ex-36 OKRun: python3 example.py
Output:
RowVersion(value='original', xmin=1, xmax=2)
RowVersion(value='updated', xmin=2, xmax=None)
ex-36 OKlearning/code/ex-36-row-version-xmin-xmax/test_example.py
"""Example 36: pytest verification for Row Versions Tagged with xmin/xmax."""
from example import RowVersion, update
def test_update_sets_xmax_on_the_old_version() -> None:
versions = [RowVersion(value="v1", xmin=1)]
update(versions, new_value="v2", txn_id=5)
assert versions[0].xmax == 5
def test_update_sets_xmin_on_the_new_version() -> None:
versions = [RowVersion(value="v1", xmin=1)]
update(versions, new_value="v2", txn_id=5)
assert versions[1].xmin == 5
assert versions[1].xmax is None # => the newest version has no successor yet
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: An update sets the OLD version's xmax to the updating transaction's id and creates a BRAND NEW version whose xmin is that same transaction id -- two separate operations, not one in-place mutation.
Why it matters: xmin/xmax (the actual PostgreSQL column names) are the entire mechanism ex-37's snapshot visibility rule is built on: a version is visible to a given snapshot precisely when its xmin committed before the snapshot and its xmax either doesn't exist yet or hasn't committed before the snapshot. Without this per-version tagging, there would be no way to answer "was this specific version visible to that specific reader" at all.
Example 37: Snapshot Visibility Rule
ex-37 · exercises co-22
A snapshot sees exactly the versions committed before it was taken and not yet deleted at that point. This example applies the visibility rule across a commit boundary and confirms a snapshot taken before a commit cannot see it.
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC
graph LR
A["v1<br/>xmin=1"]:::blue
B["v2<br/>xmin=5"]:::orange
C["snapshot<br/>taken at tx=3"]:::teal
D["sees v1<br/>not v2"]:::purple
A --> C
B -.->|invisible| C
C --> D
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef purple fill:#CC78BC,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-37-snapshot-visibility-rule/example.py
"""Example 37: Snapshot Visibility Rule."""
# Snapshot visibility (co-22) is decided entirely from xmin/xmax and commit order.
from dataclasses import dataclass # => a plain, typed record for one MVCC row version
@dataclass # => a plain, typed record -- no custom __init__ needed
class RowVersion: # => one version of one row, tagged with its creating/deleting transactions
value: str # => this version's actual data
xmin: int # => transaction id that created this version
xmax: int | None = None # => transaction id that deleted it, or None if still live
commit_order: dict[int, int] = {} # => txn_id -> the position it committed at (lower = earlier)
def commit(txn_id: int) -> None: # => records WHEN (in commit order) a transaction committed
commit_order[txn_id] = len(commit_order) # => the Nth commit gets position N
def is_visible(version: RowVersion, snapshot_at: int) -> bool: # => snapshot_at is a commit-order position
creator_pos = commit_order.get(version.xmin) # => None if the creator hasn't committed at all yet
if creator_pos is None or creator_pos >= snapshot_at: # => created too late (or not committed) -- hide it
return False # => this snapshot cannot possibly see a version created after (or not before) it
if version.xmax is not None: # => this version WAS superseded by some later transaction
deleter_pos = commit_order.get(version.xmax) # => None means the deleter hasn't committed yet either
if deleter_pos is not None and deleter_pos < snapshot_at: # => the delete was ALREADY visible too
return False # => superseded before this snapshot was taken -- hidden
return True # => created before the snapshot, and not (visibly) deleted before it either
commit(txn_id=1) # => commit position 0
version = RowVersion(value="v1", xmin=1) # => a version created by txn 1
snapshot_before = 0 # => taken BEFORE txn 1's commit is visible (at position 0, nothing has committed yet)
snapshot_after = 1 # => taken AFTER txn 1's commit
print(is_visible(version, snapshot_before)) # => Output: False
print(is_visible(version, snapshot_after)) # => Output: True
assert is_visible(version, snapshot_before) is False # => a snapshot before the commit cannot see it
assert is_visible(version, snapshot_after) is True # => a snapshot after the commit CAN see it
print("ex-37 OK") # => Output: ex-37 OKRun: python3 example.py
Output:
False
True
ex-37 OKlearning/code/ex-37-snapshot-visibility-rule/test_example.py
"""Example 37: pytest verification for the Snapshot Visibility Rule."""
from example import RowVersion, commit, commit_order, is_visible
def test_snapshot_before_commit_cannot_see_it() -> None:
commit_order.clear()
commit(txn_id=9)
version = RowVersion(value="v", xmin=9)
assert is_visible(version, snapshot_at=0) is False
def test_snapshot_after_commit_can_see_it() -> None:
commit_order.clear()
commit(txn_id=9)
version = RowVersion(value="v", xmin=9)
assert is_visible(version, snapshot_at=1) is True
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Visibility is decided by comparing a version's xmin/xmax against the reader's own snapshot commit-order position -- a version is visible only if its creator committed before the snapshot AND its (if any) deleter had not yet committed at that point.
Why it matters: This rule is precisely what makes ex-39's readers-don't-block-writers possible: a reader never needs a lock on a row, because it can independently decide -- from data already attached to each version -- exactly which version it is allowed to see, regardless of what other transactions do concurrently. This is also the foundation ex-75's write-skew example builds on: snapshot isolation is defined entirely in terms of this visibility rule.
Example 38: An Update Creates a New Version
ex-38 · exercises co-21
Under MVCC, an update never mutates a row's bytes in place -- it appends a new version to the chain. This example updates a row twice and confirms three distinct version objects exist afterward, none of them overwritten.
learning/code/ex-38-update-creates-new-version/example.py
"""Example 38: An Update Creates a New Version."""
# MVCC never mutates a row version in place (co-21) -- every update appends a new one.
from dataclasses import dataclass # => a plain, typed record for one MVCC row version
@dataclass # => a plain, typed record -- no custom __init__ needed
class RowVersion: # => one version of one row
value: str # => this version's actual data
xmin: int # => the transaction id that created this version
xmax: int | None = None # => the transaction id that deleted it, or None if still live
def update(versions: list[RowVersion], new_value: str, txn_id: int) -> RowVersion: # => append, never mutate
current = versions[-1] # => the current, live version -- about to be superseded
current.xmax = txn_id # => tag the OLD version as superseded, but do NOT change its value field
new_version = RowVersion(value=new_value, xmin=txn_id) # => a genuinely NEW object
versions.append(new_version) # => appended, not swapped in place
return new_version # => hand back the freshly created version
versions: list[RowVersion] = [RowVersion(value="v1", xmin=1)] # => the original version
original = versions[0] # => keep a reference to compare against later
update(versions, new_value="v2", txn_id=2) # => first update
update(versions, new_value="v3", txn_id=3) # => second update
print(len(versions)) # => Output: 3
print([v.value for v in versions]) # => Output: ['v1', 'v2', 'v3']
assert len(versions) == 3 # => two updates produced TWO new versions, plus the original -- three total
assert original.value == "v1" # => the ORIGINAL object's value was never mutated in place
assert versions[0] is original # => it is literally the same object, still sitting in the chain
assert len({id(v) for v in versions}) == 3 # => three genuinely distinct objects, not aliases of each other
print("ex-38 OK") # => Output: ex-38 OKRun: python3 example.py
Output:
3
['v1', 'v2', 'v3']
ex-38 OKlearning/code/ex-38-update-creates-new-version/test_example.py
"""Example 38: pytest verification for an Update Creating a New Version."""
from example import RowVersion, update
def test_update_appends_rather_than_overwrites() -> None:
versions = [RowVersion(value="v1", xmin=1)]
update(versions, new_value="v2", txn_id=2)
assert len(versions) == 2
assert versions[0].value == "v1" # => the original object's value is untouched
def test_three_versions_are_three_distinct_objects() -> None:
versions = [RowVersion(value="v1", xmin=1)]
update(versions, new_value="v2", txn_id=2)
update(versions, new_value="v3", txn_id=3)
assert len({id(v) for v in versions}) == 3 # => three genuinely distinct objects, not aliases
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: id(version) differs for every version in the chain after N updates, there are N+1 distinct version objects, and the original object's value field never changes -- MVCC genuinely appends rather than mutates.
Why it matters: This is why MVCC needs ex-40's VACUUM at all: because updates never overwrite, old versions accumulate as real, distinct objects taking real space until something explicitly reclaims them. A storage engine that mutated rows in place (no MVCC) would never have this problem, but would also lose the non-blocking concurrent-read property ex-39 demonstrates -- version accumulation is the direct cost of that benefit.
Example 39: Readers Don't Block Writers
ex-39 · exercises co-22
A snapshot read holds no lock on the row it reads, so a concurrent writer never has to wait for it. This example takes a snapshot, has a writer update the row, and confirms the snapshot's own read result is unaffected and required no lock object at all.
learning/code/ex-39-readers-dont-block-writers/example.py
"""Example 39: Readers Don't Block Writers."""
# Snapshot isolation (co-22) means readers need no lock at all.
from dataclasses import dataclass # => a plain, typed record for one MVCC row version
@dataclass # => a plain, typed record -- no custom __init__ needed
class RowVersion: # => one version of one row
value: str # => this version's actual data
xmin: int # => transaction id that created this version
xmax: int | None = None # => transaction id that deleted it, or None if still live
commit_order: dict[int, int] = {1: 0} # => txn 1 already committed the row's original version
versions: list[RowVersion] = [RowVersion(value="original", xmin=1)] # => one live version so far
def snapshot_read(snapshot_at: int) -> str | None: # => acquires NO lock -- pure read of existing data
for version in reversed(versions): # => newest-to-oldest, first visible match wins
creator_pos = commit_order.get(version.xmin) # => when (if ever) this version's creator committed
if creator_pos is not None and creator_pos < snapshot_at: # => created before this snapshot
return version.value # => the first (newest) visible version found
return None # => no version of this row was visible to the snapshot at all
def writer_update(new_value: str, txn_id: int) -> None: # => a concurrent writer, appends a new version
versions[-1].xmax = txn_id # => tag the current version as superseded
versions.append(RowVersion(value=new_value, xmin=txn_id)) # => append the new version -- no lock taken
commit_order[txn_id] = len(commit_order) # => commits immediately for this example's purposes
reader_snapshot_at = len(commit_order) # => the reader's snapshot position, taken BEFORE the writer runs
reader_result = snapshot_read(reader_snapshot_at) # => the reader's read -- acquired NO lock to do this
writer_update("written-concurrently", txn_id=2) # => the writer proceeds WITHOUT waiting on the reader
print(reader_result) # => Output: original
print(len(versions)) # => Output: 2
assert reader_result == "original" # => the reader's result reflects its OWN snapshot, unaffected
assert len(versions) == 2 # => the writer's append succeeded -- it was never blocked by the reader
print("ex-39 OK") # => Output: ex-39 OKRun: python3 example.py
Output:
original
2
ex-39 OKlearning/code/ex-39-readers-dont-block-writers/test_example.py
"""Example 39: pytest verification for Readers Not Blocking Writers."""
from example import RowVersion, commit_order, snapshot_read, versions, writer_update
def test_writer_can_proceed_after_a_snapshot_is_taken() -> None:
commit_order.clear()
commit_order[1] = 0
versions.clear()
versions.append(RowVersion(value="orig", xmin=1))
writer_update("new", txn_id=2) # => no exception, no wait -- the writer just proceeds
assert len(versions) == 2
def test_reader_snapshot_is_unaffected_by_a_later_writer() -> None:
commit_order.clear()
commit_order[1] = 0
versions.clear()
versions.append(RowVersion(value="orig", xmin=1))
snapshot_at = len(commit_order)
result = snapshot_read(snapshot_at)
writer_update("new", txn_id=2)
assert result == "orig" # => computed BEFORE the writer ran, and stays that way
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: The snapshot read function never acquires anything resembling a lock -- it just filters the version chain by commit-order position, which is why a concurrent writer's append to that same chain never blocks on it.
Why it matters: This is the single biggest practical reason MVCC displaced pure lock-based concurrency control in most modern OLTP databases: a long-running read (a report, an export) does not stall writers, and a burst of writes does not stall readers. Ex-68's two-phase locking shows the alternative this sidesteps, where a reader genuinely would need to hold a shared lock that a writer's exclusive lock request has to wait behind.
Example 40: Vacuum Reclaims Dead Versions
ex-40 · exercises co-23
Versions superseded by every currently-possible reader are dead weight that must be garbage-collected. This example marks versions dead relative to the oldest active snapshot and vacuums them, confirming reclaimed slots drop from the live set.
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph LR
A["v1, v2, v3<br/>version chain"]:::blue
B{"older than<br/>oldest snapshot?"}:::orange
C["vacuum,<br/>reclaim slot"]:::teal
A --> B
B -->|yes| C
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-40-dead-version-gc-vacuum/example.py
"""Example 40: Vacuum Reclaims Dead Versions."""
from dataclasses import dataclass # => a plain, typed record for one MVCC row version
@dataclass # => a plain, typed record -- no custom __init__ needed
class RowVersion: # => one version of one row
value: str # => this version's actual data
xmin: int # => the transaction id that created this version
xmax: int | None = None # => the transaction id that deleted it, or None if still live
commit_order: dict[int, int] = {1: 0, 2: 1, 3: 2} # => three transactions, each already committed
versions: list[RowVersion] = [ # => three versions of the same logical row
RowVersion(value="v1", xmin=1, xmax=2), # => superseded by txn 2 -- may be dead, depending on readers
RowVersion(value="v2", xmin=2, xmax=3), # => superseded by txn 3 -- may be dead, depending on readers
RowVersion(value="v3", xmin=3), # => the current, live version -- xmax is None, never dead
] # => end of the version-chain fixture
def vacuum(versions: list[RowVersion], oldest_active_snapshot: int) -> list[RowVersion]: # => co-23: reclaim
live: list[RowVersion] = [] # => survivors after vacuuming
for version in versions: # => decide each version's fate independently
if version.xmax is None: # => never superseded -- always kept
live.append(version) # => keep it -- still the current row
continue # => move on to the next version, nothing more to check
deleter_pos = commit_order.get(version.xmax) # => when (if ever) the deleter committed
if deleter_pos is None or deleter_pos >= oldest_active_snapshot: # => some active reader might need it
live.append(version) # => not yet safe to reclaim
# => else: deleter_pos < oldest_active_snapshot -- NO active reader can possibly still see it
return live # => the pruned, still-valid version set
before_count = len(versions) # => 3 versions before vacuuming
after = vacuum(versions, oldest_active_snapshot=2) # => the oldest active snapshot is at position 2
print(len(after)) # => Output: 2
assert before_count == 3 # => confirms the starting point before reclaiming anything
assert len(after) == 2 # => v1 was reclaimed -- its delete (position 1) predates the oldest active snapshot
assert [v.value for v in after] == ["v2", "v3"] # => the two still-relevant versions survive
print("ex-40 OK") # => Output: ex-40 OKRun: python3 example.py
Output:
2
ex-40 OKlearning/code/ex-40-dead-version-gc-vacuum/test_example.py
"""Example 40: pytest verification for Vacuum Reclaiming Dead Versions."""
from example import RowVersion, commit_order, vacuum
def test_a_version_dead_before_every_active_snapshot_is_reclaimed() -> None:
commit_order.clear()
commit_order.update({1: 0, 2: 1})
versions = [RowVersion(value="v1", xmin=1, xmax=2), RowVersion(value="v2", xmin=2)]
survivors = vacuum(versions, oldest_active_snapshot=2)
assert len(survivors) == 1
def test_a_version_needed_by_an_active_snapshot_is_kept() -> None:
commit_order.clear()
commit_order.update({1: 0, 2: 1})
versions = [RowVersion(value="v1", xmin=1, xmax=2), RowVersion(value="v2", xmin=2)]
survivors = vacuum(versions, oldest_active_snapshot=0) # => a reader at position 0 still needs v1
assert len(survivors) == 2
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: A version is safe to vacuum only once its xmax committed before EVERY currently active snapshot -- vacuuming too early would delete a version some still-running reader legitimately needs to see.
Why it matters: This is precisely PostgreSQL's VACUUM: without it, ex-38's version accumulation would grow storage without bound, since nothing else in the MVCC read/write path ever removes an old version on its own. A long-running transaction holding an old snapshot open is the classic cause of vacuum falling behind in production -- it keeps otherwise-dead versions artificially alive because SOME snapshot might still need them.
Example 41: LRU-K vs Plain LRU on a Scan-Then-Hot Pattern
ex-41 · exercises co-05
Plain LRU evicts based only on the single most recent access, so a one-off scan can flush out a genuinely hot page. This example runs the same access trace through plain LRU and LRU-K, and confirms LRU-K keeps the hot page that LRU evicts.
learning/code/ex-41-lru-k-vs-lru/example.py
"""Example 41: LRU-K vs Plain LRU on a Scan-Then-Hot Pattern."""
# LRU-K (co-05) ranks a page by its K-th most recent access, not just its latest one.
from collections import OrderedDict # => stdlib ordered-dict, gives free "most-recently-touched" order
CAPACITY = 3 # => the buffer pool holds at most 3 frames in this example
def simulate_lru(accesses: list[str], capacity: int) -> list[str]: # => plain LRU eviction
cache: OrderedDict[str, None] = OrderedDict() # => key order IS recency order -- newest at the end
for page in accesses: # => walk the access trace in order
if page in cache: # => a hit -- refresh its recency
cache.move_to_end(page) # => moves it to the "most recently used" end
else: # => a miss -- must load the page
if len(cache) >= capacity: # => pool is full -- must evict first
cache.popitem(last=False) # => evicts the LEAST recently used page (the front)
cache[page] = None # => insert as most recently used
return list(cache.keys()) # => final resident set, in recency order
def simulate_lru_k(accesses: list[str], capacity: int, k: int) -> list[str]: # => LRU-K eviction
cache: dict[str, None] = {} # => the resident set -- insertion order tracked by dict semantics
history: dict[str, list[int]] = {} # => page -> its last k access timestamps, oldest first
for t, page in enumerate(accesses, start=1): # => t is a monotonic logical timestamp
hist = history.setdefault(page, []) # => this page's access-time history so far
hist.append(t) # => record this access
if len(hist) > k: # => only the last K references matter for the K-distance rule
hist.pop(0) # => drop the oldest, keep exactly K entries
if page not in cache and len(cache) >= capacity: # => a miss into a full pool -- must evict
def backward_k_distance(candidate: str) -> float: # => how "cold" a page looks to LRU-K
h = history[candidate] # => that candidate page's own access-time history
if len(h) < k: # => fewer than K references ever seen -- treat as infinitely cold
return float("inf") # => a page seen once during a scan looks maximally evictable
return t - h[0] # => distance back to the K-th most recent reference
victim = max(cache, key=lambda p: (backward_k_distance(p), -history[p][-1])) # => coldest wins
del cache[victim] # => evict whichever page looks coldest under the K-distance rule
cache[page] = None # => insert (or refresh) this page as resident
return list(cache.keys()) # => final resident set, insertion order (hot inserted once, never evicted)
accesses = ["hot", "hot", "p1", "p2", "p3", "p4", "p5"] # => hot touched twice, then a long distinct scan
lru_result = simulate_lru(accesses, CAPACITY) # => run the trace through plain LRU
lru_k_result = simulate_lru_k(accesses, CAPACITY, k=2) # => and through LRU-K, for comparison
print(lru_result) # => Output: ['p3', 'p4', 'p5']
print(lru_k_result) # => Output: ['hot', 'p4', 'p5']
assert "hot" not in lru_result # => plain LRU evicted the hot page during the one-off scan
assert "hot" in lru_k_result # => LRU-K correctly kept the hot page resident
print("ex-41 OK") # => Output: ex-41 OKRun: python3 example.py
Output:
['p3', 'p4', 'p5']
['hot', 'p4', 'p5']
ex-41 OKlearning/code/ex-41-lru-k-vs-lru/test_example.py
"""Example 41: pytest verification for LRU-K vs Plain LRU."""
from example import simulate_lru, simulate_lru_k
def test_plain_lru_evicts_the_hot_page_during_a_scan() -> None:
accesses = ["hot", "hot", "a", "b", "c", "d"]
result = simulate_lru(accesses, capacity=2)
assert "hot" not in result
def test_lru_k_keeps_the_hot_page_during_the_same_scan() -> None:
accesses = ["hot", "hot", "a", "b", "c", "d"]
result = simulate_lru_k(accesses, capacity=2, k=2)
assert "hot" in result
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: LRU-K ranks a page by its K-th most recent access, not its most recent one -- a page seen only once during a scan looks far colder to LRU-K than a page seen twice before the scan even started, which is exactly why LRU-K survives scan pollution that defeats plain LRU.
Why it matters: This is a real, named algorithm (LRU-K, and its practical cousin the CLOCK-based approximation used by PostgreSQL and InnoDB) precisely because plain LRU's scan-sensitivity is a genuine production problem: a single large sequential scan can evict an entire buffer pool's worth of hot working-set pages, tanking cache hit ratio for every OTHER query running at the same time. LRU-K's extra bookkeeping (a short history per page, not just one timestamp) is the direct fix for that failure mode.
Example 42: Buffer Pool Hit Ratio
ex-42 · exercises co-06
Hit ratio is the standard health metric for a buffer pool: the fraction of accesses that found the page already resident. This example replays a small workload through a pool and confirms the computed ratio equals hits divided by total accesses.
learning/code/ex-42-buffer-pool-hit-ratio/example.py
"""Example 42: Buffer Pool Hit Ratio."""
# Hit ratio (co-06) is the standard buffer-pool health metric.
from dataclasses import dataclass, field # => typed pool + counters
@dataclass # => a plain, typed record -- no custom __init__ needed
class BufferPool: # => a minimal pool that just tracks resident pages and hit/miss counts
frames: set[int] = field(default_factory=set[int]) # => currently resident page ids
hits: int = 0 # => count of accesses that found the page already resident
misses: int = 0 # => count of accesses that had to load the page
def access(pool: BufferPool, page_id: int) -> None: # => one workload access against the pool
if page_id in pool.frames: # => already resident -- a hit
pool.hits += 1 # => tally the hit -- no load needed
else: # => not resident -- a miss, must be loaded
pool.misses += 1 # => tally the miss before loading
pool.frames.add(page_id) # => now resident for any future access
def hit_ratio(pool: BufferPool) -> float: # => co-06: the standard buffer-pool efficiency metric
total = pool.hits + pool.misses # => total accesses attempted
return pool.hits / total if total else 0.0 # => guard against dividing by zero on an empty workload
pool = BufferPool() # => a fresh, empty pool
workload = [1, 2, 1, 3, 1, 2] # => a mix of repeated (hits) and new (misses) page accesses
for page_id in workload: # => replay the whole workload through the pool
access(pool, page_id) # => feed each workload access through the pool
print(pool.hits) # => Output: 3
print(pool.misses) # => Output: 3
ratio = hit_ratio(pool) # => compute the final hit ratio for this workload
print(ratio) # => Output: 0.5
assert pool.hits == 3 # => pages 1 (two repeat hits) and 2 (one repeat hit) -- three hits total
assert pool.misses == 3 # => pages 1, 2, 3 each loaded once -- three misses total
assert ratio == pool.hits / (pool.hits + pool.misses) # => the exact formula the spec requires
print("ex-42 OK") # => Output: ex-42 OKRun: python3 example.py
Output:
3
3
0.5
ex-42 OKlearning/code/ex-42-buffer-pool-hit-ratio/test_example.py
"""Example 42: pytest verification for Buffer Pool Hit Ratio."""
from example import BufferPool, access, hit_ratio
def test_hit_ratio_equals_hits_over_total() -> None:
pool = BufferPool()
for page_id in [1, 1, 2]:
access(pool, page_id)
assert hit_ratio(pool) == pool.hits / (pool.hits + pool.misses)
def test_all_misses_gives_zero_hit_ratio() -> None:
pool = BufferPool()
for page_id in [1, 2, 3]:
access(pool, page_id)
assert hit_ratio(pool) == 0.0
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Hit ratio is nothing more than hits / (hits + misses) -- a single counter pair, sampled over a workload, is enough to characterize how well a buffer pool's size and eviction policy fit the actual access pattern.
Why it matters: Hit ratio is the number database operators watch first when diagnosing a slow system: a dropping hit ratio usually means either the working set outgrew the pool or a policy choice (plain LRU under a scan, ex-41) is evicting pages it should not. It is also the metric that directly justifies buying more memory -- a pool twice the size that raises hit ratio from 80% to 99% removes a huge fraction of the disk I/O the previous size required.
Example 43: B-Tree Bulk Load vs Insert-One-by-One
ex-43 · exercises co-08
Bulk-loading sorted keys bottom-up builds a tree in one linear pass, skipping the repeated splits a one-by-one insert would trigger. This example bulk-loads a key set and compares its lookup answers against a tree built by inserting the same keys individually.
learning/code/ex-43-btree-bulk-load/example.py
"""Example 43: B-Tree Bulk Load vs Insert-One-by-One."""
# Bulk load and incremental insert (co-08) can differ in shape but must agree on every lookup.
import bisect # => stdlib binary-search helper for sorted-list insertion
LEAF_CAPACITY = 4 # => each leaf page holds at most 4 keys before it must split
def bulk_load(sorted_keys: list[int], capacity: int) -> list[list[int]]: # => bottom-up, one linear pass
return [ # => a list comprehension -- one leaf per capacity-sized slice
sorted_keys[i : i + capacity] for i in range(0, len(sorted_keys), capacity) # => a fixed-size slice
] # => end of the bulk-load comprehension # => chunk ALREADY-SORTED input directly -- no comparisons, no splits, just slicing
def insert_one(leaves: list[list[int]], key: int, capacity: int) -> None: # => classic incremental insert
if not leaves: # => the very first key ever inserted into an empty tree
leaves.append([key]) # => the tree's first (and only) leaf so far
return # => nothing more to do for the very first key
leaf_index = 0 # => find which leaf this key belongs in, by comparing to each leaf's first key
for i, leaf in enumerate(leaves): # => walk leaves left to right
if key < leaf[0]: # => key belongs BEFORE this leaf -- stop at the previous one
break # => leaf_index already points at the right leaf
leaf_index = i # => keep tracking the rightmost leaf whose start is still <= key
leaf = leaves[leaf_index] # => the leaf this key will be inserted into
bisect.insort(leaf, key) # => insert keeping the leaf internally sorted
if len(leaf) > capacity: # => the leaf overflowed -- split it in two, like a real B-tree
mid = len(leaf) // 2 # => the split point -- roughly half the keys on each side
leaves[leaf_index] = leaf[:mid] # => left half stays at this position
leaves.insert(leaf_index + 1, leaf[mid:]) # => right half becomes a brand-new leaf
def lookup(leaves: list[list[int]], key: int) -> bool: # => point lookup across the leaf chain
return any(key in leaf for leaf in leaves) # => a real B-tree binary-searches per leaf; O(n) here for clarity
sorted_keys = list(range(0, 20, 2)) # => [0, 2, 4, ..., 18] -- ten pre-sorted keys
bulk_leaves = bulk_load(sorted_keys, LEAF_CAPACITY) # => one linear pass, chunked geometry
incremental_leaves: list[list[int]] = [] # => built up key-by-key instead
for key in sorted_keys: # => insert the SAME keys, one at a time
insert_one(incremental_leaves, key, LEAF_CAPACITY) # => grow the tree incrementally, key by key
print(bulk_leaves) # => Output: [[0, 2, 4, 6], [8, 10, 12, 14], [16, 18]]
print(incremental_leaves) # => Output: [[0, 2], [4, 6], [8, 10], [12, 14, 16, 18]]
test_keys = sorted_keys + [1, 3, 99] # => every real key, plus some keys that should be absent
bulk_answers = [lookup(bulk_leaves, k) for k in test_keys] # => query the bulk-loaded tree
incremental_answers = [lookup(incremental_leaves, k) for k in test_keys] # => query the incremental tree
assert bulk_answers == incremental_answers # => SAME lookup answers despite DIFFERENT leaf geometry
print("ex-43 OK") # => Output: ex-43 OKRun: python3 example.py
Output:
[[0, 2, 4, 6], [8, 10, 12, 14], [16, 18]]
[[0, 2], [4, 6], [8, 10], [12, 14, 16, 18]]
ex-43 OKlearning/code/ex-43-btree-bulk-load/test_example.py
"""Example 43: pytest verification for B-Tree Bulk Load vs Insert-One-by-One."""
from example import bulk_load, insert_one, lookup
def test_bulk_and_incremental_agree_on_every_lookup() -> None:
sorted_keys = list(range(0, 12, 2))
bulk_leaves = bulk_load(sorted_keys, capacity=4)
incremental_leaves: list[list[int]] = []
for key in sorted_keys:
insert_one(incremental_leaves, key, capacity=4)
for probe in sorted_keys + [1, 99]:
assert lookup(bulk_leaves, probe) == lookup(incremental_leaves, probe)
def test_absent_key_is_not_found_in_either_structure() -> None:
bulk_leaves = bulk_load([0, 2, 4], capacity=4)
assert lookup(bulk_leaves, 3) is False
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Bulk load and one-by-one insertion can produce DIFFERENT internal leaf geometry for the same key set -- what must stay identical is the answer to every lookup, not the physical shape of the resulting tree.
Why it matters: Bulk loading is the standard technique for building an index from an existing large table (a data migration, an initial load, a CREATE INDEX on a populated table) precisely because it is dramatically cheaper than one insert-and-possibly-split per row: one linear pass over already-sorted data beats thousands of independent split cascades, even though both ultimately answer every lookup identically.
Example 44: Force B-Tree Splits Up to the Root
ex-44 · exercises co-09
A split that overflows an internal node propagates upward, and when it reaches a full root the tree grows a new root and gains a level. This example inserts enough keys to force a root split and confirms the height increases by exactly one.
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73, Purple #CC78BC
graph TD
A["full root<br/>splits"]:::blue
B["new root<br/>height + 1"]:::purple
C["old root<br/>left child"]:::orange
D["new sibling<br/>right child"]:::teal
A -->|propagate up| B
B --> C
B --> D
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef purple fill:#CC78BC,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-44-btree-internal-split-propagate/example.py
"""Example 44: Force B-Tree Splits Up to the Root."""
# A B-tree only grows taller by splitting a FULL root (co-09) -- every other split stays local.
import bisect # => stdlib binary-search helper for sorted-list insertion
class BTreeNode: # => a single B-tree node -- either an internal node or a leaf
def __init__(self, leaf: bool = True) -> None: # => leaf=True until proven otherwise
self.keys: list[int] = [] # => this node's own separator/data keys, always kept sorted
self.children: list["BTreeNode"] = [] # => empty for leaves, len(keys)+1 for internal nodes
self.leaf: bool = leaf # => whether this node has no children at all
class BTree: # => a minimal B-tree with proactive split-on-the-way-down insertion (co-09)
def __init__(self, t: int) -> None: # => t is the minimum degree -- max keys per node is 2t-1
self.root: BTreeNode = BTreeNode(leaf=True) # => starts as a single empty leaf
self.t: int = t # => stored for use by split/insert below
def height(self) -> int: # => counts edges from root down to a leaf (all leaves are equidistant)
node, h = self.root, 0 # => start at the root with height 0
while not node.leaf: # => descend the leftmost path until a leaf is reached
node = node.children[0] # => step down one level, always via the first child
h += 1 # => one more edge crossed
return h # => the tree's current height, in edges from root to any leaf
def insert(self, key: int) -> None: # => the public entry point -- handles a full root specially
root = self.root # => the current root, before any possible split
if len(root.keys) == 2 * self.t - 1: # => the root itself is full -- MUST split before inserting
new_root = BTreeNode(leaf=False) # => a brand-new root, one level taller than before
new_root.children.append(root) # => the old root becomes the new root's only child, for now
self._split_child(new_root, 0) # => splits it in two, promoting a median key up
self.root = new_root # => the tree's height has now increased by exactly one
self._insert_nonfull(self.root, key) # => descend and insert, splitting full children on the way
def _split_child(self, parent: BTreeNode, i: int) -> None: # => splits parent.children[i] in two
t = self.t # => the minimum degree, cached locally for readability below
child = parent.children[i] # => the full node being split
new_node = BTreeNode(leaf=child.leaf) # => the right half -- same leaf-ness as the original
mid_key = child.keys[t - 1] # => the median key -- it moves UP into the parent
new_node.keys = child.keys[t:] # => the right half of the keys goes to the new sibling
child.keys = child.keys[: t - 1] # => the original node keeps only its left half
if not child.leaf: # => internal nodes must also split their children pointers
new_node.children = child.children[t:] # => right-half children follow their keys
child.children = child.children[:t] # => left-half children stay behind
parent.children.insert(i + 1, new_node) # => the new sibling takes its place beside the original
parent.keys.insert(i, mid_key) # => the median key becomes a new separator in the parent
def _insert_nonfull(self, node: BTreeNode, key: int) -> None: # => assumes node is never full itself
if node.leaf: # => base case -- leaves just get the key inserted in sorted order
bisect.insort(node.keys, key) # => keeps the leaf's keys sorted after the insert
return # => nothing further to do once a leaf has absorbed the key
i = len(node.keys) - 1 # => find which child subtree the key belongs under
while i >= 0 and key < node.keys[i]: # => walk right-to-left until the right slot is found
i -= 1 # => keep moving left while the key is smaller than this separator
i += 1 # => i now indexes the correct child to descend into
if len(node.children[i].keys) == 2 * self.t - 1: # => that child is full -- split it FIRST
self._split_child(node, i) # => proactively split before descending, never after
if key > node.keys[i]: # => the split may shift which child the key now belongs under
i += 1 # => the key now belongs in the newly created right sibling instead
self._insert_nonfull(node.children[i], key) # => recurse into the now-guaranteed-non-full child
tree = BTree(t=2) # => t=2 means max 3 keys per node, forcing splits quickly for this example
for key in range(1, 9): # => insert keys 1 through 8 -- enough to fill the root once already
tree.insert(key) # => each insert may cascade zero or more splits below the root
height_before = tree.height() # => height BEFORE the key that forces a root split
print(height_before) # => Output: 1
tree.insert(9) # => this ninth key overflows the (now full) root, forcing a root split
height_after = tree.height() # => height AFTER the forced root split
print(height_after) # => Output: 2
print(len(tree.root.children)) # => Output: 2
assert height_after == height_before + 1 # => the root split grew the tree by exactly one level
assert len(tree.root.children) == 2 # => a freshly split root always starts with exactly two children
print("ex-44 OK") # => Output: ex-44 OKRun: python3 example.py
Output:
1
2
2
ex-44 OKlearning/code/ex-44-btree-internal-split-propagate/test_example.py
"""Example 44: pytest verification for Forcing B-Tree Splits Up to the Root."""
from example import BTree
def test_root_split_increases_height_by_exactly_one() -> None:
tree = BTree(t=2)
for key in range(1, 9):
tree.insert(key)
before = tree.height()
tree.insert(9)
assert tree.height() == before + 1
def test_split_root_has_exactly_two_children() -> None:
tree = BTree(t=2)
for key in range(1, 10):
tree.insert(key)
assert len(tree.root.children) == 2
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: A B-tree only grows taller from the TOP: a full root splits into two children under a brand-new root, which is the only way this particular tree implementation's height ever increases -- every other split stays contained at its own level.
Why it matters: This top-growth property is exactly what keeps a B-tree perfectly balanced with zero rebalancing logic beyond the split-on-the-way-down rule itself: every leaf is always at the same depth, because the ONLY way to add a level is this single root-split operation, which by construction adds exactly one level everywhere at once, not just along one path.
Example 45: B-Tree Leaf Underflow: Merge or Borrow
ex-45 · exercises co-09
Deleting a key can drop a leaf below its minimum occupancy, which must be repaired by borrowing a key from a sibling or merging with one. This example deletes until a leaf underflows and confirms the repair restores a valid tree.
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph LR
A["leaf<br/>underflows"]:::blue
B{"sibling has<br/>spare key?"}:::orange
C["borrow<br/>from sibling"]:::teal
D["merge with<br/>sibling"]:::teal
A --> B
B -->|yes| C
B -->|no| D
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-45-btree-delete-underflow/example.py
"""Example 45: B-Tree Leaf Underflow -- Merge or Borrow."""
# Borrowing is tried first (co-09) -- merging is only the fallback when no sibling can spare a key.
CAPACITY = 4 # => the fixed leaf capacity used throughout this course's B-tree examples
MIN_KEYS = CAPACITY // 2 # => a leaf below this many keys has UNDERFLOWED and must be repaired
def delete(leaves: list[list[int]], key: int) -> None: # => removes key, then repairs any underflow
leaf_index = next(i for i, leaf in enumerate(leaves) if key in leaf) # => which leaf holds the key
leaves[leaf_index].remove(key) # => the actual removal
leaf = leaves[leaf_index] # => the (possibly now underflowed) leaf that lost a key
if len(leaf) >= MIN_KEYS or len(leaves) == 1: # => still valid, or it is the only leaf left -- done
return # => no repair needed -- occupancy is still within bounds
if leaf_index + 1 < len(leaves) and len(leaves[leaf_index + 1]) > MIN_KEYS: # => right sibling can spare one
borrowed = leaves[leaf_index + 1].pop(0) # => take its smallest key
leaf.append(borrowed) # => and give it to the underflowed leaf
return # => borrowing alone repaired the underflow -- no merge needed
if leaf_index > 0 and len(leaves[leaf_index - 1]) > MIN_KEYS: # => else try the left sibling instead
borrowed = leaves[leaf_index - 1].pop() # => take its largest key
leaf.insert(0, borrowed) # => and give it to the underflowed leaf, keeping sort order
return # => borrowing from the left sibling also repaired the underflow
if leaf_index + 1 < len(leaves): # => no sibling could spare a key -- MERGE with the right sibling
leaf.extend(leaves.pop(leaf_index + 1)) # => absorb the whole right sibling's keys
else: # => no right sibling exists -- merge with the left one instead
leaves[leaf_index - 1].extend(leaves.pop(leaf_index)) # => absorb THIS leaf into the left one
def is_valid(leaves: list[list[int]]) -> bool: # => every leaf is sorted, and non-solo leaves meet MIN_KEYS
for leaf in leaves: # => check each leaf independently
if leaf != sorted(leaf): # => a leaf's keys must always stay in ascending order
return False # => a leaf out of sort order is never a valid B-tree state
if len(leaf) < MIN_KEYS and len(leaves) > 1: # => underflowed, UNLESS it is the sole remaining leaf
return False # => an underflowed leaf with siblings means the repair logic has a bug
return True # => every leaf passed both the sort-order and occupancy checks
leaves = [[1, 2, 3, 4], [5, 6, 7, 8]] # => two full leaves, each right at capacity
assert is_valid(leaves) # => sanity check before the deletions begin
delete(leaves, 1) # => leaf 0 drops to [2,3,4] -- still >= MIN_KEYS(2), no repair needed
delete(leaves, 2) # => leaf 0 drops to [3,4] -- exactly MIN_KEYS, still valid
delete(leaves, 3) # => leaf 0 drops to [4] -- now UNDER MIN_KEYS(2): underflow triggers repair
print(leaves) # => Output: [[4, 5], [6, 7, 8]]
assert is_valid(leaves) # => the repair (a borrow, in this case) restored a valid tree
print("ex-45 OK") # => Output: ex-45 OKRun: python3 example.py
Output:
[[4, 5], [6, 7, 8]]
ex-45 OKlearning/code/ex-45-btree-delete-underflow/test_example.py
"""Example 45: pytest verification for B-Tree Leaf Underflow Repair."""
from example import delete, is_valid
def test_underflow_is_repaired_by_borrowing() -> None:
leaves = [[1, 2, 3, 4], [5, 6, 7, 8]]
delete(leaves, 1)
delete(leaves, 2)
delete(leaves, 3)
assert is_valid(leaves)
def test_underflow_falls_back_to_merge_when_no_sibling_can_spare_a_key() -> None:
leaves = [[1, 2], [3, 4]]
delete(leaves, 1) # => leaf 0 drops to [2] -- neither sibling has a spare key to lend
assert is_valid(leaves)
assert len(leaves) == 1 # => the underflowed leaf was merged away entirely
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Borrowing is tried FIRST, since it repairs the underflow without shrinking the leaf count -- merging is the fallback used only when neither sibling has a spare key to lend.
Why it matters: This borrow-then-merge order is exactly why B-tree indexes rarely need explicit maintenance even under heavy delete churn: the tree self-repairs on every delete that causes underflow, keeping every leaf at least half full without ever requiring a separate rebuild or reorganize operation the way some other structures do.
Example 46: Clustered Index: the Leaf Holds the Full Row
ex-46 · exercises co-27
In a clustered index, the leaf entry for the primary key contains the row itself, not a pointer to it. This example runs a primary-key lookup and confirms it needs exactly one fetch to get the full row.
learning/code/ex-46-clustered-index-leaf-holds-row/example.py
"""Example 46: Clustered Index -- Leaf Holds the Full Row."""
# A clustered index (co-27) stores the full row directly in the leaf.
from dataclasses import dataclass # => a plain, typed row
@dataclass # => a plain, typed record -- no custom __init__ needed
class Row: # => a full table row, exactly as it would be stored
id: int # => the primary key -- also this row's clustering key
name: str # => an ordinary column, stored right alongside the PK
email: str # => another ordinary column, stored right alongside the PK
fetch_count: int = 0 # => counts SEPARATE storage fetches -- a clustered lookup should need only one
def clustered_lookup(index: dict[int, Row], pk: int) -> Row | None: # => co-27: leaf itself holds the row
global fetch_count # => a module-level counter, mutated here to measure fetch cost
fetch_count += 1 # => ONE fetch: the leaf lookup IS the row fetch, nothing further to do
return index.get(pk) # => the full row comes back directly from the index entry
index: dict[int, Row] = { # => the clustered index -- keyed by PK, VALUED by the entire row
1: Row(id=1, name="Alice", email="alice@example.com"), # => the leaf entry for PK 1 IS the whole row
2: Row(id=2, name="Bob", email="bob@example.com"), # => same for PK 2 -- no separate heap exists
} # => end of the clustered-index fixture
fetch_count = 0 # => reset before measuring this specific lookup
row = clustered_lookup(index, pk=1) # => a single primary-key lookup
print(row) # => Output: Row(id=1, name='Alice', email='alice@example.com')
print(fetch_count) # => Output: 1
assert row is not None and row.name == "Alice" # => the correct row came back
assert fetch_count == 1 # => exactly one fetch -- no second trip to a separate heap was ever needed
print("ex-46 OK") # => Output: ex-46 OKRun: python3 example.py
Output:
Row(id=1, name='Alice', email='alice@example.com')
1
ex-46 OKlearning/code/ex-46-clustered-index-leaf-holds-row/test_example.py
"""Example 46: pytest verification for Clustered Index Leaves Holding the Full Row."""
from example import Row, clustered_lookup
def test_pk_lookup_returns_the_full_row() -> None:
index = {5: Row(id=5, name="Carol", email="carol@example.com")}
row = clustered_lookup(index, pk=5)
assert row is not None
assert row.email == "carol@example.com"
def test_pk_lookup_needs_exactly_one_fetch() -> None:
import example
example.fetch_count = 0
index = {1: Row(id=1, name="A", email="a@example.com")}
clustered_lookup(index, pk=1)
assert example.fetch_count == 1
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: A clustered PK lookup needs exactly ONE fetch -- the index leaf and the row storage are the SAME structure, so finding the leaf entry IS finding the row, with no second trip required.
Why it matters: This is why a primary-key lookup is typically the fastest access path a table offers: MySQL InnoDB's clustered PK index and this example's design are the same idea, and it is precisely why InnoDB recommends a short, monotonically increasing primary key -- every secondary index on that table stores this PK as its pointer (ex-47), so a large or non-sequential PK bloats every other index too.
Example 47: Heap Table + Secondary Index via (page, slot) Pointer
ex-47 · exercises co-27
In a heap-organized table, a secondary index stores a (page, slot) pointer instead of the row itself, so every secondary lookup costs an index step plus a heap fetch. This example resolves a secondary index entry through both steps to the actual row.
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph LR
A["secondary index<br/>key lookup"]:::blue
B["(page, slot)<br/>pointer"]:::orange
C["heap fetch<br/>actual row"]:::teal
A --> B --> C
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-47-heap-secondary-index-pointer/example.py
"""Example 47: Heap Table + Secondary Index via (page, slot) Pointer."""
# A heap table's secondary index (co-27) stores only a pointer, not the row.
from dataclasses import dataclass # => a plain, typed pointer
@dataclass(frozen=True) # => immutable -- a pointer value should never mutate after creation
class RowPointer: # => a secondary index entry never stores the row itself -- only WHERE to find it
page: int # => which heap page the row physically lives on
slot: int # => which slot within that page's slot array (co-01/co-02 layout)
heap: dict[int, list[str]] = { # => page_id -> list of row values, indexed by slot number
0: ["alice@example.com", "bob@example.com"], # => page 0 holds two rows
1: ["carol@example.com"], # => page 1 holds one row
} # => end of the heap fixture
secondary_index: dict[str, RowPointer] = { # => keyed by the INDEXED column (email), not the PK
"alice@example.com": RowPointer(page=0, slot=0), # => points AT the heap, doesn't duplicate the row
"bob@example.com": RowPointer(page=0, slot=1), # => same page, different slot
"carol@example.com": RowPointer(page=1, slot=0), # => a different page entirely
} # => end of the secondary-index fixture
def secondary_lookup(email: str) -> str | None: # => co-27: two steps -- index, THEN heap fetch
pointer = secondary_index.get(email) # => step 1: find WHERE the row lives
if pointer is None: # => not found in the index at all
return None # => the indexed value simply does not exist
return heap[pointer.page][pointer.slot] # => step 2: fetch the actual row from the heap page
result = secondary_lookup("bob@example.com") # => resolve through both steps to the heap row
print(result) # => Output: bob@example.com
assert result == "bob@example.com" # => the secondary index correctly resolved to the heap row
assert secondary_index["bob@example.com"] == RowPointer(page=0, slot=1) # => the pointer itself is exact
print("ex-47 OK") # => Output: ex-47 OKRun: python3 example.py
Output:
bob@example.com
ex-47 OKlearning/code/ex-47-heap-secondary-index-pointer/test_example.py
"""Example 47: pytest verification for Heap Secondary Index Pointers."""
from example import secondary_lookup
def test_secondary_lookup_resolves_through_the_pointer_to_the_heap_row() -> None:
result = secondary_lookup("carol@example.com")
assert result == "carol@example.com"
def test_missing_key_returns_none_without_touching_the_heap() -> None:
result = secondary_lookup("nobody@example.com")
assert result is None
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: A secondary index over a heap table is a TWO-step lookup by design -- find the pointer in the index, then follow it to the heap page and slot -- unlike the single-step clustered lookup in ex-46.
Why it matters: This two-step cost is exactly what a covering index tries to eliminate: if a query only needs columns already present in the secondary index entry itself, the storage engine can skip the heap fetch entirely and answer straight from the index, which is why "add a covering index" is such a common, effective performance fix for read-heavy queries against heap tables.
Example 48: LSM Size-Tiered Compaction
ex-48 · exercises co-13
Size-tiered compaction merges several same-size SSTables into one larger table, reducing the segment count while keeping every key. This example compacts three small tables and confirms the merged count drops and every key survives.
%% Color Palette: Blue #0173B2, Teal #029E73
graph LR
A["SSTable 1"]:::blue
B["SSTable 2"]:::blue
C["SSTable 3"]:::blue
D["merged<br/>SSTable"]:::teal
A --> D
B --> D
C --> D
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-48-lsm-size-tiered-compaction/example.py
"""Example 48: LSM Size-Tiered Compaction."""
# Size-tiered compaction (co-13) merges same-size SSTables into one.
from dataclasses import dataclass, field # => a typed SSTable representation
@dataclass # => a plain, typed record -- no custom __init__ needed
class SSTable: # => an immutable, sorted, on-disk segment (co-11/co-12 territory)
data: dict[str, str] = field(default_factory=dict[str, str]) # => key -> value, sorted by key on disk
def compact(tables: list[SSTable]) -> SSTable: # => size-tiered: merge SAME-SIZE tables into ONE bigger one
merged: dict[str, str] = {} # => the result of merging every input table
for table in tables: # => later tables (newer) override earlier ones on key conflicts
merged.update(table.data) # => co-12: newest-wins -- a later table's value replaces an earlier one
return SSTable(data=merged) # => one new, larger table replaces however many went in
tables = [ # => three small SSTables from the same size tier
SSTable(data={"a": "v1", "b": "v1"}), # => oldest SSTable
SSTable(data={"c": "v1", "d": "v1"}), # => same tier, disjoint keys
SSTable(data={"a": "v2"}), # => newest SSTable -- overwrites key "a"
] # => end of the SSTable fixture
segment_count_before = len(tables) # => three separate segments before compaction
merged = compact(tables) # => run size-tiered compaction over all three tables
segment_count_after = 1 # => size-tiered compaction always produces exactly one output segment
print(segment_count_before) # => Output: 3
print(segment_count_after) # => Output: 1
print(sorted(merged.data.items())) # => Output: [('a', 'v2'), ('b', 'v1'), ('c', 'v1'), ('d', 'v1')]
assert segment_count_after < segment_count_before # => the merged segment count genuinely dropped
assert merged.data["a"] == "v2" # => every key survived, and the NEWEST value for "a" won
assert set(merged.data.keys()) == {"a", "b", "c", "d"} # => no key was lost during the merge
print("ex-48 OK") # => Output: ex-48 OKRun: python3 example.py
Output:
3
1
[('a', 'v2'), ('b', 'v1'), ('c', 'v1'), ('d', 'v1')]
ex-48 OKlearning/code/ex-48-lsm-size-tiered-compaction/test_example.py
"""Example 48: pytest verification for LSM Size-Tiered Compaction."""
from example import SSTable, compact
def test_compaction_reduces_segment_count_to_one() -> None:
tables = [SSTable(data={"x": "1"}), SSTable(data={"y": "2"})]
merged = compact(tables)
assert isinstance(merged, SSTable)
def test_compaction_preserves_every_key_with_newest_value_winning() -> None:
tables = [SSTable(data={"k": "old"}), SSTable(data={"k": "new"})]
merged = compact(tables)
assert merged.data["k"] == "new"
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Size-tiered compaction always reduces N input segments to exactly ONE output segment, and resolves any key that exists in multiple inputs by keeping the value from the NEWEST table.
Why it matters: Fewer, larger segments is the entire point of compaction: without it, an LSM engine's SSTable count would grow unboundedly with every memtable flush, and ex-51's read amplification (SSTables touched per point read) would grow right along with it. Size-tiered is one of the two dominant compaction strategies -- ex-49 covers its counterpart, leveled compaction, which trades this strategy's simplicity for tighter space and read-amplification bounds.
Example 49: LSM Leveled Compaction
ex-49 · exercises co-13
Leveled compaction pushes an SSTable down into the next level, merging it with any overlapping tables so that level ends up with no key-range overlap at all. This example pushes a table down a level and confirms no overlap remains within that level.
%% Color Palette: Blue #0173B2, Orange #DE8F05, Teal #029E73
graph TD
A["L0<br/>overlapping"]:::blue
B["L1<br/>overlapping table"]:::orange
C["L1<br/>merged, no overlap"]:::teal
A -->|push down| B
B --> C
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef orange fill:#DE8F05,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-49-lsm-leveled-compaction/example.py
"""Example 49: LSM Leveled Compaction."""
# Leveled compaction (co-13) guarantees NO key-range overlap between tables within one level.
from dataclasses import dataclass # => a typed SSTable with an explicit key range
@dataclass # => a plain, typed record -- no custom __init__ needed
class SSTable: # => a sorted, immutable segment, tagged with its own key range
data: dict[str, str] # => key -> value, sorted by key on disk
min_key: str # => the smallest key this table contains -- used to detect overlap
max_key: str # => the largest key this table contains -- used to detect overlap
def overlaps(a: SSTable, b: SSTable) -> bool: # => do two tables' key ranges intersect at all?
return a.min_key <= b.max_key and b.min_key <= a.max_key # => standard interval-overlap test
def push_down(incoming: SSTable, level: list[SSTable]) -> list[SSTable]: # => co-13: merge overlaps away
overlapping = [t for t in level if overlaps(incoming, t)] # => every existing table this one touches
non_overlapping = [t for t in level if t not in overlapping] # => tables entirely untouched by the push
merged_data: dict[str, str] = {} # => the combined contents of the incoming table and its overlaps
for table in overlapping: # => older tables in this level, oldest key precedence first
merged_data.update(table.data) # => start from what was already in the level
merged_data.update(incoming.data) # => the incoming (newer) table's keys win any conflicts
keys = sorted(merged_data) # => the merged key range, sorted, needed for min/max below
merged = SSTable(data=merged_data, min_key=keys[0], max_key=keys[-1]) # => one new, overlap-free table
return non_overlapping + [merged] # => the level after the push: untouched tables plus the merged one
level = [ # => two disjoint existing tables in this level
SSTable(data={"a": "v1", "b": "v1"}, min_key="a", max_key="b"), # => an existing table in this level
SSTable(data={"m": "v1", "n": "v1"}, min_key="m", max_key="n"), # => a DISJOINT existing table
] # => end of the level fixture
incoming = SSTable(data={"b": "v2", "c": "v1"}, min_key="b", max_key="c") # => overlaps the first table
new_level = push_down(incoming, level) # => run leveled compaction's push-down step
print(len(new_level)) # => Output: 2
for i, table_a in enumerate(new_level): # => check EVERY pair of tables in the resulting level
for table_b in new_level[i + 1 :]: # => compare each table only against the ones after it
assert not overlaps(table_a, table_b) # => no two tables in the level may share any key range
print("ex-49 OK") # => Output: ex-49 OKRun: python3 example.py
Output:
2
ex-49 OKlearning/code/ex-49-lsm-leveled-compaction/test_example.py
"""Example 49: pytest verification for LSM Leveled Compaction."""
from example import SSTable, overlaps, push_down
def test_pushed_down_level_has_no_key_overlap() -> None:
level = [SSTable(data={"a": "1"}, min_key="a", max_key="a")]
incoming = SSTable(data={"a": "2", "b": "1"}, min_key="a", max_key="b")
result = push_down(incoming, level)
for i, t1 in enumerate(result):
for t2 in result[i + 1 :]:
assert not overlaps(t1, t2)
def test_disjoint_incoming_table_stays_separate() -> None:
level = [SSTable(data={"a": "1"}, min_key="a", max_key="a")]
incoming = SSTable(data={"z": "1"}, min_key="z", max_key="z")
result = push_down(incoming, level)
assert len(result) == 2
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Within a single level, leveled compaction guarantees every table's key range is disjoint from every other table's -- a point read only ever needs to check ONE table per level, unlike size-tiered's potentially-many-overlapping-tables-per-tier.
Why it matters: This no-overlap-within-a-level property is exactly what bounds leveled compaction's read amplification (ex-51) far more tightly than size-tiered's: a point lookup touches at most one table per level, so the total tables touched is bounded by the number of LEVELS, not the number of TABLES. The cost of that guarantee is more compaction I/O (write amplification, ex-50) since a single new table can overlap several existing ones and force them all to rewrite -- leveled and size-tiered sit at genuinely different points on the RUM trade-off.
Example 50: Count Write Amplification
ex-50 · exercises co-14
Write amplification is the ratio of bytes actually written to storage against the bytes the application asked to write, inflated by every compaction pass. This example ingests data through repeated compactions and confirms the ratio exceeds one.
learning/code/ex-50-write-amplification-count/example.py
"""Example 50: Count Write Amplification."""
# Write amplification (co-14) is bytes ACTUALLY written to disk / bytes the application asked to write.
application_bytes = 0 # => the true, logical amount of data the application wrote
disk_bytes_written = 0 # => every byte ACTUALLY written to storage, across every compaction pass
def flush_memtable(data: dict[str, str]) -> None: # => the memtable's first write to disk (a new SSTable)
global application_bytes, disk_bytes_written # => mutate both module-level counters
size = sum(len(k) + len(v) for k, v in data.items()) # => rough byte size of this batch
application_bytes += size # => this many bytes came from the application itself
disk_bytes_written += size # => the FIRST write of these bytes to disk
def compact(tables: list[dict[str, str]]) -> dict[str, str]: # => merges N tables, REWRITING every byte
global disk_bytes_written # => this counter grows on EVERY compaction, not just the first flush
merged: dict[str, str] = {} # => the compacted result -- newest table's values win
for table in tables: # => later tables' values win on key conflicts
merged.update(table) # => later tables in the list override earlier ones on conflict
size = sum(len(k) + len(v) for k, v in merged.items()) # => the merged table's own byte size
disk_bytes_written += size # => compaction WRITES these bytes again -- this is the amplification
return merged # => hand back the merged table for the next compaction round, if any
flush_memtable({"a": "v1", "b": "v1"}) # => first flush -- application writes these bytes once
flush_memtable({"c": "v1"}) # => second flush -- more application bytes
table1 = {"a": "v1", "b": "v1"} # => a plain dict standing in for a flushed SSTable
table2 = {"c": "v1"} # => a second flushed SSTable, disjoint keys from table1
merged = compact([table1, table2]) # => compaction #1 rewrites everything flushed so far
merged = compact([merged, {"a": "v2"}]) # => compaction #2 rewrites it AGAIN, plus a new update
amplification = disk_bytes_written / application_bytes # => the exact ratio the spec asks to verify
print(application_bytes) # => Output: 9
print(disk_bytes_written) # => Output: 27
print(round(amplification, 2)) # => Output: 3.0
assert amplification > 1 # => compaction rewrote bytes multiple times -- more disk I/O than logical writes
print("ex-50 OK") # => Output: ex-50 OKRun: python3 example.py
Output:
9
27
3.0
ex-50 OKlearning/code/ex-50-write-amplification-count/test_example.py
"""Example 50: pytest verification for Write Amplification Counting."""
import example
def test_write_amplification_exceeds_one() -> None:
example.application_bytes = 0
example.disk_bytes_written = 0
example.flush_memtable({"x": "1"})
example.compact([{"x": "1"}])
assert example.disk_bytes_written / example.application_bytes > 1
def test_a_single_flush_with_no_compaction_has_amplification_one() -> None:
example.application_bytes = 0
example.disk_bytes_written = 0
example.flush_memtable({"x": "1"})
assert example.disk_bytes_written / example.application_bytes == 1
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Every compaction pass rewrites its input bytes to storage AGAIN -- so total bytes written grows with the number of times a key gets caught up in a compaction, which is always more than the single write the application itself issued.
Why it matters: Write amplification is the direct cost side of the RUM conjecture's LSM-friendly bargain: LSM trees turn random writes into fast sequential appends (co-11), but pay for it later when compaction rewrites the same bytes multiple times to keep read and space amplification in check. A write-amplification factor of 10-30x is common in real LSM engines (RocksDB, Cassandra), which is why write-heavy workloads on SSD-backed LSM stores care so much about compaction tuning -- it directly trades off against flash wear and write throughput.
Example 51: Count Read Amplification
ex-51 · exercises co-14
Read amplification counts how many SSTables a point read must open, since a key could live in any of them without an overlap guarantee. This example issues a lookup against a growing number of segments and confirms the touched-table count grows with segment count.
learning/code/ex-51-read-amplification-count/example.py
"""Example 51: Count Read Amplification."""
# Read amplification (co-14) counts SSTables actually opened to answer one point read.
from dataclasses import dataclass # => a typed SSTable representation
@dataclass # => a plain, typed record -- no custom __init__ needed
class SSTable: # => a sorted, immutable segment -- newest tables are checked first
data: dict[str, str] # => key -> value
def point_read(key: str, tables: list[SSTable]) -> tuple[str | None, int]: # => returns (value, tables touched)
touched = 0 # => how many SSTables this read had to open
for table in reversed(tables): # => newest first -- co-12's newest-wins rule
touched += 1 # => opening this table counts as one unit of read amplification
if key in table.data: # => found it -- no need to check any older table
return table.data[key], touched # => found -- stop, no need to check older tables
return None, touched # => the key exists in none of the tables checked
few_tables = [SSTable(data={"a": "v1"})] # => one segment -- a miss touches just one table
many_tables = [SSTable(data={"x": str(i)}) for i in range(5)] # => five segments, none contain "a"
_, touched_few = point_read("a", few_tables) # => a miss against a single-table set
_, touched_many = point_read("a", many_tables) # => a miss against a five-table set
print(touched_few) # => Output: 1
print(touched_many) # => Output: 5
assert touched_many > touched_few # => read amplification grows with segment count, exactly as expected
print("ex-51 OK") # => Output: ex-51 OKRun: python3 example.py
Output:
1
5
ex-51 OKlearning/code/ex-51-read-amplification-count/test_example.py
"""Example 51: pytest verification for Read Amplification Counting."""
from example import SSTable, point_read
def test_read_amplification_grows_with_segment_count() -> None:
one_table = [SSTable(data={"z": "1"})]
four_tables = [SSTable(data={"z": "1"}) for _ in range(4)]
_, touched_one = point_read("y", one_table) # => "y" is absent -- every table must be checked
_, touched_four = point_read("y", four_tables) # => same absent key, but four segments to check now
assert touched_four > touched_one
def test_a_hit_stops_scanning_further_tables() -> None:
tables = [SSTable(data={"a": "1"}), SSTable(data={"a": "2"})]
value, touched = point_read("a", tables)
assert value == "2"
assert touched == 1 # => the newest table already had the key -- no need to check the older one
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Without leveled compaction's no-overlap guarantee (ex-49), a point lookup must check EVERY SSTable that might contain the key, newest first, until it finds a match or exhausts every segment -- more segments directly means more tables touched per read.
Why it matters: Read amplification is the other side of the RUM trade-off from write amplification (ex-50): fewer, larger compactions keep write amplification low but let read amplification grow unchecked, while aggressive compaction keeps read amplification low at the cost of more write I/O. Bloom filters (ex-53) are the standard mitigation that reduces read amplification WITHOUT touching this fundamental trade-off, by letting a read skip tables that provably do not contain the key at all.
Example 52: Measure Space Amplification
ex-52 · exercises co-14
Space amplification compares the bytes an engine occupies on disk to the bytes its live (current, non-stale) data actually needs. This example measures both before compaction and confirms space amplification exceeds one when stale versions are still present.
learning/code/ex-52-space-amplification-measure/example.py
"""Example 52: Measure Space Amplification."""
# Space amplification (co-14) is on-disk bytes / live (current, non-stale) bytes.
from dataclasses import dataclass # => a typed SSTable representation
@dataclass # => a plain, typed record -- no custom __init__ needed
class SSTable: # => a sorted, immutable segment that may contain STALE (overwritten) values
data: dict[str, str] # => key -> value, some of which may be superseded by a later table
def on_disk_bytes(tables: list[SSTable]) -> int: # => EVERY byte physically stored, stale or not
return sum(len(k) + len(v) for table in tables for k, v in table.data.items()) # => no dedup at all
def live_bytes(tables: list[SSTable]) -> int: # => only the bytes a user's read would actually see
live: dict[str, str] = {} # => the current, deduplicated view -- newest value per key wins
for table in tables: # => oldest to newest, so later tables correctly override earlier ones
live.update(table.data) # => co-12's newest-wins rule, applied across ALL tables at once
return sum(len(k) + len(v) for k, v in live.items()) # => bytes needed for just the live data
tables = [ # => two tables where the first key ('a') has been overwritten once
SSTable(data={"a": "original-value"}), # => this "a" is now STALE -- superseded below
SSTable(data={"a": "updated-value", "b": "v1"}), # => the CURRENT value for "a", plus a fresh key
] # => end of the tables fixture
disk = on_disk_bytes(tables) # => counts BOTH copies of "a" -- the stale one and the current one
live = live_bytes(tables) # => counts only the single, current copy of "a", plus "b"
amplification = disk / live # => the exact ratio the spec asks to verify
print(disk) # => Output: 32
print(live) # => Output: 17
print(round(amplification, 2)) # => Output: 1.88
assert amplification > 1 # => a stale version inflated on-disk bytes beyond what live data needs
print("ex-52 OK") # => Output: ex-52 OKRun: python3 example.py
Output:
32
17
1.88
ex-52 OKlearning/code/ex-52-space-amplification-measure/test_example.py
"""Example 52: pytest verification for Space Amplification Measurement."""
from example import SSTable, live_bytes, on_disk_bytes
def test_space_amplification_exceeds_one_with_stale_versions() -> None:
tables = [SSTable(data={"k": "old"}), SSTable(data={"k": "new"})]
assert on_disk_bytes(tables) / live_bytes(tables) > 1
def test_no_stale_versions_gives_amplification_of_exactly_one() -> None:
tables = [SSTable(data={"a": "1"}), SSTable(data={"b": "2"})]
assert on_disk_bytes(tables) == live_bytes(tables)
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Space amplification is on_disk_bytes / live_bytes -- every stale, overwritten, or deleted value sitting uncompacted in an old SSTable counts fully against the disk-bytes numerator even though it contributes nothing to the live data the engine would report to a user.
Why it matters: This is the amplification axis leveled compaction is specifically tuned to control: RocksDB documents leveled compaction as targeting roughly 1.1x space amplification versus size-tiered's potential 2x-or-worse, because leveled compaction proactively reclaims stale versions on every push-down rather than letting them accumulate across an entire tier before the next merge. High space amplification directly costs real money at scale -- it is disk capacity paid for data that will never be read again.
Example 53: Bloom Filters Reduce Read Amplification
ex-53 · exercises co-15
Attaching a Bloom filter to each SSTable lets a point read skip tables it can prove do not contain the key, without opening them. This example adds filters to the read path and confirms fewer tables are opened when the key is absent.
learning/code/ex-53-bloom-reduces-read-amp/example.py
"""Example 53: Bloom Filters Reduce Read Amplification."""
# A Bloom filter (co-15) has NO false negatives -- "maybe present" opens the table, "absent" skips it.
import hashlib # => stdlib hashing, used to build a tiny illustrative Bloom filter
from dataclasses import dataclass, field # => a typed SSTable with an attached filter
def bloom_bits(key: str, size: int) -> set[int]: # => two independent hash positions per key
h1 = int(hashlib.md5(key.encode()).hexdigest(), 16) % size # => first hash function's bit position
h2 = int(hashlib.sha1(key.encode()).hexdigest(), 16) % size # => second, independent hash position
return {h1, h2} # => this key sets (or checks) exactly these two bits
@dataclass # => a plain, typed record -- fields carry their own default_factory
class SSTable: # => a sorted segment, now with its own Bloom filter attached
data: dict[str, str] = field(default_factory=dict[str, str]) # => the actual key-value contents
filter_bits: set[int] = field(default_factory=set[int]) # => bits set by every key this table holds
def add(self, key: str, value: str) -> None: # => insert a key AND mark its Bloom bits
self.data[key] = value # => the real data
self.filter_bits |= bloom_bits(key, size=64) # => union in this key's two bit positions
def might_contain(self, key: str) -> bool: # => the filter check -- no false negatives, ever
return bloom_bits(key, size=64) <= self.filter_bits # => both bits must already be set
def point_read(key: str, tables: list[SSTable]) -> tuple[str | None, int]: # => (value, tables OPENED)
opened = 0 # => only counts tables actually opened -- a filter skip costs nothing
for table in reversed(tables): # => newest first, as always
if not table.might_contain(key): # => the filter proves this table cannot have the key
continue # => SKIPPED -- no table open, no read amplification incurred here
opened += 1 # => the filter said "maybe" -- must actually open and check this table
if key in table.data: # => confirm the filter's "maybe" against the real data
return table.data[key], opened # => confirmed present -- stop scanning further tables
return None, opened # => genuinely absent from every table that wasn't filter-skipped
tables = [SSTable() for _ in range(5)] # => five SSTables, none of which will ever hold "missing-key"
for i, table in enumerate(tables): # => populate each with unrelated keys
table.add(f"key-{i}", f"value-{i}") # => populate this table with one unrelated key
_, opened = point_read("missing-key", tables) # => a lookup the filters should mostly skip
print(opened) # => Output: 0
assert opened < len(tables) # => fewer tables were opened than exist -- the filters did their job
print("ex-53 OK") # => Output: ex-53 OKRun: python3 example.py
Output:
0
ex-53 OKlearning/code/ex-53-bloom-reduces-read-amp/test_example.py
"""Example 53: pytest verification for Bloom Filters Reducing Read Amplification."""
from example import SSTable, point_read
def test_absent_key_opens_fewer_tables_than_exist() -> None:
tables = [SSTable() for _ in range(4)]
for i, table in enumerate(tables):
table.add(f"present-{i}", "v")
_, opened = point_read("definitely-absent", tables)
assert opened < len(tables)
def test_present_key_is_still_found_correctly() -> None:
table = SSTable()
table.add("k", "v")
value, _ = point_read("k", [table])
assert value == "v"
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: A Bloom filter never produces a false NEGATIVE -- if it says "maybe present," the table must still be opened, but if it says "definitely absent," that table can be skipped with zero risk of missing a real match.
Why it matters: This is exactly why every production LSM engine (RocksDB, Cassandra, LevelDB) attaches a Bloom filter to every SSTable: for the extremely common case of a point read for a key that does not exist anywhere, filters turn what would be N table opens (ex-51's read amplification) into zero, at the cost of a small, fixed amount of extra memory per table -- one of the best pure trade-offs available in storage engine design.
Example 54: Row Store: One Row's Fields Are Byte-Adjacent
ex-54 · exercises co-28
A row-store lays out a table tuple-by-tuple, so every field of one row sits next to every other field of that same row on disk. This example serializes rows this way and confirms one row's fields are byte-adjacent.
learning/code/ex-54-row-store-tuple-contiguous/example.py
"""Example 54: Row Store -- One Row's Fields Are Byte-Adjacent."""
# A row store (co-28) lays out every field of ONE row contiguously before the next row begins.
import struct # => stdlib fixed-width binary packing
ROW_FORMAT = "iif" # => two 4-byte ints (id, quantity) and one 4-byte float (price), per row
ROW_SIZE = struct.calcsize(ROW_FORMAT) # => the exact byte width of one packed row
def serialize_row_store(rows: list[tuple[int, int, float]]) -> bytes: # => co-28: tuple-by-tuple layout
buf = bytearray() # => the growing byte buffer -- one row's worth appended at a time
for row_id, quantity, price in rows: # => walk rows in order, writing each one fully before the next
buf += struct.pack(ROW_FORMAT, row_id, quantity, price) # => id, quantity, price -- ALL adjacent
return bytes(buf) # => the final, immutable row-store byte layout
rows = [(1, 10, 9.99), (2, 20, 19.99)] # => two rows, three columns each
data = serialize_row_store(rows) # => the row-major byte layout
print(len(data)) # => Output: 24
row0_start = 0 * ROW_SIZE # => byte offset where row 0 begins
row0_bytes = data[row0_start : row0_start + ROW_SIZE] # => ALL of row 0's fields, one contiguous slice
row0_id, row0_qty, row0_price = struct.unpack(ROW_FORMAT, row0_bytes) # => unpack confirms it round-trips
print((row0_id, row0_qty, row0_price)) # => Output: (1, 10, 9.989999771118164)
assert row0_id == 1 # => the row we asked for came back correctly
assert len(row0_bytes) == ROW_SIZE # => one row's fields fit in exactly ROW_SIZE contiguous bytes
print("ex-54 OK") # => Output: ex-54 OKRun: python3 example.py
Output:
24
(1, 10, 9.989999771118164)
ex-54 OKlearning/code/ex-54-row-store-tuple-contiguous/test_example.py
"""Example 54: pytest verification for Row Store Tuple Contiguity."""
from example import ROW_SIZE, serialize_row_store
def test_one_row_occupies_exactly_row_size_contiguous_bytes() -> None:
data = serialize_row_store([(1, 1, 1.0)])
assert len(data) == ROW_SIZE
def test_two_rows_are_laid_out_back_to_back() -> None:
data = serialize_row_store([(1, 1, 1.0), (2, 2, 2.0)])
assert len(data) == 2 * ROW_SIZE
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: In a row store, the byte OFFSET of the second field of row N is always right after the first field of that SAME row N -- fetching an entire row is one contiguous read, but fetching one column across many rows means skipping over every other column in between.
Why it matters: This layout is exactly why row stores (PostgreSQL, MySQL InnoDB by default) are the natural choice for OLTP workloads that read or write whole rows at a time ("fetch this order", "update this customer"), while ex-55's column store inverts this trade-off for analytical workloads that scan one or two columns across millions of rows. The physical layout, not just the query language, is what determines which access pattern is cheap.
Example 55: Column Store: One Column's Values Are Byte-Adjacent
ex-55 · exercises co-28
A column-store lays out the same data column-by-column, so every row's value for one column sits next to every other row's value for that column. This example serializes the same rows column-major and confirms one column's values are byte-adjacent.
learning/code/ex-55-column-store-column-contiguous/example.py
"""Example 55: Column Store -- One Column's Values Are Byte-Adjacent."""
# A column store (co-28) lays out every ROW's value for ONE column contiguously, column by column.
import struct # => stdlib fixed-width binary packing
def serialize_column_store(rows: list[tuple[int, int, float]]) -> dict[str, bytes]: # => one buffer PER column
ids = bytearray() # => the id column's own dedicated buffer
quantities = bytearray() # => the quantity column's own dedicated buffer
prices = bytearray() # => the price column's own dedicated buffer
for row_id, quantity, price in rows: # => walk rows once, but write each field to its OWN column buffer
ids += struct.pack("i", row_id) # => append to the id column ONLY
quantities += struct.pack("i", quantity) # => append to the quantity column ONLY
prices += struct.pack("f", price) # => append to the price column ONLY
return {"id": bytes(ids), "quantity": bytes(quantities), "price": bytes(prices)} # => three separate columns
rows = [(1, 10, 9.99), (2, 20, 19.99)] # => the SAME two rows, three columns each, as ex-54
columns = serialize_column_store(rows) # => the column-major byte layout
print(len(columns["price"])) # => Output: 8
price0 = struct.unpack("f", columns["price"][0:4])[0] # => row 0's price, sliced from the price column ALONE
price1 = struct.unpack("f", columns["price"][4:8])[0] # => row 1's price, right next to row 0's in this buffer
print((round(price0, 2), round(price1, 2))) # => Output: (9.99, 19.99)
assert len(columns["price"]) == 8 # => two 4-byte floats, back-to-back, with NOTHING else interleaved
assert len(columns) == 3 # => three independent column buffers -- one per field, not one per row
print("ex-55 OK") # => Output: ex-55 OKRun: python3 example.py
Output:
8
(9.99, 19.99)
ex-55 OKlearning/code/ex-55-column-store-column-contiguous/test_example.py
"""Example 55: pytest verification for Column Store Column Contiguity."""
from example import serialize_column_store
def test_price_column_holds_only_price_bytes() -> None:
columns = serialize_column_store([(1, 1, 1.5), (2, 2, 2.5)])
assert len(columns["price"]) == 8 # => two 4-byte floats, nothing else
def test_three_columns_are_kept_in_three_separate_buffers() -> None:
columns = serialize_column_store([(1, 1, 1.0)])
assert set(columns.keys()) == {"id", "quantity", "price"}
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: Transposing the layout from ex-54 changes NOTHING about the logical data -- only WHICH values sit next to each other physically: now it is price[0], price[1], ... that are adjacent, not id[0], quantity[0], price[0].
Why it matters: Column-major layout is what makes analytical engines (Parquet, ClickHouse, column-store extensions in PostgreSQL and SQL Server) so much faster than row stores for SELECT AVG(price) FROM orders-style queries: the engine can stream through nothing but price values, skipping id and quantity entirely, which ex-56 measures directly as fewer bytes read for the same single-column aggregate.
Example 56: A Column Scan Reads Fewer Bytes for a Single-Column Aggregate
ex-56 · exercises co-28
Scanning one column in each layout makes the row-vs-column trade-off concrete: the row store must touch every field of every row, while the column store touches only the target column. This example scans the same data both ways and confirms the column store reads fewer bytes.
%% Color Palette: Blue #0173B2, Teal #029E73
graph TB
subgraph Row store
A["row 1: id,name,age"]:::blue
B["row 2: id,name,age"]:::blue
end
subgraph Column store
C["age column only"]:::teal
end
A -.->|reads all fields| X["slow scan"]:::blue
C -->|reads one field| Y["fast scan"]:::teal
classDef blue fill:#0173B2,stroke:#000000,color:#FFFFFF,stroke-width:2px
classDef teal fill:#029E73,stroke:#000000,color:#FFFFFF,stroke-width:2px
learning/code/ex-56-column-scan-fewer-bytes/example.py
"""Example 56: A Column Scan Reads Fewer Bytes for a Single-Column Aggregate."""
# The row-vs-column trade-off (co-28), made concrete: bytes actually read for ONE column's aggregate.
import struct # => stdlib fixed-width binary packing
ROW_FORMAT = "iif" # => id (4 bytes), quantity (4 bytes), price (4 bytes) -- 12 bytes per row
ROW_SIZE = struct.calcsize(ROW_FORMAT) # => the full width of one row, ALL columns included
def row_store_scan_bytes(row_count: int) -> int: # => a row store MUST read every column to get any one
return row_count * ROW_SIZE # => no way to skip quantity/price bytes even if only price is wanted
def column_store_scan_bytes(row_count: int, column_width: int) -> int: # => reads ONLY the target column
return row_count * column_width # => id and quantity bytes are never touched at all
row_count = 1000 # => a modest table for this illustration
price_width = struct.calcsize("f") # => 4 bytes -- the price column's own fixed width
row_bytes = row_store_scan_bytes(row_count) # => bytes a row store touches for a `SUM(price)`-style scan
column_bytes = column_store_scan_bytes(row_count, price_width) # => bytes a column store touches for the same
print(row_bytes) # => Output: 12000
print(column_bytes) # => Output: 4000
assert column_bytes < row_bytes # => the column store read fewer bytes for the SAME single-column aggregate
savings_ratio = row_bytes / column_bytes # => how many times more the row store had to read
print(round(savings_ratio, 2)) # => Output: 3.0
print("ex-56 OK") # => Output: ex-56 OKRun: python3 example.py
Output:
12000
4000
3.0
ex-56 OKlearning/code/ex-56-column-scan-fewer-bytes/test_example.py
"""Example 56: pytest verification for Column Scan Byte Savings."""
from example import column_store_scan_bytes, row_store_scan_bytes
def test_column_store_reads_fewer_bytes_for_a_single_column_scan() -> None:
rows = row_store_scan_bytes(500)
cols = column_store_scan_bytes(500, column_width=4)
assert cols < rows
def test_savings_scale_with_row_count() -> None:
small = row_store_scan_bytes(10) - column_store_scan_bytes(10, column_width=4)
large = row_store_scan_bytes(1000) - column_store_scan_bytes(1000, column_width=4)
assert large > small
# => Run: pytest -- Output: 2 passedVerify: pytest -q
Output:
2 passedKey takeaway: For a single-column aggregate, bytes read from a ROW store equals the FULL row size times row count, while bytes read from a COLUMN store equals just that one column's width times row count -- the difference grows directly with how many OTHER columns the table has.
Why it matters: This byte-count gap is precisely why a wide table (dozens of columns) sees a much bigger column-store speedup for narrow aggregate queries than a narrow table does: a 50-column table's row-store scan for one column's average still reads all 50 columns' worth of bytes per row, while the column store reads only the one column that actually matters to the query -- exactly the gap analytical query engines are built to exploit.
← Previous: Beginner Examples · Next: Advanced Examples →
Last updated July 26, 2026