Skip to content

hotfix(orb): fix production outage in /v1/public/stats caused by anti-join perf#5263

Merged
JSONbored merged 1 commit into
mainfrom
hotfix/orb-stats-perf
Jul 12, 2026
Merged

hotfix(orb): fix production outage in /v1/public/stats caused by anti-join perf#5263
JSONbored merged 1 commit into
mainfrom
hotfix/orb-stats-perf

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • Live incident: the anti-join added in fix(orb): stop double-counting PRs already tracked by the own-ledger #5257 (merged minutes ago) uses a correlated NOT EXISTS subquery with LOWER(ae.target_key) = .... Wrapping the indexed target_key column in LOWER() defeats the index, forcing a full scan of audit_events (100K+ rows) for every orb_pr_outcomes row — a ~600M-comparison query. This took /v1/public/stats down within minutes of deploying: D1 returned {"errors":[{"code":7429,"message":"D1 DB exceeded its CPU time limit and was reset."}]}, and the endpoint has been serving 503 (public_stats_unavailable) since.
  • Fix: replace the correlated subquery with a LEFT JOIN ... WHERE ae.id IS NULL, comparing target_key directly with no function wrapping, so D1 can actually use the index for the join.
  • Verified directly against production D1 before writing this fix: the new query runs in ~60ms reading ~31K rows (vs. timing out before), returning sane numbers.
  • Both sides of the repo#pr_number comparison come from the same GitHub API casing (orb_pr_outcomes.repository_full_name and audit_events.target_key both derive from GitHub's real full_name), so dropping LOWER() doesn't reintroduce a case-sensitivity bug.

Validation

  • Query tested directly against live production D1 first (read-only) before writing the code change.
  • npm run typecheck
  • test/integration/orb-outcomes.test.ts, test/unit/public-stats.test.ts, test/integration/public-stats-route*.test.ts — all pass unchanged (same expected numbers, confirming the rewrite is logically equivalent, not just faster).

Safety

  • No schema change, no new index needed — this is a query-shape fix only.
  • Root cause + the exact failure mode documented in the function's own comment so this doesn't regress.

The anti-join added in the previous commit used a correlated NOT
EXISTS subquery with LOWER(ae.target_key) = ... -- wrapping the
indexed target_key column in a function defeats the index, forcing
a full scan of audit_events (100K+ rows) for every orb_pr_outcomes
row. This took down /v1/public/stats in production within minutes
of deploying (D1 CPU time limit exceeded, 503s).

Replaces it with a LEFT JOIN ... WHERE ae.id IS NULL, comparing
target_key directly with no function wrapping so D1 can use the
index. Verified directly against production: ~60ms / ~31K rows
read, versus a timeout before. Both sides of the comparison come
from the same GitHub API casing, so no LOWER() is needed here.
@JSONbored JSONbored self-assigned this Jul 12, 2026
@JSONbored JSONbored merged commit 864c52d into main Jul 12, 2026
13 checks passed
@JSONbored JSONbored deleted the hotfix/orb-stats-perf branch July 12, 2026 12:30
@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.36%. Comparing base (738c8ca) to head (d1a6c83).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #5263   +/-   ##
=======================================
  Coverage   94.36%   94.36%           
=======================================
  Files         474      474           
  Lines       40096    40096           
  Branches    14620    14620           
=======================================
  Hits        37836    37836           
  Misses       1585     1585           
  Partials      675      675           
Flag Coverage Δ
shard-1 46.24% <ø> (-0.15%) ⬇️
shard-2 34.75% <ø> (+0.01%) ⬆️
shard-3 30.97% <ø> (-1.14%) ⬇️
shard-4 32.92% <ø> (+0.89%) ⬆️
shard-5 33.54% <ø> (-0.16%) ⬇️
shard-6 45.08% <ø> (+0.30%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/orb/outcomes.ts 87.50% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant