Repository navigation
fix: Stop counting routine waits as reconcile errors - #623
Conversation
|
|
ecv
left a comment
There was a problem hiding this comment.
Approve.
settleRoutineErrors only absorbs an error when every member of an aggregate or join is a conflict or the removal-pending sentinel, and a real error alongside either still surfaces; the tests cover mixed aggregate and join cases.
- Classification is sound for the shapes callers produce today: a wrapped real error,
join(conflict, real),agg[conflict, real]andjoin(pending, real)all surface. Two holes:everyErroruseserrors.As(err, &agg), which finds the first Aggregate anywhere in the tree, soerrors.Join(kerrors.NewAggregate([conflict]), real)orfmt.Errorf("%w; %w", agg, real)classifies as routine and dropsreal; type-switch on the root'sErrors()/Unwrap() []errorinstead. And a real error stringified with%vnext to a%wsentinel is lost by construction. No current controller nests these, so neither is live. - A mix of one conflict and one removal-pending in a single aggregate is reported as an error (neither predicate holds for all members). Harmless, but it is the common shape if a Finalize collects both; consider a combined predicate.
client.IgnoreNotFoundon the post-finalizationUpdateis safe:Updatedonly means the in-memory finalizer was dropped, and NotFound means the object is already gone, so no cleanup on another object is skipped (hub and downstream cleanup run insideFinalizebefore this write).- A wait that never finishes now polls every 5s forever with no error signal: the pending branch logs at V(1) only, and the backoff that used to grow to minutes is gone. A stuck hub copy or instance projection (including the DNS projection replacement, which previously backed off) shows nothing on the dashboards. Same for a 1s conflict loop. Add a counter or an info log with the elapsed wait, or return the error after a bounded wait.
- In
InternalDNSPublisherReconciler.Reconcilethe deferred settle readsctxafter the timeoutcancelhas fired; harmless for logging, but pass the original context to be safe.
Follow-ups:
- Expose a
reconcile_waits_total{reason}counter and an alert on a wait older than some threshold, so routine waits stay out of the error rate without becoming invisible.
|
Addressed the review in a9eb498 and e7e276b:
Generated by Claude Code |
ecv
left a comment
There was a problem hiding this comment.
Approve.
Both gaps from my last review are closed at e7e276b: routine retries are now counted, and a real error beside a nested aggregate is reported.
everyErrornow walks the single wrap chain and splits at the first Aggregate or multi-%w/Join it meets, recursing into each member, soerrors.Join(agg[conflict], real)andfmt.Errorf("%w; %w", agg[pending], real)both surfacereal; both cases are in the table test. A chain with no combining error falls back to matching the original error, as before.reconcile_routine_retries_total{controller,reason}is incremented on every absorbed retry, so a wait or conflict loop that never ends shows on dashboards without entering the error rate.- A conflict mixed with a removal wait now polls on the 5 s removal interval rather than erroring, and the internal DNS publisher's deferred settle captures the context before the timeout wraps it.
Follow-ups:
- Add an alert on a sustained
reconcile_routine_retries_totalrate per controller (for example, a removal wait retrying for over 30 minutes) so a stuck wait pages, not only charts.
Several controllers return errors for outcomes that resolve on retry: a write that lost to a newer version, a wait on an object's deletion, and a finalizer release on an object that is already gone. Each one counted as a reconcile failure and drew the error backoff. Key changes: - add a shared outcome helper that turns write conflicts into a 1s retry and removal waits into a 5s poll, and reports any real error combined with them - apply it to the workload, workload deployment, federator, instance, instance projector and internal DNS publisher reconcilers - report the projector's projection and DNS lifetime waits and the cell instance's write-back wait as removal pending - ignore NotFound when releasing a finalizer on an object already deleted
Review on the first pass noted that a wait which never ends polled silently, and that a real error nested beside an aggregate could be classified as routine. Key changes: - count retried reconciles by controller and reason, so a stuck wait or conflict loop shows on dashboards without entering the error rate - split combined errors at the first aggregate or join on the wrap chain, so a real error beside a nested aggregate is reported - poll on the removal interval when conflicts and removal waits mix - capture the internal DNS publisher's context before its timeout wraps it
Satisfy goconst by naming the controller and reason labels the routine retry counter and its logs share.
e7e276b to
92a14d7
Compare
Requested by Scot Schuchert-Wells · project thread
Several compute controllers report reconcile errors for outcomes that a retry resolves, which inflates the reconcile error rate and puts healthy controllers on the error backoff. The outcomes are a write that lost to a newer version of the object, a wait for a projection or write-back to finish deleting, and a finalizer release on an object that is already gone. This change retries conflicts after a second and polls deletion waits every five seconds without reporting either as a failure, and treats an already-deleted object as done. A real error combined with one of these outcomes is still reported, and every absorbed retry is counted, so genuine failures still surface and stuck waits stay visible.
Details
compute_reconcile_routine_retries_total{controller,reason}counts each absorbed retry, so a wait that never ends or a conflict loop shows as a steady rate. An alert on wait age is left as a follow-up.Test plan
🤖 Generated with Claude Code