Skip to content

Stop metric_staleness alerts firing and clearing in a loop - #412

Open
dpage wants to merge 3 commits into
mainfrom
fix/issue-405-metric-staleness-flapping
Open

Stop metric_staleness alerts firing and clearing in a loop#412
dpage wants to merge 3 commits into
mainfrom
fix/issue-405-metric-staleness-flapping

Conversation

@dpage

@dpage dpage commented Aug 12, 2026

Copy link
Copy Markdown
Member

Summary

While any probe was stale, the alerter raised and cleared the same
metric_staleness alert on every cycle, sending a fire and a clear
notification each time. The cleaner resolved the alert's metric,
probe_staleness_ratio, through metricRegistry, where the bespoke
staleness metric deliberately has no entry, and read the resulting
lookup error as proof that the condition had gone away; the evaluator
then re-raised the alert on its next pass, because unlike
triggerThresholdAlert it applied no cooldown guard.

The change is in three parts:

  • GetLatestMetricValues now returns distinguishable errors
    (ErrMetricNotSupported and ErrNoMetricData), so callers can tell a
    metric that cannot be evaluated at all, or a query that failed, apart
    from one that ran and reported nothing. checkAlertResolved clears an
    alert only in the last of those cases.

  • Probe-scoped alerts skip the registry path entirely and resolve
    through checkStalenessAlertResolved, which re-reads probe
    availability, so they still clear promptly once the probe collects
    again or stops being reported; suppressing the spurious clears does
    not leave the alert latched.

  • evaluateMetricStaleness applies the same recently-cleared cooldown
    guard as every other rule, and keys both its active-alert lookup and
    its cooldown check by probe, so several stale probes on one connection
    raise one alert each rather than fighting over a single row whose
    title and description were overwritten arbitrarily.

Issue #407 shares the root cause in checkAlertResolved, and the error
handling half of it is fixed here: a query error no longer clears an
alert. The rest of #407 is deliberately out of scope, because it needs
semantic rewrites of several metric_registry.go queries
(cache_hit_ratio latest-sample reduction, an interval mean for
slow_query_count, wider delta windows, and freshness cutoffs on the
replication slot metrics) that are independent of this fix.

Test plan

  • alerter/src/internal/engine/staleness_alerts_integration_test.go
    covers the loop itself, resolution when the probe recovers, resolution
    when the probe stops being reported, one alert per stale probe, the
    cooldown guard, and both sides of the new error handling in
    checkAlertResolved. Every one of those tests fails against the code
    as it stood before this change, verified by reverting cleanup.go and
    thresholds.go alone.

  • alerter/src/internal/engine/staleness_alerts_errors_integration_test.go
    drives the evaluator's and cleaner's database error branches by
    breaking exactly one operation at a time.

  • alerter/src/internal/database/probe_alert_queries_integration_test.go
    covers the two new probe-scoped queries and every branch of
    GetLatestMetricValues.

  • cd alerter && make coverage passes with no failures. The new and
    modified units are at 100% (checkAlertResolved,
    checkStalenessAlertResolved, GetLatestMetricValues,
    GetActiveThresholdAlertForProbe, GetRecentlyClearedAlertForProbe)
    and 96.4% (evaluateMetricStaleness), against the project's 90%
    floor. make lint and make fmt-check are clean.

Closes #405

Summary by CodeRabbit

  • New Features

    • Staleness alerts are now tracked separately for each probe.
    • Added cooldown protection to prevent repeated alerts.
    • Alerts resolve when probes recover or stop reporting.
    • Metric evaluation now distinguishes unsupported metrics, missing data, and query failures.
  • Bug Fixes

    • Prevented metric staleness alert loops and duplicate alert creation.
    • Improved cleanup behavior when metrics cannot be evaluated.
  • Documentation

    • Added changelog and troubleshooting guidance for staleness alerts and metric errors.

Interaction with PR #410

PR #410 (branch claude/dashboard-chart-stats-review-0iq76e) adds
TestAudit* regression tests that deliberately pin the current,
defective
behaviour for issues #405 to #409. It is test-only and is not
yet merged, so nothing in it needed changing here, but two of its tests
document behaviour that this PR removes and will legitimately start
failing once both land. I verified that by copying #410's two test files
into this branch and running them; the results were:

  • TestAuditC1StalenessAlertFireClearLoop fails. It asserts three alert
    rows, all cleared, and three fire plus three clear notifications after
    three cycles; it now sees one row, active, and one fire notification.
    It should be inverted to assert exactly that, which is what its Demo
    counterpart already asserts.

  • TestAuditC1StalenessAlertFireClearLoopDemo now passes. Its
    ALERTER_DEFECT_DEMO skip guard should be removed. Once the test above
    is inverted the two are equivalent, so one of them can go; the
    Demo variant's assertions are the clearer of the pair.

  • TestAuditC1StalenessPathSkipsCooldownGuard fails on its final
    assertion, "expected the staleness path to re-fire inside the
    cooldown". The staleness path now honours AlertCooldownPeriod exactly
    as the registry-backed control rule in the same test does, so the
    assertion should be flipped to require that neither path re-fires.

Everything else in #410 is unaffected: the whole internal/database
audit suite still passes, as do TestAuditC2, C6, C7, C8 and C10
in internal/engine. In particular the C8 and C9 defects are
untouched here, because this PR changes only the error handling in
checkAlertResolved, not the row-matching path those tests exercise.

I also renamed this PR's test fixtures with a staleness prefix, because
#410 defines constants of the same names (insertStalenessRuleSQL,
insertProbeConfigSQL, and friends) in the same package; with the rename
both files compile together, which I confirmed with go vet over the
combined tree.

