Skip to content

cluster: wait for replacement replicas to catch up before graceful cutover - #38940

Draft
aljoscha wants to merge 6 commits into
MaterializeInc:mainfrom
aljoscha:sql-672-caught-up-cutover
Draft

aljoscha wants to merge 6 commits into
MaterializeInc:mainfrom
aljoscha:sql-672-caught-up-cutover

Conversation

@aljoscha

@aljoscha aljoscha commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Motivation

Refs SQL-672. Builds on #38615, retaining the original commit and attribution, with separate follow-up commits.

When enabled, graceful reconfiguration waits for the required replacement replicas to hydrate and catch up to the outgoing replicas, using per-replica output progress. The default allowance is 60 seconds. The gate defaults off in production and on in mzcompose, sqllogictest, and the Rust integration-test harness. ON TIMEOUT = COMMIT remains an escape hatch. Source ingestion and hydration-burst retirement remain hydration-only.

Follow-ups to the earlier draft

  • Require a replacement to complete when an outgoing output frontier is complete. No reference replicas and a completed reference are different cases.
  • Share hydration-and-lag classification with 0dt while preserving its write-frontier policy, exclusions, and escape hatches. Identical output-progress guarantees would require leader output-frontier reporting and are not part of this PR.
  • Replace synthetic readiness fixtures with real replica collection state, covering shared MV write progress, REFRESH jumps, completion, and same-replica hydration/progress. Exercise the hydrated-but-lagging phase in the controller cutover test.
  • Remove repeated rationale and probe-count assertions. Correct historical documentation and mark the behavior as unreleased.
  • Separate production rollout from test coverage with explicit test-only defaults.

The new system parameters still need LaunchDarkly registration before rollout, as noted in #38615. Hydration-burst retirement needs a separate policy change.

Docs review: https://preview.materialize.com/materialize/38940/sql/alter-cluster/#resizing-process

Verification coverage

Regression tests cover the completed-frontier bypass and reject substituting write progress for output progress. Controller tests cover retaining outgoing replicas while targets are hydrated but lagging, then cutting over once ready.

bosconi and others added 6 commits September 19, 2026 04:45
A graceful ALTER CLUSTER reconfiguration cut over to its pending
replicas as soon as every collection reported hydrated on them.
Hydration is `as_of < output_frontier`, and the as-of is pinned to the
collection's read frontier when the replica is added. A collection
whose initial snapshot takes hours therefore reports hydrated the
instant the snapshot lands, with everything since the as-of still to
replay. The outgoing replicas keep the cluster's frontiers current
until the cut-over, so the lag is invisible; at the cut-over they are
dropped and the frontiers stall until the new replicas catch up.

On 2026-08-28 a production cluster cut over after a 4h08m
reconfiguration. The last collection had crossed an as-of from the
start of that window seconds earlier, and the cluster's frontiers
stalled for roughly 15 minutes while the new replica replayed the
backlog. The cluster-autoscaling design chose hydration-only
deliberately, calling the stronger check "a possible later refinement"
that "would only avoid a brief post-cut-over latency bump". The bump
is not brief: it scales with hydration time.

Gate the cut-over on readiness instead. A pending replica is ready
when every collection is hydrated on it and its output frontier for
the collection is within `cluster_reconfiguration_allowed_lag` (60s)
of the furthest output frontier among the reference replicas: the
ones the cut-over will drop, which is the realized-shape set. The
strategy names that set, since only it knows what a cut-over retires.
A replica that survives the cut-over, such as a hydration-burst
replica at a larger size, is not part of the reference: it cannot
regress the cluster's frontier, so it must not raise the bar.
`enable_cluster_reconfiguration_lag_gate` is the break-glass back to
hydration alone.

Output frontiers, not write frontiers: a materialized view's
replica-reported write frontier is the persist shard upper, shared by
every replica writing it, and for a REFRESH materialized view it jumps
to the next refresh time. The output frontier is the meet of the write
frontier and the dataflow's compute probe, so it is the replica's own
progress. For indexes the two coincide.

The hydration burst keeps its hydration-only signal: its durable
`steady_hydrated_at` field means exactly "hydration was observed", so
the reconfiguration strategy gets a separate `ready_replicas` signal
rather than a redefined `hydrated_replicas`. The storage-side check is
unchanged and hydration-only. Timeout semantics are unchanged: ON
TIMEOUT = COMMIT still cuts over at the deadline once the target set
exists and is recorded as forced. That is also how a downsize to a
size that hydrates but cannot sustain the input rate, which the
hydration-only gate allowed to cut over into a lagging state, now
expresses that intent.

The lag predicate `reference <= frontier + allowed_lag` is extracted to
`mz_repr::frontier_within_lag`, replacing the two open-coded copies in
the 0dt caught-up check, and saturates rather than panicking on
overflow. The per-collection decision is a pure function,
`classify_collection_readiness`, tested over plain frontiers. The
readiness probe logs "unhydrated" and "lagging" separately; in the
incident the two were indistinguishable.

Linear: https://linear.app/materializeinc/issue/SQL-672

Co-Authored-By: Claude Code <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants