Skip to content

feat(deletions): Make event deletion task self-chaining idempotent - #122777

Open
shashjar wants to merge 2 commits into
masterfrom
shashjar/make-event-deletion-task-self-chaining-idempotent
Open

feat(deletions): Make event deletion task self-chaining idempotent#122777
shashjar wants to merge 2 commits into
masterfrom
shashjar/make-event-deletion-task-self-chaining-idempotent

Conversation

@shashjar

Copy link
Copy Markdown
Member

For INC-2476.

Modeled after #118966.

The nodestore deletion task (delete_events_for_groups_from_nodestore_and_eventstore) self-chains while paging through events. Because taskbroker provides at-least-once delivery, a re-pended activation could previously enqueue another continuation and fork the chain, amplifying deletion and Snuba traffic.

This PR reuses the merge/unmerge self-chain guard: each activation checks a Redis marker keyed by its taskbroker activation ID and records the marker immediately after dispatching its continuation.

The project-level killswitch added in #122770 remains active.

@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Aug 26, 2026
@shashjar
shashjar requested review from a team, cvxluo and yuvmen August 26, 2026 20:17
@shashjar
shashjar requested a review from a team August 26, 2026 20:29
@shashjar
shashjar marked this pull request as ready for review August 26, 2026 20:31
from sentry.testutils.helpers import override_options
from sentry.utils.snuba import UnqualifiedQueryError

_TASK_KEY = "delete_events_from_nodestore_and_eventstore"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Shouldn't this import the constant from the task module instead of duplicating it?

Comment on lines +151 to +172
@patch("sentry.deletions.tasks.nodestore.mark_spawned")
@patch("sentry.deletions.tasks.nodestore.current_task", return_value=None)
def test_selfchain_is_inert_without_activation(
self, mock_current_task: MagicMock, mock_mark_spawned: MagicMock
) -> None:
events = [SimpleNamespace(event_id="event-id", timestamp="2025-01-01T00:00:00")]

with (
patch(
"sentry.deletions.tasks.nodestore.fetch_events_from_eventstore",
return_value=events,
),
patch("sentry.deletions.tasks.nodestore.delete_events_from_nodestore"),
patch("sentry.deletions.tasks.nodestore.delete_dangling_attachments_and_user_reports"),
patch.object(
delete_events_for_groups_from_nodestore_and_eventstore, "apply_async"
) as mock_apply_async,
):
self.run_deletion_task()

assert mock_apply_async.call_count == 1
mock_mark_spawned.assert_not_called()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Six mocks is a lot of mocks 😬

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

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants