Skip to content

feat(detectors): Update detection algorithm for MN+1 Experimental Detector #97533

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
@@ -0,0 +1,242 @@
{
"event_id": "1997dd9e1f434f4d9ec638624dbfd8d7",
"project": 2,
"release": null,
"dist": null,
"platform": "node",
"message": "",
"datetime": "2025-08-08T16:00:00.582776+00:00",
"tags": [
["browser", "Firefox 137.0"],
["browser.name", "Firefox"],
["client_os", "Mac OS X 10.15"],
["client_os.name", "Mac OS X"],
["customerType", "small-plan"],
["environment", "development"],
["frontendSlowdown", "False"],
["level", "info"],
["os", "macOS 15.3.2"],
["os.name", "macOS"],
["runtime", "node v18.19.1"],
["runtime.name", "node"],
["user", "email:[email protected]"],
["server_name", "MacBookPro"],
["transaction", "GET /products"]
],
"culprit": "prisma:client:transaction",
"environment": "production",
"level": "info",
"location": "prisma:client:transaction",
"logger": "",
"metadata": {
"location": "prisma:client:transaction",
"title": "prisma:client:transaction"
},
"spans": [
{
"timestamp": 1752683532.47828,
"start_timestamp": 1752683532.371497,

"op": "default",
"span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "prisma:engine:query",
"origin": "auto.db.otel.prisma",
"data": {
"sentry.origin": "auto.db.otel.prisma"
},

"hash": "4808ca9185d0c670"
},
{
"timestamp": 1752683532.394234,
"start_timestamp": 1752683532.373677,

"op": "db",
"span_id": "f3a2b1c0d9e87765",
"parent_span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "UPDATE users SET name = $1, email = $2 WHERE id = $3",
"origin": "auto.db.otel.prisma",
"data": {
"db.system": "postgresql",
"db.query.text": "UPDATE users SET name = $1, email = $2 WHERE id = $3",
"otel.kind": "CLIENT",
"sentry.op": "db",
"sentry.origin": "auto.db.otel.prisma"
},

"hash": "9747ee0db22ccb33"
},
{
"timestamp": 1752683532.394473,
"start_timestamp": 1752683532.394408,

"op": "default",
"span_id": "a4b3c2d1e0f98876",
"parent_span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "prisma:engine:serialize",
"origin": "auto.db.otel.prisma",
"data": {
"sentry.origin": "auto.db.otel.prisma"
},

"hash": "ec7cb3896f17c6fd"
},
{
"timestamp": 1752683532.415304,
"start_timestamp": 1752683532.394817,

"op": "db",
"span_id": "b5c4d3e2f1a09987",
"parent_span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "UPDATE users SET name = $1, email = $2 WHERE id = $3",
"origin": "auto.db.otel.prisma",
"data": {
"db.system": "postgresql",
"db.query.text": "UPDATE users SET name = $1, email = $2 WHERE id = $3",
"otel.kind": "CLIENT",
"sentry.op": "db",
"sentry.origin": "auto.db.otel.prisma"
},
"hash": "9747ee0db22ccb33"
},
{
"timestamp": 1752683532.415549,
"start_timestamp": 1752683532.415481,

"op": "default",
"span_id": "c6d5e4f3a2b1a098",
"parent_span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "prisma:engine:serialize",
"origin": "auto.db.otel.prisma",
"data": {
"sentry.origin": "auto.db.otel.prisma"
},

"hash": "ec7cb3896f17c6fd"
},
{
"timestamp": 1752683532.436118,
"start_timestamp": 1752683532.415906,

"op": "db",
"span_id": "d7e6f5a4b3c2b1a9",
"parent_span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "UPDATE users SET name = $1, email = $2 WHERE id = $3",
"origin": "auto.db.otel.prisma",
"data": {
"db.system": "postgresql",
"db.query.text": "UPDATE users SET name = $1, email = $2 WHERE id = $3",
"otel.kind": "CLIENT",
"sentry.op": "db",
"sentry.origin": "auto.db.otel.prisma"
},

"hash": "9747ee0db22ccb33"
},
{
"timestamp": 1752683532.436347,
"start_timestamp": 1752683532.436282,

"op": "default",
"span_id": "e8f7a6b5c4d3c2ba",
"parent_span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "prisma:engine:serialize",
"origin": "auto.db.otel.prisma",
"data": {
"sentry.origin": "auto.db.otel.prisma"
},

"hash": "ec7cb3896f17c6fd"
},
{
"timestamp": 1752683532.457153,
"start_timestamp": 1752683532.436713,

"op": "db",
"span_id": "f9a8b7c6d5e4d3cb",
"parent_span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "UPDATE users SET name = $1, email = $2 WHERE id = $3",
"origin": "auto.db.otel.prisma",
"data": {
"db.system": "postgresql",
"db.query.text": "UPDATE users SET name = $1, email = $2 WHERE id = $3",
"otel.kind": "CLIENT",
"sentry.op": "db",
"sentry.origin": "auto.db.otel.prisma"
},

"hash": "9747ee0db22ccb33"
},
{
"timestamp": 1752683532.457415,
"start_timestamp": 1752683532.457346,

"op": "default",
"span_id": "a0b9c8d7e6f5e4dc",
"parent_span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "prisma:engine:serialize",
"origin": "auto.db.otel.prisma",
"data": {
"sentry.origin": "auto.db.otel.prisma"
},

"hash": "ec7cb3896f17c6fd"
},
{
"timestamp": 1752683532.477919,
"start_timestamp": 1752683532.457749,

"op": "db",
"span_id": "b1c0d9e8f7a6f5ed",
"parent_span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "UPDATE users SET name = $1, email = $2 WHERE id = $3",
"origin": "auto.db.otel.prisma",
"data": {
"db.system": "postgresql",
"db.query.text": "UPDATE users SET name = $1, email = $2 WHERE id = $3",
"otel.kind": "CLIENT",
"sentry.op": "db",
"sentry.origin": "auto.db.otel.prisma"
},

"hash": "9747ee0db22ccb33"
},
{
"timestamp": 1752683532.478153,
"start_timestamp": 1752683532.478087,

"op": "default",
"span_id": "c2d1e0f9a8b7a6fe",
"parent_span_id": "a8b7c6d5e4f39210",
"trace_id": "a1b2c3d4e5f67890abcdef1234567890",
"status": "ok",
"description": "prisma:engine:serialize",
"origin": "auto.db.otel.prisma",
"data": {
"sentry.origin": "auto.db.otel.prisma"
},

"hash": "ec7cb3896f17c6fd"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ def _equivalent(self, a: Span, b: Span) -> bool:
if not first_op or not second_op or first_op != second_op:
return False

if first_op == "default":
return a.get("description") == b.get("description")

if first_op.startswith("db"):
return a.get("hash") == b.get("hash")

Expand Down Expand Up @@ -119,6 +122,14 @@ def _is_valid_pattern(self, pattern: Sequence[Span]) -> bool:
found_db_op = False
found_different_span = False

# Patterns shouldn't start with a serialize span, since that follows an operation or query.
first_span_description = pattern[0].get("description", "")
if (
first_span_description == "prisma:client:serialize"
or first_span_description == "prisma:engine:serialize"
):
return False

for span in pattern:
op = span.get("op") or ""
description = span.get("description") or ""
Expand Down Expand Up @@ -187,9 +198,14 @@ def next(self, span: Span) -> tuple[MNPlusOneState, PerformanceProblem | None]:

# We've broken the MN pattern, so return to the Searching state. If it
# is a significant problem, also return a PerformanceProblem.
times_occurred = int(len(self.spans) / len(self.pattern))
start_index = len(self.pattern) * times_occurred
remaining_spans = self.spans[start_index:] + [span]

# Keep more context for pattern detection by including spans that could be
# the beginning of a new pattern. Instead of just keeping the incomplete
# remainder, keep the last pattern_length spans plus the current span.
# Keep at least the last pattern_length spans (or all if we have fewer)
pattern_length = len(self.pattern)
context_start = max(0, len(self.spans) - pattern_length)
remaining_spans = self.spans[context_start:] + [span]
return (
SearchingForMNPlusOne(
settings=self.settings,
Expand Down
Loading
Loading