[← Blog]

Last Store

LastDB is the product you talk to: schemas, queries, sync, apps on a node that holds your data. Under that node there has to be something that puts bytes on disk and gets them back. For a long time that something was a solid, popular embedded store. It got us shipping. It also became the wrong shape for the product we were actually running. So we built Last Store — our own local engine — and put the primary path on it.

This is not a file format dump. It is the decision story: what hurt, what we compared, what Last Store is for, and how it sits under the node you already know.

Where it sits

You still open apps, declare shapes, and read by key. The node still owns identity, sync, and the query plane. Last Store is the layer that answers: put this key, get this key, scan this prefix, compact this collection. One machine. One home directory. No separate database server to babysit.

WHAT WE SHIP YOUR APPS board · notes · agent tools · … LASTDB NODE schemas · queries · sync · apps LAST STORE local engine · segments · groups · compact ONE MACHINE · YOUR DATA · ONE ENGINE UNDER THE NODE
Fig. 1 — Apps talk to the LastDB node. The node stores through Last Store. Last Store owns segments and groups on disk.

What was wrong with the old bag

The previous embedded engine is excellent at being a general-purpose local map. LastDB is not a general-purpose local map. We write in batches, we hold many logical collections (tips, atoms, indexes, sync planes), we care about honest disk after deletes, and we need concurrency that is not one global choke point on every mutation.

In practice that meant product pain that was hard to explain to ourselves:

  • Deletes did not look like reclaim. Space stayed “hollow” until someone understood freelist folklore. Running du felt like an argument, not a measurement.
  • Everything lived in one opaque bag. Multi-collection structure existed in our heads and in higher layers; the on-disk story did not help ops or recovery.
  • A single global lock shape fought the write patterns we actually use (batch then flush, not fsync-on-every-put).

Those are structural. You can paper over them with more RAM and more restart rituals. We did, for a while. That is not a product story we wanted to keep telling.

BEFORE ONE BAG ON DISK deletes leave hollow du is hard to trust one global choke point AFTER · LAST STORE TIPS ATOMS INDEX named collections compact reclaims bytes LAYOUT YOU CAN REASON ABOUT
Fig. 2 — Before: one opaque bag. After: named collections, segment layout, compact that reclaims bytes you can see.

What Last Store is

Last Store is the Storage v2 local engine. Program name was Storage v2; engine codename was Segstore for a while; the product name is Last Store (crate laststore). Conceptually:

  • Segment files — append-friendly storage, not a forever-growing mystery file that only the engine understands.
  • Named collections — different planes of the product land in different on-disk homes, so layout matches how we already think.
  • Group commit — batch durability that matches how LastDB already wrote (batch then flush), instead of pretending every put is a lone fsync hero.
  • Compact — reclaim is a first-class verb. When we compact a real home, disk numbers move in a direction a human can believe.
  • Hash groups — the key space is partitioned so work can fan out across many small neighborhoods instead of one global queue.
HASH GROUPS · MANY SMALL HOMES G0 G1 G2 GN A KEYED READ HITS A NEIGHBORHOOD NOT ONE LOCK ON THE WHOLE DISK · WORK CAN PARALLELIZE KEY SHAPE STILL MATTERS — SEE THE LAYOUT STORY BELOW
Fig. 3 — Hash groups: many small homes. A well-shaped key hits a neighborhood; a bad scan still can walk the grid.

The layout story

Hash groups only help if keys that belong together live together. Last Store learned that the hard way. The layout story has three acts: a placement rule that destroyed locality, a durable relayout that fixed it, and a second lesson that code can still walk every shelf even after the home is right.

Act 1 — full-key hash. Early on, each key’s group came from hashing the entire key. Rows in one logical partition share a long common prefix and differ only in a suffix. Hash the whole string and those siblings scatter across the grid. A “partition read” — the access pattern apps are supposed to use instead of a table scan — still visited every group, merged, then applied the page limit. Limit one paid like limit unbounded. The store was partitioned; the walk was not.

ACT 1 · FULL-KEY HASH one logical partition · siblings hashed by whole key HASH GROUPS ~1024 groups G0 G1 G2 G3 G4 G5 G6 G7 R R R R PARTITION READ → VISIT EVERY GROUP LIMIT DOES NOT HELP — MERGE FIRST, TAKE LAST
Fig. 4 — Full-key hash: siblings of one logical partition land in many groups. A partition read still walks the whole row.

