Skip to content

feat(limit): drop resolved alerts without an admitted firing counterpart#5381

Open
siavashs wants to merge 1 commit into
prometheus:mainfrom
siavashs:feat/limit-resolved-alerts
Open

feat(limit): drop resolved alerts without an admitted firing counterpart#5381
siavashs wants to merge 1 commit into
prometheus:mainfrom
siavashs:feat/limit-resolved-alerts

Conversation

@siavashs

Copy link
Copy Markdown
Contributor

Rework the per-alertname alert limit so resolved notifications no longer consume limit slots, and change how they are handled.

  • limit: add Bucket.Remove to free a tracked slot by value/fingerprint.
  • store: apply the per-alert limit only to firing alerts. A resolved notification is forwarded only if its firing counterpart was previously admitted (its fingerprint is still tracked); removing it frees the slot the firing alert was holding. Resolves with no admitted firing are dropped as noise, since nothing downstream ever received a firing for them from us.
  • provider/mem: add a state label (firing/resolved) to alertmanager_alerts_limited_total so dropped firing alerts and dropped resolved notifications can be distinguished.
  • docs: document the new resolved handling and metric label in alertmanager.md and configuration.md.
  • tests: add Bucket.Remove coverage (presence return, slot freeing, double remove, heap eviction-order integrity, concurrency) and store coverage for the drop-if-unseen resolved semantics.

Pull Request Checklist

Please check all the applicable boxes.

  • Please list all open issue(s) discussed with maintainers related to this change
    • Fixes #
  • Is this a new Receiver integration?
  • Is this a bugfix?
    • I have added tests that can reproduce the bug which pass with this bugfix applied
  • Is this a new feature?
    • I have added tests that test the new feature's functionality
  • Does this change affect performance?
    • I have provided benchmarks comparison that shows performance is improved or is not degraded
      • You can use benchstat to compare benchmarks
    • I have added new benchmarks if required or requested by maintainers
  • Is this a breaking change?
    • My changes do not break the existing cluster messages
    • My changes do not break the existing api
  • I have added/updated the required documentation
  • I have signed-off my commits
  • I will follow best practices for contributing to this project

Which user-facing changes does this PR introduce?

[CHANGE] limit: `alertmanager_alerts_limited_total` now has a `state` label (`firing`/`resolved`) to distinguish dropped firing alerts from dropped resolved notifications. Dashboards/alerts using this metric must be updated.
[CHANGE] limit: With `--alerts.per-alertname-limit` set, a resolved notification is only forwarded if its firing counterpart was previously admitted (which frees its slot); resolved notifications with no admitted firing alert are now dropped.

Rework the per-alertname alert limit so resolved notifications no longer
consume limit slots, and change how they are handled.

- limit: add Bucket.Remove to free a tracked slot by value/fingerprint.
- store: apply the per-alert limit only to firing alerts. A resolved
  notification is forwarded only if its firing counterpart was previously
  admitted (its fingerprint is still tracked); removing it frees the slot
  the firing alert was holding. Resolves with no admitted firing are
  dropped as noise, since nothing downstream ever received a firing for
  them from us.
- provider/mem: add a `state` label (firing/resolved) to
  alertmanager_alerts_limited_total so dropped firing alerts and dropped
  resolved notifications can be distinguished.
- docs: document the new resolved handling and metric label in
  alertmanager.md and configuration.md.
- tests: add Bucket.Remove coverage (presence return, slot freeing,
  double remove, heap eviction-order integrity, concurrency) and store
  coverage for the drop-if-unseen resolved semantics.

Signed-off-by: Siavash Safi <siavash@cloudflare.com>
@siavashs
siavashs requested a review from a team as a code owner July 13, 2026 10:00
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: cccbc651-5012-45f9-aaee-e0c50db467eb

📥 Commits

Reviewing files that changed from the base of the PR and between 2512db5 and d71ba20.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/alertmanager.md
  • docs/configuration.md
  • limit/bucket.go
  • limit/bucket_test.go
  • provider/mem/mem.go
  • provider/mem/mem_test.go
  • provider/provider.go
  • store/store.go
  • store/store_test.go

📝 Walkthrough

Walkthrough

The change adds removal support to limit buckets, forwards resolved alerts only when their firing alerts were admitted, labels limited-alert metrics by state, migrates provider and store APIs to alert.Alert, and updates tests and documentation.

Changes

Alert limits and type migration

Layer / File(s) Summary
Bucket removal primitive
limit/bucket.go, limit/bucket_test.go
Adds Bucket.Remove and tests removal, slot reuse, heap ordering, idempotency, and concurrent access.
Alert type contract migration
provider/provider.go, provider/mem/mem.go, store/store.go
Replaces types.Alert with alert.Alert across provider interfaces, callbacks, storage, garbage collection, and alert APIs.
Resolved-alert limit handling
store/store.go, provider/mem/mem.go
Removes admitted fingerprints when resolved alerts arrive, rejects unmatched resolutions, and adds a state label to the limited-alert metric.
Behavior validation and documentation
store/store_test.go, provider/mem/mem_test.go, docs/alertmanager.md, docs/configuration.md, CHANGELOG.md
Validates resolved-alert capacity behavior and updates documentation and release notes for the new semantics and metric label.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AlertInput
  participant Store
  participant LimitBucket
  participant Metrics
  AlertInput->>Store: Submit firing or resolved alert
  Store->>LimitBucket: Admit firing or remove resolved fingerprint
  LimitBucket-->>Store: Admission result
  Store->>Metrics: Record dropped alert state
  Store-->>AlertInput: Store or reject notification
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and accurately describes the main change to per-alertname limit handling.
Description check ✅ Passed The description follows the template well, includes a summary, checklist items, and release notes for the behavior and metric changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Comment thread provider/mem/mem.go
labels = append(labels, alert.Name())
}
state := "firing"
if alert.Resolved() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this state may theoretically change between when we checked in a.alerts.Set and now... Maybe we need to put the state of the ErrLimited in the error itself?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The status should not change, for firing alerts Prometheus sets EndsAt to few minutes in future by default, so this will work fine unless someone has a very weirdly short-lived alerts configured on prometheus.

For resolved alerts they are already resolved when they hit Alertmanager.

Maybe we need to put the state of the ErrLimited in the error itself?

Do you mean a special error for the case of resolved vs. firing?

Comment thread store/store.go

// Apply per alert limits if necessary
// Apply per alert limits if necessary.
if a.perAlertLimit > 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the "firing" side it might be that if a firing alert is rate-limited, it still shows as resolved in a.alerts[fp] until GC, if it resolved before being subsequently rate-limited. Do we care?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a firing alert is dropped Alertmanager does not track it and it will not be in the bucket or the store.

Comment thread limit/bucket.go
// If the bucket is full, oldest expired item is evicted based on priority and the new value is added.
// Otherwise the new value is ignored and the method returns false.
func (b *Bucket[V]) Upsert(value V, priority time.Time) (ok bool) {
if b.capacity < 1 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this check? Would there be a point for a capacity 0 bucket? Should we fail at NewBucket instead in that case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bucket limiter was added as a generic implementation, zero here means drop all, we can change it in future to allow all.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants