Erase Before Write
An interactive SSD simulator: watch a flash translation layer remap every write, run garbage collection, and pay for it in write amplification. Tested TypeScript core; every run reproducible from a seed.
The idea
An SSD can’t overwrite data in place. NAND flash programs a page once, and the only way to reuse it is to erase the whole block it lives in, dozens or hundreds of pages at a time. So every drive runs firmware that quietly lies to the operating system: the flash translation layer keeps a map from logical addresses to physical pages, sends every update to a fresh page, and leaves the old copy behind as stale.
Eventually the fresh pages run out. Garbage collection picks a victim block, copies whatever is still valid somewhere else, and erases it. Those copies are writes the host never asked for. The ratio of flash writes to host writes is the write amplification, and it decides how fast a drive slows down and wears out. I wanted to watch it happen instead of reading about it, so I built the drive.
Try it
- Host writes
- 0
- Flash writes
- 0
- GC copies
- 0
- Erases
- 0
- Free blocks
- 0
- Drive writes
- 0.0×
- Erases/block min · avg · max
- 0 · 0.0 · 0
Each tile is one erase block of 32 pages. Blue pages hold live data, amber pages are stale copies waiting for garbage collection, gray pages are erased. The two outlined tiles are the open blocks: one takes new host writes, the other takes pages that garbage collection is relocating.
Things worth trying:
- Sequential writes stay at 1.00×. Overwriting the drive in order makes whole blocks go stale together, so garbage collection never has to copy a page.
- Random writes are expensive. At 7% over-provisioning the drive settles around 7.5×: every page the host writes costs about seven and a half flash writes. Drag the slider to 28% and it drops to about 2.4×.
- Skewed traffic rewards a smarter victim. Under hot/cold writes, cost-benefit beats greedy, because it leaves recently written blocks alone until their data has had time to go stale. The wear view shows greedy erasing the same blocks over and over.
- TRIM is spare area for free. Trim a quarter of the drive mid-run and recent write amplification falls by more than half, until those addresses are written again.
Measured, not assumed
The engine is a deterministic state machine driven by a seeded workload generator, so these numbers are reproducible. Steady state after a sequential fill and four drive writes, measured over the next eight, mean of five seeds:
| Workload | Over-provisioning | Greedy | Cost-benefit | FIFO |
|---|---|---|---|---|
| Random | 7% | 7.45× | 7.76× | 9.25× |
| Random | 15% | 3.90× | 3.99× | 4.37× |
| Random | 28% | 2.43× | 2.46× | 2.58× |
| Hot/cold 80/20 | 7% | 7.28× | 6.94× | 8.67× |
| Hot/cold 80/20 | 15% | 3.95× | 3.58× | 4.64× |
| Hot/cold 80/20 | 28% | 2.58× | 2.29× | 2.93× |
Two results were not what I expected. On uniform random traffic, cost-benefit is slightly worse than greedy: when every address is equally likely to be rewritten, a block’s age says nothing about its future, so weighting by age only adds noise. And FIFO, the worst policy for write amplification everywhere, wears the flash most evenly. Under hot/cold traffic at 15% over-provisioning, after a dozen drive writes, the gap between the most- and least-erased block is 6 erases with FIFO, 10 with cost-benefit, and 26 with greedy.
As a check on the small drive, the same engine with 1,024 blocks of 64 pages lands within 5% of these random-write numbers, so the demo’s size isn’t what produces them.
How it works
- Page-level mapping. A logical-to-physical table, its reverse, and a valid-page count per block. An overwrite programs the next free page of the open block, marks the old page stale, and updates both tables.
- Separate open blocks for host writes and relocations. Pages that garbage collection copies are usually cold. Writing them next to fresh host data would mix lifetimes in one block and make the next cleanup more expensive.
- A reserve of erased blocks. Garbage collection runs before the host gets a new block whenever only one erased block is left. The constructor refuses any geometry where, even with every closed block full of valid data, the collector could fail to find a stale page to reclaim.
- Three victim policies. Greedy takes the block with the fewest valid pages. FIFO takes the oldest. Cost-benefit, the flash variant of the cleaner from Rosenblum and Ousterhout’s log-structured file system, weighs the space a block would free and its age against the cost of copying out its live pages.
- Dynamic wear leveling. New blocks come from the least-erased end of the free pool.
- The page is only a view. The browser runs the same module as the tests. The canvas batches pages by state into three fills per frame, so even turbo speed, 400 host writes a frame, stays smooth.
Tested
The simulation core is plain TypeScript with no DOM and no dependencies, tested with Node’s built-in test runner. An audit method cross-checks both mapping tables, every valid-page count, the free list, and the identity flash writes = host writes + relocations. The suite runs it through 60,000 mixed writes and trims under every policy, then pins down each claim on this page: sequential writes never copy a page, more over-provisioning means less amplification, greedy beats FIFO on random traffic, cost-benefit beats greedy on skewed traffic, TRIM helps, and a seed reproduces a run exactly.
What it leaves out
Real firmware handles a lot this model ignores: parallelism across planes and dies, an SLC write cache, read disturb and data retention, ECC and bad blocks, static wear leveling, and keeping the mapping table intact through a power cut. It’s a model for building intuition about garbage collection, not a drive.