You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
adapter: fix graceful cluster reconfig with arrangement compresssion (#38241)
## The bug
Arrangement compression is a replica config shape dimension. It is part
of
`ReconfigurationTarget`, the reconcile kernel distinguishes replicas by
it, and
the controller transitions it through the hydrate-overlap exactly like
`SIZE`.
Three hand-maintained dimension lists forgot it.
**1. The realized-config leak (the one that actually hurts).** The
reshape path
writes the reconfiguration record and resets the shape carried by
`new_config`
back to the realized values, so the transition is deferred to the
record's
cut-over. That reset enumerated size, replication factor, availability
zones and
logging, and missed compression. So a compression-carrying `ALTER`
flipped the
realized config *immediately*. The baseline strategy then saw the new
shape,
decided the existing baseline replicas no longer matched, and bounced
them in the
middle of the reconfiguration. That defeats the graceful overlap the
record
exists to provide, on a statement the user expected to be graceful.
The fix stops hand-listing dimensions: the reset goes through
`realized_reconfiguration_target` and a new inverse
`apply_reconfiguration_target`. Both destructure `ReconfigurationTarget`
exhaustively, so a future dimension fails to compile until it is handled
here.
**2. The planner's `WAIT` gate** rejected `WITH (WAIT ...)` on a
compression-only
`ALTER` as having nothing to wait on, even though it transitions through
a
hydrate-overlap like any other shape change.
**3. Observability.** `mz_cluster_reconfigurations.changes` diffed only
the other
four dimensions, so a compression-only record reported an empty diff
while in
progress, and the `SHOW CLUSTERS` activity summary showed such a cluster
as
steady.
## Testing
The realized-config behaviour is asserted in `cluster-controller.td`,
where
testdrive's retries make it deterministic. The `cc_preserve` section now
checks
that a folding compression `ALTER` lands in the record's target and
`changes`
while the realized config stays untouched and only the overlap replica
is
bounced. A new section runs a compression-only `ALTER` through the
record path
end to end.
The `managed_cluster.slt` compression `ALTER`s previously depended on
the leak:
their `SHOW CREATE` readback only saw the new value because the reshape
wrote it
early. With the leak gone the value moves at cut-over, and the suite
pins
`enable_background_alter_cluster` on, so the statement returns before
that. Those
`ALTER`s are exercised for acceptance instead, one with a `WAIT` clause
to cover
the planner gate.
## Migration
Changing the MV definition changes its builtin fingerprint, so the
builtin schema
migration registers a replacement step for
`mz_cluster_reconfigurations`. The
catalog-docs golden and the `mz_show_clusters` plan golden are
rewritten.
Copy file name to clipboardExpand all lines: doc/user/content/reference/system-catalog/mz_internal.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -181,7 +181,7 @@ shape is in [`mz_clusters`](../mz_catalog/#mz_clusters).
181
181
|`status`|[`text`]| The lifecycle status of the reconfiguration: `in-progress` while the controller converges on the target, then a terminal `finalized`, `timed-out`, `cancelled`, or `resource-exhausted`. The record is retained after it settles, so the latest outcome stays inspectable until a later reconfiguration overwrites it. |
182
182
|`deadline`|[`mz_timestamp`]| The deadline by which the reconfiguration must complete. After it passes, the `on_timeout` action applies. |
183
183
|`on_timeout`|[`text`]| The action applied if `deadline` passes before the target hydrates: `commit` (cut over to the not-yet-hydrated target) or `rollback` (revert to the pre-reconfiguration shape). |
184
-
|`target`|[`jsonb`]| The config shape the cluster is reconfiguring to, as JSON: `size`, `replication_factor`, `availability_zones`, and `logging`. The realized (current) shape is in `mz_clusters`. |
184
+
|`target`|[`jsonb`]| The config shape the cluster is reconfiguring to, as JSON: `size`, `replication_factor`, `availability_zones`, `logging`, and `arrangement_compression`. The realized (current) shape is in `mz_clusters`. |
185
185
|`changes`|[`jsonb`]| The dimensions in which `target` differs from the cluster's realized configuration, as a JSON object holding the target value per changed dimension. Empty (`{}`) once a record settles with its target applied. A rolled-back record keeps the abandoned diff. |
"The config shape the cluster is reconfiguring to, as JSON: `size`, `replication_factor`, `availability_zones`, and `logging`. The realized (current) shape is in `mz_clusters`.",
879
+
"The config shape the cluster is reconfiguring to, as JSON: `size`, `replication_factor`, `availability_zones`, `logging`, and `arrangement_compression`. The realized (current) shape is in `mz_clusters`.",
880
880
),
881
881
(
882
882
"changes",
@@ -953,7 +953,9 @@ SELECT
953
953
CASE WHEN r.target->'availability_zones' != r.config->'availability_zones'
954
954
THEN jsonb_build_object('availability_zones', r.target->'availability_zones') ELSE '{}'::jsonb END ||
955
955
CASE WHEN r.target->'logging' != r.config->'logging'
956
-
THEN jsonb_build_object('logging', r.target->'logging') ELSE '{}'::jsonb END
956
+
THEN jsonb_build_object('logging', r.target->'logging') ELSE '{}'::jsonb END ||
957
+
CASE WHEN r.target->'arrangement_compression' != r.config->'arrangement_compression'
958
+
THEN jsonb_build_object('arrangement_compression', r.target->'arrangement_compression') ELSE '{}'::jsonb END
Copy file name to clipboardExpand all lines: test/sqllogictest/autogenerated/mz_internal.slt
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -129,7 +129,7 @@ cluster_id text The␠ID␠of␠the␠cluster.␠Corresponds␠to␠`mz_cluste
129
129
status text The␠lifecycle␠status␠of␠the␠reconfiguration:␠`in-progress`␠while␠the␠controller␠converges␠on␠the␠target,␠then␠a␠terminal␠`finalized`,␠`timed-out`,␠`cancelled`,␠or␠`resource-exhausted`.␠The␠record␠is␠retained␠after␠it␠settles,␠so␠the␠latest␠outcome␠stays␠inspectable␠until␠a␠later␠reconfiguration␠overwrites␠it.
on_timeout text The␠action␠applied␠if␠`deadline`␠passes␠before␠the␠target␠hydrates:␠`commit`␠(cut␠over␠to␠the␠not-yet-hydrated␠target)␠or␠`rollback`␠(revert␠to␠the␠pre-reconfiguration␠shape).
Map: ("reconfiguring " || case when ("" = array_to_string(array[("size to " || (#5{changes} ->> "size")), ("replication factor to " || (#5{changes} ->> "replication_factor")), case when ((#5{changes} -> "availability_zones")) IS NOT NULL then "availability zones" else null end, case when ((#5{changes} -> "logging")) IS NOT NULL then "introspection settings" else null end], ", ")) then null else array_to_string(array[("size to " || (#5{changes} ->> "size")), ("replication factor to " || (#5{changes} ->> "replication_factor")), case when ((#5{changes} -> "availability_zones")) IS NOT NULL then "availability zones" else null end, case when ((#5{changes} -> "logging")) IS NOT NULL then "introspection settings" else null end], ", ") end), true
2012
+
Map: ("reconfiguring " || case when ("" = array_to_string(array[("size to " || (#5{changes} ->> "size")), ("replication factor to " || (#5{changes} ->> "replication_factor")), case when ((#5{changes} -> "availability_zones")) IS NOT NULL then "availability zones" else null end, case when ((#5{changes} -> "logging")) IS NOT NULL then "introspection settings" else null end, case when ((#5{changes} -> "arrangement_compression")) IS NOT NULL then "arrangement compression" else null end], ", ")) then null else array_to_string(array[("size to " || (#5{changes} ->> "size")), ("replication factor to " || (#5{changes} ->> "replication_factor")), case when ((#5{changes} -> "availability_zones")) IS NOT NULL then "availability zones" else null end, case when ((#5{changes} -> "logging")) IS NOT NULL then "introspection settings" else null end, case when ((#5{changes} -> "arrangement_compression")) IS NOT NULL then "arrangement compression" else null end], ", ") end), true
filter=(("Cluster" = (#0{data} ->> "kind")) AND (json_null != #2) AND (#2 != json_null))
5374
-
map=(((((#0{data} -> "value") -> "config") -> "variant") -> "Managed"), (#1 -> "reconfiguration"), parse_catalog_id(((#0{data} -> "key") -> "id")), (#2 -> "target"), case when ("InProgress" = (#2{reconfiguration} ->> "status")) then "in-progress" else case when ("Finalized" = (#2 ->> "status")) then "finalized" else case when ("TimedOut" = (#2 ->> "status")) then "timed-out" else case when ("Cancelled" = (#2 ->> "status")) then "cancelled" else case when ("ResourceExhausted" = (#2 ->> "status")) then "resource-exhausted" else (#2 ->> "status") end end end end end, text_to_mz_timestamp((#2{reconfiguration} ->> "deadline")), case when ("Commit" = (#2{reconfiguration} ->> "on_timeout")) then "commit" else case when ("Rollback" = (#2 ->> "on_timeout")) then "rollback" else (#2 ->> "on_timeout") end end, (((case when ((#1{config} -> "size") != (#4{target} -> "size")) then jsonb_build_object("size", jsonbable_to_jsonb((#4 -> "size"))) else {} end || case when ((#1{config} -> "replication_factor") != (#4{target} -> "replication_factor")) then jsonb_build_object("replication_factor", jsonbable_to_jsonb((#4 -> "replication_factor"))) else {} end) || case when ((#1{config} -> "availability_zones") != (#4{target} -> "availability_zones")) then jsonb_build_object("availability_zones", jsonbable_to_jsonb((#4 -> "availability_zones"))) else {} end) || case when ((#1{config} -> "logging") != (#4{target} -> "logging")) then jsonb_build_object("logging", jsonbable_to_jsonb((#4 -> "logging"))) else {} end))
5374
+
map=(((((#0{data} -> "value") -> "config") -> "variant") -> "Managed"), (#1 -> "reconfiguration"), parse_catalog_id(((#0{data} -> "key") -> "id")), (#2 -> "target"), case when ("InProgress" = (#2{reconfiguration} ->> "status")) then "in-progress" else case when ("Finalized" = (#2 ->> "status")) then "finalized" else case when ("TimedOut" = (#2 ->> "status")) then "timed-out" else case when ("Cancelled" = (#2 ->> "status")) then "cancelled" else case when ("ResourceExhausted" = (#2 ->> "status")) then "resource-exhausted" else (#2 ->> "status") end end end end end, text_to_mz_timestamp((#2{reconfiguration} ->> "deadline")), case when ("Commit" = (#2{reconfiguration} ->> "on_timeout")) then "commit" else case when ("Rollback" = (#2 ->> "on_timeout")) then "rollback" else (#2 ->> "on_timeout") end end, ((((case when ((#1{config} -> "size") != (#4{target} -> "size")) then jsonb_build_object("size", jsonbable_to_jsonb((#4 -> "size"))) else {} end || case when ((#1{config} -> "replication_factor") != (#4{target} -> "replication_factor")) then jsonb_build_object("replication_factor", jsonbable_to_jsonb((#4 -> "replication_factor"))) else {} end) || case when ((#1{config} -> "availability_zones") != (#4{target} -> "availability_zones")) then jsonb_build_object("availability_zones", jsonbable_to_jsonb((#4 -> "availability_zones"))) else {} end) || case when ((#1{config} -> "logging") != (#4{target} -> "logging")) then jsonb_build_object("logging", jsonbable_to_jsonb((#4 -> "logging"))) else {} end) || case when ((#1{config} -> "arrangement_compression") != (#4{target} -> "arrangement_compression")) then jsonb_build_object("arrangement_compression", jsonbable_to_jsonb((#4 -> "arrangement_compression"))) else {} end))
0 commit comments