Repository: getsentry/sentry

Issue:
Stop calling snuba for every occurrence sent to `post_process_group`
In `post_process_group`, we fetch the event for an occurrence via `get_event_by_id` https://github.com/getsentry/sentry/blob/5e4bc9e1e892ffe4dc740202fa295fd76eaf80b7/src/sentry/tasks/post_process.py#L578-L591

https://github.com/getsentry/sentry/blob/411f0b49e421d8d7138b02b2215f9e91dca2816c/src/sentry/eventstore/snuba/backend.py#L299-L306

Currently, this hits nodestore and then hits snuba to get the `group_id` and `occurrence_id`. This call to snuba isn't necessary - we already have the `occurrence_id` and `group_id` available in post process group.

Modify `get_event_by_id` to accept an `occurrence_id`, and skip the call to snuba if it's already present. Then we should pass in the occurrence id in the call from `post_process_group`


Discussion:

Resolving patch:
diff --git a/src/sentry/eventstore/base.py b/src/sentry/eventstore/base.py
--- a/src/sentry/eventstore/base.py
+++ b/src/sentry/eventstore/base.py
@@ -219,7 +219,15 @@ def get_unfetched_events(
         """
         raise NotImplementedError
 
-    def get_event_by_id(self, project_id, event_id, group_id=None, tenant_ids=None):
+    def get_event_by_id(
+        self,
+        project_id,
+        event_id,
+        group_id=None,
+        skip_transaction_groupevent=False,
+        tenant_ids=None,
+        occurrence_id=None,
+    ):
         """
         Gets a single event of any event type given a project_id and event_id.
         Returns None if an event cannot be found.
diff --git a/src/sentry/eventstore/snuba/backend.py b/src/sentry/eventstore/snuba/backend.py
--- a/src/sentry/eventstore/snuba/backend.py
+++ b/src/sentry/eventstore/snuba/backend.py
@@ -303,6 +303,7 @@ def get_event_by_id(
         group_id=None,
         skip_transaction_groupevent=False,
         tenant_ids=None,
+        occurrence_id: Optional[str] = None,
     ):
         """
         Get an event given a project ID and event ID
@@ -328,6 +329,17 @@ def get_event_by_id(
             or (event.get_event_type() == "transaction" and skip_transaction_groupevent)
         ):
             event.group_id = group_id
+        elif occurrence_id is not None and group_id is not None:
+            event.group_id = group_id
+
+            event._snuba_data = {
+                "event_id": event_id,
+                "group_id": group_id,
+                "project_id": project_id,
+                "timestamp": event.timestamp,
+                "occurrence_id": occurrence_id,
+            }
+
         elif event.get_event_type() != "transaction" or group_id:
             # Load group_id from Snuba if not a transaction
             raw_query_kwargs = {}
diff --git a/src/sentry/tasks/post_process.py b/src/sentry/tasks/post_process.py
--- a/src/sentry/tasks/post_process.py
+++ b/src/sentry/tasks/post_process.py
@@ -583,6 +583,7 @@ def get_event_raise_exception() -> Event:
                     occurrence.event_id,
                     group_id=group_id,
                     skip_transaction_groupevent=True,
+                    occurrence_id=occurrence_id,
                 )
                 if retrieved is None:
                     raise EventLookupError(


Test patch:
diff --git a/tests/sentry/tasks/test_post_process.py b/tests/sentry/tasks/test_post_process.py
--- a/tests/sentry/tasks/test_post_process.py
+++ b/tests/sentry/tasks/test_post_process.py
@@ -2470,6 +2470,44 @@ def test_occurrence_deduping(self, mock_processor):
         # Make sure we haven't called this again, since we should exit early.
         assert mock_processor.call_count == 1
 
+    @patch("sentry.tasks.post_process.handle_owner_assignment")
+    @patch("sentry.tasks.post_process.handle_auto_assignment")
+    @patch("sentry.tasks.post_process.process_rules")
+    @patch("sentry.tasks.post_process.run_post_process_job")
+    @patch("sentry.rules.processor.RuleProcessor")
+    @patch("sentry.signals.event_processed.send_robust")
+    @patch("sentry.utils.snuba.raw_query")
+    def test_full_pipeline_with_group_states(
+        self,
+        snuba_raw_query_mock,
+        event_processed_signal_mock,
+        mock_processor,
+        run_post_process_job_mock,
+        mock_process_rules,
+        mock_handle_auto_assignment,
+        mock_handle_owner_assignment,
+    ):
+        event = self.create_event(data={"message": "testing"}, project_id=self.project.id)
+        call_order = [mock_handle_owner_assignment, mock_handle_auto_assignment, mock_process_rules]
+        mock_handle_owner_assignment.side_effect = None
+        mock_handle_auto_assignment.side_effect = None
+        mock_process_rules.side_effect = None
+        self.call_post_process_group(
+            is_new=False,
+            is_regression=True,
+            is_new_group_environment=False,
+            event=event,
+        )
+        assert event_processed_signal_mock.call_count == 0
+        assert mock_processor.call_count == 0
+        assert run_post_process_job_mock.call_count == 1
+        assert call_order == [
+            mock_handle_owner_assignment,
+            mock_handle_auto_assignment,
+            mock_process_rules,
+        ]
+        assert snuba_raw_query_mock.call_count == 0
+
     @pytest.mark.skip(reason="those tests do not work with the given call_post_process_group impl")
     def test_processing_cache_cleared(self):
         pass