Skip to content

console: configure a cluster from a drawer on the redesigned cluster page - #39992

Merged
leedqin merged 3 commits into
mainfrom
cluster-page-configuration
Nov 2, 2026
Merged

leedqin merged 3 commits into
mainfrom
cluster-page-configuration

Conversation

@leedqin

@leedqin leedqin commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Closes: CNS-204

Seventh PR in the cluster details redesign stack, on top of #39889. The redesigned cluster page kept the classic page's Replicas tab as a stopgap, and its only way to edit or drop the cluster was the breadcrumb's overflow menu. This moves a cluster's configuration into a drawer opened from the page header, in the same format as the Connect drawer, and drops the Replicas tab.

Description

Opening it. A gear button next to the cluster's title, after its size and replica pills, opens Configure ‹cluster›.

  • The open section is the configure search parameter (overview, replicas or auto-scaling), so it survives a reload and can be linked.
  • useSyncObjectToSearchParams, which lists such as the Objects tab's Sources and Sinks use to put their sort in the URL, now keeps search parameters it didn't write, so configure survives on every tab. It still removes a key it wrote once its object drops it, such as a cleared filter.
  • The classic Replicas tab's URL, …/replicas, redirects to ?configure=replicas.
  • A rename from Edit keeps the parameter, so the drawer stays open on the renamed cluster.
  • On the redesigned page, the breadcrumb no longer shows its Alter/Drop overflow menu, so the drawer is the one place to edit or drop the cluster. The classic page is unchanged.
  • The gear and the drawer each look the cluster up in their own component, so another cluster's status updates don't re-render the page.

Layout. It's a 640px side drawer, like the Connect drawer, with a row of ConnectMethodCards that switch the panel below:

  • Overview, subtitled with the size, or "Unmanaged".
    • Shows name, size, management, replication factor, owner and creation time.
    • While a resize is in progress, an info alert names its target size, and its target replication factor when that changes too. mz_clusters, and so the details, keep the current values until it finishes.
    • Edit opens the existing Alter cluster modal for managed user clusters. Like the cluster list's Alter cluster item since console: Fix second loading spinner on the cluster list #39875, it reads the sizes without suspending. It stays disabled until the sizes load. The Alter cluster modal now starts its Replicas field at the cluster's replication factor, which the app-wide clusters subscribe now carries. It used to start at the replica count, which also counts a hydration burst's or a graceful resize's extra replicas, so saving any edit during one kept them. It also sends the size and replication factor only when they change, and leaves out the SET when neither does. While a resize runs, mz_clusters reports the realized size and factor, and setting both back to them cancels the resize, so saving a rename used to cancel it. Both fix the classic page's Alter cluster item too.
    • Owners of a user cluster also get a Drop cluster danger zone, using the existing drop confirmation.
  • Replicas, subtitled "N online" or "N of M online".
    • A UniversalTable of each replica's name, size, status and creation time. An offline replica's pill carries its reason.
    • Unmanaged clusters get New replica and a per-replica drop for owners, through the existing modals. Managed clusters explain that they run one replica per unit of replication factor, counted from the factor so a running burst replica isn't included, since the server rejects dropping one of their replicas. Only user clusters are pointed at Edit.
  • Auto-scaling, subtitled "Enabled" or "Off", shown only from v26.34.0, when mz_internal.mz_cluster_auto_scaling_strategies first shipped.
    • Shows the burst size for hydration and the linger duration.
    • A note while a burst replica is running, including after its policy was removed and the strategy reads as null.
    • Copyable ALTER CLUSTER … SET (AUTO SCALING STRATEGY = …) and RESET statements to change or remove the policy, or the statement to turn one on. The linger is written as whole seconds where it is one, and in milliseconds otherwise, so copying the statement keeps a 0s or 1.5s linger. System clusters get none, since they can't be altered.
    • Unmanaged clusters are told autoscaling needs a managed cluster.

Data. The cluster list already carries name, size, management, the replication factor (added to its subscribe here), owner id and replica statuses, and useOwners already has every role's name. Everything else comes from one executeSqlV2 request when the drawer opens:

  • creation times, from mz_object_lifetimes;
  • the autoscaling strategy, by cluster id;
  • an in-progress reconfiguration's target size and replication factor, from mz_cluster_reconfigurations, which shipped with the autoscaling relation in v26.34.0.

The request reads at strict serializable, the session default. A serializable read can come back from before a write that just committed, and since the drawer doesn't poll, a replica added or a policy altered moments earlier could stay missing until the drawer reopened.

Cluster and replica ids share the u<N> form but come from separate counters, so a cluster's id can also be a replica's. buildObjectCreationTimesQuery now takes the object types to read and returns each row's object_type, and the drawer matches creation times on type and id.

When the replica set changes, for example a resize or a burst replica coming and going, the request's key changes. The drawer keeps showing the previous answer for the same cluster until the new one arrives, rather than blanking the overview.

mz_cluster_auto_scaling_strategies and mz_cluster_reconfigurations are added to types/materialize.d.ts by hand, matching the generated shape, with the file's TODO for hand-added relations.

The owners, the replica limit and, where Edit can show, the sizes are read once by the drawer and passed to its sections, so switching sections makes no requests.

buildObjectCreationTimesQuery requires its object types, and the earlier caller passes its two explicitly.

Also: a SettingsIcon in the icon set, and an ownerName lookup on useOwners.

Verification

  • clusterConfigurationModel.test.ts:
    • the strategy JSON's hydration size, linger and in-flight burst, no policy, and a burst that outlasts a removed policy;
    • a replica is online only when every process is;
    • the generated statements quote the cluster and write the linger in seconds, or in milliseconds when it isn't a whole number of seconds, including 0;
    • a size with a quote is escaped;
    • the resize note names the target size, and the replication factor when it changes too, and says nothing when neither changes.
  • ClusterConfigurationDrawer.test.tsx, with creation-time fixtures where each id is also an object of the other type:
    • the overview shows the owner, the replication factor, the cluster's own creation time, Edit and Drop;
    • an in-progress resize's target size shows in an alert on the overview;
    • Edit is disabled while the sizes load, then opens the Alter cluster modal, filled from the replication factor even while a burst replica runs;
    • switching sections back and forth reads the owners, the replica limit and the sizes once;
    • the Replicas text counts the replication factor rather than a burst replica, falls back when the factor can't be read, and doesn't point a system cluster at Edit;
    • an unmanaged cluster's owner can drop a replica from its row;
    • a zero linger shows and copies as 0s;
    • a non-owner gets no Drop, and a system cluster gets no Edit, Drop or statements;
    • the Replicas card switches to the replica table with each replica's own creation time;
    • an unmanaged cluster's owner gets New replica, and its auto-scaling section says it needs a managed cluster;
    • a configured policy, an unset one, and a running burst after the policy is removed;
    • a failed read shows an error and keeps what the cluster list has;
    • an environment before v26.34.0 has no auto-scaling card and opens a link to it on the overview.
  • ClusterDetail.test.tsx:
    • the redesigned page has no Replicas tab and has the gear;
    • the gear opens the drawer on the overview;
    • the classic Replicas tab's URL opens it on the replicas;
    • the gear opens it over the Sources list, whose real sort hook rewrites the query string.
  • useSyncObjectToSearchParams.test.ts: a search parameter the hook didn't write survives, and one it wrote goes once its object drops it.
  • queries.test.ts: useOwners names an owner by its role id.
  • clusterConfiguration.test.sql.ts, against a real Materialize: creation times told apart for a cluster and a replica that share an id, and autoscaling policies, with and without a linger, parsed as the drawer parses them, and no resize target once a resize settles or for a cluster never resized.
  • clusterList.test.sql.ts reads a managed cluster's replication factor and none for an unmanaged one, and the clusters subscribe tests expect the new column.
  • AlterClusterModal.test.tsx: the Replicas field starts at the replication factor, not a running burst's replica count; a rename sends only the RENAME; a replication factor change sends no SIZE.

🤖 Generated with Claude Code


Devin Review

@leedqin
leedqin requested a review from a team as a code owner October 19, 2026 17:54
@leedqin
leedqin requested a review from Alphadelta14 October 19, 2026 17:54
@leedqin
leedqin added this pull request to stack #39509 October 19, 2026 17:54
@def-

def- commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

QA LLM Review

1. MEDIUM -- Edit during a hydration burst silently raises the replication factor

console/src/platform/clusters/ClusterPage/Configuration/ClusterConfigurationDrawer.tsx:126

The drawer's Edit opens AlterClusterModal with the cluster-list Cluster, and that modal fills its "Replicas" field from cluster.replicas.length (AlterClusterModal.tsx:82). It always submits REPLICATION FACTOR = <field>. While a burst replica is up, that count is the replication factor plus one, so saving any edit, even a rename, permanently adds a steady replica. The overview next to the button shows the real factor, which this PR fetches but does not pass to the modal.

Details

Scenario: a managed 100cc cluster with REPLICATION FACTOR 1 and ON HYDRATION (HYDRATION SIZE = '800cc') gets a new MV, and the controller adds the 800cc burst replica. The cluster list now has two replicas. The user opens the drawer, which shows "Replication factor 1" and the burst note, clicks Edit, renames the cluster, and saves. alterClusterSettingsStatement runs ALTER CLUSTER ... SET (SIZE = '100cc', REPLICATION FACTOR = 2). sequence_alter_cluster_managed_to_managed (src/adapter/src/coord/sequencer/inner/cluster.rs) treats replication_factor as the committed steady baseline, separate from the burst. The cluster keeps two 100cc replicas after the burst retires, which doubles its steady credit rate. A graceful resize in progress has the same effect, because its target replicas are also listed in cluster.replicas.

The bug is in the modal's default and also affects the classic Alter menu item. This PR adds a new entry point for it, makes it the only edit path on the redesigned page, and places it in the panel built to surface bursts. The Replicas panel copy at ClusterConfigurationDrawer.tsx:390 uses the same count and says "runs 2 replicas of its size" during a burst.

Fix: pass configuration.details.replicationFactor into AlterClusterModal as the default for the Replicas field, and keep Edit disabled until it loads. The panel copy should use the same value.

@leedqin
leedqin force-pushed the cluster-page-configuration branch from 035c674 to 25dfeec Compare October 19, 2026 18:20
@linear-code

linear-code Bot commented Oct 19, 2026

Copy link
Copy Markdown

CNS-204

@leedqin
leedqin requested review from jdonelson and removed request for Alphadelta14 October 19, 2026 19:26
@leedqin
leedqin force-pushed the cluster-page-configuration branch from 25dfeec to cb64252 Compare October 19, 2026 22:03
@leedqin
leedqin force-pushed the cluster-page-configuration branch 2 times, most recently from 43f9e6a to d1ae6a3 Compare October 20, 2026 18:32
@leedqin

leedqin commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Edit during a hydration burst raises the replication factor is addressed in d1ae6a3. AlterClusterModal takes an optional replicationFactor, and the drawer passes the one it reads from mz_clusters, so the Replicas field starts at the real factor and a rename during a burst keeps it. Edit stays disabled until the factor loads. The Replicas panel's copy counts from the factor too. The new drawer test opens Edit on a cluster with a burst replica and a factor of 1, and the field reads 1. The classic page's Alter cluster item still defaults to the replica count, since the cluster list doesn't carry the factor; that's left for a follow-up.

@leedqin
leedqin force-pushed the cluster-page-configuration branch from d1ae6a3 to 1bcb91e Compare October 20, 2026 19:39
@leedqin

