This project builds a small database one layer at a time. The goal is learning, not production readiness.
- In-memory key-value store
Basic
SET,GET,DELETE, andKEYSbehavior stored only in memory. - Append-only file persistence Changes are written to disk so data survives restarting the REPL.
- Indexed current-state storage plus append-only audit log MiniDB keeps fast current-state lookup while preserving an audit history of changes.
- Page-tree storage with write-ahead log recovery Values are stored as root and leaf pages, with WAL recovery protecting interrupted writes.
- Compaction so deleted/old slots stop wasting space
COMPACTrewrites the data file with only live records. - Checksummed page-tree storage Pages include CRC32 checksums so MiniDB can detect corrupted on-disk pages. Older unchecksummed tree pages are migrated when loaded.
MiniDB is moving from the key/value foundation to a document-oriented model. Instead of tables and fixed rows, it will store JSON-like objects in named collections. Each stored document will have user data plus database-managed metadata needed for querying and future distribution.
A logical document will look like this:
{
"_meta": {
"id": "01J...",
"collection": "users",
"version": 3,
"created_at": "2026-07-22T10:00:00Z",
"updated_at": "2026-07-22T10:05:00Z",
"origin_node": "node-a",
"tombstone": false
},
"data": {
"name": "Ada Lovelace",
"active": true,
"profile": {
"language": "Python"
}
}
}The metadata/data separation keeps application fields flexible while reserving the information required for replication, version comparison, deletion propagation, and conflict handling. The storage engine completed in steps 1-6 remains the local persistence layer beneath this model.
The first version uses a single-node document API. Distribution will be added only after document identity, versioning, tombstones, querying, and local indexes have well-defined behavior.
MiniDB currently supports:
COMPACT: remove dead slots from the data filePUT <collection> [id] <json>: insert or replace a JSON documentGET_DOC <collection> <id>: read a live document by IDDELETE_DOC <collection> <id>: create a deletion tombstoneFIND <collection> [query-json]: match documents by field valuesCOLLECTIONS: list document collectionsHELP: show commandsEXIT: leave the shell
When you run the REPL, MiniDB stores documents in data.minidb. It also writes
storage changes to data.minidb.audit, which is append-only history, and uses
data.minidb.wal for crash recovery.
The raw key/value engine is intentionally internal. Application writes must go through the document API so every record receives identity, version, origin, timestamp, and tombstone semantics. This prevents writes that cannot be safely replicated later.
MiniDB keeps an in-memory hash index of key -> root page offset. The root page
points to leaf pages, and each leaf page stores one chunk of the value. Updating
an existing key can jump directly to the root and leaf offsets. If only one
chunk changes, MiniDB writes only that leaf page instead of rewriting the whole
value.
Each page also stores a CRC32 checksum. When MiniDB starts, it verifies every checksummed page before trusting it, so simple on-disk corruption fails with a clear error instead of silently returning bad data. Older unchecksummed tree pages are still readable and are rewritten with checksums after loading.
The write order is:
- Write changed tree nodes to
data.minidb.wal - Flush the WAL
- Apply those node writes to
data.minidb - Clear the WAL
- Append a summary to
data.minidb.audit
From this folder:
py -3 -m minidb.replYou must run that command from the project root, the folder that contains
README.md and the minidb folder. If your terminal is inside minidb, go up
one level first:
cd ..
py -3 -m minidb.replYou can also use the simpler launcher from the project root:
py -3 run.pyThen try:
db> PUT users user-1 {"name":"Ada Lovelace","active":true}
{"_meta":{"collection":"users","created_at":"...","id":"user-1","origin_node":"local","tombstone":false,"updated_at":"...","version":1},"data":{"active":true,"name":"Ada Lovelace"}}
db> EXIT
bye
Now start it again:
py -3 run.pyThe document is still there because MiniDB loaded data.minidb:
db> GET_DOC users user-1
{"_meta":{"collection":"users","created_at":"...","id":"user-1","origin_node":"local","tombstone":false,"updated_at":"...","version":1},"data":{"active":true,"name":"Ada Lovelace"}}
db> COMPACT
OK
db> DELETE_DOC users user-1
OK
db> GET_DOC users user-1
NULL
The document interface operates on collections rather than tables:
PUT users user-1 {"name":"Ada Lovelace","active":true}
FIND users {"active":true}
GET_DOC users user-1
DELETE_DOC users user-1
Omit the ID in PUT to generate one automatically. Reusing an ID replaces the
document data and advances its metadata version. FIND performs exact matches;
dotted paths such as {"profile.language":"Python"} address nested fields.
py -3 -m unittest discover -s testsWe will build the database in small learning steps:
- In-memory key-value store
- Append-only file persistence
- Indexed current-state storage plus append-only audit log
- Page-tree storage with write-ahead log recovery
- Compaction so deleted/old slots stop wasting space
- Add checksums to detect corrupted pages
- JSON-like documents, collections, and system metadata
- Document queries and field-path matching
- Secondary indexes for selected document fields
- Node identity, cluster membership, and inter-node protocol
- Replication log and follower catch-up
- Versioning, tombstones, and deterministic conflict resolution
- Partitioning, replica placement, and rebalancing
- Configurable consistency for reads and writes
Each step should leave the database runnable and testable.