compute: delay retracting a dropped export's lifecycle rows [deferred, unsound] - #38417
Draft
antiguru wants to merge 2 commits into
Draft
Conversation
antiguru
force-pushed
the
claude/lifecycle-retraction-delay-js1ycm
branch
3 times, most recently
from
August 24, 2026 12:14
c56501a to
5981d21
Compare
The lifecycle log retracts an export's rows the moment it is dropped, so an
object's history vanishes with the object. That loses exactly the episodes worth
looking at: a dataflow that was dropped before it hydrated, or one whose
hydration is the reason someone is reading the log at all. It also means a short
lived dataflow can come and go inside one introspection interval and leave no
trace.
Delay the retraction instead, and record the drop as a stage of its own.
Two delays, because the two populations churn at completely different rates. A
transient export is created per peek and per subscribe, so retaining those for
minutes costs hundreds of megabytes on a busy replica: at eight workers and two
hundred peeks per second, a five minute window holds around 1.4 million rows.
A few seconds is enough for a reader to observe them and costs single digit
megabytes. A user object is dropped by DDL, so even a thousand drops inside the
window is under ten megabytes, and there the history is worth keeping.
compute_lifecycle_retraction_delay default 5 min
compute_lifecycle_retraction_delay_transient default 5 s
Both are floored at the logging interval in code rather than by convention. The
demux rounds update timestamps up to that interval, so a shorter delay can round
to the same timestamp as the insertion and leave the rows never separately
visible, which would defeat the point.
The delays are read per batch rather than at construction, so an
`UpdateConfiguration` command takes effect without recreating the logging
dataflow, and a per-replica override applies to a replica under investigation
without touching the rest.
The `dropped` stage is what makes the delay readable. Without it a lingering row
says only that an export reached some stage, not whether it still exists, so
"hydrated but never written" could not be told apart from "dropped before it
wrote". With it the object's last event names its fate and the delay is pure
retention, and a row whose export id no longer appears in `mz_objects` is
explained by its own `dropped` event rather than reading as a leak.
Part of CPU-226
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ZVCMBSLdxzGus78ZKWhZz
The design doc says `catalog_server_explain.slt` needs no change when a builtin log is added, on the grounds that its query filters `o.id NOT LIKE 'si%'` and so never enumerates a per-replica introspection index. The filter is real, but the conclusion does not follow. The plans already in the file embed the inlined builtin `VALUES` sets as `Constant (N rows)` nodes, so every count over a catalog relation that gained a row moves, and adding an ontology entity and link moves two more. Record the question that catches this: not whether a new plan appears, but whether the existing plans change. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ZVCMBSLdxzGus78ZKWhZz
antiguru
force-pushed
the
claude/lifecycle-retraction-delay-js1ycm
branch
from
August 24, 2026 13:02
5981d21 to
4541a11
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Blocker: delayed retraction is unsound while ids can be reused
The delay retracts an export's rows at a future timestamp while removing its
ExportStateimmediately. Nothing records that a retraction is pending, so a new export with the sameGlobalIdinside the window inserts a fresh row and the relation ends up holding two rows with the same(export_id, worker_id, event)from different incarnations. That breaks the at-most-once property the relation documents and thattest/testdrive/compute-lifecycle-events.tdasserts undermax-tries=1, and it corrupts any duration a reader computes withmax(occurred_at) FILTER (...).Transient ids are reused because
TransientIdGen::new()starts at 1 per process (src/repr/src/global_id.rs:123-129), so a plain environmentd restart — replicas survive and reconcile, transient dataflows are dropped, new generation reissuest1…— collides inside the window.Non-transient ids can also be reused during reconciliation, so the window is not a transient-only problem. That kills the obvious fix: excluding transient exports from the delay does not make this sound, and the split between
compute_lifecycle_retraction_delayandcompute_lifecycle_retraction_delay_transientis not the axis that matters. Any future attempt needs to make retraction and re-insertion of the same id ordered — for example apending_retractionsmap flushed at the current timestamp whenhandle_exportsees an id whose retraction is still outstanding — before the delay is worth having at all.Note the base PR is unaffected: it retracts at the drop timestamp, so no window exists in which two incarnations of an id coexist.
Two further findings, unaddressed
compute_lifecycle_retraction_delaypanics the replica ints_at'sexpect("must fit"). There is a lower clamp to the logging interval but no upper one, andALTER SYSTEM SETreaches it.export_idis absent frommz_compute_exports_per_workerfor up to the delay. The ontology description was written beforedroppedexisted and still implies a lifecycle row means a live export, and the design doc and the testdrive orphan check disagree about whether an orphaned row is expected.Original motivation
The lifecycle log retracts an export's rows the moment it is dropped, so an object's history vanishes with the object. That loses exactly the episodes worth looking at: a dataflow dropped before it hydrated, or one whose hydration is the reason someone opened the log. A short-lived dataflow can also come and go inside one introspection interval and leave no trace.
Part of CPU-226.
What is in the diff
Two commits on top of #38403: the retraction delay with a
droppedstage and two dyncfgs, and an unrelated design-doc correction aboutcatalog_server_explain.slt.The
droppedstage is the part worth keeping from this attempt. Without it a lingering row says only that an export reached some stage, not whether it still exists, so "hydrated but never written" cannot be told apart from "dropped before it wrote".The storage reasoning also stands independently of the soundness problem: a transient export exists per peek and per subscribe, so at eight workers and two hundred peeks per second a five-minute window holds around 1.4 million rows, hundreds of megabytes, while a user object is dropped by DDL and a thousand drops inside the window is under ten megabytes. Whatever replaces this will still need to treat those two populations differently, just not by delay alone.