Browse Source

feat(perf-issues): Prepare MN+1 DB for release (#44664)

- Add release flags
- Create issue as an N+1 issue (how it will be displayed + grouped)
- Add an MN+1-triggering transaction to bin/load-mocks
Matt Quinn 2 years ago
parent
commit
b557c4b1e4

+ 54 - 0
bin/load-mocks

@@ -1160,6 +1160,58 @@ def create_mock_transactions(
                 },
             )
 
+        def load_m_n_plus_one_issue():
+            trace_id = uuid4().hex
+            transaction_user = generate_user()
+
+            parent_span_id = uuid4().hex[:16]
+            duration = 200
+
+            def make_repeating_span(i):
+                nonlocal timestamp
+                nonlocal duration
+                start_timestamp = timestamp + timedelta(milliseconds=i * (duration + 1))
+                end_timestamp = start_timestamp + timedelta(milliseconds=duration)
+                op = "http" if i % 2 == 0 else "db"
+                description = "GET /" if i % 2 == 0 else "SELECT * FROM authors WHERE id = %s"
+                hash = "63f1e89e6a073441" if i % 2 == 0 else "a109ff3ef40f7fb3"
+                return {
+                    "timestamp": end_timestamp.timestamp(),
+                    "start_timestamp": start_timestamp.timestamp(),
+                    "description": description,
+                    "op": op,
+                    "span_id": uuid4().hex[:16],
+                    "parent_span_id": parent_span_id,
+                    "hash": hash,
+                }
+
+            span_count = 10
+            repeating_spans = [make_repeating_span(i) for i in range(span_count)]
+
+            parent_span = {
+                "timestamp": (
+                    timestamp + timedelta(milliseconds=span_count * (duration + 1))
+                ).timestamp(),
+                "start_timestamp": timestamp.timestamp(),
+                "description": "execute",
+                "op": "graphql.execute",
+                "parent_span_id": uuid4().hex[:16],
+                "span_id": parent_span_id,
+                "hash": "0f43fb6f6e01ca52",
+            }
+
+            create_sample_event(
+                project=backend_project,
+                platform="transaction",
+                transaction="/m_n_plus_one_db/backend/",
+                event_id=uuid4().hex,
+                user=transaction_user,
+                timestamp=timestamp + timedelta(milliseconds=span_count * (duration + 1) + 100),
+                start_timestamp=timestamp,
+                trace=trace_id,
+                spans=[parent_span] + repeating_spans,
+            )
+
         def load_performance_issues():
             print(f"    > Loading performance issues data")  # NOQA
             print(f"    > Loading n plus one issue")  # NOQA
