Skip to content

Batch triggerer unused trigger cleanup deletes - #70961

Draft
shivaam wants to merge 1 commit into
apache:mainfrom
shivaam:fix-68243-batch-trigger-cleanup
Draft

Batch triggerer unused trigger cleanup deletes#70961
shivaam wants to merge 1 commit into
apache:mainfrom
shivaam:fix-68243-batch-trigger-cleanup

Conversation

@shivaam

@shivaam shivaam commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Trigger.clean_unused() runs synchronously in every triggerer supervisor loop. It currently removes every eligible trigger in one transaction, so busy metadata databases can hold many row locks and delay the triggerer heartbeat and concurrent writers.

This change selects and deletes unused trigger IDs in bounded transactions, committing between batches. It keeps the current NOT EXISTS predicates and SKIP LOCKED candidate locking from #68244, uses a portable bounded select-ID/delete-ID shape on every dialect, and retries a failed batch in a fresh transaction. A new positive [triggerer] unreferenced_triggers_cleanup_batch_size option defaults to 500; non-positive values fail fast rather than restoring an unbounded path.

Focused coverage exercises empty, below/exactly/above-threshold and multi-batch cleanup, protected deferred-task, asset, and callback references, invalid configuration, committed progress across a later failed batch, fresh retry transactions, and eventual drain.

Prior work: #68269 proposed the original batching direction. This implementation is a clean current-main rewrite because that draft predates #68244 and committed inside a caller-injected session.

Scope note: this PR leaves the pre-existing unbounded TaskInstance reference update and portable callback.trigger_id indexing unchanged; those require separate batching/index and migration designs.

Validation:

  • Focused PostgreSQL and MySQL tests through Breeze: 9 passed on each backend.
  • Real scheduler/triggerer PostgreSQL validation with 1,005 eligible rows, bounded [500, 500] deletes while 5 rows were concurrently locked, eventual drain after lock release, and protected asset/callback rows preserved.
  • Ruff, formatting, YAML, spelling, mypy, security, and changed-file repository checks passed.
  • Rebased cleanly onto upstream/main at 66444504c4; the rebased patch ID is identical to the runtime-tested patch.
  • The Docker-backed default-configuration hook passed before rebase. Its post-rebase rerun is currently blocked before validation by a local Colima file bind-mount error after a successful CI-image rebuild; CI can independently confirm it.

closes: #68243


Was generative AI tooling used to co-author this PR?
  • Yes — Codex (GPT-5)

Generated-by: Codex (GPT-5) following the guidelines

Large trigger tables make synchronous cleanup hold locks until every eligible row is deleted, delaying triggerer progress and database writers.
update(TaskInstance)
.where(
TaskInstance.state != TaskInstanceState.DEFERRED,
TaskInstance.trigger_id.is_not(None),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing TaskInstance.trigger_id cleanup update remains unbounded. I’m keeping that separate from this PR because batching it safely requires selecting TaskInstances by their id, locking the triggers, and validating the approach across supported databases. This PR stays focused on #68243’s Trigger deletion problem

select(cls.id)
.where(
~cls.assets.any(),
~cls.callback.has(),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed that callback.trigger_id has no explicit index, which could make this lookup slower.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Batch the triggerer's Trigger.clean_unused() cleanup deletes

1 participant