Local reclamation inventory¶
LocalCheckpointStore.inspectReclamation reports which complete published
manifests and associated segments are obsolete under an explicit retention choice.
It acquires exclusive reader protection, verifies every published graph against
the actual owned journal, and returns immutable metadata. It deletes no files.
The result is an inspection snapshot, with no authority to delete objects later.
Close an existing LocalDatabase owner before opening the store and its journal.
Use an empty retained set to retain only the newest published generation, or supply
additional published ManifestId values for physical retention or an in-progress
backup. The newest generation is always retained automatically:
try (var store = LocalCheckpointStore.open(directory);
var journal = LocalJournal.open(store.journalPath())) {
var inventory = store.inspectReclamation(java.util.Set.of(), journal);
if (inventory.status() == ReclamationInventory.Status.VERIFIED) {
var metadata = inventory.obsoleteManifests();
var segments = inventory.obsoleteSegments();
long encodedCandidateBytes = inventory.candidateBytes();
}
}
Imports are org.meridiandb.storage.manifest.LocalCheckpointStore,
org.meridiandb.storage.manifest.ReclamationInventory and
org.meridiandb.storage.journal.LocalJournal; directory is a java.nio.file.Path.
The IDs can be saved from publish or publishCompaction, or obtained from an
admitted reader before closing it. Retained IDs must resolve to existing published
manifests. Supplying an ID creates no durable pin or backup: repeat every required
retained root on each future inspection or removal operation.
Roots and candidate policy¶
| Object or condition | Inventory treatment |
|---|---|
| Highest uniquely named generation | Always a live root; complete graph and actual journal prefix must verify |
| Explicit retained/backup manifest ID | Additional live root; a missing or invalid root fails inspection |
| Other published manifest | Candidate only after its complete graph and actual owner prefix verify |
| Segment used by any live root | Preserved even if an obsolete generation also uses it |
| Segment used only by verified obsolete roots | Candidate with its exact encoded byte length |
| Unreferenced segment, unknown file or subdirectory | Preserved; filename shape or apparent age alone grants no candidate status |
Any pending- name |
Inspection fails pending explicit diagnostic review; no candidate is adopted or discarded |
| Journal, store ownership file and permanent reader guard | Preserved |
| Live lazy reader in this or another process | DEFERRED_READERS, with no verified inventory or candidate totals |
| Aliased metadata or referenced segment | Fail before another object channel opens; preserve the alias/evidence |
Every published graph is checked, including old generations proposed for retirement. A damaged newest generation never falls back to an older one. A damaged old graph also prevents a complete inventory; this conservative policy avoids guessing which objects belong to failed or inconsistent history. Generation-number gaps are legal.
Candidate segments are the dependencies of verified obsolete manifests minus the dependencies of all live roots. Unrelated orphan files stay outside that set. Unknown metadata does not implicitly designate a root: declare any backup's required published IDs explicitly. Unknown/diagnostic names remain untouched, and a physical alias to an inspected object causes refusal. Dangling or inaccessible names that prevent a same-file identity check also fail closed.
Protection, limits and failures¶
The store keeps exclusive publisher ownership and a nonblocking exclusive reader barrier through discovery and verification. A participating reader can outlive store close, so any such reader defers the whole inspection. Known contention leaves the store usable; retry after readers close. The preview releases exclusive protection before returning. First admission can create/force the permanent empty guard and synchronize its directory entry; inspection is therefore not a promise of an unchanged namespace.
The adapter bounds directory entries and published generations as described in local checkpoints. Inventory additionally limits work:
| Budget | Default and hard maximum | Accounting |
|---|---|---|
| Verified encoded bytes | 64 MiB | All manifest images plus referenced segment lengths, counting repeated references again for each root |
| Verified history records | 262,144 | Sum of each published root's transaction cutoff |
| Same-file identity comparisons | 1,000,000 | Distinct inspected metadata/segment names times other directory entries |
Use the overload with new ReclamationInventory.Limits(maxBytes, maxRecords) for
smaller byte/record budgets. Preflight record/reference totals before segment
loads; bound metadata reads by the remaining byte budget and segment reads by
their exact declared lengths, so oversized corrupt files cannot bypass it. These are serialized
work bounds, not exact heap or elapsed-time limits. Complete verification still
replays repeated history and hashes actual owner-journal prefixes for every root.
The byte budget counts inspected graph images; journal acquisition/replay and actual
prefix validation use the journal adapter's own frame/cutoff bounds. A mismatching
actual prefix may require more journal bytes than the graph declares.
The current same-file checks can be costly in a large directory; exceeding a
budget fails inspection without partial results.
Read, validation, identity, protection or cleanup failure poisons the store. Close and reopen before another operation. If protection release fails, store close retries it and independently attempts publisher cleanup. Uncertain native channel close remains quarantined until process restart under the shared ownership rules. No successful inventory escapes after a cleanup error. No failure authorizes repair, deletion, automatic adoption or logical history pruning.
obsoleteManifests, obsoleteSegments and preservedNames have deterministic
name ordering; returned collections are immutable. retainedManifestIds includes
the selected root, with no set-iteration-order promise. verifiedBytes and
verifiedRecords report the accounting above. Candidate bytes count each eligible
name once. They describe encoded files, with no promise of freed blocks, completed
reclamation or faster queries. A later remover must recompute under both forms of
ownership, durably retire obsolete metadata before dependencies, and verify safe
interruption behavior. No removal API is currently implemented.
Example and verification¶
After ./mvnw -B verify, run:
java --class-path target/classes examples/ReclamationInventoryExample.java
The example publishes a repacked generation, demonstrates reader deferral, then compares selected-only and explicit-root inventories. It retains every storage object until its successful temporary-example cleanup after all handles close.
Focused tests:
./mvnw -B -Dtest=LocalReclamationInventoryTest,LocalReclamationInventoryIntegrationTest test
The tests cover roots and shared dependencies, exact bounds, invalid old/new state, actual journal binding, immutable results, diagnostic evidence, alias rejection, protection cleanup and cross-process reader lifetime. They establish inventory behavior; actual removal, interrupted deletion and integrated reclaimed-space conformance remain separate work. See ADR 0018.