Browse Source

feat(perf-issues): Collect metrics for updated N+1 detector (#49805)

The N+1 detector does not consider N+1s where the repeated database
spans happen in parallel. That means that we don't collect many N+1s in
Node, where N+1s often occur concurrently. Use the N+1 DB Extended
detector to collect metrics showing how our detection rates change when
overlapping database spans are considered. No actual issues will be
created.
Matt Quinn 1 year ago
parent
commit
b791c6caf6

+ 333 - 0
fixtures/events/performance_problems/parallel-n-plus-one-in-django-index-view.json

@@ -0,0 +1,333 @@
+{
+  "event_id": "da78af6000a6400aaa87cf6e14ddeb40",
+  "datetime": "2022-08-31T14:57:53.995835+00:00",
+  "culprit": "/books/",
+  "environment": "production",
+  "location": "/books/",
+  "contexts": {
+    "trace": {
+      "trace_id": "10d0b72df0fe4392a6788bce71ec2028",
+      "span_id": "1756e116945a4360",
+      "parent_span_id": "d71f841b69164c33",
+      "op": "http.server",
+      "status": "ok",
+      "type": "trace"
+    }
+  },
+  "spans": [
+    {
+      "timestamp": 1661957873.995433,
+      "start_timestamp": 1661957869.628498,
+      "exclusive_time": 0.129223,
+      "description": "django.middleware.security.SecurityMiddleware.__call__",
+      "op": "django.middleware",
+      "span_id": "97b250f72d59f230",
+      "parent_span_id": "adecc71b05091633",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "tags": {
+        "django.function_name": "django.utils.deprecation.MiddlewareMixin.__call__",
+        "django.middleware_name": "django.middleware.security.SecurityMiddleware"
+      },
+      "hash": "0f43fb6f6e01ca52",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.995365,
+      "start_timestamp": 1661957869.628559,
+      "exclusive_time": 0.149727,
+      "description": "django.contrib.sessions.middleware.SessionMiddleware.__call__",
+      "op": "django.middleware",
+      "span_id": "8036c3b6cbee46a9",
+      "parent_span_id": "97b250f72d59f230",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "tags": {
+        "django.function_name": "django.utils.deprecation.MiddlewareMixin.__call__",
+        "django.middleware_name": "django.contrib.sessions.middleware.SessionMiddleware"
+      },
+      "hash": "3dc5dd68b38e1730",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.99531,
+      "start_timestamp": 1661957869.628654,
+      "exclusive_time": 0.163079,
+      "description": "django.middleware.common.CommonMiddleware.__call__",
+      "op": "django.middleware",
+      "span_id": "8cffaf9d9d2085da",
+      "parent_span_id": "8036c3b6cbee46a9",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "tags": {
+        "django.function_name": "django.utils.deprecation.MiddlewareMixin.__call__",
+        "django.middleware_name": "django.middleware.common.CommonMiddleware"
+      },
+      "hash": "424c6ae1641f0f0e",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.995261,
+      "start_timestamp": 1661957869.628768,
+      "exclusive_time": 0.087976,
+      "description": "django.middleware.csrf.CsrfViewMiddleware.__call__",
+      "op": "django.middleware",
+      "span_id": "852d1fb01c3df4f3",
+      "parent_span_id": "8cffaf9d9d2085da",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "tags": {
+        "django.function_name": "django.utils.deprecation.MiddlewareMixin.__call__",
+        "django.middleware_name": "django.middleware.csrf.CsrfViewMiddleware"
+      },
+      "hash": "d5da18d7274b34a1",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.995238,
+      "start_timestamp": 1661957869.628833,
+      "exclusive_time": 0.069141,
+      "description": "django.contrib.auth.middleware.AuthenticationMiddleware.__call__",
+      "op": "django.middleware",
+      "span_id": "9b5ca86add2c1a77",
+      "parent_span_id": "852d1fb01c3df4f3",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "tags": {
+        "django.function_name": "django.utils.deprecation.MiddlewareMixin.__call__",
+        "django.middleware_name": "django.contrib.auth.middleware.AuthenticationMiddleware"
+      },
+      "hash": "ac72fc0a4f5fe381",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.995221,
+      "start_timestamp": 1661957869.628885,
+      "exclusive_time": 0.172854,
+      "description": "django.contrib.messages.middleware.MessageMiddleware.__call__",
+      "op": "django.middleware",
+      "span_id": "b66385cad8e05d12",
+      "parent_span_id": "9b5ca86add2c1a77",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "tags": {
+        "django.function_name": "django.utils.deprecation.MiddlewareMixin.__call__",
+        "django.middleware_name": "django.contrib.messages.middleware.MessageMiddleware"
+      },
+      "hash": "ac1468d8e11a0553",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.995151,
+      "start_timestamp": 1661957869.628988,
+      "exclusive_time": 0.289201,
+      "description": "django.middleware.clickjacking.XFrameOptionsMiddleware.__call__",
+      "op": "django.middleware",
+      "span_id": "9d95a06d2ca3ca0a",
+      "parent_span_id": "b66385cad8e05d12",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "tags": {
+        "django.function_name": "django.utils.deprecation.MiddlewareMixin.__call__",
+        "django.middleware_name": "django.middleware.clickjacking.XFrameOptionsMiddleware"
+      },
+      "hash": "d8681423cab4275f",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957869.629113,
+      "start_timestamp": 1661957869.629101,
+      "exclusive_time": 0.011921,
+      "description": "django.middleware.csrf.CsrfViewMiddleware.process_view",
+      "op": "django.middleware",
+      "span_id": "9cd865428b72dc0e",
+      "parent_span_id": "9d95a06d2ca3ca0a",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "tags": {
+        "django.function_name": "django.middleware.csrf.CsrfViewMiddleware.process_view",
+        "django.middleware_name": "django.middleware.csrf.CsrfViewMiddleware"
+      },
+      "hash": "e853d2eb7fb9ebb0",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.995039,
+      "start_timestamp": 1661957869.629177,
+      "exclusive_time": 34.107923,
+      "description": "index",
+      "op": "django.view",
+      "span_id": "8dd7a5869a4f4583",
+      "parent_span_id": "9d95a06d2ca3ca0a",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "6a992d5529f459a4",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957871.585034,
+      "start_timestamp": 1661957869.629686,
+      "exclusive_time": 1620.908975,
+      "description": "connect",
+      "op": "db",
+      "span_id": "82428e8ef4c5a539",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "b640a0ce465fa2a4",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957871.249481,
+      "start_timestamp": 1661957871.249481,
+      "exclusive_time": 169.507981,
+      "description": "\n                SELECT VERSION(),\n                       @@sql_mode,\n                       @@default_storage_engine,\n                       @@sql_auto_is_null,\n                       @@lower_case_table_names,\n                       CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL\n            ",
+      "op": "db",
+      "span_id": "b33db57efd994615",
+      "parent_span_id": "82428e8ef4c5a539",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "3a91b85ea92a26b9",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957871.58474,
+      "start_timestamp": 1661957871.419809,
+      "exclusive_time": 164.93082,
+      "description": "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED",
+      "op": "db",
+      "span_id": "aae50fb6aa040c31",
+      "parent_span_id": "82428e8ef4c5a539",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "061710eb39a66089",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957871.899103,
+      "start_timestamp": 1661957871.58556,
+      "exclusive_time": 313.542843,
+      "description": "SELECT `books_book`.`id`, `books_book`.`title`, `books_book`.`author_id` FROM `books_book` LIMIT 10",
+      "op": "db",
+      "span_id": "9179e43ae844b174",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "c23d7b23e98a04c5",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957872.07855,
+      "start_timestamp": 1661957871.904139,
+      "exclusive_time": 174.411059,
+      "description": "SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+      "op": "db",
+      "span_id": "b8be6138369491dd",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "63f1e89e6a073441",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957872.29085,
+      "start_timestamp": 1661957872.07755,
+      "exclusive_time": 208.201886,
+      "description": "SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+      "op": "db",
+      "span_id": "b2d4826e7b618f1b",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "63f1e89e6a073441",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957872.46439,
+      "start_timestamp": 1661957872.28985,
+      "exclusive_time": 170.755148,
+      "description": "SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+      "op": "db",
+      "span_id": "b3fdeea42536dbf1",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "63f1e89e6a073441",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957872.637623,
+      "start_timestamp": 1661957872.46339,
+      "exclusive_time": 169.772148,
+      "description": "SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+      "op": "db",
+      "span_id": "b409e78a092e642f",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "63f1e89e6a073441",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957872.948552,
+      "start_timestamp": 1661957872.636623,
+      "exclusive_time": 308.277846,
+      "description": "SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+      "op": "db",
+      "span_id": "86d2ede57bbf48d4",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "63f1e89e6a073441",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.123204,
+      "start_timestamp": 1661957872.947552,
+      "exclusive_time": 170.983076,
+      "description": "SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+      "op": "db",
+      "span_id": "8e554c84cdc9731e",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "63f1e89e6a073441",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.338406,
+      "start_timestamp": 1661957873.122204,
+      "exclusive_time": 212.155103,
+      "description": "SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+      "op": "db",
+      "span_id": "94d6230f3f910e12",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "63f1e89e6a073441",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.509047,
+      "start_timestamp": 1661957873.337406,
+      "exclusive_time": 168.129921,
+      "description": "SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+      "op": "db",
+      "span_id": "a210b87a2191ceb6",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "63f1e89e6a073441",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.678543,
+      "start_timestamp": 1661957873.508047,
+      "exclusive_time": 167.357206,
+      "description": "SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+      "op": "db",
+      "span_id": "88a5ccaf25b9bd8f",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "63f1e89e6a073441",
+      "same_process_as_parent": true
+    },
+    {
+      "timestamp": 1661957873.993492,
+      "start_timestamp": 1661957873.677543,
+      "exclusive_time": 312.819958,
+      "description": "SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+      "op": "db",
+      "span_id": "bb32cf50fc56b296",
+      "parent_span_id": "8dd7a5869a4f4583",
+      "trace_id": "3c080d683c904cb68a18361a093aa6ce",
+      "hash": "63f1e89e6a073441",
+      "same_process_as_parent": true
+    }
+  ],
+  "start_timestamp": 1661957869.624976,
+  "timestamp": 1661957873.995835,
+  "title": "/books/",
+  "transaction": "/books/",
+  "transaction_info": {"source": "route"},
+  "type": "transaction"
+}

+ 26 - 7
src/sentry/utils/performance_issues/detectors/n_plus_one_db_span_detector.py

@@ -18,6 +18,7 @@ from ..base import (
     get_notification_attachment_body,
     get_span_duration,
     get_span_evidence_value,
+    total_span_time,
 )
 from ..performance_problem import PerformanceProblem
 from ..types import Span
@@ -164,18 +165,13 @@ class NPlusOneDBSpanDetector(PerformanceDetector):
         if not self.source_span or not self.n_spans:
             return
 
-        count = self.settings.get("count")
-        duration_threshold = timedelta(milliseconds=self.settings.get("duration_threshold"))
-
         # Do we have enough spans?
+        count = self.settings.get("count")
         if len(self.n_spans) < count:
             return
 
         # Do the spans take enough total time?
-        total_duration = timedelta()
-        for span in self.n_spans:
-            total_duration += get_span_duration(span)
-        if total_duration < duration_threshold:
+        if not self._is_slower_than_threshold():
             return
 
         # We require a parent span in order to improve our fingerprint accuracy.
@@ -243,6 +239,13 @@ class NPlusOneDBSpanDetector(PerformanceDetector):
                 },
             )
 
