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.

@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: Pro Plus

Run ID: 2d0a5fae-efea-4f40-8ba0-fd68b867638e

📥 Commits

Reviewing files that changed from the base of the PR and between d71ba20 and 74c0127.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • docs/configuration.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • docs/configuration.md
  • CHANGELOG.md

Included review availability: Your plan includes up to 8 reviews per rolling hour; 6 remain after this review.


📝 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

Merge Risk: ⚪ Minimal · up to 74c01

The per-alertname limit now tracks firing alerts and forwards only matching resolved notifications, with corresponding metric, documentation, and test updates. No actionable merge-blocking risk remains beyond normal checks and review.

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 clearly identifies the limit area and the primary behavior change for resolved alerts.
Description check ✅ Passed The description follows the template, documents the behavior, records tests and documentation, and includes release notes.
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.

@siavashs
siavashs requested a review from ultrotter July 21, 2026 09:25
@siavashs
siavashs force-pushed the feat/limit-resolved-alerts branch from d71ba20 to 74c0127 Compare August 17, 2026 09:17
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 force-pushed the feat/limit-resolved-alerts branch from 74c0127 to 4d4ecc3 Compare August 17, 2026 11:37
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