Skip to content

fix(dashboards): prefetch alert threshold and subscribers on read paths - #70832

Merged
thmsobrmlr merged 2 commits into
masterfrom
posthog-code/fix-dashboard-alert-nplus1
Jul 14, 2026
Merged

fix(dashboards): prefetch alert threshold and subscribers on read paths#70832
thmsobrmlr merged 2 commits into
masterfrom
posthog-code/fix-dashboard-alert-nplus1

Conversation

@thmsobrmlr

Copy link
Copy Markdown
Collaborator

TL;DR (eli5)

When a dashboard has alerts on its insights, opening it made the server ask the database two extra questions for every single alert (who's subscribed? what's the threshold?). A dashboard with lots of alerted insights asked hundreds of tiny questions one by one. Now it asks once for all of them upfront — same answer, a fraction of the round trips.

Problem

AlertSerializer serializes threshold and subscribed_users for every alert, but the alert prefetch on dashboard GET (DashboardSerializer.get_tiles) and insight list only does select_related("created_by"). Each alert on the page costs two extra Postgres queries, so alert-heavy dashboards degrade to O(alerts) queries per load. Found while benchmarking dashboard loads against real dashboards: an alert-per-insight dashboard paid ~3 extra queries per tile, dwarfing its fixed query cost.

Why: follow-up from the query benchmarking of #70085 — the biggest per-tile query multiplier on dashboard load turned out to be alerts, not the fixed costs that PR addresses.

Changes

  • Both alert prefetch sites now use select_related("created_by", "threshold") + prefetch_related("subscribed_users").
  • Measured on a 30-tile dashboard with two alerts per insight (one threshold, one detector, one subscriber each): 176 → 27 queries, wall time −72%; query count is now flat as tile count grows.

How did you test this code?

  • test_insight_alerts_do_not_nplus1_dashboard_gets (dashboard GET) and test_listing_insights_with_alerts_does_not_nplus1 (insight list): constant-query-count tests that create insights with threshold + detector alerts and assert the per-request query count doesn't grow with more insights. Each catches the regression of dropping threshold/subscribed_users from its endpoint's prefetch — no existing test creates alerts on dashboard/listed insights, so this N+1 was invisible to the suite. Both fail on the previous prefetch and pass with this change (verified locally by stashing the fix).

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

No user-facing behavior change; no docs affected.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Authored with PostHog Code (Claude). Skills invoked: /improving-drf-endpoints, /writing-tests. The N+1 was located by benchmarking dashboard GETs per-commit for #70085, then diffing captured SQL against production dashboards; the repeating statements were per-alert posthog_user (subscribed_users, serialized twice per alert) and posthog_threshold selects. Fix validated with a standalone query-count benchmark (176 → 27 queries at 30 tiles) and the two new regression tests, run locally against Postgres + ClickHouse.


Created with PostHog Code

AlertSerializer serializes threshold and subscribed_users for every alert, but the
alert prefetch on dashboard GET and insight list only select_related created_by. Each
alert on the page therefore costs two extra Postgres queries, so alert-heavy dashboards
degrade to O(alerts) queries per load.

Extend both prefetch sites with select_related("threshold") and
prefetch_related("subscribed_users"). Measured on a 30-tile dashboard with two alerts
per insight: 176 -> 27 queries, wall time -72%; the query count is now flat as tiles grow.

Adds constant-query-count regression tests for both endpoints; each fails on the
previous prefetch and passes with this one.

Generated-By: PostHog Code
Task-Id: edcf7979-e685-4b20-a3b1-940d04c033ad
@thmsobrmlr
thmsobrmlr marked this pull request as ready for review July 14, 2026 18:33
@thmsobrmlr thmsobrmlr added the stamphog Request AI approval (no full review) label Jul 14, 2026
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team July 14, 2026 18:33
@pr-assigner-resolver-posthog

Copy link
Copy Markdown

👀 Auto-assigned reviewers

These soft owners were skipped because they only have minor changes here. Nothing blocks merge, so self-assign if you'd like a look:

  • @PostHog/team-analytics-platform (products/dashboards/**)

Soft owners come from CODEOWNERS-soft and each product's product.yaml. Generated files and lockfiles are ignored when deciding ownership.

@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(dashboards): prefetch alert threshol..." | Re-trigger Greptile

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Contained N+1 query fix adding select_related/prefetch_related to existing alert querysets, with new regression tests proving the fix; no schema, API contract, or security changes, and diff matches the description.

  • Author wrote 0% of the modified lines and has 13 merged PRs in these paths (familiarity MODERATE).
  • 👍 on the PR from greptile-apps[bot], hex-security-app[bot].
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list no deny categories matched
size 12L, 2F substantive, 100L/4F incl. docs/generated/snapshots — within ceiling
tier T1-agent / T1b-small (100L, 4F, two-areas, fix)
stamphog 2.0.0b3 .stamphog/policy.yml @ 182dd44 · reviewed head 4ea189d

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Playwright — all passed

All tests passed.

View test results →

⚠️ Backend snapshots — 1 updated (1 modified, 0 added, 0 deleted)

Query snapshots: Backend query snapshots updated

Changes: 1 snapshots (1 modified, 0 added, 0 deleted)

What this means:

  • Query snapshots have been automatically updated to match current output
  • These changes reflect modifications to database queries or schema

Next steps:

  • Review the query changes to ensure they're intentional
  • If unexpected, investigate what caused the query to change

Review snapshot changes →

@stamphog

stamphog Bot commented Jul 14, 2026

Copy link
Copy Markdown

Retaining stamphog approval — delta since last review classified as trivial_paths.

@trunk-io

trunk-io Bot commented Jul 14, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@thmsobrmlr
thmsobrmlr enabled auto-merge (squash) July 14, 2026 19:01

Copy link
Copy Markdown
Collaborator Author

Per-change benchmark of this fix

(Comment posted by PostHog Code — benchmarked each half of the prefetch change separately.)

Same harness as before: local PG, dashboard GET with 30 tiles, 2 alerts per insight (one threshold + one detector, 1 subscriber each → 60 alerts, 30 thresholds). Median of 19 requests.

Variant Queries Wall ms
unfixed (created_by only) 176 1388
+ select_related("threshold") only 146 (−30) ~1390 (~0)
+ prefetch_related("subscribed_users") only 57 (−119) 435 (−69%)
both (this PR) 27 427

Cost of the heavier prefetch statement itself (60 alerts, statement-level micro-benchmark): the threshold LEFT JOIN takes the alert prefetch from 3.5ms → 4.7ms; adding the subscribed_users prefetch (one extra batched query) brings the pair to ~17ms — replacing 149 individual queries.

Reading: the subscribed_users prefetch is the dominant win — each alert was fetching the full ~60-column user row twice. The threshold join contributes no local wall time (each eliminated lookup was a ~0.1ms PK hit) but removes 1 query per threshold alert; over a real network round trip to the primary that's meaningful latency and connection load, and it costs +1.2ms of JOIN on the prefetch statement. Both halves keep the query count flat as tiles grow.

@thmsobrmlr
thmsobrmlr merged commit 774320f into master Jul 14, 2026
234 checks passed
@thmsobrmlr
thmsobrmlr deleted the posthog-code/fix-dashboard-alert-nplus1 branch July 14, 2026 19:18
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 14, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-14 19:51 UTC Run
prod-us ✅ Deployed 2026-07-14 20:14 UTC Run
prod-eu ✅ Deployed 2026-07-14 20:17 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stamphog Request AI approval (no full review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant