Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ def on_complete(self) -> None:
self._maybe_store_problem()

def _is_db_op(self, op: str) -> bool:
return op.startswith("db") and not op.startswith("db.redis")
return (
op.startswith("db")
and not op.startswith("db.redis")
and not op.startswith("db.connection")
)

def _maybe_use_as_source(self, span: Span) -> None:
parent_span_id = span.get("parent_span_id", None)
Expand Down Expand Up @@ -279,6 +283,11 @@ def get_valid_db_span_description(span: Span) -> str | None:
"""
default_description = span.get("description", "")
db_system = span.get("sentry_tags", {}).get("system", "")

# Connection spans can have `op` as `db` but we don't want to trigger on them.
if "pg-pool.connect" in default_description:
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: N+1 Detector Span Processing Inconsistency

The N+1 detector inconsistently handles pg-pool.connect spans. They pass the initial _is_db_op() check, allowing partial processing, but are later filtered out by get_valid_db_span_description(). This can lead to false N+1 detections or problems failing to be stored.

Fix in Cursor Fix in Web

Comment on lines +288 to +289
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential bug: The code does not handle cases where a span's description is explicitly None, causing a TypeError when checking for a substring in a NoneType object.
  • Description: The code retrieves a span's description using span.get("description", ""). If a span contains an explicit {"description": None} entry, this method returns None rather than the default empty string. The subsequent line, if "pg-pool.connect" in default_description:, then attempts a substring check on this None value. This operation raises a TypeError because the in operator is not supported on NoneType objects. This unhandled exception will cause a crash in the performance issue detection pipeline whenever it processes a span with an explicit None description. Evidence from the codebase, including the Span.to_python() method and SpanBuilder type hints, confirms that a span's description can be None.

  • Suggested fix: Ensure the default_description variable is always a string by changing its assignment to default_description = span.get("description") or "". This safely handles cases where the description key is missing or its value is None, preventing the TypeError.
    severity: 0.7, confidence: 0.95

Did we get this right? 👍 / 👎 to inform future reviews.


# Trigger pathway on `mongodb`, `mongoose`, etc...
if "mongo" in db_system:
description = span.get("sentry_tags", {}).get("description")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from sentry.issues.issue_occurrence import IssueEvidence
from sentry.models.options.project_option import ProjectOption
from sentry.testutils.cases import TestCase
from sentry.testutils.issue_detection.event_generators import get_event
from sentry.testutils.issue_detection.event_generators import create_event, create_span, get_event


@pytest.mark.django_db
Expand Down Expand Up @@ -368,6 +368,34 @@ def test_detects_n_plus_one_with_mongoose(self) -> None:
assert len(problems) == 1
assert "mongoose" not in problems[0].desc

def test_does_not_detect_n_plus_one_with_pg_pool_connect(self) -> None:
source_span = create_span(
"db",
100,
"SELECT * FROM users WHERE id = 1",
hash="source_hash",
)

repeating_spans = [
create_span(
"db",
100,
"pg-pool.connect",
hash="pool_hash",
)
for _ in range(11)
]

event = create_event([source_span] + repeating_spans)
event["contexts"] = {
"trace": {
"span_id": "a" * 16,
"op": "http.server",
}
}

assert self.find_problems(event) == []


@pytest.mark.django_db
class NPlusOneDbSettingTest(TestCase):
Expand Down
Loading