Segment Store Garbage Collection

Oak's segment store uses generational garbage collection to reclaim disk space. Content writes create immutable segments, but edits and deletes leave "garbage" (unreachable segments). GC compacts live data into a new generation and deletes old TAR files.

This is the "cost" of the append-only DAG architecture. Understanding it is important for operating Oak Chain at scale.

Status

Segment GC and binary GC run in every validator by default, and neither is paid. A bounded live run on one host (two enterprise clusters of three validators each, mock mode) passed: each member of the compacting cluster reclaimed 3 to 4 MB of deleted content and its store shrank to about 120 kB; three deleted binaries were released and deleted on every member while a kept binary stayed; and the other cluster read the remaining content through its mount on every attempt, with no missing read, no 503 and no 500. Not yet run: several hosts, chain-backed mode, or sustained load.

How Oak Chain Runs GC

GC is not paid and not voted on

Writes and deletes are the paid operations: in chain-backed mode each is settled on the payment contract. Garbage collection is ordinary maintenance. Each validator runs Oak's own revision GC on its own segment store, on a schedule, with no payment, no vote and no HTTP trigger.

Validators of one cluster hold the same content but not the same bytes: segment ids are random and segment boundaries depend on each member's flushes. GC does not change content, so members do not need to agree on it. Each member compacts its own store.

Two kinds of GC run:

Segment GC

What Runs

Oak's own tailGC() and fullGC(): estimation, compaction, then cleanup of segments older than the retained generations. A tail compaction rewrites only what changed since the last compaction; a full compaction rewrites all live content. Oak's guards stay on: a run is skipped when estimation finds too little growth, when free memory is low, or too soon after the last successful run.

When It Runs

Writers Are Not Stalled

Replicated writes apply on Aeron's service thread. Oak can finish a compaction by blocking all writers for up to its force timeout (60 seconds by default). The validator sets the force timeout to 0, which Oak treats as "never force": a compaction that cannot catch up with concurrent writes gives up, cleanup still runs, and the next slot tries again. Writes keep landing during compaction.

Remote Readers

Another enterprise's cluster can mount this cluster's namespaces and read them over HTTP. Its view is a head of this cluster's store. Every compaction starts a new generation, and cleanup removes segments older than the retained generations, so a mounted head stays readable for about the reader window: the shorter compaction interval × (retained generations − 1), one day with the defaults.

Generational Model

gc-generations

How Generations Work

  1. Generation N is the current (active) generation
  2. Generation N-1 is retained as a safety buffer
  3. Generation N-2 is deleted during cleanup

Why Retain 2 Generations?

Readers may hold references to segments in the previous generation. Keeping 2 generations means a reader whose view is less than about one compaction interval old can still read it. A reader with an older view can hit a "segment not found" error, which is why remote mounts refresh their view and answer 503 mount_stale instead (see Remote Readers).

This is a trade-off:

Oak retains 2 generations by default; the cleanup log reports it (with 2 retained generations).

TAR File Cleanup

gc-cleanup

The Cleanup Process

  1. Scan each TAR file's segment index
  2. Check if segments are referenced by current generation
  3. Mark empty TAR files for deletion
  4. Rewrite partial TAR files (optional, to reclaim more space)
  5. Reaper thread deletes marked files

TAR File Naming

data00000a.tar  ← Generation 1
data00001a.tar  ← Generation 1
data00000b.tar  ← Generation 2
data00001b.tar  ← Generation 2

The letter suffix indicates the generation. During cleanup, entire generations of TAR files are removed.

Binary GC

Binaries live in IPFS, outside the segment store. Binary GC releases binaries that no content references and removes them from IPFS. It is on by default.

  1. Stamp. Every replicated write that stores or references a validator-hosted binary records the log position and cluster time on its entry in the replicated binary index (/:oak-chain-binaries).
  2. Mark (leader). Once per interval the leader notes its applied log position, collects the binary references of its segment store (every generation it still keeps), and proposes a BINARY_RELEASE for index entries that nothing references and that were last referenced longer ago than the grace period.
  3. Release (every member). Applying the release removes each listed entry that was last referenced before the mark; an entry referenced since is skipped. Every member reaches the same result from the same log.
  4. Delete (every member). After a short delay each validator checks the index again and deletes its own copy of each released binary. With IPFS Cluster, the cluster pin is removed only when ipfs.cluster.unpin-on-delete is set, which is safe only when the IPFS Cluster serves this enterprise cluster alone.

A deleted binary's bytes are released only after segment GC has dropped every generation that still references it: typically two full compactions. Kubo reclaims the blocks with its own GC (ipfs daemon --enable-gc or Datastore.GCPeriod). A CID an author supplied without the validator storing it is never released.

Settings

Data Table
Property / variable
Default
Meaning
oak.gc.enabled / OAK_GC_ENABLED
true
Segment GC on this member
oak.gc.tail.interval.ms / OAK_GC_TAIL_INTERVAL_MS
1 day
Slot length; every slot runs one compaction (minimum 60 s)
oak.gc.full.interval.ms / OAK_GC_FULL_INTERVAL_MS
7 days
How often a slot runs full instead of tail compaction
oak.gc.retained.generations / OAK_GC_RETAINED_GENERATIONS
2
Generations kept by cleanup (minimum 2)
oak.gc.force.timeout.seconds / OAK_GC_FORCE_TIMEOUT_SECONDS
0
Longest writer block to finish a compaction; 0 never blocks writers
oak.gc.estimation.size.delta.bytes
1 GiB
Growth below which Oak skips a compaction
oak.gc.memory.threshold.percent
15
Free-heap percentage below which Oak skips or stops a compaction
oak.gc.binary.enabled / OAK_GC_BINARY_ENABLED
true
Binary GC marks and deletes on this member
oak.gc.binary.interval.ms / OAK_GC_BINARY_INTERVAL_MS
1 day
How often the leader marks
oak.gc.binary.grace.ms / OAK_GC_BINARY_GRACE_MS
1 day
Minimum age of the last reference before release; must exceed the longest wait between an upload and its write
oak.gc.binary.delete.delay.ms / OAK_GC_BINARY_DELETE_DELAY_MS
10 minutes
Delay between a release and the local delete
ipfs.cluster.unpin-on-delete / IPFS_CLUSTER_UNPIN_ON_DELETE
false
Remove the IPFS Cluster pin of a deleted binary (dedicated cluster only)

Monitoring GC

The Economics of GC

The prices are contract constants (see Smart Contract); validators do not set them.