Skip to content
Course contents

The Simplest Database That Works

Two lines of shell make a real database. Understanding exactly why it becomes unusable is the whole of this module in miniature.

Lesson 1 of 59 min read

By the end of this lesson you can

  • Explain why appending to a log is the fastest way to write
  • State the cost of the two-line database's read path and why it is unacceptable
  • Describe what compaction reclaims and why the log can still be append-only

Here is a database. It is not a toy — it has properties that real storage engines work hard to keep.

db_set () { echo "$1,$2" >> database; }
db_get () { grep "^$1," database | sed -e "s/^$1,//" | tail -n 1; }

db_set appends a line. db_get finds every line for a key and takes the last one. That is it.

$ db_set 42 '{"name":"Ada"}'
$ db_set 42 '{"name":"Ada Lovelace"}'
$ db_get 42
{"name":"Ada Lovelace"}

It handles updates — by appending a newer record and letting the reader prefer the last one. It survives a crash mid-write, because a truncated final line leaves every earlier record intact.

Why the write path is genuinely excellent

db_set is, near enough, the fastest possible write. Appending to a file is sequential, and sequential beats random by a wide margin on every storage device ever built — for different reasons each time, which is why the advantage keeps surviving hardware changes.

On spinning disks the head had to physically move for a random write. On SSDs there is no head, but the flash translation layer must erase a whole block before rewriting part of one, so scattered small writes turn into a great deal of internal copying. Sequential appends let the drive fill blocks in order and mostly avoid that.

The idea

An append-only log is the highest-throughput write pattern available. Nearly every high-write storage engine is, underneath, a log plus some machinery to make reading it bearable — including the write-ahead log inside the update-in-place engines you will meet later in this module.

Why the read path is unusable

db_get scans the entire file. Every time. For every key.

That is O(n) in the number of records ever written — not records currently live, records ever written, because the old versions are still sitting there. Double the writes and every read gets twice as slow, including reads of keys you wrote years ago.

The second problem: the file never stops growing

Write the same key a thousand times and the file holds a thousand records, of which 999 are dead. Disk fills with history nobody asked for.

The fix is compaction: read the log, keep only the most recent value for each key, write a new file, swap it in, delete the old one.

before   k1=a  k2=b  k1=c  k3=d  k1=e  k2=f
after    k3=d  k1=e  k2=f

The clever part is that compaction is itself a sequential read and a sequential write, and it happens in the background on files nobody is appending to. The live log stays append-only throughout; a compacted file is produced beside it and swapped in when complete. That structure — never modify, always produce a new file and switch — is what makes crash recovery straightforward, and it will reappear in every engine in this module.

What we actually need

To fix reads without giving up the append-only write, we need to answer where is the record for key k without scanning. That is precisely what an index is.

An index is derived data — an additional structure maintained on the side, from which the original data could always be reconstructed. That gives us the governing trade-off of the entire module:

The index trade

Every index speeds up reads that use it and slows down every write, because the write must now update the data and the index. There is no such thing as a free index — the question is always whether the reads you gain are worth the writes you pay.

This is why databases do not index every column by default, and why a table with nine indexes is slow to insert into. It is also why the next lesson starts with the simplest possible index and finds out where it breaks.

Check yourself

Why does an append-only log beat updating records in place, even on an SSD with no moving parts?

What to take away

The two-line database is fast to write, impossible to read, and grows forever. Compaction fixes the growth. The rest of this module is about fixing the reads — and every solution will cost you something on the write path, on the read path, or in space.

Finished this one?

skip for now