You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while building the #6196 fixtures (PR #6287), and confirmed on main at a03af88.
What happens
Force-deleting a record with a broken chunk chain frees the HEAD slot only. Its continuation chunks are
orphaned - and nothing ever collects them. A bucket whose one 200 KB record was force-deleted by CHECK DATABASE FIX:
before FIX: totalMultiPageRecords=1 totalChunks=4 totalAllocatedRecords=11
after FIX: totalMultiPageRecords=0 totalChunks=4 totalAllocatedRecords=10
Four NEXT_CHUNK slots, each holding up to a page of content, reachable by nothing and reported as chunks of a
record that no longer exists. Repeated runs do not change it.
Why the documented answer is not the actual one
Three places in the tree tell the reader these get cleaned up later:
deleteRecordInternal: "the chunks past this break are orphaned - a bounded space leak reclaimed by
compaction or a later database check"
check(): "any unreachable chunks are reclaimed later by compaction or a database check"
deleteRecord(RID, boolean)'s javadoc: "orphaned (a bounded space leak) to be reclaimed by compaction or a
database check"
Neither mechanism does it:
check() counts a NEXT_CHUNK slot under totalChunks and moves on. There is no pass that walks the live
chains to find which chunk slots nothing points at, and no orphanedChunks field beside the orphanedEdgeSegmentsReclaimed / orphanedExternalRecordsFixed the checker already reports for the OTHER two
kinds of orphan it knows about.
compressPage/getOrderedRecordsInPage re-flow a page's live slots; an orphaned chunk still has a live slot
entry and a NEXT_CHUNK marker, so it is re-flowed along with everything else, never dropped.
The leak is bounded per incident but permanent, and it survives every repair an operator can run short of
exporting and reimporting the database.
Worth considering
Reclaiming a chunk requires knowing that no chain points at it, which is not answerable from the chunk's own
slot - the same asymmetry that makes #6196's content records need a marker. Two shapes fit the checker as it
already works:
Free them at the source: have the force-delete keep walking past the break rather than stopping, so the
chunks it can still reach are freed instead of abandoned. Cheaper, but strictly weaker - it cannot reach
chunks past a second break, or ones orphaned by the paths that leave chunks behind without deleting anything
(GraphEngine.rebuildVertexEdgeChain, an interrupted updateMultiPageRecord shrink).
The two compose: (2) shrinks the population, (1) is what makes the claim in those three comments true. Until one
of them exists, those comments should say that the chunks leak.
Found while building the #6196 fixtures (PR #6287), and confirmed on
mainat a03af88.What happens
Force-deleting a record with a broken chunk chain frees the HEAD slot only. Its continuation chunks are
orphaned - and nothing ever collects them. A bucket whose one 200 KB record was force-deleted by
CHECK DATABASE FIX:Four
NEXT_CHUNKslots, each holding up to a page of content, reachable by nothing and reported as chunks of arecord that no longer exists. Repeated runs do not change it.
Why the documented answer is not the actual one
Three places in the tree tell the reader these get cleaned up later:
deleteRecordInternal: "the chunks past this break are orphaned - a bounded space leak reclaimed bycompaction or a later database check"
check(): "any unreachable chunks are reclaimed later by compaction or a database check"deleteRecord(RID, boolean)'s javadoc: "orphaned (a bounded space leak) to be reclaimed by compaction or adatabase check"
Neither mechanism does it:
check()counts aNEXT_CHUNKslot undertotalChunksand moves on. There is no pass that walks the livechains to find which chunk slots nothing points at, and no
orphanedChunksfield beside theorphanedEdgeSegmentsReclaimed/orphanedExternalRecordsFixedthe checker already reports for the OTHER twokinds of orphan it knows about.
compressPage/getOrderedRecordsInPagere-flow a page's live slots; an orphaned chunk still has a live slotentry and a
NEXT_CHUNKmarker, so it is re-flowed along with everything else, never dropped.The leak is bounded per incident but permanent, and it survives every repair an operator can run short of
exporting and reimporting the database.
Worth considering
Reclaiming a chunk requires knowing that no chain points at it, which is not answerable from the chunk's own
slot - the same asymmetry that makes #6196's content records need a marker. Two shapes fit the checker as it
already works:
Mark-and-sweep within the bucket, on the single pass
check()already makes (Follow-ups from #5764: CHECK DATABASE materialises every record twice, two arms still say "removing it" when nothing is removed, and three smaller checker/test cleanups #5773): collect thecontinuation pointers of every chunk head it walks into a
LongHashSet, and after the pass report - and withfix, free - everyNEXT_CHUNKslot not in it. One set entry per chunk, on an operation that already readsevery page and walks every chain. This is what
orphanedEdgeSegmentsalready does for edge segments, so itis a shape the checker and its result schema both already have.
Free them at the source: have the force-delete keep walking past the break rather than stopping, so the
chunks it can still reach are freed instead of abandoned. Cheaper, but strictly weaker - it cannot reach
chunks past a second break, or ones orphaned by the paths that leave chunks behind without deleting anything
(
GraphEngine.rebuildVertexEdgeChain, an interruptedupdateMultiPageRecordshrink).The two compose: (2) shrinks the population, (1) is what makes the claim in those three comments true. Until one
of them exists, those comments should say that the chunks leak.