leedqin commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on the replication factor, in 1bcb91e: the factor now comes with each cluster from the app-wide clusters subscribe, and AlterClusterModal starts its Replicas field from cluster.replicationFactor. That fixes the classic page's Alter cluster item too, and the drawer no longer reads the factor separately. AlterClusterModal.test.tsx covers a cluster with a burst replica and a factor of 1.

@leedqin
leedqin force-pushed the cluster-page-configuration branch from 71c4b3b to bf9719b Compare October 23, 2026 21:33
@leedqin
leedqin force-pushed the cluster-page-configuration branch from bf9719b to 3215409 Compare October 26, 2026 16:06

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

Devin Review

Comment on lines +76 to +80
const Detail = ({
label,
tooltip,
children,
}: React.PropsWithChildren<{ label: string; tooltip?: string }>) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Drawer components lack named props interfaces

New drawer components such as Detail declare props inline instead of using the required exported component-specific interfaces. The header and other panels repeat this convention violation.

Learn more

The Console component convention requires an exported, component-specific props interface above each component. The drawer uses inline prop declarations in Detail, PanelHeader, EditClusterButton, DropClusterCard, OverviewPanel, ReplicaStatusCell, ReplicasPanel, AutoScalingPanel, and ConfigurationContent; the new ClusterTitle and ConfigurationDrawer do the same. This makes the new component contracts inconsistent with the explicit repository convention.

Example: Detail currently places { label: string; tooltip?: string } inside React.PropsWithChildren. Its props have no exported DetailProps interface for a caller to import.

Recommended fix: Define and export a named props interface above each new component and use it in the component signature. Keep each existing prop's optionality and child type unchanged.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3274d31. Each drawer component, plus ClusterTitle and ConfigurationDrawer, takes a named props interface. The ones for file-private components stay unexported, as in the Workers tab's WorkerBars.tsx.

Comment on lines +281 to +283
const replicaColumnHelper = createColumnHelper<ReplicaRow>();

const replicaColumns = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Replica table constants break naming convention

The new replicaColumnHelper and replicaColumns are module-level constants named in camelCase. Console rules require SCREAMING_CAPS for module-level constants.

Learn more

Console's module-level constant naming convention requires SCREAMING_CAPS. Both the column helper and column array are initialized once at module scope, and the drawer uses the array in ReplicasPanel. Renaming only the declarations leaves references inconsistent.

Example: replicaColumns is a fixed array constructed at module initialization, but its name follows the naming pattern for a local variable.

Recommended fix: Rename both constants to uppercase names and update their uses throughout the module.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving these as they are. Module-level column helpers and column arrays are camelCase across the Console, columnHelper in nine tables and columns in ClusterTable.tsx and ClusterUsageTable.tsx, so renaming only these would make them the odd ones out.

id: Generated<string>;
}