+    def _is_slower_than_threshold(self) -> bool:
+        duration_threshold = timedelta(milliseconds=self.settings.get("duration_threshold"))
+        total_duration = timedelta()
+        for span in self.n_spans:
+            total_duration += get_span_duration(span)
+        return total_duration >= duration_threshold
+
     def _contains_valid_repeating_query(self, span: Span) -> bool:
         query = span.get("description", None)
         return query and PARAMETERIZED_SQL_QUERY_REGEX.search(query)
@@ -290,6 +293,22 @@ class NPlusOneDBSpanDetectorExtended(NPlusOneDBSpanDetector):
         "n_spans",
     )
 
+    def is_creation_allowed_for_organization(self, organization: Optional[Organization]) -> bool:
+        # Only collecting metrics.
+        return False
+
+    def is_creation_allowed_for_project(self, project: Optional[Project]) -> bool:
+        # Only collecting metrics.
+        return False
+
+    def _overlaps_last_span(self, span: Span) -> bool:
+        # Ignore overlapping spans for determining if an N+1 happened.
+        return False
+
+    def _is_slower_than_threshold(self) -> bool:
+        duration_threshold = self.settings.get("duration_threshold")
+        return total_span_time(self.n_spans) >= duration_threshold
+
 
 def contains_complete_query(span: Span, is_source: Optional[bool] = False) -> bool:
     # Remove the truncation check from the n_plus_one db detector.

+ 4 - 0
tests/sentry/utils/performance_issues/test_n_plus_one_db_span_detector.py

@@ -189,6 +189,10 @@ class NPlusOneDbDetectorTest(unittest.TestCase):
             ),
         ]
 
+    def test_does_not_detect_overlapping_n_plus_one(self):
+        event = get_event("parallel-n-plus-one-in-django-index-view")
+        assert self.find_problems(event) == []
+
 
 @pytest.mark.django_db
 class NPlusOneDbSettingTest(TestCase):

+ 231 - 0
tests/sentry/utils/performance_issues/test_n_plus_one_overlapping_db_span_detector.py

@@ -0,0 +1,231 @@
+import unittest
+from typing import Any, Dict, List
+
+import pytest
+
+from sentry.eventstore.models import Event
+from sentry.issues.grouptype import PerformanceNPlusOneGroupType
+from sentry.testutils.performance_issues.event_generators import get_event
+from sentry.testutils.silo import region_silo_test
+from sentry.utils.performance_issues.base import DetectorType
+from sentry.utils.performance_issues.detectors import NPlusOneDBSpanDetectorExtended
+from sentry.utils.performance_issues.performance_detection import (
+    PerformanceProblem,
+    get_detection_settings,
+    run_detector_on_data,
+)
+
+
+@region_silo_test
+@pytest.mark.django_db
+class NPlusOneOverlappingDbDetectorTest(unittest.TestCase):
+    def setUp(self):
+        super().setUp()
+        self.settings = get_detection_settings()
+
+    def find_problems(
+        self, event: Event, setting_overides: Dict[str, Any] = None
+    ) -> List[PerformanceProblem]:
+        if setting_overides:
+            for option_name, value in setting_overides.items():
+                self.settings[DetectorType.N_PLUS_ONE_DB_QUERIES][option_name] = value
+
+        detector = NPlusOneDBSpanDetectorExtended(self.settings, event)
+        run_detector_on_data(detector, event)
+        return list(detector.stored_problems.values())
+
+    def test_does_not_detect_issues_in_fast_transaction(self):
+        event = get_event("no-issue-in-django-detail-view")
+        assert self.find_problems(event) == []
+
+    def test_does_not_detect_n_plus_one_with_unparameterized_query_with_parameterized_detector(
+        self,
+    ):
+        event = get_event("n-plus-one-in-django-index-view-unparameterized")
+        assert self.find_problems(event) == []
+
+    def test_does_not_detect_n_plus_one_with_source_redis_query_with_noredis_detector(
+        self,
+    ):
+        event = get_event("n-plus-one-in-django-index-view-source-redis")
+        assert self.find_problems(event) == []
+
+    def test_does_not_detect_n_plus_one_with_repeating_redis_query_with_noredis_detector(
+        self,
+    ):
+        event = get_event("n-plus-one-in-django-index-view-repeating-redis")
+        assert self.find_problems(event) == []
+
+    def test_ignores_fast_n_plus_one(self):
+        event = get_event("fast-n-plus-one-in-django-new-view")
+        assert self.find_problems(event) == []
+
+    def test_detects_slow_span_but_not_n_plus_one_in_query_waterfall(self):
+        event = get_event("query-waterfall-in-django-random-view")
+        assert self.find_problems(event) == []
+
+    def test_finds_n_plus_one_with_db_dot_something_spans(self):
+        event = get_event("n-plus-one-in-django-index-view-activerecord")
+        assert self.find_problems(event) == [
+            PerformanceProblem(
+                fingerprint="1-GroupType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES-8d86357da4d8a866b19c97670edee38d037a7bc8",
+                op="db",
+                desc="SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+                type=PerformanceNPlusOneGroupType,
+                parent_span_ids=["8dd7a5869a4f4583"],
+                cause_span_ids=["9179e43ae844b174"],
+                offender_span_ids=[
+                    "b8be6138369491dd",
+                    "b2d4826e7b618f1b",
+                    "b3fdeea42536dbf1",
+                    "b409e78a092e642f",
+                    "86d2ede57bbf48d4",
+                    "8e554c84cdc9731e",
+                    "94d6230f3f910e12",
+                    "a210b87a2191ceb6",
+                    "88a5ccaf25b9bd8f",
+                    "bb32cf50fc56b296",
+                ],
+                evidence_data={
+                    "op": "db",
+                    "parent_span_ids": ["8dd7a5869a4f4583"],
+                    "cause_span_ids": ["9179e43ae844b174"],
+                    "offender_span_ids": [
+                        "b8be6138369491dd",
+                        "b2d4826e7b618f1b",
+                        "b3fdeea42536dbf1",
+                        "b409e78a092e642f",
+                        "86d2ede57bbf48d4",
+                        "8e554c84cdc9731e",
+                        "94d6230f3f910e12",
+                        "a210b87a2191ceb6",
+                        "88a5ccaf25b9bd8f",
+                        "bb32cf50fc56b296",
+                    ],
+                },
+                evidence_display=[],
+            )
+        ]
+
+    def test_n_plus_one_db_detector_has_different_fingerprints_for_different_n_plus_one_events(
+        self,
+    ):
+        index_n_plus_one_event = get_event("n-plus-one-in-django-index-view")
+        new_n_plus_one_event = get_event("n-plus-one-in-django-new-view")
+
+        index_problems = self.find_problems(index_n_plus_one_event)
+        new_problems = self.find_problems(new_n_plus_one_event)
+
+        index_fingerprint = index_problems[0].fingerprint
+        new_fingerprint = new_problems[0].fingerprint
+
+        assert index_fingerprint
+        assert new_fingerprint
+        assert index_fingerprint != new_fingerprint
+
+    def test_detects_n_plus_one_with_multiple_potential_sources(self):
+        event = get_event("n-plus-one-in-django-with-odd-db-sources")
+
+        assert self.find_problems(event, {"duration_threshold": 0}) == [
+            PerformanceProblem(
+                fingerprint="1-GroupType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES-e55ea09e1cff0ca2369f287cf624700f98cf4b50",
+                op="db",
+                type=PerformanceNPlusOneGroupType,
+                desc='SELECT "expense_expenses"."id", "expense_expenses"."report_id", "expense_expenses"."amount" FROM "expense_expenses" WHERE "expense_expenses"."report_id" = %s',
+                parent_span_ids=["81a4b462bdc5c764"],
+                cause_span_ids=["99797d06e2fa9750"],
+                offender_span_ids=[
+                    "9c7876a6d7a26c72",
+                    "b31f67541d38ad0c",
+                    "aff9d1545b41f1de",
+                    "86a56025d94edb85",
+                    "b5e340041cfc2532",
+                    "b77a0b154e782baa",
+                    "9c46a977962d6ed1",
+                    "b03da8752eeddebe",
+                    "8c173716d4c7e41b",
+                    "b4e6f90c66e90238",
+                    "987affc4f2faa24b",
+                    "b7d323b4f5f8b2b0",
+                    "a4f0a57410b61072",
+                    "a6120e2d88c86ea4",
+                    "a87019f03438311e",
+                    "b5487ad7228cfd6e",
+                    "bc44d59a63a4115c",
+                    "84b05df439e4a6ee",
+                    "be85dffe4a9a3120",
+                    "a3c381b1952dd7fb",
+                ],
+                evidence_data={
+                    "op": "db",
+                    "parent_span_ids": ["81a4b462bdc5c764"],
+                    "cause_span_ids": ["99797d06e2fa9750"],
+                    "offender_span_ids": [
+                        "9c7876a6d7a26c72",
+                        "b31f67541d38ad0c",
+                        "aff9d1545b41f1de",
+                        "86a56025d94edb85",
+                        "b5e340041cfc2532",
+                        "b77a0b154e782baa",
+                        "9c46a977962d6ed1",
+                        "b03da8752eeddebe",
+                        "8c173716d4c7e41b",
+                        "b4e6f90c66e90238",
+                        "987affc4f2faa24b",
+                        "b7d323b4f5f8b2b0",
+                        "a4f0a57410b61072",
+                        "a6120e2d88c86ea4",
+                        "a87019f03438311e",
+                        "b5487ad7228cfd6e",
+                        "bc44d59a63a4115c",
+                        "84b05df439e4a6ee",
+                        "be85dffe4a9a3120",
+                        "a3c381b1952dd7fb",
+                    ],
+                },
+                evidence_display=[],
+            ),
+        ]
+
+    def test_detects_overlapping_n_plus_one(self):
+        event = get_event("parallel-n-plus-one-in-django-index-view")
+        assert self.find_problems(event) == [
+            PerformanceProblem(
+                fingerprint="1-GroupType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES-8d86357da4d8a866b19c97670edee38d037a7bc8",
+                op="db",
+                desc="SELECT `books_author`.`id`, `books_author`.`name` FROM `books_author` WHERE `books_author`.`id` = %s LIMIT 21",
+                type=PerformanceNPlusOneGroupType,
+                parent_span_ids=["8dd7a5869a4f4583"],
+                cause_span_ids=["9179e43ae844b174"],
+                offender_span_ids=[
+                    "b8be6138369491dd",
+                    "b2d4826e7b618f1b",
+                    "b3fdeea42536dbf1",
+                    "b409e78a092e642f",
+                    "86d2ede57bbf48d4",
+                    "8e554c84cdc9731e",
+                    "94d6230f3f910e12",
+                    "a210b87a2191ceb6",
+                    "88a5ccaf25b9bd8f",
+                    "bb32cf50fc56b296",
+                ],
+                evidence_data={
+                    "op": "db",
+                    "parent_span_ids": ["8dd7a5869a4f4583"],
+                    "cause_span_ids": ["9179e43ae844b174"],
+                    "offender_span_ids": [
+                        "b8be6138369491dd",
+                        "b2d4826e7b618f1b",
+                        "b3fdeea42536dbf1",
+                        "b409e78a092e642f",
+                        "86d2ede57bbf48d4",
+                        "8e554c84cdc9731e",
+                        "94d6230f3f910e12",
+                        "a210b87a2191ceb6",
+                        "88a5ccaf25b9bd8f",
+                        "bb32cf50fc56b296",
+                    ],
+                },
+                evidence_display=[],
+            )
+        ]

+ 0 - 17
tests/sentry/utils/performance_issues/test_performance_detection.py

@@ -176,23 +176,6 @@ class PerformanceDetectionTest(TestCase):
             )
         ]
 
-    @override_options(BASE_DETECTOR_OPTIONS)
-    def test_n_plus_one_extended_detection_matches_previous_group(self):
-        n_plus_one_event = get_event("n-plus-one-in-django-index-view")
-        sdk_span_mock = Mock()
-
-        with override_options({"performance.issues.n_plus_one_db.problem-creation": 0.0}):
-            n_plus_one_extended_problems = _detect_performance_problems(
-                n_plus_one_event, sdk_span_mock, self.project
-            )
-
-        with override_options({"performance.issues.n_plus_one_db_ext.problem-creation": 0.0}):
-            n_plus_one_original_problems = _detect_performance_problems(
-                n_plus_one_event, sdk_span_mock, self.project
-            )
-
-        assert n_plus_one_original_problems == n_plus_one_extended_problems
-
     @override_options(BASE_DETECTOR_OPTIONS)
     def test_overlap_detector_problems(self):
         n_plus_one_event = get_event("n-plus-one-db-root-parent-span")