A from-scratch implementation of core database storage engine internals in pure Python (no external dependencies) — built to understand, hands-on, how a database keeps data on disk, indexes it efficiently, and survives a crash.
Most engineers use databases as a black box. This project peels back that abstraction by implementing the pieces that make a storage engine durable and queryable:
- B-Tree index — how data is organized on disk for
O(log n)search, insert, update, and delete, including node splitting and merging. - Write-Ahead Log (WAL) — how writes are made durable before they're applied, and how a crashed database recovers without losing committed data.
- Checkpointing & recovery — how a database avoids replaying its entire history on every restart.
Deliberately out of scope: replication/failover, transactions & concurrency control (isolation levels), and query parsing/optimization — the focus is the storage layer itself, with everything else simplified away.
┌─────────────────────────────────────────────────────────────┐
│ Database │
│ │
│ insert(k, v) / update(k, v) / delete(k) │
│ │ │
│ ├──1. append_log──▶ WAL (wal_<table>.txt) │
│ │ append-only, flushed to disk │
│ │ before the operation is applied │
│ │ │
│ └──2. apply────────▶ B-Tree (in-memory index) │
│ │
│ every N operations ──▶ checkpoint: │
│ - serialize B-Tree ──▶ checkpoint_<table>.txt │
│ - truncate WAL │
│ │
│ on startup ──▶ recover: │
│ - load checkpoint (if any) │
│ - replay remaining WAL entries on top │
└─────────────────────────────────────────────────────────────┘
Every mutation follows the classic log-before-apply pattern:
- The operation is wrapped in an
LSN(Log Sequence Number) record —{operation_type, data, timestamp}. - The
LSNis serialized andfsync'd to the WAL file first. If the process crashes here, the operation can still be replayed on restart. - Only then is the operation applied to the in-memory B-Tree.
On startup, Database._recover():
- Loads the last checkpoint (a full serialized snapshot of the B-Tree), if one exists.
- Replays every WAL entry written after that checkpoint, reconstructing any state lost between the last checkpoint and the crash.
Replaying an ever-growing WAL on every restart doesn't scale. Every
checkpoint_trigger_operation_count operations, the engine:
- Serializes the entire B-Tree to
checkpoint_<table>.txt(JSON). - Truncates the WAL, since its entries are now captured in the checkpoint.
storage_engines/btree.py implements a textbook B-Tree of configurable
minimum degree m:
- Search — recursive descent,
O(log n). - Insert — inserts into the correct leaf, splitting nodes (and propagating the split upward, including creating a new root) on overflow.
- Delete — handles all three classic cases: deletion from a leaf, deletion of an internal key (replaced by its in-order predecessor/successor), and underflow resolution via borrowing from a sibling or merging siblings.
- Serialize / deserialize — JSON round-trip of the whole tree, used for WAL replay and checkpointing.
database/
database.py # Database: ties B-Tree + WAL + checkpoint/recovery together
storage_engines/
btree.py # BTree / BTreeNode: insert, search, update, delete, (de)serialize
wal/
wal.py # WAL: append-only log file, read, truncate
shared/
lsn.py # LSN: the log record format written to the WAL
from database.database import Database
db = Database("users")
db.insert(1, "alice")
db.insert(2, "bob")
db.update(1, "alice-updated")
print(db.btree.search(1)) # -> "alice-updated"
db.delete(2)
print(db) # pretty-prints the B-Tree level by levelEvery call above is first written to wal_users.txt, then applied to the
B-Tree. Kill the process mid-way and re-run — Database.__init__ will replay
the WAL and restore exactly the state before the crash. Every 2 operations
(checkpoint_trigger_operation_count), the tree is snapshotted to
checkpoint_users.txt and the WAL is cleared.
python3 -c "
from database.database import Database
db = Database('demo')
for i in range(5):
db.insert(i, f'value-{i}')
print(db)
"
# Re-run — recovers from checkpoint + WAL, picks up where it left off
python3 -c "
from database.database import Database
db = Database('demo')
print(db)
"This project follows the original learning spec — the B-Tree + WAL path above is implemented; the rest is planned next:
- Hash table storage engine (linear probing / chaining) implementing the same insert/search/delete interface, for a direct architectural comparison with the B-Tree.
- Benchmark suite comparing B-Tree vs. hash table on response time and memory/disk usage across workloads.
- CLI entry point for interactive use.
- Python 3.14, standard library only (no runtime dependencies).
- ruff for linting/formatting, enforced via pre-commit hooks.