Skip to content

adapter: let the synchronous cut-over change the replication factor - #38174

Draft
aljoscha wants to merge 1 commit into
aljoscha/cluster-legacy-06-compression-fixfrom
aljoscha/cluster-legacy-07-cutover-factor
Draft

adapter: let the synchronous cut-over change the replication factor#38174
aljoscha wants to merge 1 commit into
aljoscha/cluster-legacy-06-compression-fixfrom
aljoscha/cluster-legacy-07-cutover-factor

Conversation

@aljoscha

@aljoscha aljoscha commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Stacked on #38112. Addresses both of @ggevay's review comments on #38103.

Let the synchronous cut-over change the replication factor

Changing the factor while a reconfiguration is in progress is refused, because
the record captures a target factor when written and the controller's cut-over
writes it back to the realized config a tick later. A factor change applied
independently in the meantime gets silently clobbered.

That reasoning doesn't reach the synchronous cut-over: it folds the factor into
the target it transacts and retires the record in the same transaction, and no
consumer reads a retired record's target (desired_replicas, target_hydrated
and the controller's own cut-over all sit behind is_in_progress()). There's no
later cut-over left to do the clobbering, so it's exempt.

The refusal cost the escape hatch its most useful shape: forcing a wedged resize
through while scaling down had to be two statements, and in between the cluster
ran the old factor at the new size — the most expensive combination available,
exactly while someone is firefighting. fold_reconfiguration_target already
computed the right answer for a re-targeted factor; only the guard made that
branch unreachable.

The exemption is narrow. A zero-timeout rollback still leaves a record in
flight to settle on a tick, so it keeps the refusal. The error hint now offers
the cut-over as a third way out, which is useful precisely because after this
change the hint only ever fires when you didn't ask for one.

Test coverage was the reason this held: the existing size-and-factor case runs
with no record in flight (its own comment says so). The cut-over section now also
wedges a record, asserts the refusal still fires without a WAIT, then forces a
combined size-and-factor cut-over and checks both dimensions land, the replica set
converges, and the record settles cancelled — the record's own target factor was
abandoned, so cancelled rather than finalized is correct.

Drop Op::UpdateClusterReplicaConfig

Deleting the staged reconfiguration machine removed its last producer. The
variant, its transact.rs apply arm and its ddl.rs audit match arm were dead.
Confirmed no producers remain in src/ or test/.

Not in scope

@ggevay's comment prompted a wider question: should a factor change be allowed
while a reconfiguration is in progress generally, not just on the cut-over? The
factor is already a re-targetable dimension at record creation, and
mz_cluster_reconfigurations.changes plus the SHOW CLUSTERS summary already
render replication factor to N — so the fold, the durable target and the
user-facing surface all support it, and only re-targeting an existing record is
refused.

That's a user-visible semantics change on a surface about to go GA, with at least
three coherent designs that differ in what the user pays, so it's tracked in
SQL-626 rather than smuggled in here. This PR is a bug fix with no semantic
change: it's correct under every one of those designs.

Changing a cluster's replication factor while a graceful reconfiguration is in
progress is refused, because the record captures a target factor when written
and the controller's cut-over writes it back to the realized config a tick
later. A factor change applied independently in the meantime would be silently
clobbered, with nothing in the ALTER's response to say so.

That reasoning does not reach the synchronous cut-over. It folds the factor into
the target it transacts and retires the record in the same transaction, and no
consumer reads a retired record's target: `desired_replicas`, `target_hydrated`
and the controller's own cut-over all sit behind `is_in_progress()`. There is no
later cut-over left to clobber the write, so exempt it.

The refusal cost the escape hatch its most useful shape. Forcing a wedged resize
through while scaling down had to be two statements, and between them the
cluster ran the old factor at the new size, the most expensive combination
available, exactly while an operator is firefighting. `fold_reconfiguration_target`
already computed the right answer for a re-targeted factor; only the guard made
that branch unreachable.

The escape hatch is the only exemption. A zero-timeout rollback still leaves a
record in flight to settle on a tick, so it keeps the refusal, and the error hint
now points at the cut-over as the third way out.

The cut-over section gains the wedged case: the refusal still fires without a
`WAIT`, and a combined size-and-factor cut-over lands both dimensions, converges
the replica set, and settles the record `cancelled` because the record's own
target factor was abandoned. The existing factor case ran with no record in
flight, which is why this gap held.

Also drops `Op::UpdateClusterReplicaConfig`. Deleting the staged reconfiguration
machine removed its last producer, leaving the variant, its apply arm and its
audit match arm dead.
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.

1 participant