Repository navigation
compute: retry metric sink collector registration - #38910
Conversation
3f23cab to
8051e57
Compare
QA LLM Review1. MEDIUM -- Moving the registration guard into the operator closure unregisters the collector when the operator shuts down, not when the sink is dropped
DetailsThe operator is built with Concretely reachable: The same mechanism also means a registration that is still retrying when the input closes never completes: the operator is gone before the next Suggested fix: keep the guard's lifetime tied to the collection rather than the operator. Share the |
| // Drain so the operator isn't rescheduled forever. There is no state to | ||
| // fold into on this worker. | ||
| ok_input.for_each(|_, _| {}); | ||
| err_input.for_each(|_, _| {}); | ||
| return; |
There was a problem hiding this comment.
If we don't have a registration, do we not have to store the data locally so that we can publish it subsequently? Our inputs should be changes to metrics, not the metrics on each tick, so here we'd only start recording metrics once they change, right?
There was a problem hiding this comment.
I'm not sure I 100% understand your concern.
I think there are a few things going on:
-
The sink reads its source with
with_snapshot: true. So my understanding is that it means that it gets the entire current contents at theas_of(as diffs), and then subsequent changes. So there isn't only recording of new changes. -
If we don't have a registration, that means we are a none active worker (only one worker receives data in the model). So a non active worker has nothing to store regardless of whether the registration is sucessful or not.
-
The active worker accumulates regardless of registration outcome. After try_register, we don't have early return. Failed attempts schedules a retry, but execution continues. So the diffs arriving while registration is pending are accumulated in theSinkState. And then a scrape after registration exposes the snapshot
So I don't quite follow where we would miss metrics.
antiguru
left a comment
There was a problem hiding this comment.
Left a comment; I think we can miss metrics with this approach. Do you mind checking this?
| true | ||
| } | ||
| Err(prometheus::Error::AlreadyReg) => { | ||
| metrics.inc_metric_sink_registration_retry(); |
There was a problem hiding this comment.
should we log before retrying instead of after?
There was a problem hiding this comment.
nvm, inc_ is the verb here, not _retry. not obvious on the first read through.
antiguru
left a comment
There was a problem hiding this comment.
Checked this against the SQL-680 occurrence in nightly 18362, "Checks 0dt restart of the entire Mz with forced migrations 2" (panic at services.log line 514103). The collision there came from curated sinks. On boot, environmentd reinstalled them under new transient ids (for replica u1, mz_metric_arrangement_sizes went from t291 to t273), and they share the definition-name label, so both incarnations have one Desc id. The registering worker is sink_id.hashed() % peers (metric_sink.rs:73), so old and new usually land on different workers. Worker::reconcile runs per worker without a barrier, so the new one can register before the other worker drops the old handle. User sinks cannot hit this: their durable id keeps both incarnations on one worker, which drops before it re-creates. The retry converges for this race, so it fixes that path.
- The concern from def-'s QA review on 09-18 is still open on the current head. The registration guard lives in the operator closure, and the operator has no outputs or capabilities. Once both input frontiers close, for example with a constant-folded source, the closure and the guard drop, and the sink silently stops publishing, taking any in-flight retry with it. Keeping the guard in
sink_token, shared with the operator asRc<RefCell<PendingRegistration>>, still converges and restores unregistering on token drop. - A permanent
AlreadyRegcollision now retries every second forever, with oneinfo!and an unlabeled counter. Today it soft-panics and CI catches it. Suggest escalating after a bound, e.g.soft_panic_or_log!after 30-60 s. The doc claim that a predecessor always eventually drops is not established. The original SQL-680 report (plain restart, u3 missing, only u4 visible) fits neither this race nor a late drop, so rerunning its minimal repro on this branch would show whether that case is permanent. - The NOTE at
metric_sink.rs:90-96, the doc oftry_register_collector_with_dropperand the PR description say nothing sequences teardown before render. Within a worker it is sequenced, and the gap is across workers. Naming that, and noting that user sinks are not exposed, would make the contract clear. Transient ids are also reused across boots, so "fresh transientGlobalId" is misleading. - The tests cover the
PendingRegistrationstate machine only. A test that the registration survives the input closing would have caught 1, and a multi-worker reconcile in clusterd-test-driver with a shared label could cover the race.
Posted by Claude Code.
Problem: A curated metric sink keys its gauges on its stable definition name, so every incarnation shares one prometheus Desc id. When a restarted environmentd reconciles against a still-running replica, the old incarnation and the new one live in the same process, and nothing sequences the old dataflow's teardown (which drops its registration) before the new one renders. Registration collided and register_collector_with_dropper soft-panicked. Solution: Add MetricsRegistry::try_register_collector_with_dropper, a fallible variant that returns the prometheus error, and re-base the soft-panicking one on it so its callers keep the backstop. The sink operator owns a PendingRegistration and registers at build time, before the first activation, so a sink with a slow input still publishes its series promptly. On a Desc-id collision it retries once a second, self-scheduling via its activator so a quiescent input still gets another try. The guard lives in the operator closure, so it drops with the dataflow, freeing the Desc id for the retrying incarnation. A new counter, mz_compute_metric_sink_registration_retries_total, makes retries visible.
8051e57 to
3f74b79
Compare
antiguru
left a comment
There was a problem hiding this comment.
The fallible try_register_collector_with_dropper split is clean, and replacing the old "never collides" NOTE is right.
Should fix
- Retries aren't rate-limited.
try_registerruns on every activation of the active worker, and each failure schedules anotheractivate_after(1s). With a busy input during the collision window, that means a registration attempt (collector clone plus a registry write lock) per activation, a pile-up of delayed activations, and amz_compute_metric_sink_registration_retries_totalthat counts activations rather than retries, so its help text is misleading. Suggest anext_attempt: InstantonPendingRegistration: return early before it, and only schedule a retry when none is pending. - PR description is stale. It says "The guard lives in the operator closure", but the code deliberately returns it as the sink token, as the comment at the
Rc<dyn Any>return explains. - Retries stop if the inputs close first. Timely drops the operator closure once both input frontiers close, which ends the retry loop. The token then holds a
PendingRegistrationwithhandle: Noneforever, and the sink never publishes. That's probably rare for curated sinks, but it contradicts "retried until the predecessor drops". Either note it as a known gap or keep an activation pending until the handle is set.
Nits
- "Once per streak" doesn't apply.
first_collisionandescalatedare never reset, and there are no more attempts after success, so there is only ever one streak. - New unchecked claim in the
NOTE. "User sinks … both land on one worker that drops before it re-creates, and never collide" is the same kind of claim the oldNOTEmade, and that one turned out wrong. Point at what enforces it (the ordering inWorker::reconcile) or cover it with a test. registration_survives_operator_closure_droponly testsRcrefcounting. It doesn't build an operator, so it wouldn't catch the operator no longer sharing the token. A dataflow-level test would, for example a constant-input sink whose series survives its input closing.- Repeated struct literal. The seven-field
PendingRegistration { … }literal appears three times. APendingRegistration::new(label, collector)constructor would remove that.
Generated by Claude Code
8a4e327 to
d6b071e
Compare
QA LLM Review1. MEDIUM --
|
Release notes for the v26.45 train, published as **v26.45.1**. v26.45.0 did not ship, so (as with v26.44) its notes are folded in here and [#39477](#39477) is closed unmerged. - **v26.45.0 content**: 8 Improvements, 2 Agent Skills, 8 Bug Fixes, copied verbatim from #39477 with anchors renamed to `{#v26.45.1-…}`. - **v26.45.1 content**: Alerting for Self-Managed and the external dependency monitoring Improvement (editor-added). The release's only PR, [#39659](#39659), is omitted. - **Dates**: Cloud 2026-10-08, Self-Managed 2026-10-09. The operator compatibility row is `v26.45.1`. `v26.45.md` (`patch: 1`, no `rc`) is already on `main`.⚠️ **Open questions** - **Group-sync bullet**: `oidc_group_role_sync_enabled` defaults to `false` at the tag. Is it on in Cloud? If not, cut the "Quieter logins" bullet. - **Certificate SAN requirements** ([#39316](#39316), [#39415](https://github.com/MaterializeInc/materialize/pull/39415)/[#39459](https://github.com/MaterializeInc/materialize/pull/39459)) need action at upgrade time but sit in Improvements. Should they be called out more loudly? Also, does [#39351](#39351) (S3 on rustls) impose the same SAN rule on custom S3 endpoints? - **[#39659](#39659) (omitted)**: is `enable_upsert_paged_spill` on in production? A draft bullet is in [omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.45.1/final/omitted.md#borderline). - **`EXPLAIN ANALYZE ... CPU`**: [agent-skills#90](MaterializeInc/agent-skills#90) says it overstates joins, reductions, and TopK by 1.5x–3x before v26.46. Not published. Is that a known issue? - **Metric sinks**: [#39332](#39332), [#38910](#38910), and [#39337](#39337) are withheld because `enable_metric_sink` is `false`. #39337 fixes a panic any superuser can reach, so consider publishing it regardless. - **Possible cuts**: [#39362](#39362) was observed only in CI. The latency figures from [#39179](#39179) were measured on one noisy machine. - **Possible adds**: [#38894](https://github.com/MaterializeInc/materialize/pull/38894)–[#38896](https://github.com/MaterializeInc/materialize/pull/38896) (MV correction buffer) need an end-to-end number. [#39149](#39149) fixes the kind install guide, which crash-loops today. [#39549](#39549) (jemalloc page) is omitted because no released version had the regression. Snapshot drafts in mz-skills: [v26.45.0](https://github.com/MaterializeInc/mz-skills/tree/main/data/mz-release-notes/v26.45.0) (rc.1–rc.4, final), [v26.45.1](https://github.com/MaterializeInc/mz-skills/tree/main/data/mz-release-notes/v26.45.1/final). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Valerie Chiang <[email protected]>
Problem:
A curated metric sink keys its gauges on its stable definition
name, so every incarnation shares one prometheus Desc id. When a
restarted environmentd reconciles against a still-running replica, the
old incarnation and the new one live in the same process, and nothing
sequences the old dataflow's teardown (which drops its registration)
before the new one renders. Registration collided and
register_collector_with_dropper soft-panicked.
Solution:
Add MetricsRegistry::try_register_collector_with_dropper, a
fallible variant that returns the prometheus error, and re-base the
soft-panicking one on it so its callers keep the backstop. The sink
operator owns a PendingRegistration and retries once a second,
self-scheduling via its activator so a quiescent input still gets
another try. The guard lives in the operator closure, so it drops with
the dataflow, freeing the Desc id for the retrying incarnation.
A new counter, mz_compute_metric_sink_registration_retries_total,
makes retries visible.