Repository navigation
console: add data for the redesigned cluster page's hydration tab - #40044
Conversation
1b6ea8c to
709a135
Compare
QA LLM Review1. MEDIUM -- A restart after a hydration that finished before the window is reported as an unfinished hydration
DetailsThe bounds don't match:
I checked this by running the model on: replica created and online at 2026-10-18 10:00, OOM-killed at 2026-10-20 11:30, back online at 11:31 and rehydrated by 11:35, window starting at 11:00. The output is Two possible fixes: skip a restart whose |
709a135 to
809b437
Compare
809b437 to
c6aeab0
Compare
QA LLM Review1. MEDIUM -- A zero-downtime upgrade's rehydration is classified as a new-objects episode
On Kubernetes, the full rehydration a 0dt upgrade runs on every replica finishes before status history records any boot for it, so Details
I ran the model on that timeline: the gen-N processes boot at 10:00, the episode runs from 10:00:05 to 10:06, and the leader's Boot detection needs a signal that also sees read-only boots. |
c6aeab0 to
2114ccc
Compare
2114ccc to
9c941b8
Compare
QA LLM Review1. MEDIUM -- Every restart of a replica with no indexes or materialized views is reported as an unfinished hydration
The episodes query drops zero-object episodes, but DetailsThe collector does record an episode after each boot of such a replica. Since 26.43, I ran the model on a source-only replica that came online at 2026-10-21 14:01 and was OOM-killed at 2026-10-22 03:00. The output is an unfinished hydration from 14:01 to 03:00 with reason This is separate from the earlier window-boundary finding. The boot here is inside the window, and any One option is to keep zero-object episodes for the unfinished check and filter them only from the hydrations that are displayed. |
9c941b8 to
9a2a02e
Compare
|
Thanks, all three are addressed in 9a2a02e:
Each has a regression test in |
9a2a02e to
31e12c7
Compare
QA LLM Review1. MEDIUM -- On Kubernetes, every new replica reports an unfinished hydration at its creation
DetailsHow the row gets written:
I ran the model on this case: replica created at 10:00:00.000, In #40074, One fix: count an |
31e12c7 to
44073ca
Compare
|
Thanks, fixed in 44073ca. An This push also makes live progress complete at once for a cluster with no replicas, for the finding on #40074. |
44073ca to
5a32288
Compare
QA LLM Review1. MEDIUM -- New objects created together are classified as a full hydration once they outnumber the cluster's existing objects
DetailsAt its start the cluster holds its N existing objects plus the first new one, which is created just before it is installed ( I ran the model on this case: a replica up since 08:00 with one index, then two new objects created at 10:00:00 and 10:00:01 that hydrate together by 10:03. The output is In #40074 that duration feeds "Its last full hydration took …" while a replica rehydrates after a restart, and also the hydrated verdict and the per-size typical value. Both then understate how long a full rehydration takes. The NOTE about dropped objects doesn't cover this case, since nothing was dropped. Comparing against the objects the cluster had when the episode finished ( |
a3d347c to
0021de5
Compare
0021de5 to
5e618dc
Compare
5e618dc to
ad6886e
Compare
QA LLM Review1. MEDIUM -- The replica-pinned live subscribe is resent in a tight loop for viewers without USAGE on the cluster
Pinning Details
One fix is to resend only after an error that a retry can clear, such as the target replica failing, and to leave other errors in place until the session restarts. Backing off between resends would also bound the loop. |
bcf56e9 to
c5e2aac
Compare
QA LLM Review1. MEDIUM -- Merging nearby restarts can erase a failed rehydration
DetailsFor example, a replica created at 09:59 finishes hydrating at 10:04, is OOM-killed at 10:05:00, comes online at 10:05:10, and is OOM-killed again at 10:05:40 before rehydrating. With all events inside the window, the model returns The one-minute proximity check should not merge events across an intervening online transition. Preserving that boundary makes this reproduction report the failed attempt while retaining the existing test that merges multiple processes' reports of one restart. 2. MEDIUM -- An early detail fetch stays cached after object history catches up
DetailsThe backend runs object collection and replica collection as separate mutations, in that order ( The detail query's key contains only the replica and episode timestamps, so later history polling does not refresh it. A hook probe returned an empty first response, made the completed object available, then remounted the same episode: it still returned zero rows after only one request. Allow recently completed episode details to refetch while their object history catches up. |
0f92770 to
f55f514
Compare
First of two PRs adding a Hydration tab to the redesigned cluster page, on top of the configuration drawer. This one adds the data the tab reads. The next one adds the tab. - History, read with react-query since the history tables have no index: a cluster's hydration episodes from mz_replica_hydration_history (26.43 and later, one row per episode with the highest peaks across its processes), the cluster's replicas from mz_cluster_replica_history so dropped ones keep their name and size, an episode's objects from mz_object_hydration_history, and the highest CPU sampled during an episode. - Live state, in SUBSCRIBE-fed TanStack DB collections: whether each user object on each replica is hydrated (a lookup on mz_compute_hydration_times_ind), and each object's queued, working or hydrated state on one replica, from that replica's own introspection (26.39 and later) in a session pinned to it. - A model that counts an episode as a full hydration when it starts within two minutes of its replica booting, finds replicas that went offline before they finished hydrating, such as out-of-memory restarts, and splits an episode's objects into time waiting for a hydration slot and time working. - Collection subscribes keep their session variables across connection attempts. Each attempt sent the defaults instead, so a cluster given to a collection subscribe never reached the server. No existing caller passed one. - types/materialize.d.ts regenerated with yarn gen:types.
f55f514 to
b1f1ab3
Compare
- An end after a replica came back online is a restart of its own, so a failed rehydration shortly after a restart is no longer merged away. - An attempt that began before the window counts once an object was created in the window during it, dated from that object. - An episode's object hydrations are fetched each minute until they reach the episode's object count, since the collector can record an episode a visit before some of its objects. - The cluster's replica history isn't kept once unused, so a set of replicas that comes back, as after a burst replica is dropped, fetches again. - SubscribeManager backs off resending a statement that keeps failing without a snapshot, such as a replica-pinned subscribe the user lacks USAGE for. The first resend stays immediate. The delay reuses the reconnect backoff, now shared as `backoffDelayMs`. - The creation times' placeholder checks overlap with a set.
|
Thanks, these are fixed in 79e79a4:
|
A restart is unfinished when the replica was hydrating an object created during the attempt. The creation times came only from the cluster's current objects, so once the object that caused an out-of-memory restart was dropped, the restart read as finished. The tab now also reads the cluster's indexes and materialized views created over the window and dropped since, from `mz_object_history`. That relation is indexed only by id, so the read scans every object the environment has created: 8.5 ms at 32,000 objects on v26.47, plus about 0.23 ms per 1,000. It runs only while a restart in the window reads as finished with the current objects, and again only when those restarts change. `findRestarts` is split out of `findUnfinishedHydrations` so the hook can tell which restarts read as finished.
|
Follow-up on item 4 above: restarts caused by an object dropped since are now covered in 5deb050. While a restart in the window reads as finished, the tab also reads the cluster's indexes and materialized views created in the window and dropped since, from |
QA LLM Review1. MEDIUM -- Objects dropped before a restart produce false hydration failures
The new dropped-object input treats an object canceled before a restart as still hydrating at that restart. After a user drops an unfinished index or materialized view, a later steady-state OOM can therefore appear as a failed hydration even though the replica had returned to being fully hydrated. DetailsFor example, a replica finishes hydrating at 10:02, starts a new object at 10:10, drops it unfinished at 10:11, and suffers an unrelated OOM at 10:20. The model returns an unfinished hydration from 10:10 to 10:20. Without the newly added dropped-object timestamps, it correctly returns none.
Return the drop time and consider each dropped object's creation only for restarts that occurred while that object still existed. |
Dropping an object that hadn't finished hydrating records no finished hydration, so a later restart read as cutting it short. The dropped-object read now returns when each object was dropped, and an object counts toward a restart only if it still existed then.
|
Thanks, fixed in 66b9083. The dropped-object read now returns when each object was dropped, and an object counts toward a restart only if it still existed then. There's a model test with your timeline. |
DROP INDEX now fails while a newer index or a materialized view on the same table reads from the index (#39098). The test dropped its oldest index first, so it failed on builds with that change. Drop the materialized view first and the indexes newest first, and put the kept index on its own table so it doesn't read from the dropped ones.
### Motivation Tenth PR in the cluster details redesign stack (#39509), on top of #40044, which adds the data. The redesign's prototype has a Hydration report tab. This adds a Hydration tab built on what the database records about hydration, and says which parts the Console infers. Part of [CNS-173](https://linear.app/materializeinc/issue/CNS-173) and [CNS-209](https://linear.app/materializeinc/issue/CNS-209). There's no Figma design; the layout follows the prototype and the PM's feedback on it. ### Description - **Tab:** Hydration, after Objects. Its link carries the `?replica=` choice like the Metrics and Workers tabs, and the classic page redirects a Hydration link to its overview. - **Controls:** the replica select shared with the other tabs, and a time range of 24 hours, 7 days (the default) or 30 days. Restarts come from the replica status history, which is kept 30 days. - **Alert:** only when something needs attention. A replica that keeps restarting with no finished hydration recorded, with when it began (date and time), how many times, and a link to the docs on hydration failures. A replica that's offline, whatever its last progress said, or one that hasn't reported yet on a cluster with objects. A hydrating replica gets no alert, since the progress card shows it. A cluster without indexes or materialized views says it has nothing to hydrate. - **Hydration in Progress:** shown while a replica isn't hydrated, with elapsed times that tick every second, dated from its boot or, when every object it hasn't hydrated is newer than the boot, from the first one's creation. A replica that hasn't reported reads as starting only for two minutes after it boots. How many objects it has hydrated so far, and "Working on `<object>` for `<time>`" for each object it works on, read from the replica itself and mounted only while it hydrates. A replica offline between restarts isn't hydrating: it gets no timer, progress bar, in-progress mark or live subscribe. - **History chart:** a dot per hydration by replica, filled for a full hydration and hollow for an additive one, with restarts that never finished in a lane below. Restarts close together are one mark, counted in its tooltip, so a restart loop stays readable. The y axis is hydration time, ticked at round durations, and the x axis shows dates, or times of day over 24 hours. Replicas of different names are drawn a dot's width apart, at most 48px in all, and replicas of one name, such as the ones a schedule or a resize replaced, share a place, a color and a legend entry. Each mark is a button that selects its hydration. - **Detail:** the last successful hydration by default, or the one selected. Its duration, peak memory, disk and heap, each as a share of its limit with the bytes (heap turns red near the limit), and the highest CPU sample during it. Below, a waterfall of each object's working time, slowest first, or of its time waiting for a slot and working in start order, 30 objects a page. For a restart, why there is nothing to break down. - **Past Hydrations:** every hydration, timed by when it began or, for a restart, when the replica restarted, with its peak memory as a bar like the cluster list's, and its peak disk and heap; each row's time selects it. - **Replica select:** a retired replica, such as a burst replica, stays selectable while its history is in the window. - **Labels:** tooltips say what each figure is. The Console's inferences (full or additive, restarts before finishing) say "Inferred". The collector visits one replica per interval and can miss a hydration that finished just before a restart, so a restart says only that no finished hydration was recorded. Without hydration history (before 26.43) no restarts are inferred. - **Loading:** each section renders as soon as its own data arrives and shows its own error. The history doesn't wait for the live progress, and the alert waits for both, since telling a restart before finishing from a crash after hydrating needs the history. The history's restarts come from the current replicas' status, so it waits for that and fails with it, and keeps its last answer while a resize or burst changes the replica set. Creation times are read for every object, not only the window's, and the elapsed time waits for a new object's. The history waits for the cluster's objects and their creation times, which tell a full hydration from an additive one, and falls back to replica starts alone when the catalog can't be read. A new replica or index keeps the history up while its data loads, and the detail holds its height while another hydration's object timings load. Shared pieces: the stack's `ReplicaSelect`, which can now mark a replica retired, a `HYDRATION_TAB_PATH` in `tabLinks.ts`, `percentOf` exported from `processReadingsModel.ts`, `replicaStatus` from the configuration drawer, each replica's size allocation from `useClusterHydrationData`, `PercentBar` from the cluster list, a `keepOverlapping` option on `useReplicaStatusHistory`, an optional range on `useObjectCreationTimes`, and `PaginationControls`, split out of `TablePagination` so the waterfall pages the same way tables do. This is larger than the stack's other PRs, about 2,400 lines of components and 1,200 of tests, because it adds the whole tab at once. ### Verification Not yet checked in a real browser. Worth a look: dark mode, chart spacing with many hydrations and a restart loop, the waterfall's pages on a cluster with many objects, the red heap stat, and a replica that is hydrating, for the live object list. `ClusterHydration.test.tsx` renders the tab against MSW handlers and the subscribe socket mock and covers: - the last successful hydration shown by default with no alert, and selecting another from the table; - a hydration's objects ordered slowest first by default, without waiting, or by start with it, and paged; - Past Hydrations with each hydration's peak memory and no kind filter; - each replica named once in the legend, and a dated time axis; - object timings kept when the CPU sample fails to load; - a replica restarting before it finishes, with the restart count, the docs link, and its restarts merged into one mark; - a replica offline between restarts kept off the clock; - a restart counted once the index it was hydrating is dropped, from the dropped objects #40044 reads; - a hydrating replica's working objects, with no alert and no Hydrating label; - an offline replica called out rather than its last progress; - a cluster without indexes or materialized views; - a retired replica offered while its history is in the window, with no alert about the current replicas; - the history kept up while a new replica's or object's data loads; - elapsed times that move on, and restarts that went offline labelled as such; - a tooltip opening on keyboard focus; - the history showing without waiting for live progress; - narrowing to the replica chosen on the cluster page; - live progress kept when the history fails to load; - the history waiting for the catalog before it classifies hydrations and counts new objects, and falling back to replica starts, with object timings by id, when the catalog fails to load; - live progress only, and no inferred restarts, on environments older than 26.43; - the history's error when the replicas' status fails to load; - a new object's elapsed time left out until its creation time loads; - a new object's hydration dated from its creation rather than the replica's boot; - a cluster with no replicas. `HydrationHistoryChart.test.tsx` covers a replica replaced every hour for a week, whose marks all land in the plot in time order, and replicas of different names hydrating at once, set apart within a bounded spread. `hydrationView.test.ts` covers ordering finished and unfinished hydrations, restarts counted only since a replica last finished, when the current hydration began, an offline replica between restarts, offline and starting replicas, and each alert, including none once every replica is hydrated or hydrating. `ClusterDetail.test.tsx` covers the Hydration tab in the tab list, opening it from its link, and the classic page's redirect. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/materializeinc/materialize/pull/40074" 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 -->
### 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)
The final snapshot adds no new entries: the v26.50.0-rc.1...v26.50.0 compare holds exactly one commit, the version bump, and no agent-skills PR merged in the window, so the increment is empty. Dates are now real rather than provisional (Cloud 2026-11-12, Self-Managed 2026-11-13); rc.1 had estimated the same pair, so no date text changes. The whole-release pass found three entries that main already publishes in its v26.49.1 section, because the v26.49.1 patch train cherry-picked four Console PRs that also merged to main inside this train's rc.1 window. They are removed here so they are not published twice: - "Diagnose hydration from the cluster details page" (#40044, #40074), which main carries as the feature section "Right size your clusters, using the new Hydration tab on the console", with a screenshot. - "The Freshness page on Self-Managed" (#40511), which main carries as "Freshness and cluster details pages on Self-Managed", covering both pages. - "Swap usage in the Console's Disk column" (#40492), which main carries as a Bug Fix. That leaves v26.50.0 with 5 Improvements and 7 Bug Fixes. Draft with PR links: https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.50.0/final/release_notes.md Co-Authored-By: Claude Opus 5 <[email protected]>
Dates in RC snapshots are provisional until the release ships. One commit is added per snapshot; the final snapshot sets the real dates. **The `final` snapshot has been applied: the dates below are the real ones** (Cloud 2026-11-12, Self-Managed 2026-11-13). The `final` snapshot adds no entries — `v26.50.0-rc.1...v26.50.0` is one commit, the version bump, and no agent-skills PR merged in the window. Its substantive work was the dates, a rebase onto `main`, and the three removals below. **Rebased onto `main`.** The branch was 107 commits behind and conflicted: `main`'s `## v26.49.1` section replaced the stale `## v26.49.0` stub the branch was cut from. Resolved by keeping `main`'s section verbatim and re-inserting `## v26.50.0` above it; same for the compatibility YAML (`v26.50`, then `v26.49.1`, then `v26.48.2`). **Three entries removed as already published on `main`.** The `v26.49.1` patch train cherry-picked four Console PRs that also merged to `main` inside this train's `rc.1` window, so both trains classified them independently and `main` already carries them under `## v26.49.1`: | PR(s) | Published on `main` in `## v26.49.1` as | Removed from `## v26.50.0` | |---|---|---| | [#40044](#40044), [#40074](#40074) | the feature section *"Right size your clusters, using the new Hydration tab on the console"*, with a screenshot | *"**Diagnose hydration from the cluster details page**"* | | [#40511](#40511) | *"**Freshness and cluster details pages on Self-Managed**"*, covering both pages | *"**The Freshness page on Self-Managed**"* | | [#40492](#40492) | the Bug Fix about the Console's clusters list showing a dash in the Disk column | *"**Swap usage in the Console's Disk column**"* | `main`'s wording is the later and fuller treatment in all three cases. That leaves `## v26.50.0` with **5 Improvements and 7 Bug Fixes**. Decisions required: - **MCP protocol default flip** ([#39762](#39762)): published as an Improvement because `enable_mcp_protocol_2026_07_28` is `true` at the tag and the flip *is* the change. The PR was held in draft "until the 2026-07-28 code ships in a release and passes a test on a deployed environment" — is it confirmed on? - **Columnar accumulators** ([#40364](#40364)): omitted as Borderline — the default is `true` but the PR states no measured user-facing effect. Is there a memory or CPU figure worth publishing? - **MySQL snapshot/replication concurrency** ([#40206](#40206), [#40207](#40207)): omitted as Borderline — gated on `storage_persist_sink_description_lookahead`, which is `Duration::ZERO` at the tag, same as the PostgreSQL counterparts held back in v26.47.0. If the flag is served on, both halves want announcing together. - **MCP servers GA** ([#40448](#40448)): omitted as Borderline — the preview banners are gone, but the GA call belongs to the release owner. Publish it? - **Balancer connection metrics** ([#40192](#40192)): omitted as Borderline — `mz_balancer_connection_status{status="error"}` changes meaning, so Self-Managed dashboards and alerts on that series will see a step change. Worth a note? - **Console query comments** ([#40075](#40075)): omitted as Borderline — every Console statement's `sql_hash` changes once when this deploys. Worth calling out? Every flag above was re-read in source at the `v26.50.0` ship tag; not one default moved between `rc.1` and ship, so all six holds still read correctly. Snapshot drafts (with PR links) in [mz-skills](https://github.com/MaterializeInc/mz-skills): - rc.1: [release_notes.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.50.0/rc.1/release_notes.md), [omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.50.0/rc.1/omitted.md) - final: [release_notes.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.50.0/final/release_notes.md), [omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.50.0/final/omitted.md) **Borderline PRs omitted:** rc.1: 7 — [omitted.md#borderline](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.50.0/rc.1/omitted.md#borderline), final: 0 — [omitted.md#borderline](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.50.0/final/omitted.md#borderline) (no PRs in scope; `rc.1`'s six holds re-examined at the ship tag and unchanged) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Pranshu Maheshwari <[email protected]> Co-authored-by: Valerie Chiang <[email protected]>
Motivation
Ninth PR in the cluster details redesign stack (#39509), on top of #40043. The redesign's prototype has a Hydration report tab. This PR adds the data a Hydration tab needs, built on the hydration history the database now records. The next PR in the stack, #40074, adds the tab. Part of CNS-173.
Description
mz_replica_hydration_history, one row per episode with the highest peaks across its processes. Gated on 26.43, which records a row per process, and leaves out older rows. Episodes with no objects stay in: on a replica with nothing to hydrate, such as one running only sources, they're the record that a boot finished;mz_cluster_replica_history, so a dropped replica (a resize, a burst replica) keeps its name and size. Fetched again when the cluster's current replicas change, and not kept once unused, so a set of replicas that comes back, as after a burst replica is dropped, fetches again. The same cluster's answer stays up meanwhile, filtered to the window as it moves on;mz_object_hydration_history, with catalog ids throughmz_object_global_ids. Fetched each minute until they reach the episode's object count, since the collector can record an episode a visit before some of its objects;mz_cluster_replica_metrics_history.mz_compute_hydration_times_ind, on the fast path;mz_compute_hydration_times_per_workerin a session pinned to the replica. Gated on 26.39. It runs on the replica, not onmz_catalog_server, so the tab mounts it only for a replica that is hydrating.hydrationModel.ts):mz_object_history, so dropping the index behind an out-of-memory restart doesn't hide it. A dropped object counts only for the restarts while it existed, since dropping one unfinished records no finished hydration;useObjectCreationTimeskeeps its earlier answer up while the objects overlap, as after one is created, so a new index doesn't blank what depends on it.useClusterHydrationDataanduseHydrationDetailload and derive what the tab shows. The detail's object timings and CPU sample load and fail on their own, and the timings don't wait on a catalog that failed.SubscribeManagerstarts a clean snapshot when it resends a subscribe the server ended, as when the subscribe's target replica restarts. The old run's rows and error used to stay, so a per-replica subscribe read as failed for as long as it stayed mounted. The held state stays until the new run updates it, so a statement that fails every time keeps its error without flickering. Resends of a statement that keeps failing without a snapshot back off from 1s to 30s, so a replica-pinned subscribe the user lacks USAGE for isn't resent as fast as the round trip. The first resend stays immediate. The delay is the reconnect backoff, now shared asbackoffDelayMs.mz_catalog_server.types/materialize.d.tsis regenerated withyarn gen:types. Most of its diff is the generator catching up with the catalog: builtin materialized views are no longerGenerated<>, and relations added since the last run (including the two hydration histories andmz_object_global_ids) appear.yarn gen:typesdoesn't run against Materialize as is: kysely 0.28's Postgres introspector callspg_get_serial_sequence, which Materialize lacks, and lists only tables and views, which leaves out the catalog's builtin materialized views. The types here were generated with both patched locally innode_modules. Fixing the script is a follow-up.Cost on
mz_catalog_server: the episode query and the object query read storage, scoped to one cluster or replica and a time bound, as the redesign's status timeline already does. Episodes poll at the history's 60s collection interval. The replica history scans its index, fetched once per change to the replicas and per mount of the tab. An episode's objects poll each minute only while the open detail is missing some. The dropped objects read scansmz_object_history's index, which holds every object the environment has created: 8.5 ms at 32,000 objects on v26.47, plus about 0.23 ms per 1,000. It runs only while a restart reads as finished, and again only when those restarts change. Looking the window's ids up instead costs about 2 ms plus 60 µs an id and takes several round trips to find them, so it beats the scan only past roughly 70,000 objects.The Metrics tab's "Hydrating · N objects" badge counts introspection dataflows as well as user objects, so for a moment after a replica boots it can disagree with this PR's per-replica progress.
Tests
hydrationHistory.test.sql.ts: each builder against shadowed relations (grouping and peaks per episode, the 26.43 filter, episodes with no objects, dropped replicas in the window, user objects only, per-worker states combined into one per object, catalog ids for dropped objects), and against the real catalog, including a new index's hydration streamed from a session pinned to the quickstart replica.hydrationModel.test.ts: full and additive episodes, a zero-downtime upgrade's rehydration counted as full by the objects it covered, a burst of new objects that outnumbers the existing ones, out-of-memory restarts that never finished, a restart merged across processes, restarts after a finished hydration and on drop, a restart while hydrating an object created after the replica finished, a restart after the replica came back online kept apart from the one before it, a crash loop a new object started on a replica up since before the window, the restarts an object missing from the creation times could explain, an object dropped since counted only for the restarts while it existed, an attempt that began before the window, an upgrade's late online row, a new pod's offline row, a boot with nothing to hydrate, the latest full hydration by finish, dropped replicas outside the window, heap limits, and object spans.useLiveHydration.test.ts: progress counted per replica from the stream onmz_catalog_server, progress complete at once for a cluster with no replicas, per-object states read in a session pinned to the replica, following the replica's new process after its subscribe fails over a restart, and no subscribe before 26.39.SubscribeManager.test.ts: a subscribe the server ends and runs again drops its error and old rows, one that fails every time keeps reporting its error, and one that fails every time is resent with growing delays.queries.test.ts: the replica history fetched again when a set of replicas comes back, an episode's objects fetched until all are recorded, including on reopening the episode, and the dropped objects read only once a restart needs them.objectCreationTimes.test.sql.ts: against the real catalog, the dropped-objects query returns when the cluster's indexes and materialized views created since the start and dropped since were created and dropped, and leaves out ones still there, created before the start, or of another type.replicaProcessMetrics.test.sql.tsreads its snapshot through the new sharedreadSubscribeSnapshothelper.🤖 Generated with Claude Code