Skip to content

Chapter 01 · 10 minutes

The one-line database

Everyone's first database is a dictionary saved as JSON. Pull its plug and watch what is left.

Goal

Meet the simplest database that could work, then crash it at every byte it writes and count how often your data survives.

Set up

You need uv and git. uv installs Python for you. The lab uses the Python standard library only.

git clone https://github.com/jars-demo/build-a-database-demo.git
cd build-a-database-demo/lab
uv sync

Every command in this workshop runs from lab/.

The one-line database

Here is the whole thing, from lab/stash/naive.py:

    def set(self, key: str, value: str) -> None:
        self.data[key] = value
        self.path.write_text(json.dumps(self.data))  # empties the file, then writes it again

Keep everything in a dictionary. On every change, write all of it to disk. It is simple, it is readable, and plenty of real apps ship exactly this.

Pull the plug

A crash can happen at any moment: a power cut, a killed process, a laptop lid closing. The interesting moment is in the middle of a write. stash plug replays 100 writes and stops the disk at every single byte of them, then checks what a restart would find:

uv run python -m stash plug
pulled the plug at every byte of 100 writes
  naive  kept your data at     144 of  66,400 crash points (0.2%)
  stash  kept your data at   4,754 of   4,754 crash points (100.0%)

"Kept your data" means every write that had already finished was still there after the restart. Losing the one write that was half done is fair. Losing earlier writes is not.

The naive database fails at 99.8% of the crash points, and it fails badly: write_text empties the file first, so a crash at almost any byte leaves half a JSON document that does not parse. Not the last write lost. Everything lost.

The second line is the database you build in this workshop. By chapter 04 you will know why it scores 100%.

Check it works

  • stash plug prints both lines, and naive keeps your data at under 1% of crash points.

Exercise

Fix the naive database the classic way: write the JSON to path + ".tmp", then call os.replace(tmp, path). A rename is atomic: after a crash the file is either the old version or the new one, never half of each. Then think about the cost: with a million keys, how many bytes does one set write?

More to try

  • Open lab/stash/plug.py and read naive_every_byte. Why does it count 66,400 crash points for 100 writes?
  • Find a project of yours that saves state as one JSON file. What happens if it crashes mid-save?