Skip to content

Commit adccfcb

Browse files
armenzgandrewshie-sentry
authored andcommitted
chore(deletions): Remove old group deletion task (#96457)
This deletes the old task plus updates some tests to work properly with the new task. Follow-up to #96310.
1 parent 79933c6 commit adccfcb

File tree

6 files changed

+65
-290
lines changed

6 files changed

+65
-290
lines changed

src/sentry/api/helpers/group_index/delete.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
from rest_framework.request import Request
1111
from rest_framework.response import Response
1212

13-
from sentry import audit_log, eventstream, options
13+
from sentry import audit_log
1414
from sentry.api.base import audit_logger
1515
from sentry.deletions.defaults.group import GROUP_CHUNK_SIZE
16-
from sentry.deletions.tasks.groups import delete_groups as delete_groups_task
1716
from sentry.deletions.tasks.groups import delete_groups_for_project
1817
from sentry.issues.grouptype import GroupCategory
1918
from sentry.models.group import Group, GroupStatus
@@ -100,23 +99,13 @@ def delete_group_list(
10099
# `Group` instances that are pending deletion
101100
GroupInbox.objects.filter(project_id=project.id, group__id__in=group_ids).delete()
102101

103-
if options.get("deletions.groups.use-new-task"):
104-
# Schedule a task per GROUP_CHUNK_SIZE batch of groups
105-
for i in range(0, len(group_ids), GROUP_CHUNK_SIZE):
106-
delete_groups_for_project.apply_async(
107-
kwargs={
108-
"project_id": project.id,
109-
"object_ids": group_ids[i : i + GROUP_CHUNK_SIZE],
110-
"transaction_id": str(transaction_id),
111-
}
112-
)
113-
else:
114-
eventstream_state = eventstream.backend.start_delete_groups(project.id, group_ids)
115-
delete_groups_task.apply_async(
102+
# Schedule a task per GROUP_CHUNK_SIZE batch of groups
103+
for i in range(0, len(group_ids), GROUP_CHUNK_SIZE):
104+
delete_groups_for_project.apply_async(
116105
kwargs={
117-
"object_ids": group_ids,
106+
"project_id": project.id,
107+
"object_ids": group_ids[i : i + GROUP_CHUNK_SIZE],
118108
"transaction_id": str(transaction_id),
119-
"eventstream_state": eventstream_state,
120109
}
121110
)
122111

src/sentry/deletions/tasks/groups.py

Lines changed: 2 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from collections.abc import Mapping, Sequence
1+
from collections.abc import Sequence
22
from typing import Any
33

44
import sentry_sdk
55

6-
from sentry import deletions, eventstream
6+
from sentry import deletions
77
from sentry.deletions.defaults.group import GROUP_CHUNK_SIZE
88
from sentry.deletions.tasks.scheduled import MAX_RETRIES, logger
99
from sentry.exceptions import DeleteAborted
@@ -16,75 +16,6 @@
1616
from sentry.utils import metrics
1717

1818

19-
@instrumented_task(
20-
name="sentry.deletions.tasks.groups.delete_groups",
21-
queue="cleanup",
22-
default_retry_delay=60 * 5,
23-
max_retries=MAX_RETRIES,
24-
acks_late=True,
25-
silo_mode=SiloMode.REGION,
26-
taskworker_config=TaskworkerConfig(
27-
namespace=deletion_tasks,
28-
retry=Retry(
29-
times=MAX_RETRIES,
30-
delay=60 * 5,
31-
),
32-
),
33-
)
34-
@retry(exclude=(DeleteAborted,))
35-
@track_group_async_operation
36-
def delete_groups(
37-
object_ids: Sequence[int],
38-
transaction_id: str,
39-
eventstream_state: Mapping[str, Any] | None = None,
40-
**kwargs: Any,
41-
) -> None:
42-
current_batch, rest = object_ids[:GROUP_CHUNK_SIZE], object_ids[GROUP_CHUNK_SIZE:]
43-
44-
# Select first_group from current_batch to ensure project_id tag reflects the current batch
45-
first_group = Group.objects.filter(id__in=current_batch).order_by("id").first()
46-
if not first_group:
47-
raise DeleteAborted("delete_groups.no_group_found")
48-
49-
# The tags can be used if we want to find errors for when a task fails
50-
sentry_sdk.set_tags(
51-
{
52-
"project_id": first_group.project_id,
53-
"transaction_id": transaction_id,
54-
},
55-
)
56-
57-
logger.info(
58-
"delete_groups.started",
59-
extra={
60-
"object_ids_count": len(object_ids),
61-
"object_ids_current_batch": current_batch,
62-
"first_id": first_group.id,
63-
# These can be used when looking for logs in GCP
64-
"project_id": first_group.project_id,
65-
# All tasks initiated by the same request will have the same transaction_id
66-
"transaction_id": transaction_id,
67-
},
68-
)
69-
70-
task = deletions.get(
71-
model=Group, query={"id__in": current_batch}, transaction_id=transaction_id
72-
)
73-
has_more = task.chunk()
74-
if has_more or rest:
75-
delete_groups.apply_async(
76-
kwargs={
77-
"object_ids": object_ids if has_more else rest,
78-
"transaction_id": transaction_id,
79-
"eventstream_state": eventstream_state,
80-
},
81-
)
82-
else:
83-
# all groups have been deleted
84-
if eventstream_state:
85-
eventstream.backend.end_delete_groups(eventstream_state)
86-
87-
8819
@instrumented_task(
8920
name="sentry.deletions.tasks.groups.delete_groups_for_project",
9021
queue="cleanup",
@@ -142,9 +73,6 @@ def delete_groups_for_project(
14273
f"is greater than GROUP_CHUNK_SIZE"
14374
)
14475

145-
# This is a no-op on the Snuba side, however, one day it may not be.
146-
eventstream_state = eventstream.backend.start_delete_groups(project_id, object_ids)
147-
14876
# These can be used for debugging
14977
extra = {"project_id": project_id, "transaction_id": transaction_id}
15078
sentry_sdk.set_tags(extra)
@@ -162,6 +90,3 @@ def delete_groups_for_project(
16290
# Use this to query the logs
16391
tags={"transaction_id": transaction_id},
16492
)
165-
166-
# This will delete all Snuba events for all deleted groups
167-
eventstream.backend.end_delete_groups(eventstream_state)

src/sentry/options/defaults.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -856,12 +856,7 @@
856856
flags=FLAG_AUTOMATOR_MODIFIABLE,
857857
)
858858

859-
register(
860-
"deletions.groups.use-new-task",
861-
type=Bool,
862-
default=False,
863-
flags=FLAG_AUTOMATOR_MODIFIABLE,
864-
)
859+
865860
register(
866861
"issues.severity.first-event-severity-calculation-projects-allowlist",
867862
type=Sequence,

tests/sentry/issues/endpoints/test_organization_group_index.py

Lines changed: 43 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -4311,78 +4311,55 @@ def assert_deleted_groups(self, groups: Sequence[Group]) -> None:
43114311
assert not Group.objects.filter(id=group.id).exists()
43124312
assert not GroupHash.objects.filter(group_id=group.id).exists()
43134313

4314-
@patch("sentry.eventstream.backend")
4315-
def test_delete_by_id(self, mock_eventstream: MagicMock) -> None:
4316-
eventstream_state = {"event_stream_state": str(uuid4())}
4317-
mock_eventstream.start_delete_groups = Mock(return_value=eventstream_state)
4314+
@patch("sentry.eventstream.snuba.SnubaEventStream._send")
4315+
@patch("sentry.eventstream.snuba.datetime")
4316+
def test_delete_by_id(self, mock_datetime: MagicMock, mock_send: MagicMock) -> None:
4317+
fixed_datetime = datetime.now()
4318+
mock_datetime.now.return_value = fixed_datetime
43184319

4319-
group1 = self.create_group(status=GroupStatus.RESOLVED)
4320-
group2 = self.create_group(status=GroupStatus.UNRESOLVED)
4321-
group3 = self.create_group(status=GroupStatus.IGNORED)
4322-
group4 = self.create_group(
4323-
project=self.create_project(slug="foo"),
4324-
status=GroupStatus.UNRESOLVED,
4325-
)
4326-
4327-
hashes = []
4328-
for g in group1, group2, group3, group4:
4329-
hash = uuid4().hex
4330-
hashes.append(hash)
4331-
GroupHash.objects.create(project=g.project, hash=hash, group=g)
4320+
groups = self.create_n_groups_with_hashes(2, project=self.project)
4321+
group_ids = [group.id for group in groups]
43324322

43334323
self.login_as(user=self.user)
4334-
with self.feature("organizations:global-views"):
4335-
response = self.get_response(
4336-
qs_params={"id": [group1.id, group2.id], "group4": group4.id}
4337-
)
4338-
4339-
mock_eventstream.start_delete_groups.assert_called_once_with(
4340-
group1.project_id, [group1.id, group2.id]
4341-
)
4342-
4343-
assert response.status_code == 204
4344-
4345-
assert Group.objects.get(id=group1.id).status == GroupStatus.PENDING_DELETION
4346-
assert not GroupHash.objects.filter(group_id=group1.id).exists()
4347-
4348-
assert Group.objects.get(id=group2.id).status == GroupStatus.PENDING_DELETION
4349-
assert not GroupHash.objects.filter(group_id=group2.id).exists()
4350-
4351-
assert Group.objects.get(id=group3.id).status != GroupStatus.PENDING_DELETION
4352-
assert GroupHash.objects.filter(group_id=group3.id).exists()
4353-
4354-
assert Group.objects.get(id=group4.id).status != GroupStatus.PENDING_DELETION
4355-
assert GroupHash.objects.filter(group_id=group4.id).exists()
4356-
4357-
Group.objects.filter(id__in=(group1.id, group2.id)).update(status=GroupStatus.UNRESOLVED)
4358-
4359-
with self.tasks():
4360-
with self.feature("organizations:global-views"):
4361-
response = self.get_response(
4362-
qs_params={"id": [group1.id, group2.id], "group4": group4.id}
4363-
)
4324+
with self.tasks(), self.feature("organizations:global-views"):
4325+
response = self.get_response(qs_params={"id": group_ids})
4326+
assert response.status_code == 204
43644327

4365-
# XXX(markus): Something is sending duplicated replacements to snuba --
4366-
# once from within tasks.deletions.groups and another time from
4367-
# sentry.deletions.defaults.groups
4368-
assert mock_eventstream.end_delete_groups.call_args_list == [
4369-
call(eventstream_state),
4370-
call(eventstream_state),
4328+
# Extract transaction_id from the first call
4329+
transaction_id = mock_send.call_args_list[0][1]["extra_data"][0]["transaction_id"]
4330+
4331+
assert mock_send.call_args_list == [
4332+
call(
4333+
self.project.id,
4334+
"start_delete_groups",
4335+
extra_data=(
4336+
{
4337+
"transaction_id": transaction_id,
4338+
"project_id": self.project.id,
4339+
"group_ids": group_ids,
4340+
"datetime": json.datetime_to_str(fixed_datetime),
4341+
},
4342+
),
4343+
asynchronous=False,
4344+
),
4345+
call(
4346+
self.project.id,
4347+
"end_delete_groups",
4348+
extra_data=(
4349+
{
4350+
"transaction_id": transaction_id,
4351+
"project_id": self.project.id,
4352+
"group_ids": group_ids,
4353+
"datetime": json.datetime_to_str(fixed_datetime),
4354+
},
4355+
),
4356+
asynchronous=False,
4357+
),
43714358
]
43724359

4373-
assert response.status_code == 204
4374-
4375-
assert not Group.objects.filter(id=group1.id).exists()
4376-
assert not GroupHash.objects.filter(group_id=group1.id).exists()
4377-
4378-
assert not Group.objects.filter(id=group2.id).exists()
4379-
assert not GroupHash.objects.filter(group_id=group2.id).exists()
4380-
4381-
assert Group.objects.filter(id=group3.id).exists()
4382-
assert GroupHash.objects.filter(group_id=group3.id).exists()
4383-
4384-
assert Group.objects.filter(id=group4.id).exists()
4385-
assert GroupHash.objects.filter(group_id=group4.id).exists()
4360+
for group in groups:
4361+
assert not Group.objects.filter(id=group.id).exists()
4362+
assert not GroupHash.objects.filter(group_id=group.id).exists()
43864363

43874364
@patch("sentry.eventstream.backend")
43884365
def test_delete_performance_issue_by_id(self, mock_eventstream: MagicMock) -> None:
@@ -4417,85 +4394,6 @@ def test_bulk_delete_for_many_projects_without_option(self) -> None:
44174394
groups_1 = self.create_n_groups_with_hashes(2, project=self.project)
44184395
groups_2 = self.create_n_groups_with_hashes(5, project=project_2)
44194396

4420-
with (
4421-
self.tasks(),
4422-
patch("sentry.deletions.tasks.groups.GROUP_CHUNK_SIZE", NEW_CHUNK_SIZE),
4423-
patch("sentry.deletions.tasks.groups.logger") as mock_logger,
4424-
patch(
4425-
"sentry.api.helpers.group_index.delete.uuid4",
4426-
side_effect=[self.get_mock_uuid("foo"), self.get_mock_uuid("bar")],
4427-
),
4428-
):
4429-
self.login_as(user=self.user)
4430-
response = self.get_success_response(qs_params={"query": ""})
4431-
assert response.status_code == 204
4432-
batch_1 = [g.id for g in groups_2[0:2]]
4433-
batch_2 = [g.id for g in groups_2[2:4]]
4434-
batch_3 = [g.id for g in groups_2[4:]]
4435-
assert batch_1 + batch_2 + batch_3 == [g.id for g in groups_2]
4436-
4437-
calls_by_project: dict[int, list[tuple[str, dict[str, Any]]]] = defaultdict(list)
4438-
for log_call in mock_logger.info.call_args_list:
4439-
calls_by_project[log_call[1]["extra"]["project_id"]].append(log_call)
4440-
4441-
assert len(calls_by_project) == 2
4442-
assert calls_by_project[self.project.id] == [
4443-
call(
4444-
"delete_groups.started",
4445-
extra={
4446-
"object_ids_count": len(groups_1),
4447-
"object_ids_current_batch": [g.id for g in groups_1],
4448-
"first_id": groups_1[0].id,
4449-
"project_id": self.project.id,
4450-
"transaction_id": "bar",
4451-
},
4452-
),
4453-
]
4454-
assert calls_by_project[project_2.id] == [
4455-
call(
4456-
"delete_groups.started",
4457-
extra={
4458-
"object_ids_count": 5,
4459-
"object_ids_current_batch": batch_1,
4460-
"first_id": batch_1[0],
4461-
"project_id": project_2.id,
4462-
"transaction_id": "foo",
4463-
},
4464-
),
4465-
call(
4466-
"delete_groups.started",
4467-
extra={
4468-
"object_ids_count": 3,
4469-
"object_ids_current_batch": batch_2,
4470-
"first_id": batch_2[0],
4471-
"project_id": project_2.id,
4472-
"transaction_id": "foo",
4473-
},
4474-
),
4475-
call(
4476-
"delete_groups.started",
4477-
extra={
4478-
"object_ids_count": 1,
4479-
"object_ids_current_batch": batch_3,
4480-
"first_id": batch_3[0],
4481-
"project_id": project_2.id,
4482-
"transaction_id": "foo",
4483-
},
4484-
),
4485-
]
4486-
4487-
self.assert_deleted_groups(groups_1 + groups_2)
4488-
4489-
def test_bulk_delete_for_many_projects_with_option(self) -> None:
4490-
NEW_CHUNK_SIZE = 2
4491-
with (
4492-
self.options({"deletions.groups.use-new-task": True}),
4493-
self.feature("organizations:global-views"),
4494-
):
4495-
project_2 = self.create_project(slug="baz", organization=self.organization)
4496-
groups_1 = self.create_n_groups_with_hashes(2, project=self.project)
4497-
groups_2 = self.create_n_groups_with_hashes(5, project=project_2)
4498-
44994397
with (
45004398
self.tasks(),
45014399
patch("sentry.api.helpers.group_index.delete.GROUP_CHUNK_SIZE", NEW_CHUNK_SIZE),

tests/snuba/api/endpoints/test_group_details.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ def test_delete_error_issue(self) -> Any:
326326
url = f"/api/0/issues/{group.id}/"
327327

328328
with patch(
329-
"sentry.api.helpers.group_index.delete.delete_groups_task.apply_async"
329+
"sentry.api.helpers.group_index.delete.delete_groups_for_project.apply_async"
330330
) as mock_apply_async:
331331
response = self.client.delete(url, format="json")
332332
mock_apply_async.assert_called_once()
@@ -353,7 +353,7 @@ def test_delete_issue_platform_issue(self) -> Any:
353353
url = f"/api/0/issues/{group.id}/"
354354

355355
with patch(
356-
"sentry.api.helpers.group_index.delete.delete_groups_task.apply_async"
356+
"sentry.api.helpers.group_index.delete.delete_groups_for_project.apply_async"
357357
) as mock_apply_async:
358358
response = self.client.delete(url, format="json")
359359
assert response.status_code == 202

0 commit comments

Comments
 (0)