Repository navigation
Ruler: stop retrying staleness markers that can never be accepted - #7888
Open
danielblando wants to merge 3 commits into
Open
danielblando wants to merge 3 commits into
danielblando wants to merge 3 commits into
Conversation
Signed-off-by: Daniel Blando <ddeluigg@amazon.com>
yeya24
approved these changes
Oct 20, 2026
SungJin1212
reviewed
Oct 20, 2026
Comment on lines
+120
to
+132
| } else if onlyStaleMarkers { | ||
| // A 4xx means the rejected samples will never be accepted, and that the | ||
| // rest of the batch was written. That is not the all-or-nothing rollback | ||
| // a non-nil Commit promises, and callers that retry on Commit failure | ||
| // take it literally: rules.Group.cleanupStaleSeries keeps its staleSeries | ||
| // list until Commit succeeds and re-appends the whole list on every | ||
| // evaluation, so a marker that can never be accepted is retried until the | ||
| // ruler restarts. | ||
| // | ||
| // Report success when the transaction is only staleness markers, so that | ||
| // the caller drops them. Transactions carrying rule results keep returning | ||
| // the error: the ruler reports it as rule health and does not retry it. | ||
| err = nil |
Member
There was a problem hiding this comment.
Should 429 be handled differently here? Unlike a 400 from out-of-order or duplicate samples, a 429 from the ingestion rate limiter rejects the whole request before anything is written in the Distributor layer.
Couldn't this leave stale markers unwritten because of a 429?
What about if onlyStaleMarkers && resp.Code != http.StatusTooManyRequests?
Signed-off-by: Daniel Blando <ddeluigg@amazon.com>
danielblando
force-pushed
the
ruler-stale-marker-retry
branch
from
October 21, 2026 18:44
a4bab66 to
fb5a55e
Compare
Signed-off-by: Daniel Blando <daniel@blando.com.br>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does:
Stops the Ruler retrying staleness markers that can never be accepted.
The failure case
When a recording rule is moved from one rule group to another within the same namespace, the two groups race over the rule's output series:
R. Every seriesRproduced is queued into group A'sstaleSerieslist.Rand immediately starts writing those same series.cleanupStaleSeriesappends a staleness marker for series that group B keeps advancing. The append is rejected and the push returns 400.cleanupStaleSeriesclearsstaleSeriesonly whenCommit()succeeds, so the whole list is re-appended on the next evaluation, and the next, until the ruler process restarts.Each retry uses the current evaluation timestamp, so it is not replaying an old sample: it re-attempts the same series set against series another group keeps advancing faster. It can never succeed.
Group.CopyStatealso copiesstaleSeriesto the successor group, so reloading the namespace does not reset it.Why Cortex hits this and Prometheus does not
Prometheus' own appenders return these errors synchronously from
Append, so the per-sampleswitchincleanupStaleSeriesconsumes them, the rejected sample is never staged,Commit()succeeds and the list is cleared:PusherAppenderbatches:Appendbuffers and returns nil, the batch is sent inCommit(), and the rejection surfaces there instead. Thestorage.Appendercontract says a non-nilCommitmeans the transaction was rolled back, so a caller is entitled to read it as "nothing was written, retry". For a 4xx that is not what happened — the rejected samples will never be accepted, and the rest of the batch was written.The fix
PusherAppender.Commit()reports success for a 4xx when the transaction consists only of staleness markers, so the caller drops them instead of retrying forever. 5xx and non-HTTP errors are unchanged: those are genuine failures where retrying is correct.A transaction carrying rule results keeps returning the error, because the Ruler surfaces it as rule health (
SetHealth(HealthBad),SetLastError,EvalFailures) rather than retrying it. Swallowing it there would hide recording-rule output that is not landing.cleanupStaleSeriesis the ruler's only write path that retries at all — rule results and in-evaluation markers are recomputed each evaluation at a new timestamp, so they are bounded by construction.Dropping a marker costs at most one lookback window of a retired series lingering in query results. In this race the marker is wrong anyway, since the series is still being written by the other group, and the list is already discarded silently on every ruler restart — which is how these episodes end today.
Which issue(s) this PR fixes:
No existing issue — the behaviour is described above.
Checklist
CHANGELOG.mdupdated - the order of entries should be[CHANGE],[FEATURE],[ENHANCEMENT],[BUGFIX]docs/configuration/v1-guarantees.mdupdated if this PR introduces experimental flags — n/a, no new flags.