Keep every snapshot, never the eraser — a write adds a new page instead of rubbing out the old one.
A normal store overwrites: put(key, value) replaces the old bytes and the previous version is gone. A versioned store never destroys; each write appends a new immutable version under the same key. The key always resolves to the latest version, but every past version is still addressable by its number.
That history buys two things you can't get from overwrite-in-place: you can diff any two versions to see exactly what changed, and you can restore an old version by writing its content forward as a brand-new version. Nothing is mutated; restore is just another append.
Each key maps to an append-only list of versions. put appends and returns the new version id; get returns the last one. diff(a, b) compares two stored contents. restore(n) reads version n and writes it as a fresh version — so the history grows and you can even undo the restore.
class VersionedStore:
def __init__(self):
self.history = {} # key -> [v1, v2, ...]
def put(self, key, value):
self.history.setdefault(key, []).append(value)
return len(self.history[key]) # 1-based version id
def get(self, key, version=None):
vs = self.history[key]
return vs[-1] if version is None else vs[version - 1]
def diff(self, key, a, b):
old = set(self.get(key, a).items())
new = set(self.get(key, b).items())
return {"added": dict(new - old), "removed": dict(old - new)}
def restore(self, key, version):
return self.put(key, self.get(key, version)) # append, never mutate
Real systems add content-addressed dedup (identical versions share storage) and retention policies so history doesn't grow without bound.
| Operation | Cost | Note |
|---|---|---|
put / append | O(1) | New immutable version |
get latest | O(1) | Tail of the list |
get version n | O(1) | Indexed lookup |
diff | O(size) | Compares two contents |
restore | O(size) | Append of an old version |
| Storage | O(versions × size) | Dedup & retention to bound it |
A teammate ships a config change in v4 that breaks production. With overwrite-in-place, v3's content is gone — you reconstruct it from memory. With a versioned store, you diff(v3, v4), see the single bad key that changed, and restore(v3). That restore appends a v5 identical to v3. The system is healthy again, and the whole timeline — including the mistake and the rollback — is preserved for the post-incident review.
You have versions v1…v4 and run restore(v2). What is the latest version afterwards?