Act 2 — partition pin + relayout. The better rule places a key by a shelf prefix — everything through a reserved mark in the key — so rows that share a shelf co-locate into a small neighborhood (fanout groups, not the whole grid). That rule only matters on disk. Placement is durable: a new binary on an old home still sits on the old map. Merged code without a migration is a no-op. The fix was a real relayout of the home, a bumped layout epoch, and a layout descriptor that names the rule. After that, a well-keyed partition read could prune.

ACT 2 · PARTITION PIN + RELAYOUT place by shelf prefix · rewrite the home so old keys move HASH GROUPS layout epoch bumped G0 G1 G2 G3 G4 G5 G6 G7 ~FANOUT GROUPS · SIBLINGS HOME LAYOUT DESCRIPTOR code alone is inert migration rewrites placement on disk durable · not a runtime knob
Fig. 5 — Partition pin + relayout: siblings share a neighborhood. The layout descriptor is law until a migration rewrites it.

Act 3 — the shelf-mark rule. Even with the home relaid out, a prefix scan only prunes when the prefix already carries the shelf mark. No mark, no middle ground: the engine falls back to every group in the collection. Some hot paths used molecule-wide prefixes that never included that mark. Layout was right; the walk was still a full sweep. Fixing those paths was software — reuse indexes, prefer point keys, stop rediscovering what we already know — not a second rewrite of every product row. That chapter is Checking Every Shelf.

ACT 3 · THE SHELF-MARK RULE layout right · walk still wrong without a shelf mark in the key SHELF MARK PRESENT PREFIX · · · | · · · MARK PRUNE TO NEIGHBORHOOD NO SHELF MARK PREFIX · · · · · · · NO MARK FULL COLLECTION SWEEP NO MIDDLE — ANCHORED OR ALL GROUPS
Fig. 6 — Shelf mark present: prune to a neighborhood. Absent: full collection sweep. Layout alone is not enough.

Two different “why are we slow?” answers

Wrong placement needs a durable migration — rewrite where bytes live. Right placement, unanchored walks needs better keys and read paths. Confusing the two burns a week: you ship the pin, the home looks correct, and the board is still checking every shelf.

Why not just tune the old engine?

Because the pain was the shape, not the missing knob. We needed a store whose compact story, collection layout, and concurrency model matched LastDB — and that we own end to end when the dogfood primary is on fire at midnight.

What we measured before we switched

We did not cut over on vibes. A head-to-head harness compared Last Store to the previous engine under the durability model we actually use. In that harness, Last Store matched raw write throughput on tiny values, was faster on larger puts and hot gets, stayed competitive on cold gets, and was dramatically smaller on disk after compact — on the order of several times to tens of times less space for the same work, depending on the case.

That was the go decision for Storage v2. Cutover still required the hard bar: real-data copies of a lived-in home, green reads, app paths that no longer assumed the old scan habits. The engine being faster does not forgive an app that still full-scans.

Live now — and still honest

The primary path we dogfood daily runs Last Store. You do not install a second database. You install LastDB; Last Store is how that node holds bytes.

Owning the engine does not mean every read is free. Partitioned layouts only help when the key you scan with can name a shelf — the layout story above, then Checking Every Shelf. Last Store gave us a place where that diagnosis is visible and fixable; it did not erase the need for access patterns.

The lesson we keep

Build the engine that matches the product. Measure it against the durability model you actually run. Then measure the apps again — because a beautiful store will still walk the warehouse if you ask it to.

What this means for you

  • Same product surface. Apps, schemas, keyed reads, sync. You are not learning a new query language for the engine.
  • Disk is a story we can finish. Compact and collection layout are product ops, not folklore.
  • Concurrency has a map. Hash groups are how local work spreads; good keys stay cheap.
  • We dogfood it. The board and knowledge tools we run on LastDB run on Last Store underneath. When it is wrong, we feel it first.

The short version

Last Store is LastDB’s own local storage engine: segments, collections, groups, and compact that reclaims. We left a general-purpose bag because LastDB is not a general- purpose bag. The node is still the product. Last Store is how that product keeps a promise about your disk.

Related: Checking Every Shelf · Thin Tips and Honest History · The Fix Was Subtraction

[← Blog]