|
| 1 | +# Garbage Collection |
| 2 | + |
| 3 | +Garbage collection in the storage engine is split by ownership boundary. Each |
| 4 | +collector needs a proof that its target is unreachable for every active |
| 5 | +snapshot before it can physically remove state. |
| 6 | + |
| 7 | +## Transaction Purge |
| 8 | + |
| 9 | +Transaction purge is governed by `Global_Min_STS`, the oldest active snapshot |
| 10 | +timestamp used by the purge workers. A committed undo or index cleanup record is |
| 11 | +purgeable only when its commit timestamp is strictly older than |
| 12 | +`Global_Min_STS`. |
| 13 | + |
| 14 | +Purge removes runtime-only obligations: |
| 15 | + |
| 16 | +- row undo links whose next version is no longer visible to active snapshots |
| 17 | +- index undo branches after their owning row undo can no longer be rolled back |
| 18 | +- delete overlays created by transaction cleanup when the row/deletion proof is |
| 19 | +available |
| 20 | + |
| 21 | +## Row-Page Undo GC |
| 22 | + |
| 23 | +Row-page undo-chain GC is local to the row page and keeps historical versions |
| 24 | +needed by active readers. Runtime unique-key links are part of that same |
| 25 | +lifecycle: they preserve old unique-key ownership across row chains and are not |
| 26 | +owned by secondary-index full-scan cleanup. |
| 27 | + |
| 28 | +Runtime unique-key links are not collectible merely because a row crossed the |
| 29 | +column-store pivot, disappeared from the deletion buffer, or became cold. They |
| 30 | +are collectible only when rollback/index-undo obligations are gone and |
| 31 | +`Global_Min_STS` proves no active snapshot can require the old owner. |
| 32 | + |
| 33 | +## MemIndex Full-Scan Cleanup |
| 34 | + |
| 35 | +User-table secondary indexes use a hot `MemIndex` and a checkpointed cold |
| 36 | +`DiskTree`. Full-scan cleanup is a memory cleanup pass only. It never mutates |
| 37 | +`DiskTree` and never rebuilds checkpointed cold entries into `MemIndex`. |
| 38 | + |
| 39 | +The cleanup pass captures: |
| 40 | + |
| 41 | +- table checkpoint timestamp |
| 42 | +- `pivot_row_id` |
| 43 | +- `ColumnBlockIndex` root |
| 44 | +- secondary `DiskTree` roots |
| 45 | +- deletion checkpoint cutoff |
| 46 | +- caller-supplied `Global_Min_STS` |
| 47 | + |
| 48 | +Live entries can be removed when the captured row id is below the captured |
| 49 | +pivot and the captured `DiskTree` already has the same durable entry: |
| 50 | + |
| 51 | +- unique: same encoded logical key maps to the same row id |
| 52 | +- non-unique: same encoded exact `(logical_key, row_id)` key exists |
| 53 | + |
| 54 | +Delete overlays require overlay-obsolescence proof, not `DiskTree` absence. A |
| 55 | +unique delete-shadow or non-unique delete-marked exact entry can be removed |
| 56 | +when one of these facts is true: |
| 57 | + |
| 58 | +- a deletion-buffer marker is committed and older than `Global_Min_STS` |
| 59 | +- the captured table root is older than `Global_Min_STS`, the row id is below |
| 60 | + the captured pivot, and the captured `ColumnBlockIndex` proves the row id is |
| 61 | + absent |
| 62 | +- the captured table root is older than `Global_Min_STS`, the row id is below |
| 63 | + the captured pivot, and the captured cold LWC row still exists but its current |
| 64 | + indexed values encode to a different scanned MemIndex key |
| 65 | + |
| 66 | +The last case covers committed key changes for cold rows: the row still exists, |
| 67 | +but the scanned delete overlay no longer protects the row's current secondary |
| 68 | +key. If the captured cold row still owns the same encoded key, cleanup retains |
| 69 | +the overlay unless whole-row deletion is proven. Hot row-page key-obsolescence |
| 70 | +proof remains transaction index GC's job because it needs row-page undo-chain |
| 71 | +visibility. |
| 72 | + |
| 73 | +A matching stale cold entry may still exist in the captured `DiskTree` after a |
| 74 | +row-deletion overlay is removed. That is safe because normal row/deletion |
| 75 | +visibility checks filter the row; `DiskTree` mutation remains checkpoint-owned. |
| 76 | + |
| 77 | +Invalid cleanup proofs: |
| 78 | + |
| 79 | +- deletion-buffer absence |
| 80 | +- `row_id < pivot_row_id` by itself |
| 81 | +- a `RowLocation::NotFound` result from a moving current root |
| 82 | +- the existence of a newer `DiskTree` root not captured with the table snapshot |
| 83 | +- hot row-page key mismatch without transaction index GC's row-page proof |
| 84 | + |
| 85 | +Cleanup removes scanned entries with encoded compare-delete operations that |
| 86 | +also check the expected row id or delete-bit state. If an entry changed after |
| 87 | +the scan, cleanup retains it. |
| 88 | + |
| 89 | +## DiskTree And CoW Roots |
| 90 | + |
| 91 | +`DiskTree` roots are published as companion state of table checkpoint and |
| 92 | +deletion checkpoint. Old `DiskTree` pages become reclaimable only after the |
| 93 | +table-file CoW root that references them is no longer reachable by active |
| 94 | +readers. Root reachability GC is separate from `MemIndex` cleanup. |
| 95 | + |
| 96 | +## Deletion Buffer |
| 97 | + |
| 98 | +The deletion buffer tracks tombstones for persisted column-store rows. A marker |
| 99 | +is globally purgeable only after its delete timestamp is committed and older |
| 100 | +than `Global_Min_STS`. |
| 101 | + |
| 102 | +Deletion-buffer absence is deliberately not a general proof. Some hot-origin |
| 103 | +secondary-index overlays never had a cold delete marker, and a missing marker |
| 104 | +does not prove that every active snapshot can ignore the old row/key owner. |
| 105 | + |
| 106 | +## Summary |
| 107 | + |
| 108 | +Use the narrowest proof owned by the component being collected: |
| 109 | + |
| 110 | +- transaction purge uses `Global_Min_STS` and undo/index-undo ownership |
| 111 | +- row-page GC uses row undo-chain visibility |
| 112 | +- runtime unique-key links use the undo GC horizon |
| 113 | +- `MemIndex` cleanup uses captured checkpoint roots plus deletion proof |
| 114 | +- `DiskTree` page GC uses table-file CoW root reachability |
0 commit comments