fivenines
17/40

Guided Problem

MiniDock Build 13: Reclaim Only Unreachable Content

Time
25m
Level
intermediate
Artifacts
not specified
Progress0%
Lesson 17 · Distribution

Tags, References, and Garbage

Design claim: Mutable references and immutable content require reachability-based cleanup rather than deletion by name.

Starting model

  • You can publish a complete release without retransmitting shared content or exposing partial state.
  • The useful vocabulary at this point is deliberately small: image tag, mutable reference, live root, reachability.

Names move; content remains

Operators need movable release names and reclaimed storage without deleting layers still shared by another manifest. Distribution adds concurrency and remote failure to an immutable object graph. The useful question is when another client may safely observe a release.

The tempting shortcut is straightforward: delete every blob when its tag is removed. The happy path makes the shortcut appear atomic even though metadata and large byte transfers complete at different times. Tags point to manifests, manifests share configs and layers, and untagged manifests or active uploads may still make content reachable. Once a reader or retry can interleave with publication, visibility and durability need an explicit order.

Treat references as roots

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.

References are mutable roots over an immutable object graph, and garbage collection removes only objects unreachable from every protected root. The protocol separates authorization, object identity, transfer, and publication so each response has a precise meaning.

Read the architecture as a trust boundary around metadata and bytes, then identify the one operation that makes a graph visible.

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

It shows tags as roots over manifests and shared immutable objects. Its central claim is that content is reclaimable only when no live root or in-progress publication can reach it; the labels therefore describe authority rather than decorative grouping.

Mark first, sweep after the graph is stable

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.

Tag updates change root edges; mark walks manifests and descriptors; sweep removes unmarked content after writers are excluded or coordinated. At each message, ask what the client and registry can now prove about the object graph.

The second figure tests the same model in motion: it traces mark and sweep while preserving shared and publishing content.

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

It traces mark and sweep while preserving shared and publishing content. The ordering is valid only when it continues to preserve the stated invariant under retries and interruption.

Shared content outlives any one tag

One tag is deleted while another manifest shares its base layer, so collection must retain the shared digest. Interruption may leave resumable work, but it must not leave a visible release whose referenced content cannot be served.

Content is reclaimable only when no live root or in-progress publication can reach it. The rule binds remote visibility to verified reachability rather than to connection success.

Let MiniDock reclaim only unreachable truth

MiniDock separates human release names from content lifetime and collects by graph reachability. MiniDock gains a distribution contract while placement remains a design decision.

What carries forward

  • Content is reclaimable only when no live root or in-progress publication can reach it.
  • Reclaim unreachable image objects without breaking tagged or active releases.
  • The rejected shortcut remains a diagnostic: if the design starts depending on it again, the original constraint has probably been lost.
Next step

See what actually stuck.

Take the practice scenarios now.