Skip to content

benchmarks: price oversubscription and publication at scale - #38678

Open
antiguru wants to merge 1 commit into
mh/interactive-09-benchmarksfrom
mh/interactive-10-benchmarks-load
Open

benchmarks: price oversubscription and publication at scale#38678
antiguru wants to merge 1 commit into
mh/interactive-09-benchmarksfrom
mh/interactive-10-benchmarks-load

Conversation

@antiguru

@antiguru antiguru commented Sep 5, 2026

Copy link
Copy Markdown
Member

Stacks on #38676. Tracked by CPU-216.

The first nightly with the interactive runtime on showed reads isolated from hydration (point lookups 37x better at p99 under churn, introspection reads 100 ms instead of minutes) and nothing else moving. It left three questions the benchmarks did not ask.

What a saturated interactive runtime does to maintenance. Two runtimes are twice the worker threads on the same cores. MaintenanceUnderPeekSaturation loads an eight-worker replica, half the cores of the nightly agents, with one join peek in flight per worker and two hydration churn loops, while a writer advances a small materialized view. The measured query reads that view strict serializable from a separate one-worker cluster, so it cannot answer before the view's write frontier passes the write and nothing else competes with it: its latency is the view's maintenance lag under the peek load. Read on the loaded replica it would queue behind the joins on the runtime that serves peeks instead, which is what the join loops' own latency reports.

What publishing costs at scale with nobody reading. ManyIndexesIdle and ManyIndexesRestart publish two hundred indexed views over one table, each a distinct arrangement. The first measures one lookup and, through the clusterd memory column, the resident cost of two hundred publications. The second restarts the replica and waits for a read that imports every index, so every publication is rendered and bound again on the way up. ManyReexportsIdle is the other shape: two hundred indexes on one relation and key share one arrangement and its publication point. Before #38389 aliased re-exports it measured 125 to 185 KB per re-export, the operators of a re-import.

Whether PeekIsolationUnderExpensivePeeks guards anything. Its two sides came out identical: at 100k rows and 2/s the walks used under 10% of the worker and the lookups never noticed them. It now walks 500k rows at 3/s, about 60%. Its docstring says what it guards: both queries are peeks, so it measures how the serving runtime interleaves them, not the split between peeks and maintenance, which is why two runtimes cannot move it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VDm7opomJLxbNUEP3r9BLk

@antiguru
antiguru force-pushed the mh/interactive-10-benchmarks-load branch 3 times, most recently from 638270b to 8f1f647 Compare September 5, 2026 16:16
@antiguru
antiguru force-pushed the mh/interactive-09-benchmarks branch from 7d7df7c to 9d7eb75 Compare September 5, 2026 16:59
@antiguru
antiguru requested review from a team as code owners September 5, 2026 16:59
@antiguru
antiguru force-pushed the mh/interactive-10-benchmarks-load branch from 8f1f647 to b137fa1 Compare September 5, 2026 16:59
@antiguru
antiguru force-pushed the mh/interactive-09-benchmarks branch from 9d7eb75 to 3bdd8ab Compare September 5, 2026 18:50
@antiguru
antiguru force-pushed the mh/interactive-10-benchmarks-load branch from b137fa1 to bfcf64b Compare September 5, 2026 18:50
@antiguru
antiguru force-pushed the mh/interactive-09-benchmarks branch from 3bdd8ab to 8394f09 Compare September 5, 2026 19:00
@antiguru
antiguru force-pushed the mh/interactive-10-benchmarks-load branch from bfcf64b to 844936b Compare September 5, 2026 19:00
@antiguru
antiguru force-pushed the mh/interactive-09-benchmarks branch from 8394f09 to 9f994e3 Compare September 7, 2026 09:36
@antiguru
antiguru force-pushed the mh/interactive-10-benchmarks-load branch 2 times, most recently from 640573a to 94ecbc8 Compare September 7, 2026 11:22
@antiguru
antiguru force-pushed the mh/interactive-09-benchmarks branch from 18ac9dc to 789172c Compare September 7, 2026 11:39
@antiguru
antiguru force-pushed the mh/interactive-10-benchmarks-load branch from 94ecbc8 to 4ada910 Compare September 7, 2026 11:39
@antiguru
antiguru force-pushed the mh/interactive-09-benchmarks branch from 789172c to a444110 Compare September 7, 2026 17:10
@antiguru
antiguru force-pushed the mh/interactive-10-benchmarks-load branch from 4ada910 to b0e1cf3 Compare September 7, 2026 17:10
@antiguru
antiguru force-pushed the mh/interactive-09-benchmarks branch from a444110 to 7ae0458 Compare September 7, 2026 18:19
@antiguru
antiguru force-pushed the mh/interactive-10-benchmarks-load branch from b0e1cf3 to 69f4b04 Compare September 7, 2026 18:19
@def-

def- commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

QA LLM Review

1. MEDIUM -- ManyReexportsIdle samples clusterd memory without waiting for the 200 re-exports to hydrate

misc/python/materialize/feature_benchmark/scenarios/interactive_runtime.py:202

The scenario's stated purpose is that "the clusterd memory measurement is the resident cost of INDEXES publications of the same arrangement", but nothing in init() waits for those 200 index dataflows to become resident, so the reported figure can be a snapshot taken while they are still building. Its sibling ManyIndexesIdle closes exactly this gap and this one does not, which makes the number understate precisely on the builds where re-exports are expensive.

Details

CREATE INDEX returns once the catalog transaction commits; hydration proceeds in the background. The only gate at the end of init() is SELECT count(*) FROM t (line 202). import_into_dataflow imports every index on t, but prune_and_annotate_dataflow_index_imports then drops the ones the optimized plan does not need, so that read binds a single index and waits for a single frontier. The measured SELECT f1 FROM t WHERE f1 = 1 in benchmark() is a fast-path peek on one index and does not gate either. Contrast ManyIndexes.init(), which ends on SELECT c FROM all_v: that read imports all 200 indexes, so under strict serializable it cannot answer until every one has hydrated.

What that costs: Benchmark.run() terminates after at least 6 iterations, each roughly a testdrive docker exec plus two docker stats calls, and _memory_clusterd_filter = FilterFirst(3) discards the first three samples, so the first recorded clusterd sample lands ~10s after init(). MinAggregation then reports the minimum over the recorded samples, i.e. deliberately the earliest, least-hydrated one. When re-exports share the arrangement the 200 dataflows hydrate ~instantly and this is harmless. When they do not — 200 re-imports each arranging a 100k-row collection on the one-worker cluster_default, tens of millions of record insertions on a single worker — hydration outlasts that window, and the min sample is taken mid-build. That is the regression the scenario exists to price, and it is the case in which the number is attenuated.

A gate in the same shape as the sibling's, appended to init():

> SELECT bool_and(hydrated) FROM mz_internal.mz_hydration_statuses
  WHERE object_id IN (SELECT id FROM mz_catalog.mz_indexes WHERE on_id = (SELECT id FROM mz_catalog.mz_tables WHERE name = 't'))
true

mz_internal.mz_hydration_statuses covers indexes via mz_catalog.mz_indexes LEFT JOIN mz_internal.mz_compute_hydration_statuses, with COALESCE(hydrated, false), so a not-yet-reporting index reads as false rather than dropping out of the aggregate. Worth confirming an aliased re-export does report hydrated there before relying on it; if it does not, the same effect can be had by making the last statement a read that the optimizer cannot satisfy from a single index.

The first nightly with the interactive runtime showed reads isolated from
hydration and nothing else moving, and left three questions the benchmarks did
not ask. What a saturated interactive runtime does to maintenance, since two
runtimes are twice the worker threads on the same cores. What publishing every
index costs when there are hundreds of them and nobody reads. And whether
`PeekIsolationUnderExpensivePeeks` guards anything, since its two sides came
out identical.

`MaintenanceUnderPeekSaturation` loads an eight-worker replica, half the cores
of the nightly agents, with one join peek in flight per worker and two
hydration churn loops, while a writer advances a small materialized view. The
measured query reads that view strict serializable from a separate one-worker
cluster, so it cannot answer before the view's write frontier passes the write
and nothing else competes with it: its latency is the view's maintenance lag
under the peek load. Read on the loaded replica it would instead queue behind
the joins on the runtime that serves peeks, which is what the join loops' own
latency reports.

`ManyIndexesIdle` and `ManyIndexesRestart` publish two hundred indexed views
over one table, each a distinct arrangement. The first measures one lookup and,
through the clusterd memory column, the resident cost of two hundred
publications. The second restarts the replica and waits for a read that imports
every index, so every publication is rendered and bound again on the way up.
`ManyReexportsIdle` is the other shape: two hundred indexes on one relation and
key share one arrangement and re-export it, each under its own publication.

`PeekIsolationUnderExpensivePeeks` walks five times as many rows half again as
often, about 60% of the worker where the old sizing measured under 10%, at which
the lookups did not notice the walks. Its docstring now says what it guards:
both queries are peeks, so it measures how the serving runtime interleaves
them, not the split between peeks and maintenance.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDm7opomJLxbNUEP3r9BLk
@antiguru
antiguru force-pushed the mh/interactive-09-benchmarks branch from 7ae0458 to 69afac9 Compare September 7, 2026 18:43
@antiguru
antiguru force-pushed the mh/interactive-10-benchmarks-load branch from 69f4b04 to 9afbb53 Compare September 7, 2026 18:43
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