Skip to content

adapter: Restrict clusters by workload class - #39997

Draft
jubrad wants to merge 4 commits into
MaterializeInc:mainfrom
jubrad:source-only-cluster-sizes
Draft

jubrad wants to merge 4 commits into
MaterializeInc:mainfrom
jubrad:source-only-cluster-sizes

Conversation

@jubrad

@jubrad jubrad commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Motivation

We want cluster SKUs that can only run some kinds of work, for example cheap ingest-only sizes for sources. Without enforcement, a cheap size becomes a compute arbitrage vector. This PR lets replica sizes restrict the workload classes their clusters run, and enforces it in the adapter.

Workload classes

  • ingest: sources, subsources, tables from sources, webhook sources, COPY FROM.
  • sink: sinks.
  • compute_maintained: indexes, materialized views, metric sinks, SUBSCRIBE.
  • compute_interactive: SELECT, COPY TO, INSERT, UPDATE, DELETE.
  • compute_introspection: SELECT and SUBSCRIBE whose dependencies are all system objects, at least one of them per-replica introspection data. Only the target cluster's replicas can answer these, so restricted sizes can still allow debugging. This shares its dependency analysis with auto-routing to mz_catalog_server.

A replica size can list the classes it allows in allowed_workloads in the size configuration. The cluster_family_allowed_workloads system parameter, a JSON object such as {"legacy": ["ingest", "compute_introspection"]}, restricts every size of a family. Sizes and families without an entry allow every class. A malformed parameter value is logged as an error and ignored.

Invariant

Every object on a cluster must have a class allowed by every size the cluster runs or is about to run: its managed size, the target size of an in-flight reconfiguration, its hydration burst size, and each replica's size. Clusters carry no workload property of their own. The invariant is checked:

  • when an object is created, in catalog_transact's CreateItem op. Subsources and tables from sources are checked against their ingestion's cluster.
  • when a statement runs, at the existing check_cluster_restrictions call sites (coordinator, frontend peek, frontend read-then-write). Statements are classified by an exhaustive match over Plan with no catch-all, so a new plan kind fails to compile until it is classified.
  • when replicas are added: CREATE CLUSTER REPLICA, and ALTER CLUSTER changing size, replication factor, managed-ness, or autoscaling strategy.

Objects that predate a restriction keep running, but replica changes onto sizes that do not allow them are rejected. Resizing to an unrestricted size is always allowed, which is the migration path. Internal users and system clusters are exempt.

NOTE: Every INSERT counts as compute_interactive, because whether it reads on the cluster is only known after optimization. On a restricted cluster, users switch clusters to write.

mz_cluster_replica_sizes gains an allowed_workloads text list column with the effective classes from both mechanisms. It is repacked from the implications phase when cluster_family_allowed_workloads changes. Adding the column needs a Replacement builtin schema migration step, which loses nothing because the table is repacked from the size configuration at every boot.

Tests

  • New test/testdrive/cluster-workload-restrictions.td, using new ingest-only and no-ingest sizes in the mzcompose size map. It covers each class being rejected and allowed, introspection queries and subscribes on a restricted cluster, data flowing from an ingest-only cluster, the internal-user exemption, the replica, ALTER CLUSTER, and hydration-size checks, resizing away from a restricted size, the family parameter, and the allowed_workloads column tracking it.
  • ReplicaAllocation deserialization test for allowed_workloads.
  • Updated expected output for the new column in autogenerated/mz_catalog.slt, mz_catalog_server_index_accounting.slt, and system-cluster.slt.
  • cluster_family_allowed_workloads is pinned to {} in mzcompose, because the sqllogictest and TestHarness sizes are all in the legacy family. Parallel workload varies it between {} and {"legacy": ["ingest", "compute_introspection"]}, since it only uses cc sizes.

Release notes

This release will add an allowed_workloads column to mz_cluster_replica_sizes. Clusters whose replica sizes restrict workload classes reject objects and statements of other classes.

🤖 Generated with Claude Code

jubrad and others added 2 commits October 19, 2026 13:26
A replica size can now be source-only, either by setting `source_only` in
the size configuration or by listing its family in the new
`enforce_source_only_cluster_families` system parameter. A user cluster
that is configured with a source-only size, or has any replica of one,
can host sources but cannot run queries or host any other object.

Enforcement points:
* Item creation, in `catalog_transact`'s `CreateItem` op, next to the
  existing system cluster check.
* Queries, at the existing `check_cluster_restrictions` call sites in the
  coordinator, frontend peek, and frontend read-then-write paths.
* `ALTER CLUSTER ... SET (SIZE ...)` and `CREATE CLUSTER REPLICA`, which
  may not give a source-only size to a cluster already hosting objects
  other than sources.

Internal users and system clusters are exempt. A malformed
`enforce_source_only_cluster_families` is logged as an error and
ignored.

`mz_cluster_replica_sizes` gains a `source_only` column reflecting both
mechanisms, which is repacked when the families parameter changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Generalize source-only cluster sizes into workload classes: `ingest`,
`sink`, `compute_maintained`, and `compute_interactive`. A replica size
lists the classes it allows in `allowed_workloads`, and the
`cluster_family_allowed_workloads` system parameter maps a size family to
its allowed classes. Sizes and families without an entry allow every
class.

Every object on a cluster must have a class allowed by every size the
cluster runs or is about to run: its managed size, the target of an
in-flight reconfiguration, its hydration burst size, and each replica's
size. This is checked when objects are created (subsources and tables
from sources against their ingestion's cluster), when replicas are
created or a cluster is altered to a new size, replication factor, or
autoscaling strategy, and when statements run. Statement classification
is an exhaustive match over `Plan`, so a new plan kind must be classified
before it can run on a restricted cluster. Objects that predate a
restriction keep running, but replica changes onto sizes that do not
allow them are rejected.

`mz_cluster_replica_sizes.source_only` becomes `allowed_workloads`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jubrad jubrad changed the title adapter: Add source-only cluster sizes adapter: Restrict clusters by workload class Oct 22, 2026
jubrad and others added 2 commits October 22, 2026 15:02
Adding the `allowed_workloads` column changes the builtin table's
fingerprint, so a 0dt upgrade panics at catalog open without a migration
step. The table is repacked from the size configuration at every boot, so
a replacement step loses nothing.

Also rewrite `system-cluster.slt`, whose plan over the table shifts by the
new column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Queries over per-replica introspection relations can only run on the
cluster whose replicas they inspect, so restricting a cluster's sizes
to `ingest` would otherwise block debugging it. A `SELECT` or
`SUBSCRIBE` whose dependencies are all system objects, at least one of
them per-replica introspection data, is now `compute_introspection`.
Any user object in its dependencies keeps it `compute_interactive` or
`compute_maintained`.

The dependency analysis is shared with auto-routing to
`mz_catalog_server`, which moves system-only queries unless they read
per-replica introspection data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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