[HDX-4997] Persist alert evaluation errors and analytics in AlertHistory - #2834
Conversation
🦋 Changeset detectedLatest commit: 34732ec 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 persists alert evaluation and notification failures in
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains in the eligible follow-up review scope. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/api/src/tasks/checkAlerts/index.ts | Classifies query timeouts, captures evaluation analytics, persists failures against scheduled windows, and excludes ERROR rows from evaluation scheduling inputs. |
| packages/api/src/tasks/checkAlerts/providers/default.ts | Extends provider persistence to upsert ERROR histories and remove stale query-error records after successful retries or backfills. |
| packages/api/src/tasks/checkAlerts/errors.ts | Adds bounded cause-chain timeout detection for ClickHouse client, server, and socket errors. |
| packages/api/src/tasks/checkAlerts/providers/index.ts | Expands the provider contract with evaluation-window, analytics, and evaluated-range metadata. |
| packages/common-utils/src/clickhouse/index.ts | Exposes the configured request timeout through a read-only client accessor. |
| packages/api/src/tasks/checkAlerts/tests/checkAlerts.int.test.ts | Adds integration coverage for error persistence, retry and backfill behavior, stale-error cleanup, timeout classification, and analytics. |
| packages/api/src/tasks/checkAlerts/tests/errors.test.ts | Adds focused timeout-classification coverage, including wrapped causes and cycle/depth safeguards. |
Sequence Diagram
sequenceDiagram
participant Scheduler
participant AlertTask
participant ClickHouse
participant History as AlertHistory
participant Webhook
Scheduler->>AlertTask: Evaluate alert window
AlertTask->>ClickHouse: Run alert query
alt Query fails
ClickHouse-->>AlertTask: Error or timeout
AlertTask->>History: Upsert ERROR row for window
else Query succeeds
ClickHouse-->>AlertTask: Evaluation results
AlertTask->>Webhook: Send transition notification
AlertTask->>History: Persist normal rows and analytics
AlertTask->>History: Remove superseded query ERROR rows
opt Notification fails
AlertTask->>History: Upsert WEBHOOK_ERROR row
end
end
Reviews (7): Last reviewed commit: "Merge branch 'main' into warren/HDX-4997..." | 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 • 277 passed • 1 skipped • 1042s
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