fivenines
17/40
Lesson 17 · Distribution

Tags, References, and Garbage

In one sentence: Tags are mutable pointers into an immutable blob DAG, so deleting things safely is mark-and-sweep — walk from every live root (tags and containers), keep what's reachable, sweep the rest.

You already know

  • From Lesson 8: the blob store is a DAG — blobs can have many parents.
  • From Lesson 13: the reference store is the only place names live.

Pointers move; bytes don't

Retagging latest from build 41 to build 42 rewrites one row in the reference store — the old manifest and its layers still exist, now possibly unreferenced ("dangling"). Multiply by hundreds of builds and the blob store fills with orphans. But per Lesson 8's DAG, you can never delete a blob just because one manifest stopped needing it — another image may share it. The only safe answer is reachability.

Mark and sweep

Garbage collection walks the DAG from every root: all tags in the reference store, plus every existing container (a container's image must survive even if untagged — Lesson 13's dashed edge). Everything reached is marked live; every unmarked blob is swept. The delicate part is concurrency: a push racing a GC could reference a blob mid-sweep, so real registries GC with writes paused or use grace periods for recent blobs.

Architecture — reachability from the roots
flowchart TB
  T1["root: tag api:2.2"] --> M2["manifest v2.2 ✓"]
  C1["root: container c-42
(runs untagged build 41)"] --> M1["manifest v2.1 ✓"] M2 --> BASE["base layer ✓
(shared — 2 parents)"] M1 --> BASE M2 --> APP2["app layer v2.2 ✓"] M1 --> APP1["app layer v2.1 ✓"] M0["manifest v2.0
(no root → dangling)"] --> BASE M0 --> APP0["app layer v2.0 ✗ sweep"] style M0 fill:#fff8ec,stroke:#f2ddb0 style APP0 fill:#fff8ec,stroke:#f2ddb0
Sequence — one GC cycle
sequenceDiagram
  participant GC as GC
  participant RS as Reference store
  participant CS as Container store
  participant BS as Blob store
  GC->>RS: all tags → root manifests
  GC->>CS: all containers → root images
  GC->>BS: walk DAG from roots, mark reachable
  BS-->>GC: marked: v2.1, v2.2 trees (base counted once)
  GC->>BS: sweep unmarked (v2.0 manifest + its unique layer)
  note over GC,BS: shared base survives —
it was reached from live roots

Anchor these

  • Tags move freely; blobs are deleted only by proof of unreachability.
  • Roots = tags and containers — a running container pins its untagged image.
  • Shared blobs survive as long as any parent is live; GC must not race writers.
Next step

See what actually stuck.

Take the practice scenarios now.