export interface MzClusterAutoScalingStrategies {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Manual catalog type lacks rollout annotation

The new MzClusterAutoScalingStrategies declaration is hand-added for version compatibility without the required cleanup TODO. Future type generation can overwrite it unnoticed.

Learn more

Console's backwards-compatibility instructions require a TODO annotation when materialize.d.ts is manually modified so subsequent code generation does not silently remove rollout compatibility work. The new relation type and database entry are hand-added while the runtime query is gated by AUTO_SCALING_MIN_VERSION.

Example: A later regeneration from a catalog snapshot without the relation could erase this interface without a nearby note explaining why it was added.

Recommended fix: Add a TODO next to the manually added declaration explaining when it can be replaced by generated types, and preserve the version gate until supported environments contain the relation.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 3274d31, in the form the file already uses for hand-added relations. The query stays gated on AUTO_SCALING_MIN_VERSION.

Comment on lines +117 to +121
` ON HYDRATION (HYDRATION SIZE = '${hydrationSize}'${
lingerMs === undefined
? ""
: `, LINGER DURATION = ${lingerLiteral(lingerMs)}`
})`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Copied autoscaling SQL leaves size unescaped

When a catalog size contains a quote, setAutoScalingStatement emits it inside an unescaped SQL literal. Pasting that statement can execute unintended SQL.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3274d31: the size goes through escapedLiteral, and clusterConfigurationModel.test.ts covers a size with a quote. #40043 replaces these copyable statements with the Alter cluster form, which escapes it the same way.

@leedqin
leedqin force-pushed the cluster-page-configuration branch from 3215409 to 76965d2 Compare October 26, 2026 17:52
@leedqin
leedqin force-pushed the cluster-page-configuration branch 2 times, most recently from 4f25b2b to af35c37 Compare October 28, 2026 20:42
@leedqin
leedqin force-pushed the cluster-page-configuration branch 4 times, most recently from bc57f6d to 0d22ae4 Compare October 29, 2026 18:07
@def-

def- commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

QA LLM Review

1. MEDIUM -- Gear does nothing on the Objects tab's Sources and Sinks lists

console/src/platform/clusters/ClusterPage/ClusterPage.tsx:93

The drawer's open state is the configure search parameter. On objects/sources and objects/sinks, useConnectorListSortOptions overwrites the whole query string with ?sortField=…&sortOrder=… whenever it differs, so configure is stripped as soon as the gear sets it and the drawer never opens. This PR also hides the breadcrumb's Alter/Drop menu, so on those two tabs there is no way left to edit or drop the cluster.

Details

Sources.tsx:78 and Sinks.tsx:69 call useConnectorListSortOptions() unconditionally, even for an empty list. That hook uses useSyncObjectToSearchParams (hooks/useSyncObjectToSearchParams.ts:91), which builds a fresh URLSearchParams holding only its own keys. Its effect runs on every searchParams change and calls setSearchParams(newSearchParams, { replace: true }) when the strings differ. The gear's functional update produces ?sortField=status&sortOrder=asc&configure=overview. The effect then replaces it with ?sortField=status&sortOrder=asc, and ConfigurationDrawer sees isSection(null), so the drawer stays closed.

Reproduced at this head with a variant of ClusterDetail.test.tsx whose Sources mock calls the real useConnectorListSortOptions. On /u1/default, clicking "Configure cluster" opens the dialog. On /u1/default/objects/sources, the URL reads ?sortField=status&sortOrder=asc both before and after the click, and no "Configure default" dialog renders. The existing gear test only covers the Metrics tab, where nothing rewrites the query string.

Possible fixes: have useSyncObjectToSearchParams keep keys it doesn't own (merge through a functional setSearchParams), or keep the drawer's state somewhere the object lists don't rewrite.

Base automatically changed from cluster-page-worker-skew to main October 29, 2026 19:00
…page

Adds a gear button next to the redesigned cluster page's title. It opens a
configuration drawer in the connect drawer's format, with cards for the
cluster's overview, its replicas, and its autoscaling policy, and replaces
the Replicas tab. Editing reuses the existing alter cluster, new replica, and
drop flows.
@leedqin
leedqin force-pushed the cluster-page-configuration branch from 0d22ae4 to 6e0a7dd Compare October 29, 2026 19:00
…y, URL params kept, props interfaces

The Alter cluster modal sends SIZE and REPLICATION FACTOR only when they
differ from what it opened with, and skips the SET when neither does.
While a resize runs, mz_clusters reports the realized size and factor, and
setting both back to them cancels the resize, so saving a rename used to
cancel it.

useSyncObjectToSearchParams keeps search params it didn't write. It
rebuilt the whole query string, so on the Objects tab's Sources and Sinks
lists it stripped the drawer's `configure` param and the gear did nothing.
It still removes a key it wrote once the object drops it.

The drawer's and page's components take named props interfaces, the
copied autoscaling statement escapes the size, and the hand-added
mz_cluster_auto_scaling_strategies type carries the usual TODO.
@leedqin

leedqin commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Edit during a background resize cancels the resize is fixed in 3274d31. The Alter cluster modal now sends SIZE and REPLICATION FACTOR only when they differ from the values it opened with, and leaves out the SET when neither changed, so a rename during a resize leaves its target alone. That covers the classic page's Alter cluster item too, and the modal's comment no longer reads as if resending the factor were safe mid-resize. AlterClusterModal.test.tsx checks that a rename sends only the RENAME and that a replication factor change sends no SIZE. Showing the in-flight target size from mz_cluster_reconfigurations is left for a follow-up.

@leedqin

leedqin commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Gear does nothing on the Objects tab's Sources and Sinks lists is fixed in 3274d31. useSyncObjectToSearchParams now keeps search parameters it didn't write, so a list's sort and the drawer's configure share the query string, and it still removes a key it wrote once its object drops it, such as a cleared filter. ClusterDetail.test.tsx opens the drawer on objects/sources with a Sources mock that runs the real useConnectorListSortOptions, and the hook's own test covers both halves.

…rview

While a resize runs, mz_clusters, and so the overview, keeps the cluster's
current size and replication factor. The drawer's request now also reads
the in-progress reconfiguration's target from mz_cluster_reconfigurations,
behind the same v26.34 gate as autoscaling, and the overview names the
target size and replication factor in an info alert.
@leedqin

leedqin commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up on the background resize finding: 846bf30 adds the optional part rather than leaving it for later. The drawer's request now reads the in-progress target from mz_cluster_reconfigurations, behind the same v26.34 gate as autoscaling, and the overview shows an info alert naming the target size, and the replication factor when it changes too, while the details keep the current values. ClusterConfigurationDrawer.test.tsx covers the alert, and clusterConfigurationModel.test.ts the wording for each case.

@leedqin
leedqin merged commit 9a7c24f into main Nov 2, 2026
84 checks passed
@leedqin
leedqin deleted the cluster-page-configuration branch November 2, 2026 19:29
leedqin added a commit that referenced this pull request Nov 3, 2026
…rm (#40043)

### Motivation

Part of the cluster details redesign stack, on top of #39992. The
configuration drawer shows a cluster's autoscaling policy, but changing
it meant copying an `ALTER CLUSTER` statement into the SQL shell. Review
feedback asked for a form. This adds the policy to the Alter cluster
form.

### Description

**The form.** Alter cluster gets an optional **Auto-Scaling Policy**
section for managed clusters on environments with autoscaling (v26.34.0
and later): an Enable checkbox, a burst size, and a linger duration such
as `15s`, `2m`, `1m 30s` or `1500ms`, empty for the environment's
`default_hydration_burst_linger`. The section is a labelled group, and
the checkbox is described by its explanation. An **Equivalent Code**
panel shows the statements a submit will run.

- The form opens on a policy it reads after opening, so a policy set or
removed moments earlier is never shown stale, from the Clusters list or
the configuration drawer. It diffs against the policy it was built from,
so a refetch landing while it's open doesn't turn an unchanged checkbox
into a change. If the policy can't be read, the section is left out
rather than shown as off.
- It names only what changes. Enabling a policy or renaming during a
graceful resize never retargets or cancels it: any SET naming `SIZE` or
`REPLICATION FACTOR` while a reconfiguration is in flight goes through
`reshape_alter_cluster_managed`, where restoring the realized values
cancels it (`cluster.rs` ~325-335). With nothing to change, the preview
says so and a submit closes the form without a request.
- The burst size defaults to the next larger size in the cluster's size
family, and to nothing at a family boundary. During an in-progress
resize it defaults to the size after the resize's target, and the target
itself is rejected while the size is unchanged, as the server does
(`HYDRATION SIZE must differ from the target SIZE ... of the in-progress
cluster resize`, `cluster.rs` ~1503). A submit that also changes Size
retargets the resize, and that SET takes the reshape path, which accepts
the old target as the burst size.
- While a resize is in flight, Size reads "Resizing to <size>." under
the field.
- From v26.35.0 a size change shows that the cluster keeps serving from
its current replicas until the new ones catch up; earlier versions keep
the downtime warning.

The modal is shared, so the classic page's Alter cluster item gets the
section and these fixes too. It used to send the realized `SIZE` and
`REPLICATION FACTOR` on every submit, which cancels an in-flight resize.
The flag behind the SQL, `enable_auto_scaling_strategy`, defaults on and
has no LaunchDarkly override.

**The SQL.** Each statement commits on its own, in order:

- **Settings:** one `ALTER CLUSTER ... SET (...)` naming only the
changed `SIZE`, `REPLICATION FACTOR` and `AUTO SCALING STRATEGY`. A
policy is `AUTO SCALING STRATEGY = (ON HYDRATION (HYDRATION SIZE =
'...'[, LINGER DURATION = '...']))`.
- **Removing a policy with other changes** joins the same SET as `AUTO
SCALING STRATEGY = ()`, so a SET the server rejects (a size that isn't
allowed, a replica or credit limit) leaves the policy in place. The
planner checks the new size against the effective config, so resizing to
the old burst size in the same statement passes. A removal on its own is
`ALTER CLUSTER ... RESET (AUTO SCALING STRATEGY)`, which also works with
the feature flag off. Neither cancels an in-flight resize.
- **Rename:** `ALTER CLUSTER ... RENAME TO ...` only for a new name; a
same-name rename writes an audit event.
- **Reads:** the form reads
`mz_internal.mz_cluster_auto_scaling_strategies` and the in-progress
target from `mz_internal.mz_cluster_reconfigurations` (`status =
'in-progress'`, `target->>'size'`) in one request. Both are fast-path
index lookups on `mz_catalog_server`, and both shipped in v26.34.0. The
request reads at strict serializable: the form makes it right after an
ALTER, and a serializable read can come back from before the write.

**Self-managed.** Self-managed has no LaunchDarkly, so a flagged UI
preview ships there outright. A preview can now set `selfManagedOptIn`,
which offers the same per-browser opt-in there: the profile menu's UI
previews and the page pill. The cluster page redesign sets it, and
`cluster-details-page-cns-173` leaves `disabledFlexibleDeploymentFlags`,
so self-managed users get the classic page until they opt in. The
cluster list preview still ships outright.

**The drawer.** The Auto-Scaling panel's copyable SET and RESET
statements become an **Edit** action that opens the form at the
auto-scaling section. The form does its own post-mount read, since it
also needs the resize target, so Edit waits only for the cluster sizes.

### Verification

- `alterCluster.test.sql.ts`, against a real Materialize:
  - setting, replacing and keeping a policy;
- a rejected removal-plus-resize leaves the policy in place, and
removing a policy while resizing to its burst size succeeds in one
statement;
- during an in-flight resize, the target is read, a burst equal to it is
rejected, and setting a policy, removing it and renaming all leave the
resize running until it finalizes at the new size;
- one SET retargets an in-flight resize and sets its old target as the
burst size;
  - settled or absent resizes read no target.
- `clusterConfiguration.test.sql.ts`: policies with and without a
linger, parsed as the drawer parses them.
- `alterCluster.test.ts`: statements for size-only, replicas-only,
strategy-only, removal with and without other changes, rename-only and
nothing changed.
- `autoScalingForm.test.ts`: linger parsing and display, size families,
the default burst size with and without an in-progress resize, and when
a submit sets, removes or keeps the policy.
- `AlterClusterModal.test.tsx`:
- the reopen sequence that used to revert a just-set or just-removed
policy now shows it and sends only the change;
  - props changing under an open form;
- no request when nothing changed, and the preview without `NaN` while
Replicas is empty;
  - the in-progress resize's target under Size;
- errors for an invalid linger, a burst equal to the cluster's size, and
one equal to an in-progress resize's target;
- the labelled group, the version-gated resize copy, no section before
v26.34.0, for unmanaged clusters, or when the policy can't be read.
- `useUiPreview.test.tsx`: the cluster page redesign is offered in
self-managed, off until the user opts in.
- `ClusterConfigurationDrawer.test.tsx`: the Auto-Scaling panel's Edit
action, and Edit waiting only for the sizes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- devin-review-badge-begin -->

---

<a
href="https://app.devin.ai/review/materializeinc/materialize/pull/40043"
target="_blank"><picture><source media="(prefers-color-scheme: dark)"
srcset="https://static.devin.ai/assets/gh-devin-review-dark.svg?v=4"><img
src="https://static.devin.ai/assets/gh-devin-review-light.svg?v=4"
alt="Devin Review"></picture></a>
<!-- devin-review-badge-end -->
leedqin added a commit to leedqin/materialize that referenced this pull request Nov 6, 2026
MaterializeInc#39992 typed mz_cluster_auto_scaling_strategies and
mz_cluster_reconfigurations by hand while gen:types was broken. The
generator now emits both, and AUTO_SCALING_MIN_VERSION gates them at
runtime, so the file is exactly the generator output.
leedqin added a commit that referenced this pull request Nov 6, 2026
### Motivation

`yarn gen:types` fails with `function "pg_get_serial_sequence" does not
exist`. kysely-codegen runs kysely's Postgres introspector for its table
query. Since the bump to kysely 0.28 (#35669), that query calls
`pg_get_serial_sequence`, which Materialize lacks. kysely 0.29 still
does. The query also lists only relkinds `r`, `v` and `p`, so the
catalog's builtin materialized views (`m`) never reached the types under
kysely-codegen 0.17. #40044's types were generated with both patched by
hand in `node_modules`.

This also bumps kysely to 0.29 and kysely-codegen to 0.20, superseding
#39686.

### Description

- `bin/kysely-codegen.ts` runs kysely-codegen programmatically with an
introspector that overrides only the table query. kysely-codegen's
materialized view query, enum handling and serializer still apply.
- The script writes the copyright header to `types/materialize.d.ts`.
The codegen output lacks it, and the copyright lint fails without it.
- `bin/generate-types` exits on errors, pulls the requested image (a
cached `latest` went stale), and stops the container on exit. Before,
each run left a container bound to port 6875, and the next run generated
types from that old container.
- kysely 0.28.17 → 0.29.8 and kysely-codegen 0.17 → 0.20.0:
- 0.29 rejects `limit(sql.raw(...))`. `rawLimit` and the lag history
limit now call `query.limit(sql.lit(n))`, which still inlines the limit
as a literal, as Materialize requires.
- `rawLimit` used to append its limit with `modifyEnd`. In 0.29,
compiling end modifiers calls `Array.prototype.toSorted`, which Chrome
109 lacks. No console query uses end modifiers now. Neither `rawLimit`
caller has an OFFSET or another end modifier, so the compiled SQL is
unchanged.
- kysely 0.29 is ESM-only and kysely-codegen 0.20 `require`s it, so
`yarn gen:types` needs Node 22.12 or later. CI and the Volta pin run
Node 26, and #39683 updates `engines.node`.
- `types/materialize.d.ts` is regenerated against v26.49.1.
kysely-codegen 0.17 and 0.20 produce identical output.
  - Adds `mz_builtin_columns` and `mz_builtin_index_columns`.
- Builtin columns lose their `Generated<>` wrappers, because #40316
changed `pg_attribute.atthasdef` to report no default for them. The
console only selects from these relations, where `Generated<T>` and `T`
read the same.
- Drops the two hand-added `TODO` markers from #39992. The generator now
emits `mz_cluster_auto_scaling_strategies` and
`mz_cluster_reconfigurations`, and `AUTO_SCALING_MIN_VERSION` still
gates them at runtime. The file is now exactly the generator output.

Worth checking on the preview, since these run the changed limits:
- the Query History list;
- a sink's Errors tab;
- the freshness lag charts on the environment overview and the cluster
pages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

3 participants