While any probe was stale, the alerter raised and cleared the same
metric_staleness alert on every cycle, sending a fire and a clear
notification each time. The cleaner resolved the alert's metric,
probe_staleness_ratio, through the metric registry, where the bespoke
staleness metric deliberately has no entry, and read the resulting
lookup error as proof that the condition had gone away; the evaluator
then re-raised the alert on its next pass, because unlike
triggerThresholdAlert it applied no cooldown guard.

GetLatestMetricValues now returns distinguishable errors, so callers can
tell a metric that cannot be evaluated at all, or a query that failed,
apart from one that ran and reported nothing. checkAlertResolved clears
an alert only in the last of those cases, since neither of the other two
says anything about whether the alerting condition still holds. Probe
scoped alerts skip the registry path entirely and resolve against probe
availability instead, so they still clear promptly once the probe
collects again or stops being reported.

The staleness evaluator now applies the same recently-cleared cooldown
guard as every other rule, and keys both its active-alert lookup and its
cooldown check by probe, so several stale probes on one connection raise
one alert each rather than fighting over a single row whose title and
description were overwritten arbitrarily.

Closes #405
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d65ff7a7-64bb-449b-9eb9-b5247657d9d7

📥 Commits

Reviewing files that changed from the base of the PR and between 2e545e3 and 4786ea3.

📒 Files selected for processing (3)
  • .claude/golang-expert/metrics-queries.md
  • alerter/src/internal/engine/staleness_alerts_errors_integration_test.go
  • alerter/src/internal/engine/staleness_alerts_integration_test.go

Walkthrough

The PR adds classified metric lookup errors, probe-scoped staleness alert queries, cooldown enforcement, and probe-aware resolution. Integration tests cover alert creation, recovery, failures, suppression, probe isolation, and metric result handling. Documentation records the corrected lifecycle.

Changes

Staleness alert lifecycle

Layer / File(s) Summary
Metric and alert query contracts
alerter/src/internal/database/metric_queries.go, alerter/src/internal/database/alert_queries.go, alerter/src/internal/database/probe_alert_queries_integration_test.go
Metric queries now distinguish unsupported metrics, missing data, and query failures. Probe-specific active-alert and cooldown queries were added and tested.
Probe-scoped alert evaluation
alerter/src/internal/engine/thresholds.go, alerter/src/internal/engine/staleness_alerts_integration_test.go
Staleness alerts now use rule, connection, and probe identity. Probe-specific cooldown checks prevent repeated creation. Tests cover recovery, disappearing probes, per-probe alerts, and cooldown behavior.
Classified alert resolution
alerter/src/internal/engine/cleanup.go, alerter/src/internal/engine/staleness_alerts_errors_integration_test.go, alerter/src/internal/engine/staleness_alerts_integration_test.go
Staleness alerts resolve from probe availability and staleness ratios. Standard alerts resolve only for ErrNoMetricData; unsupported metrics and query failures leave alerts active.
Documentation and regression coverage
.claude/golang-expert/metrics-queries.md, docs/changelog.md
Documentation describes the new error categories, probe-scoped lookups, and corrected metric_staleness lifecycle.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

  • pgEdge/ai-dba-workbench#410 — Both changes modify probe-specific staleness alert creation, cooldown handling, resolution, and integration tests.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address issue #405 by adding cooldowns, probe-scoped alerts, safe cleanup behavior, and recovery handling.
Out of Scope Changes check ✅ Passed The changes support the linked issue and stated objectives; no unrelated code changes are evident.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary fix for repeated metric_staleness alert firing and clearing.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-405-metric-staleness-flapping

Comment @coderabbitai help to get the list of available commands.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 158 complexity · 59 duplication

Metric Results
Complexity 158
Duplication 59

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/golang-expert/metrics-queries.md:
- Around line 199-202: Update the documentation around checkAlertResolved to
clarify that ErrNoMetricData is the only error result that clears an alert; also
mention that alerts clear when no current value matches or the current value no
longer violates the threshold, while unsupported metrics and failed queries
leave alerts active.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f2e7fd71-a115-4521-87da-a187b21b7f77

📥 Commits

Reviewing files that changed from the base of the PR and between 19c645d and 2e545e3.

📒 Files selected for processing (9)
  • .claude/golang-expert/metrics-queries.md
  • alerter/src/internal/database/alert_queries.go
  • alerter/src/internal/database/metric_queries.go
  • alerter/src/internal/database/probe_alert_queries_integration_test.go
  • alerter/src/internal/engine/cleanup.go
  • alerter/src/internal/engine/staleness_alerts_errors_integration_test.go
  • alerter/src/internal/engine/staleness_alerts_integration_test.go
  • alerter/src/internal/engine/thresholds.go
  • docs/changelog.md

Comment thread .claude/golang-expert/metrics-queries.md Outdated
dpage added 2 commits August 12, 2026 12:57
CodeRabbit rightly pointed out that checkAlertResolved also clears an
alert on paths that involve no error at all, so the note now scopes the
ErrNoMetricData statement to the error case.
PR #410 adds audit_defects_test.go to the same package with fixtures
called insertStalenessRuleSQL, insertProbeConfigSQL, and friends, so the
two files would have collided on those identifiers once both landed.
Prefixing the fixtures here with 'staleness' keeps the two sets apart
and lets them coexist without either side needing edits at merge time.
@dpage

dpage commented Aug 12, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

metric_staleness alerts fire and clear in a loop, sending ~3 spurious notifications per minute

1 participant