[HDX-4997] Persist alert evaluation errors and analytics in AlertHistory - #2834
[HDX-4997] Persist alert evaluation errors and analytics in AlertHistory#2834wrn14897 wants to merge 2 commits into
Conversation
🦋 Changeset detectedLatest commit: 9e231cd The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 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 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThe PR durably records alert evaluation and notification failures in AlertHistory while preserving retry and backfill behavior.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/api/src/tasks/checkAlerts/index.ts | Adds timeout classification, evaluation analytics, ERROR-history persistence inputs, and ERROR exclusion from scheduling calculations. |
| packages/api/src/tasks/checkAlerts/providers/default.ts | Persists per-window ERROR histories, cleans recovered failures, and stores notification failures alongside normal evaluation histories. |
| packages/api/src/tasks/checkAlerts/errors.ts | Detects client, server, and socket timeout failures through bounded error-cause traversal. |
| packages/api/src/tasks/checkAlerts/tests/checkAlerts.int.test.ts | Exercises error persistence, retry, backfill cleanup, timeout classification, notification failures, and analytics. |
| packages/common-utils/src/clickhouse/index.ts | Exposes the configured ClickHouse request timeout for actionable alert error messages. |
Sequence Diagram
sequenceDiagram
participant Task as Alert task
participant CH as ClickHouse
participant Mongo as AlertHistory
participant Hook as Notification
Task->>CH: Evaluate alert window
alt Query fails
CH-->>Task: Error or timeout
Task->>Mongo: Upsert ERROR history for window
else Query succeeds
CH-->>Task: Evaluation results
Task->>Hook: Send transition notification
Task->>Mongo: Write normal histories and analytics
Task->>Mongo: Remove superseded query ERROR rows
alt Notification fails
Hook-->>Task: Delivery error
Task->>Mongo: Upsert ERROR history for current window
end
end
Reviews (6): Last reviewed commit: "fix(alerts): clear stale ERROR rows acro..." | Re-trigger Greptile
|
PLACEHOLDER |
829fd2f to
f02ffbf
Compare
50d2a33 to
8b31195
Compare
|
material for review below |
|
Addressed the stale-ERROR-row cleanup gap in 06c74cc:
|
06c74cc to
5f36f4e
Compare
a4b448b to
c32b78e
Compare
5f36f4e to
a68ddbd
Compare
Deep Review✅ No critical (P0/P1) issues found. The core design is sound: ERROR rows are consistently excluded from the due-ness gate ( 🟡 P2 -- recommended
🔵 P3 nitpicks (2)
Reviewers (returned at synthesis time): api-contract, performance, learnings-researcher, plus orchestrator code analysis. Testing gaps:
|
…#2833) Linear Issue: [HDX-4997](https://linear.app/clickhouse/issue/HDX-4997/record-alert-evaluation-errors-in-alerthistory-and-show-them-on-the) ## Stack (1/3) This is the base of a 3-PR stack that splits #2786 for reviewability: 1. **→ this PR** — evaluations read model + endpoint (api, common-utils) 2. #2834 — persist evaluation errors/analytics in the alert task (api) 3. #2835 — alert detail page UI (app) PRs 2 and 3 both base on this branch but are independent of each other; once this merges they can land in either order (GitHub retargets them to `main` automatically when this branch is deleted on merge). ## Why To surface alert evaluation history (including failures) on a per-alert detail page, we need a read model over `AlertHistory` that can answer "what happened in each evaluation window?" — including windows that errored, per-group results for group-by alerts, and evaluation analytics. Today `AlertHistory` only stores OK/ALERT rows and there is no per-alert evaluations API. ## What - **Types (`common-utils`)** for evaluation errors (`AlertError`/`AlertErrorType` incl. `QUERY_TIMEOUT`), per-window evaluations with per-group breakdown (capped at `ALERT_EVALUATION_GROUPS_LIMIT`, firing-first), and evaluation analytics (`queryDurationMs`, `webhookDurationMs`, `backfilledBuckets`). - **`AlertHistory` schema** gains optional `errors` + `analytics` fields, and `AlertState` gains `ERROR` (only ever used on history rows). - **`GET /alerts/:id/evaluations`**: per-window evaluation history scoped to a `startTime`/`endTime` range (clamped to the 31d retention window), grouped across group-by groups newest-first, with a hard-bounded scan of at most ~(limit+1) intervals per request and a server-provided `nextBefore` cursor that always advances past the scanned slice so paging progresses across gaps instead of stalling. - Windows with ERROR rows surface their errors (deduped, newest-first) and rank as ERROR; firing-transition annotations exclude ERROR rows. Nothing writes ERROR rows or analytics yet — the alert task's write side lands in PR 2 of the stack. ## Testing - `packages/api` + `packages/common-utils`: `ci:lint` (eslint + tsc), `ci:unit` green - Integration: `alertHistory.int.test.ts` (new, 80 cases), `routers/api/alerts.int.test.ts`, and the full `*alerts.int*` set pass locally (278 tests)
… (HDX-4997) When an alert evaluation fails (ClickHouse query error/timeout, webhook failure), the only persisted signal was alert.executionErrors — a latest-only snapshot wiped by the next successful run. - Failed evaluations are recorded as ERROR-state AlertHistory rows carrying error type/message/timestamp, upserted per evaluation window so per-tick retries collapse into a single row; rows expire with the existing 30d TTL. - Webhook/notification failures also produce an ERROR row alongside the normal evaluation rows; a stale ERROR row from a failed earlier tick is removed when a clean same-window retry succeeds. - Retry/backfill semantics are untouched: ERROR rows are excluded from the due-ness gate, the retry date-range computation, and consecutive-window counting — recording an error never marks the window as evaluated, so the failed window is still retried every tick and backfilled on recovery. - Query timeouts are classified as QUERY_TIMEOUT (client request timeout/abort, server-side TIMEOUT_EXCEEDED/159, socket timeouts — walking the cause chain since the query client wraps failures) with an actionable message that includes the configured evaluation timeout. - Evaluation analytics (queryDurationMs, webhookDurationMs, backfilledBuckets) are recorded on every history row the evaluation writes, including ERROR rows.
…covery The clean-evaluation cleanup only deleted the ERROR row at the current window's createdAt, but the time-series path folds backfilled earlier-window buckets into rows stamped with the current window start — so a window that failed on tick N and recovered via backfill on tick N+1 kept its ERROR row until the 30d TTL and rendered as ERROR in the evaluations view despite recovering. The common case for short-interval alerts. updateAlertState now receives the evaluated date range and, on a clean save, deletes ERROR rows with createdAt in (rangeStart, currentWindowStart]. The lower bound is exclusive: an ERROR row at exactly the previous anchor belongs to an already-evaluated window (e.g. a webhook failure recorded alongside its normal rows) that is never retried, so it survives as a truthful record — same reason a never-backfilled failed window (no anchor, one-window lookback) keeps its row. The cleanup also runs when the evaluation itself succeeded but this run's webhook failed, so stale query-failure rows from older windows are cleared before the fresh WEBHOOK_ERROR row is upserted.
a68ddbd to
9e231cd
Compare
🔴 Tier 4 — CriticalTouches authentication, tenancy data models, the public API or shipped database config — or substantially changes 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
|
E2E Test Results✅ All tests passed • 276 passed • 1 skipped • 1083s
Tests ran across 4 shards in parallel. |
Linear Issue: HDX-4997
Stack (2/3)
Splits #2786 for reviewability. Now based on
main(#2833 merged); independent of the UI PR (3/3).Why
When an alert evaluation fails (ClickHouse query error/timeout, webhook failure), the only persisted signal is
alert.executionErrors— a latest-only snapshot wiped by the next successful run. There is no durable, per-window record of which evaluations failed, so the alert detail page (and any postmortem) can't show failure history.What
AlertHistoryrows carrying error type/message/timestamp, upserted per evaluation window so per-tick retries collapse into a single row; rows expire with the existing 30d TTL.QUERY_TIMEOUT(client request timeout/abort, server-sideTIMEOUT_EXCEEDED/159, socket timeouts — walking the cause chain since the query client wraps failures) with an actionable message that includes the configured evaluation timeout.queryDurationMs,webhookDurationMs,backfilledBuckets) are recorded on every history row the evaluation writes, including ERROR rows.Testing
packages/api:ci:lint(eslint + tsc + openapi),ci:unitgreen (incl. newerrors.test.tstimeout-classification unit tests)checkAlerts.int.test.tsfull suite passes locally (160 tests), including the new error-recording / QUERY_TIMEOUT / webhook-failure / backfill-analytics cases