Skip to content

feat(observability): report why a process runs without a pending drops cleaner - #1063

Draft
aparajon wants to merge 7 commits into
mainfrom
armand/pending-drops-cleaner-decline-reasons
Draft

feat(observability): report why a process runs without a pending drops cleaner#1063
aparajon wants to merge 7 commits into
mainfrom
armand/pending-drops-cleaner-decline-reasons

Conversation

@aparajon

Copy link
Copy Markdown
Collaborator

Stacked on #1057 — review that first; this PR's diff is the last commit only.

Quarantining without reaping is what leaves tables on a target forever, so the reason a process is not reaping is the thing an operator needs to see. A single predicate reported all four causes with one message, which could not distinguish a process that has nothing to reap from one that quarantines and leaves reaping to a deployment that may not exist.

Each cause is now reported separately, states its consequence definitively, and is counted by reason so the dangerous combinations are alertable:

reason meaning
quarantine_disabled Expected. DROP TABLE executes as written, so there is nothing to reap.
no_local_targets Expected for a control plane. Every target is routed to the deployment that executes against it and reaps it.
cleanup_disabled_for_process Safe only while another deployment reaps the same targets.
invalid_retention Config bug. Blocks reaping until it is fixed.

The no_local_targets message now carries the routed-target count, which is what separates a control plane whose deployments reap their own targets from a process with no MySQL topology at all. That distinction is also why this case stays at info: warning on it would fire on every control plane for a benign state.

Authored by Claude Code (claude-opus-5).

The quarantine and its cleaner are two halves of one feature, but only
the quarantine was on by default. A deployment that could rename a
dropped table into `_pending_drops` on its target could not necessarily
reach that same target to reap it later, so quarantined tables piled up
on target servers with nothing scheduled to remove them.

Default `pending_drops.enabled` to false. With no `pending_drops` block
a `DROP TABLE` executes as written, and turning the quarantine on is a
deliberate statement that this deployment reaps its own targets.
The declarative differ emits a bare DROP TABLE, and the DROP phase
re-runs from its first statement every time an apply resumes. A phase
that dropped some of its tables before being stopped failed on the
second attempt with "unknown table" and never reached the tables still
standing, so the apply could not finish without an operator editing the
schema.

Check each target before dropping it and skip the ones that are already
absent, which converges on exactly the state the plan asked for. The
skip is logged and surfaced on the apply log, because on a first attempt
it means something outside the apply removed the table.
A table removed between the existence check and the drop has already
reached the state the plan asked for, so failing the apply on it defeats
the convergence the check exists to provide. The check still decides
what is reported as already absent.
…opt-in

# Conflicts:
#	pkg/engine/spirit/pending_drops.go
…s cleaner

Quarantining without reaping is what leaves tables on a target forever,
so the reason a process is not reaping is the thing an operator needs to
see. A single predicate reported all of them with one message, which
could not distinguish a process that has nothing to reap from one that
quarantines and leaves reaping to a deployment that may not exist.

Split the decline into its causes, state the consequence of each
definitively, and count them by reason so the dangerous combinations are
alertable. The no-local-targets message now carries the routed target
count, which is what separates a control plane whose deployments reap
their own targets from a process with no MySQL topology at all.
The metadata key the servers send is how a deployment states whether it
quarantines, but the local client still read an absent key as "quarantine".
An embedder that builds a LocalConfig itself, bypassing both server paths,
therefore got the quarantine with no cleaner guaranteed — the orphaned
quarantine this opt-in exists to prevent.

Read the key as an explicit opt-in, and have both server paths state it
either way rather than only when disabled, so a data plane that predates
the new default cannot read an omitted key as consent during a rolling
deploy.

Also record a counter when a DROP target is already absent, so the case
is alertable rather than only greppable; cover the direct path's
view/temporary/IF EXISTS forwarding, which was only exercised with the
quarantine enabled; and correct a comment that still called the
quarantine the default.
Base automatically changed from armand/pending-drops-opt-in to main August 17, 2026 05:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant