Immutable objects: overwrite is a new version, not an edit — delete markers and the atomic pointer flip

Bytes at a storage location never change — an overwrite writes brand-new bytes and atomically flips the index pointer to them, and a delete just inserts a zero-byte delete marker, so the old bytes persist (and keep billing) behind it.

Previously

The index points at an object's current bytes — so we need to pin down what those bytes are: they turn out to be immutable, and 'overwrite' is not what you think.

Scene 04

Immutable objects: overwrite is a new version, not an edit

  1. Watch
  2. Try it
  3. Predict
  4. Capture
immutable object — overwrite writes a new versionKEYphotos/cat.jpgversioning ONINDEXcurrent →v1atomic flipVERSION STACK · newest on topv1640 KBCURRENTGET → 200 OK
What to watch for

One key, one blob. Watch a PUT to the SAME key: it drops a brand-new blob on top, the index pointer flips up to it, and the old blob greys out but stays. Then watch an attempt to edit a byte in place — it's refused.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Index.put
an overwrite writes new bytes — it never edits old ones
def put(key, body):
# bytes are sealed at write time; allocate a fresh blob
versionId = newVersionId()
storage.writeImmutable(versionId, body)
if versioning.enabled(key):
# keep the old blob as a recoverable version
index[key].versions.prepend(versionId)
else:
old = index[key].current
index[key].versions = [versionId]
storage.free(old) # old bytes discarded
index[key].current = versionId # atomic pointer flip
Index.delete
a delete inserts a zero-byte marker — it erases nothing
def delete(key):
if not versioning.enabled(key):
storage.free(index[key].current)
del index[key]
return
# versioning on: nothing is erased
marker = newDeleteMarker() # 0 bytes, not billed
index[key].versions.prepend(marker)
index[key].current = marker # GET will now 404
# prior versions still sit on disk, still billing
Index.get / reapVersion
read follows the pointer; only reap actually frees bytes
def get(key):
cur = index[key].current
if cur.isDeleteMarker:
return 404 # marker is current; bytes live behind it
return storage.read(cur) # newest sealed bytes
def reapVersion(key, versionId):
# the ONLY op that frees bytes and stops their bill
index[key].versions.remove(versionId)
storage.free(versionId)

Where this sits in Build an S3-style distributed object store

Scene 04 of 12, in the Name & shape act — The flat keyspace, its index, and immutable objects.. Bytes never change: overwrite = new version + atomic pointer flip; delete drops a marker the old bytes hide behind.

Up next. If a stored object is a sealed, never-changing blob, we can finally store it durably against weekly disk death — and the cheapest safe way to do that is not what most people reach for.

All 12 scenes in Build an S3-style distributed object store · Every curriculum

Built with Arqly
Every scene in Build an S3-style distributed object store builds on the one before it.All 12 Build an S3-style distributed object store scenes