Skip to content

Commit dc897aa

Browse files
authored
fix(aci): Record DetectorGroup associations for new error groups (#100684)
Guarded behind a flag for now. We expect this to be 140k-190k writes per hour, expected to double the scale of DetectorGroup in under a week.
1 parent 21e932d commit dc897aa

File tree

5 files changed

+79
-10
lines changed

5 files changed

+79
-10
lines changed

src/sentry/event_manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
from sentry.utils.safe import get_path, safe_execute, setdefault_path, trim
144144
from sentry.utils.sdk import set_span_attribute
145145
from sentry.utils.tag_normalization import normalized_sdk_tag_from_event
146+
from sentry.workflow_engine.processors.detector import associate_new_group_with_detector
146147

147148
from .utils.event_tracker import TransactionStageStatus, track_sampled_event
148149

@@ -1494,6 +1495,7 @@ def create_group_with_grouphashes(job: Job, grouphashes: list[GroupHash]) -> Gro
14941495
record_new_group_metrics(event)
14951496

14961497
group = _create_group(project, event, **_get_group_processing_kwargs(job))
1498+
associate_new_group_with_detector(group)
14971499
add_group_id_to_grouphashes(group, grouphashes)
14981500

14991501
return GroupInfo(group=group, is_new=True, is_regression=False)

src/sentry/issues/ingest.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
from sentry.utils import json, metrics, redis
3636
from sentry.utils.strings import truncatechars
3737
from sentry.utils.tag_normalization import normalized_sdk_tag_from_event
38-
from sentry.workflow_engine.models import DetectorGroup, IncidentGroupOpenPeriod
38+
from sentry.workflow_engine.models import IncidentGroupOpenPeriod
39+
from sentry.workflow_engine.processors.detector import associate_new_group_with_detector
3940

4041
issue_rate_limiter = RedisSlidingWindowRateLimiter(
4142
**settings.SENTRY_ISSUE_PLATFORM_RATE_LIMITER_OPTIONS
@@ -255,10 +256,7 @@ def save_issue_from_occurrence(
255256
project, event, primary_hash, **issue_kwargs
256257
)
257258
if is_new and occurrence.evidence_data and "detector_id" in occurrence.evidence_data:
258-
DetectorGroup.objects.get_or_create(
259-
detector_id=occurrence.evidence_data["detector_id"],
260-
group_id=group.id,
261-
)
259+
associate_new_group_with_detector(group, occurrence.evidence_data["detector_id"])
262260

263261
open_period = get_latest_open_period(group)
264262
if open_period is not None:

src/sentry/options/defaults.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3097,6 +3097,13 @@
30973097
flags=FLAG_AUTOMATOR_MODIFIABLE,
30983098
)
30993099

3100+
register(
3101+
"workflow_engine.associate_error_detectors",
3102+
type=Bool,
3103+
default=False,
3104+
flags=FLAG_AUTOMATOR_MODIFIABLE,
3105+
)
3106+
31003107
register(
31013108
"grouping.grouphash_metadata.ingestion_writes_enabled",
31023109
type=Bool,

src/sentry/workflow_engine/processors/detector.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77

88
import sentry_sdk
99

10+
from sentry import options
1011
from sentry.db.models.manager.base_query_set import BaseQuerySet
12+
from sentry.grouping.grouptype import ErrorGroupType
1113
from sentry.issues.issue_occurrence import IssueOccurrence
1214
from sentry.issues.producer import PayloadType, produce_occurrence_to_kafka
15+
from sentry.models.group import Group
1316
from sentry.services.eventstore.models import GroupEvent
1417
from sentry.utils import metrics
1518
from sentry.workflow_engine.models import DataPacket, Detector
19+
from sentry.workflow_engine.models.detector_group import DetectorGroup
1620
from sentry.workflow_engine.types import (
1721
DetectorEvaluationResult,
1822
DetectorGroupKey,
@@ -68,8 +72,6 @@ class _SplitEvents(NamedTuple):
6872
def _split_events_by_occurrence(
6973
event_list: list[GroupEvent],
7074
) -> _SplitEvents:
71-
from sentry.grouping.grouptype import ErrorGroupType
72-
7375
events_with_occurrences: list[tuple[GroupEvent, int]] = []
7476
error_events: list[GroupEvent] = [] # only error events don't have occurrences
7577
events_missing_detectors: list[GroupEvent] = []
@@ -116,8 +118,6 @@ def get_detectors_by_groupevents_bulk(
116118
"""
117119
Given a list of GroupEvents, return a mapping of event_id to Detector.
118120
"""
119-
from sentry.grouping.grouptype import ErrorGroupType
120-
121121
if not event_list:
122122
return {}
123123

@@ -281,3 +281,25 @@ def process_detectors[T](
281281
results.append((detector, detector_results))
282282

283283
return results
284+
285+
286+
def associate_new_group_with_detector(group: Group, detector_id: int | None = None) -> bool:
287+
"""
288+
Associate a new Group with it's Detector in the database.
289+
If the Group is an error, it can be associated without a detector ID.
290+
291+
Return whether the group was associated.
292+
"""
293+
if detector_id is None:
294+
# For error Groups, we know there is a Detector and we can find it by project.
295+
if group.type == ErrorGroupType.type_id:
296+
if not options.get("workflow_engine.associate_error_detectors", False):
297+
return False
298+
detector_id = Detector.get_error_detector_for_project(group.project.id).id
299+
else:
300+
return False
301+
DetectorGroup.objects.get_or_create(
302+
detector_id=detector_id,
303+
group_id=group.id,
304+
)
305+
return True

tests/sentry/workflow_engine/processors/test_detector.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
import pytest
77
from django.utils import timezone
88

9+
from sentry.grouping.grouptype import ErrorGroupType
910
from sentry.incidents.grouptype import MetricIssue
10-
from sentry.issues.grouptype import PerformanceNPlusOneAPICallsGroupType
11+
from sentry.issues.grouptype import FeedbackGroup, PerformanceNPlusOneAPICallsGroupType
1112
from sentry.issues.issue_occurrence import IssueOccurrence
1213
from sentry.issues.producer import PayloadType
1314
from sentry.issues.status_change_message import StatusChangeMessage
@@ -23,7 +24,9 @@
2324
from sentry.workflow_engine.handlers.detector import DetectorStateData
2425
from sentry.workflow_engine.handlers.detector.stateful import get_redis_client
2526
from sentry.workflow_engine.models import DataPacket, Detector, DetectorState
27+
from sentry.workflow_engine.models.detector_group import DetectorGroup
2628
from sentry.workflow_engine.processors.detector import (
29+
associate_new_group_with_detector,
2730
get_detector_by_event,
2831
get_detectors_by_groupevents_bulk,
2932
process_detectors,
@@ -1017,3 +1020,40 @@ def test_mixed_occurrences_missing_detectors(self) -> None:
10171020

10181021
assert result == {}
10191022
mock_metrics.incr.assert_called_with("workflow_engine.detectors.error", amount=1)
1023+
1024+
1025+
class TestAssociateNewGroupWithDetector(TestCase):
1026+
def setUp(self) -> None:
1027+
super().setUp()
1028+
self.metric_detector = self.create_detector(project=self.project, type="metric_issue")
1029+
self.error_detector = self.create_detector(project=self.project, type="error")
1030+
1031+
def test_metrics_group_with_known_detector(self) -> None:
1032+
group = self.create_group(project=self.project, type=MetricIssue.type_id)
1033+
1034+
# Should return True and create DetectorGroup
1035+
assert associate_new_group_with_detector(group, self.metric_detector.id)
1036+
assert DetectorGroup.objects.filter(
1037+
detector_id=self.metric_detector.id, group_id=group.id
1038+
).exists()
1039+
1040+
def test_error_group_with_feature_disabled(self) -> None:
1041+
group = self.create_group(project=self.project, type=ErrorGroupType.type_id)
1042+
1043+
with self.options({"workflow_engine.associate_error_detectors": False}):
1044+
assert not associate_new_group_with_detector(group)
1045+
assert not DetectorGroup.objects.filter(group_id=group.id).exists()
1046+
1047+
def test_error_group_with_feature_enabled(self) -> None:
1048+
group = self.create_group(project=self.project, type=ErrorGroupType.type_id)
1049+
1050+
with self.options({"workflow_engine.associate_error_detectors": True}):
1051+
assert associate_new_group_with_detector(group)
1052+
assert DetectorGroup.objects.filter(
1053+
detector_id=self.error_detector.id, group_id=group.id
1054+
).exists()
1055+
1056+
def test_feedback_group_returns_false(self) -> None:
1057+
group = self.create_group(project=self.project, type=FeedbackGroup.type_id)
1058+
assert not associate_new_group_with_detector(group)
1059+
assert not DetectorGroup.objects.filter(group_id=group.id).exists()

0 commit comments

Comments
 (0)