Repository navigation
Conversation
Every alert in a tick received one previousMap holding the latest history of every group of every alert, and the skip check plus three evaluation paths scanned it in full. Tick CPU grew with alerts times total groups. Split the shared map into per-alert maps once, right after loading the tasks. Keys, values, types, loaders and readers are unchanged. Refs HDX-6022
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: f4204fd The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Duplicate code analysisNo new duplicate code introduced by this PR. jscpd compared this branch against |
🔴 Tier 4 — CriticalTouches authentication, tenancy data models, the public API or shipped database config — or substantially changes the query rendering engine, background tasks, the OTel pipeline, image build, or release CI. Why this tier:
Review process: Deep review from a domain expert. Synchronous walkthrough may be required. Stats
|
| * hand every alert one map shared across the tick, and every reader scans the | ||
| * map, so a shared map makes tick CPU grow with alerts times total groups. | ||
| */ | ||
| export const partitionPreviousMapsByAlert = ( |
There was a problem hiding this comment.
🔵 minor — Per-alert history is already split at the source, then merged into one map and split again by parsing keys
getPreviousAlertHistories (index.ts:1983-2045) already runs one query per alert and gets results[i] for alertIds[i]. It then flattens them into one shared map, and this helper rebuilds the per-alert split by running indexOf('||') on each key. Two ways to fix it: have the source return Map<alertId, Map<key, history>> and let loadAlert (providers/default.ts:403-407) attach previousAlerts.get(alert.id). Or, if the task-level pass stays so it covers every provider, bucket by history._id.toString() instead of parsing the key. Then the split doesn't depend on the delimiter.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
| @@ -331,6 +463,53 @@ describe('CheckAlertTask', () => { | |||
| ); | |||
There was a problem hiding this comment.
🔵 minor — No test covers the behaviour change on the task path: other alerts' ALERT/PENDING rows no longer affect a grouped alert
The PR changes what processAlert does in two places. hasAlertsInPreviousMap (index.ts:1708) now only sees the alert's own rows, and so does the missing-group loop (index.ts:1811-1833), which used to map another alert's bare key to group '' through extractGroupKeyFromMapKey. The new tests only check which map processAlert receives (it's mocked), so neither change is tested. Add an integration case: a grouped alert with an empty bucket, while a different alert's history is ALERT. Assert that no spurious '' OK history is written and that the empty-bucket branch takes the !hasAlertsInPreviousMap path.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
PR Review
1 finding(s): 🔴 0 critical · 🟠 0 major · 🔵 1 minor 1 posted as inline comment(s) on the changed lines. Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides. |
E2E Test Results✅ All tests passed • 465 passed • 1 skipped • 1001s
Tests ran across 4 shards in parallel. |
Deep Review✅ No critical issues found. The change is small, well-scoped, and strongly tested. No P2 items rise above advisory for this diff. 🔵 P3 nitpicks (2)
Reviewers (8): correctness, performance, maintainability, kieran-typescript, testing, reliability, adversarial, project-standards. Testing gaps:
Prior PR comments (5) were all automated (Vercel, changeset-bot, jscpd, pr-triage, e2e-results) with no human review feedback to verify, so the previous-comments reviewer was not run. No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond |
Run two alerts through one shared history map, split it the way CheckAlertTask.execute() does, and evaluate the grouped alert for real. Asserts that another alert's ALERT row no longer adds an empty-group history or collapses the alert's empty buckets into one. Refs HDX-6022
| * hand every alert one map shared across the tick, and every reader scans the | ||
| * map, so a shared map makes tick CPU grow with alerts times total groups. | ||
| */ | ||
| export const partitionPreviousMapsByAlert = ( |
There was a problem hiding this comment.
🔵 minor — Cross-alert history leak is fixed in a wrapper, not in processAlert where the bug is
Move the per-alert scoping to the layer that causes the problem, so every path gets it and the key isn't parsed in a second place. Two options. (1) In getPreviousAlertHistories (index.ts:1983-2045), results already arrives as one array per alert. Return a Map<alertId, Map<key, history>> from there and have loadAlert (providers/default.ts:403-406) attach previousMap: byAlert.get(alert.id) ?? new Map(). That avoids re-deriving the alert id with a second hand-written indexOf('||') split alongside getAlertKeyPrefix/extractGroupKeyFromMapKey. (2) Apply the same key === alert.id || key.startsWith(getAlertKeyPrefix(alert.id)) filter that shouldSkipAlertCheck already uses to the hasAlertsInPreviousMap check (index.ts:1708) and to the missing-group loop (index.ts:1811-1834). As written, processAlert still writes a spurious group-'' OK history whenever it gets a shared map, and the PR itself says direct callers 'behave as before'.
Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond
Summary
On a deployment with about 2,900 alerts, one check-alerts tick took about 3 minutes, with the worker's Node thread at 100% CPU. One-minute alerts were evaluated every 3 to 4 minutes, so every notification was late.
The default provider hands every alert in a tick the same
previousMap. That map holds the latest history of every group of every alert. Each alert then scans it in full: once inshouldSkipAlertCheck(about 21 ms per alert on that deployment), and again ingetAlertEvaluationDateRange, the empty-bucket check and the missing-group loop. Tick CPU grew with alerts times total groups.This PR adds
partitionPreviousMapsByAlertand calls it once inCheckAlertTask.execute(), right aftergetAlertTasks():alertIdoralertId||group). Alerts with no history get an empty map.recentHistoryMapis left alone because it is only read with.get().AlertProvider.Side effect: on
main, the empty-bucket check (previousMap.values().some(...)) and the missing-group loop read entries of other alerts. As far as I can tell from the code (not reproduced), a grouped alert could write a spurious OK history for group''when a different alert was ALERT or PENDING. With per-alert maps that no longer happens on the task path. Callers that pass a shared map straight toprocessAlertbehave as before.Not in this PR: reading less history from the database (#3508 and #3509).
How to reproduce
Starting point: a local checkout. The change is in the check-alerts worker and has no UI.
On
main:should pass each alert only its own previous historytest from this PR, with itsbuildHistoryandbuildPartitionTaskhelpers, topackages/api/src/tasks/checkAlerts/__tests__/checkAlertsTask.test.ts.packages/api, runyarn ci:unit src/tasks/checkAlerts/__tests__/checkAlertsTask.test.ts. It fails: both alerts receive all three history entries.On this branch:
make dev-int FILE="checkAlerts.int.test --testNamePattern=shared.by.every.alert"runs two integration tests that split a shared map and evaluate a grouped alert for real while another alert is ALERT. Both fail if the map reachesprocessAlertunsplit: one gets an extra empty-group OK history, the other records 1 of its 3 empty buckets.make dev-int FILE=checkAlertspasses.How to test on Vercel preview
N/A — non-UI change.
References
getPreviousAlertHistoriesreads every history row of the last 7 days, so check-alerts slows down and runs out of memory as history grows #3508 (history read cost, not fixed here)