@@ -1170,6 +1222,8 @@ def create_mock_transactions(
             load_uncompressed_asset_issue()
             print(f"    > Loading render blocking asset issue")  # NOQA
             load_render_blocking_asset_issue()
+            print(f"    > Loading MN+1 issue")  # NOQA
+            load_m_n_plus_one_issue()
 
         load_performance_issues()
 

+ 2 - 0
src/sentry/conf/server.py

@@ -1177,6 +1177,8 @@ SENTRY_FEATURES = {
     "organizations:performance-issues-compressed-assets-detector": False,
     # Enable render blocking assets performance issue type
     "organizations:performance-issues-render-blocking-assets-detector": False,
+    # Enable MN+1 DB performance issue type
+    "organizations:performance-issues-m-n-plus-one-db-detector": False,
     # Enable the new Related Events feature
     "organizations:related-events": False,
     # Enable usage of external relays, for use with Relay. See

+ 1 - 0
src/sentry/features/__init__.py

@@ -116,6 +116,7 @@ default_manager.add("organizations:performance-consecutive-db-issue", Organizati
 default_manager.add("organizations:performance-n-plus-one-api-calls-detector", OrganizationFeature)
 default_manager.add("organizations:performance-issues-compressed-assets-detector", OrganizationFeature)
 default_manager.add("organizations:performance-issues-render-blocking-assets-detector", OrganizationFeature)
+default_manager.add("organizations:performance-issues-m-n-plus-one-db-detector", OrganizationFeature)
 default_manager.add("organizations:performance-issues-dev", OrganizationFeature, True)
 default_manager.add("organizations:performance-issues-all-events-tab", OrganizationFeature, True)
 default_manager.add("organizations:performance-issues-search", OrganizationFeature)

+ 4 - 0
src/sentry/options/defaults.py

@@ -583,6 +583,10 @@ register("performance.issues.render_blocking_assets.problem-creation", default=0
 register("performance.issues.render_blocking_assets.la-rollout", default=0.0)
 register("performance.issues.render_blocking_assets.ea-rollout", default=0.0)
 register("performance.issues.render_blocking_assets.ga-rollout", default=0.0)
+register("performance.issues.m_n_plus_one_db.problem-creation", default=0.0)
+register("performance.issues.m_n_plus_one_db.la-rollout", default=0.0)
+register("performance.issues.m_n_plus_one_db.ea-rollout", default=0.0)
+register("performance.issues.m_n_plus_one_db.ga-rollout", default=0.0)
 
 
 # System-wide options for default performance detection settings for any org opted into the performance-issues-ingest feature. Meant for rollout.

+ 1 - 0
src/sentry/utils/performance_issues/base.py

@@ -61,6 +61,7 @@ DETECTOR_TYPE_ISSUE_CREATION_TO_SYSTEM_OPTION = {
     DetectorType.UNCOMPRESSED_ASSETS: "performance.issues.compressed_assets.problem-creation",
     DetectorType.SLOW_DB_QUERY: "performance.issues.slow_db_query.problem-creation",
     DetectorType.RENDER_BLOCKING_ASSET_SPAN: "performance.issues.render_blocking_assets.problem-creation",
+    DetectorType.M_N_PLUS_ONE_DB: "organizations:performance-issues-m-n-plus-one-db-detector.problem-creation",
 }
 
 

+ 11 - 1
src/sentry/utils/performance_issues/performance_detection.py

@@ -1017,7 +1017,7 @@ class ContinuingMNPlusOne(MNPlusOneState):
             fingerprint=self._fingerprint(db_span["hash"], parent_span),
             op="db",
             desc=db_span["description"],
-            type=PerformanceMNPlusOneDBQueriesGroupType,
+            type=PerformanceNPlusOneGroupType,
             parent_span_ids=[parent_span["span_id"]],
             cause_span_ids=[],
             offender_span_ids=[span["span_id"] for span in offender_spans],
@@ -1073,6 +1073,16 @@ class MNPlusOneDBSpanDetector(PerformanceDetector):
         self.stored_problems = {}
         self.state = SearchingForMNPlusOne(self.settings, self.event())
 
+    def is_creation_allowed_for_organization(self, organization: Optional[Organization]) -> bool:
+        return features.has(
+            "organizations:performance-issues-m-n-plus-one-db-detector",
+            organization,
+            actor=None,
+        )
+
+    def is_creation_allowed_for_project(self, project: Project) -> bool:
+        return True  # Detection always allowed by project for now
+
     def visit_span(self, span):
         self.state, performance_problem = self.state.next(span)
         if performance_problem:

+ 3 - 3
tests/sentry/utils/performance_issues/test_m_n_plus_one_db_detector.py

@@ -4,7 +4,7 @@ from unittest.mock import Mock, call
 import pytest
 
 from sentry.eventstore.models import Event
-from sentry.issues.grouptype import PerformanceMNPlusOneDBQueriesGroupType
+from sentry.issues.grouptype import PerformanceNPlusOneGroupType
 from sentry.testutils import TestCase
 from sentry.testutils.performance_issues.event_generators import get_event
 from sentry.testutils.silo import region_silo_test
@@ -37,7 +37,7 @@ class MNPlusOneDBDetectorTest(TestCase):
             PerformanceProblem(
                 fingerprint="1-1011-6807a9d5bedb6fdb175b006448cddf8cdf18fbd8",
                 op="db",
-                type=PerformanceMNPlusOneDBQueriesGroupType,
+                type=PerformanceNPlusOneGroupType,
                 desc="SELECT id, name FROM authors INNER JOIN book_authors ON author_id = id WHERE book_id = $1",
                 parent_span_ids=[],
                 cause_span_ids=[],
@@ -69,7 +69,7 @@ class MNPlusOneDBDetectorTest(TestCase):
                 ],
             )
         ]
-        assert problems[0].title == "MN+1 Query"
+        assert problems[0].title == "N+1 Query"
 
     def test_does_not_detect_truncated_m_n_plus_one(self):
         event = get_event("m-n-plus-one-db/m-n-plus-one-graphql-truncated")