Skip to content

console: keep the redesigned cluster page's live data in TanStack DB collections - #39607

Merged
leedqin merged 1 commit into
cluster-page-memory-by-objectfrom
cluster-page-live-utilization
Oct 21, 2026
Merged

leedqin merged 1 commit into
cluster-page-memory-by-objectfrom
cluster-page-live-utilization

Conversation

@leedqin

@leedqin leedqin commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Fifth PR in the cluster details redesign stack, on top of #39510. The resource usage charts and the memory by object card read their live data from TanStack DB collections instead of per-hook subscribe state, so switching windows reads data that's already loaded, and coming back to a cluster paints its last data at once.

Description

Charts. Each cluster gets a collection fed by the 3h or 24h utilization SUBSCRIBE, and each subscribe covers its view's full retention. Switching windows within a tier reads the collection instead of resubscribing. Leaving the Metrics tab closes the subscribe; coming back paints the held rows at once and shows the refreshing spinner until the new snapshot lands. If the held rows have all left the window, the card shows loading rather than "No resource usage". Windows past 24h still poll, through usePolledReplicaUtilizationHistory, which mounts no subscribe hooks and so opens no idle WebSocket. The page no longer calls useReplicaUtilizationHistory, so its includeOfflineEvents option goes away.

Memory by object. Each replica gets a collection fed by a SUBSCRIBE to mz_object_arrangement_sizes, an index lookup on replica_id. A live query joins it to allObjectsCollection for names and keeps the ten largest. The card waits for that collection, so objects never show as raw ids, and a replica without a heap limit drops the bar and lists each object's size. Under All replicas, the replica comes from the largest-replica query. A replica named in the page's Replica select skips that query: its id, name and size come from the clusters subscribe, and its heap limit from the per-process limits and the size's process count the resource usage card already reads, so switching replicas makes no request before the new replica's subscribe opens. The previous replica's objects stay on screen, dimmed, until the new replica's first snapshot. useLargestClusterReplica moves from useSuspenseQuery to useQuery, and the named-replica filter #39510 added to it is gone. Sizes update live, and the card no longer polls sizes every 60s or object names every 5 minutes. Environments before 26.35 keep the polled card.

Other memory. Object sizes come from arrangement sizes, which undercount, so the listed objects don't add up to what the replica uses. A grey last segment, "Other memory", shows heap in use (summed over the replica's processes) minus the listed objects' sizes, as a share of the heap limit, with a tooltip saying so. It's hidden without a heap limit or heap metrics. Heap use comes from the same per-process mz_cluster_replica_metrics lookup as the heap limits, now also selecting heap_bytes and polled every 60s while the card is mounted.

Collections are keyed by environment and id, because cluster and replica ids repeat across environments, through one createScopedSubscribeCollections helper that drops an earlier environment's collections when the scope changes. They aren't persisted. A subscribe collection's status gains isRefreshing, set while held rows are served and no running subscribe has delivered a newer snapshot, including while a reopened socket resubscribes. Ending a collection's session sets it, so coming back before the next subscribe starts (for example while the cluster's lineage refetches) shows loading rather than an empty window.

Both live hooks share useScopedSubscribeCollection, which resolves the environment scope, picks the key's collection and runs the subscribe into it. No subscribe runs until the scope resolves. It lives in its own module rather than useSubscribe.ts, because importing the sync engine cache there closes an import cycle that broke the billing routes' tests. The sync engine design doc gains a short section on these per-key scoped collections.

Measured in headless Chrome against a dev server (React dev mode, so absolute times run high), median of 3 runs, time until the charts are drawn:

Step Before After
Open a cluster 594 ms 630 ms
Switch 1h to 3h 222 ms 25 ms
Switch back to 1h 238 ms 24 ms
Objects tab back to Metrics 338 ms 102 ms
Back to the cluster from the list 347 ms 95 ms

Under All replicas the card's first open still waits on the largest replica query. On mz_catalog_server the card's subscribe costs the same as the polls it replaces. Each held-open subscribe adds about 0.2 mCPU per viewer on environmentd for its progress updates.

Verification

  • useLiveClusterUtilization.test.ts: 3h samples bin into the window's buckets, keeping each bucket's peak, and 24h buckets clip to the window in time order.
  • useLiveLargestObjects.test.ts: the largest objects are named from the catalog, largest first; an object missing from the catalog is marked as an orphaned dataflow; only the ten largest are kept.
  • MemoryByObject.test.tsx: on an environment that streams sizes, objects are named from the catalog; a replica without a heap limit shows no share; the card waits for the catalog before naming objects.
  • MemoryByObject.test.tsx also covers: a named replica's heap limit across all its processes with no largest-replica request; switching replicas keeping the previous objects until the new snapshot; one largest-replica request after switching to All replicas; Other memory's size, label and tooltip, and its absence without heap use or a heap limit.
  • queries.test.ts: useReplicaHeapBytes leaves out a replica with a process missing its reading, and keeps polling after the heap limits settle.
  • replicaHeapLimits.test.sql.ts, against a real Materialize: the heap read returns heap_bytes per process, nulls kept, as a fast-path index lookup.
  • ResourceUsage.test.tsx: a 1h window draws from the live subscribe.
  • useLiveClusterUtilization.test.ts: coming back with held rows that have left the window reports loading and refreshing on every render until the new snapshot lands, including while an evicted lineage query refetches.
  • useSubscribe.test.tsx: ending a collection session marks its rows as refreshing.
  • subscribeCollection.test.ts: a scope change drops the earlier scope's collections.
  • subscribeCollection.test.ts: held rows are marked refreshing until a new subscribe's snapshot lands, and while a reopened socket resubscribes.
  • useLiveClusterUtilization.test.ts also covers: a window change within a tier keeps the subscribe it has; a cluster switch serves none of the previous cluster's rows; and rows stay held as refreshing while a dropped socket resubscribes.

🤖 Generated with Claude Code

@leedqin
leedqin added this pull request to stack #39509 October 7, 2026 02:26
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch 2 times, most recently from 1e4837a to e76d608 Compare October 8, 2026 04:11
name: object?.name ?? size.objectId,
size: size.size,
memoryPercentage:
size.size === null ? null : (size.size / heapLimit) * 100,

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.

heapLimit is Number(largestReplica.heapLimit), which is 0 when no replica has reported metrics yet (or on the emulator). Dividing by it makes the largest object fill the whole bar and the legend read "Infinity%". Returning a null percentage like the polled path avoids that, but then the bar is empty and formatMemoryUsage shows "-" for every object, hiding sizes we do have. Could the card drop the bar when the heap limit is unknown and list each object's size on its own?

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.

Good point, done in 40df6d7: without a heap limit the card drops the bar and lists each object's size, with "Size on " as the subtitle.

@leedqin
leedqin force-pushed the cluster-page-live-utilization branch from e76d608 to f193b47 Compare October 12, 2026 23:36
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch 3 times, most recently from 902dac4 to 42bb039 Compare October 15, 2026 15:59
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch 3 times, most recently from e578716 to 40df6d7 Compare October 15, 2026 18:11
@leedqin
leedqin marked this pull request as ready for review October 15, 2026 19:28
@leedqin
leedqin requested a review from a team as a code owner October 15, 2026 19:28
@leedqin
leedqin requested a review from jdonelson October 15, 2026 19:28
@linear-code

linear-code Bot commented Oct 15, 2026

Copy link
Copy Markdown

CNS-203

@leedqin
leedqin force-pushed the cluster-page-live-utilization branch from bf415da to 0ecfe6f Compare October 16, 2026 17:32
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch 2 times, most recently from 0e277ad to d85f634 Compare October 16, 2026 19:41
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch 2 times, most recently from b49a0ab to 9c23755 Compare October 19, 2026 22:03
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch from 9c23755 to c5256d9 Compare October 20, 2026 15:41
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch 2 times, most recently from a3915ac to 9e0d658 Compare October 20, 2026 19:39
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch from 9e0d658 to 60611b4 Compare October 20, 2026 20:31
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch from 60611b4 to 35d013a Compare October 20, 2026 23:23
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch 2 times, most recently from e9bf81d to 46252ce Compare October 21, 2026 15:21
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch from 46252ce to 169d2d2 Compare October 21, 2026 21:16
…collections

Fifth in the cluster details redesign stack. The resource usage charts and
the memory by object card read their live data from TanStack DB collections
instead of per-hook subscribe state.

Charts: each cluster gets a collection fed by the 3h or 24h utilization
SUBSCRIBE, and each subscribe covers its view's full retention. Switching
windows within a tier reads the collection instead of resubscribing.
Leaving the Metrics tab closes the subscribe; coming back paints the held
rows at once and marks the card as refreshing until the new snapshot lands.
Held rows that have all left the window show as loading, not as a window
with no usage. Windows past 24h still poll, through a poll-only hook that
mounts no subscribe hooks and so opens no idle WebSocket. The page no
longer uses useReplicaUtilizationHistory, so its includeOfflineEvents option
goes away.

Memory by object: each replica gets a collection fed by a SUBSCRIBE to
mz_object_arrangement_sizes, an index lookup on replica_id. A live query
joins it to the all objects collection for names and keeps the ten largest.
The card waits for that collection, so objects never show as raw ids, and
a replica without a heap limit shows sizes without shares. Sizes update
live, and the card no longer polls sizes every 60s or object names every 5
minutes. Environments before 26.35 keep the polled card.

Collections are keyed by environment and id, since cluster and replica ids
repeat across environments, through one createScopedSubscribeCollections
helper that drops an earlier environment's collections when the scope
changes. They are not persisted. A subscribe collection's status gains
isRefreshing for the held-rows case, and ending a collection's session
marks its rows as held, so a return before the next subscribe starts shows
loading rather than an empty window.
@leedqin
leedqin force-pushed the cluster-page-live-utilization branch from 169d2d2 to 2ca0909 Compare October 21, 2026 22:07
@leedqin
leedqin enabled stacked auto-merge (squash) October 21, 2026 22:24
@leedqin
leedqin merged commit cfc4bb1 into main Oct 21, 2026
77 checks passed
@leedqin
leedqin deleted the cluster-page-live-utilization branch October 21, 2026 23:19
@bosconi bosconi mentioned this pull request Oct 23, 2026
leedqin added a commit that referenced this pull request Oct 27, 2026
… memory by object (#40102)

### Motivation

Follow-up to #39508, #39510 and #39607, which merged with review
findings still open: a QA review on #39510, and a self-review of the
three PRs with the Console review skill. It sits at the bottom of the
cluster details stack, under #39889.

### Description

**Memory by object**
- **A load error no longer sticks across replicas.**
`LargestReplicaBoundary`'s error boundaries never reset when the Replica
select changed, so after one replica's objects failed, every replica
picked next showed the same "might mean it's busy" alert under its own
name, and stopped polling. `useResetOnChange` re-keys a boundary once
the replica differs from the one it caught an error for. A boundary that
hasn't caught keeps its key: a plain `key={replica}` would swap the card
for its loading spinner on every pick, because the router's navigation
runs inside `startTransition` and a new boundary has no content to keep.
Coming back to a replica that failed from a healthy one shows its cached
error without a new request, until react-query evicts it.
- **Deep link, then All replicas, no longer blanks the card.** Opening
`?replica=r1` and then picking All replicas showed the spinner until the
largest-replica query first returned. The loader now keeps the last
replica on screen, dimmed with a refreshing spinner, while that query is
pending. `useLastDefined` moves to `hooks/` for both uses.
- **Multi-process replicas.** The bar is replica-wide (objects and heap
use summed over processes, against the size's heap limit times its
processes), while the resource usage card shows the fullest process, so
a replica at 95% and 20% read 57.5% here. The subtitle now says so: "%
of heap limit across 2 processes on r1". The Other memory tooltip also
says it counts the objects past the top ten and memory that arrangement
sizes don't count, not only undercounting.

**Resource usage card**
- **The tooltip's last memory row says what it measures.** Memory and
Disk read against their own limits, summed over processes, as the
Clusters list does. The row below them was still the fullest process
against one process's heap limit, the bar's value, so on a multi-process
replica it read less than its parts. It's now "Heap … of heap limit" on
one process and "Fullest process … of process heap limit" on more.
- **The header wraps.** The toggles' `flex="1"` gave them a zero basis,
so `flexWrap` never applied, and at narrow widths a long replica name
overlapped the title. Checked in Chromium at 360 and 900px.
- **Small CPU readings** read "< 0.01 cores" instead of "0.00 cores".
- **The past-replicas swatch** stops at three stripes.
- **The test fixture's heap limit** is memory plus disk, as the
controller sets it.

### Verification

- `MemoryByObject.test.tsx`: a replica's load error is replaced by the
next replica's objects; picking a replica inside `startTransition` keeps
the old card with no spinner; a deep link then All replicas keeps the
last replica dimmed; the subtitle names the process count; the Other
memory tooltip copy.
- `ResourceUsage.test.tsx`: the heap row's label on one process and on a
two-process replica, with readings consistent with the fixture; "< 0.01
cores"; the swatch's stripe cap.
- Each new test fails with its fix reverted.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@leedqin leedqin added the A-CONSOLE Area: Console label Oct 28, 2026
val-materialize added a commit that referenced this pull request Oct 30, 2026
Release notes for the **v26.48.0** train. One commit is added per
snapshot; the `final` snapshot sets the real dates. **Applied: `rc.1`,
`rc.2`, `rc.3`, `final` — the train is complete.**

- **Content**: 3 Feature sections (upsert hydration; Kafka sink headers
GA; MySQL `EXCLUDE ALL NOT NULL CONSTRAINTS`), 9 Improvements, 2 Guides,
4 Bug Fixes. No **Agent Skills** section — every agent-skills PR in all
four windows was omitted.
- **Scope**: 123 PRs across the four snapshots — rc.1 114 (14 included),
rc.2 5 (0 included), rc.3 1 (1 included), final 3 (0 included).
- **Dates — now real, not provisional**: Cloud **2026-10-29**,
Self-Managed **2026-10-30**. The `rc.1`-anchored provisional estimate
was right, so `final` left both files unchanged. The `v26.48.0` tag's
commit date is 2026-10-28T18:35:05Z, a Wednesday; the first Thursday on
or after it is 2026-10-29, with Self-Managed the day after.
- **Operator compatibility**: row for `v26.48`, release date
`2026-10-30`. Unchanged by rc.2, rc.3, and final.
- **Branch base**: `main`, whose newest published section was `##
v26.46.1` when this branch was cut; v26.47.0 and v26.47.1 have since
merged in. `v26.48.0` stays at the top of the list.

### final increment — empty, and verified empty

`final`'s commit is empty. The tracking issue
([release#627](MaterializeInc/release#627))
lists exactly one item, the unassociated `4edd60a8d` "release: bump to
version v26.48.0", which the release-process-automation rule omits. The
three agent-skills PRs in the window
([#118](MaterializeInc/agent-skills#118),
[#119](MaterializeInc/agent-skills#119),
[#120](MaterializeInc/agent-skills#120)) were
all omitted.

This was **verified rather than assumed**, because a zero-PR snapshot
and a failed fetch produce identical output:
`compare/v26.48.0-rc.3...v26.48.0` returns `total_commits: 1`, and that
commit is the version bump. Nothing merged onto the release branch in
the 42 h 49 m between the `rc.3` tag and the ship tag. Across the train,
every agent-skills PR from
[#111](MaterializeInc/agent-skills#111) to
[#120](MaterializeInc/agent-skills#120) falls in
exactly one window, with no gaps or overlaps.

⚠️ **One decision is still open for the release owner — see "Upsert v2"
below.** Every flag this train held open was re-read in source at the
`v26.48.0` ship tag rather than inherited from the RC snapshots:

| Flag | Default at `v26.48.0` | Source | Held since | Disposition |
| -- | -- | -- | -- | -- |
| `enable_upsert_v2` | **`false`** |
`src/storage-types/src/dyncfgs.rs:505-509` | rc.2 | ⚠️ premise
contradicted — see below |
| `enable_upsert_chunked_stash` | **`false`** |
`src/storage-types/src/dyncfgs.rs:469-475` | rc.2 | same |
| `enable_upsert_paged_spill` | **`false`** |
`src/storage-types/src/dyncfgs.rs:450-455` | rc.2 | same |
| `enable_mcp_protocol_2026_07_28` | **`false`** |
`src/adapter-dyncfgs/src/dyncfgs.rs:295-299` | rc.1 | hold stands —
still omitted |
| `cluster-details-page-cns-173` | n/a (LaunchDarkly) | no source
default | rc.1 | hold stands — still omitted |

**The rc.2 hold on
[#40189](#40189) rests
on a premise this branch now contradicts.** rc.2 omitted it at
Borderline on exactly one ground — every measurement is taken on a path
`enable_upsert_v2` keeps off by default — and asked whether any
environment actually runs upsert v2. The branch answers that without
anyone feeding the answer back: the human-added **"Improved hydration
times and memory efficiency for upsert sources"** section states *"This
change has been rolled out to all Materialize Cloud environments and
applies to all existing and new clusters."* The three flags still read
`false` at the ship tag, which is exactly the
source-default-versus-LaunchDarkly divergence the rollout-lever rule
covers: the published docs call wins, and the sole stated reason for the
omit has fallen away.

- **Not already covered.** The published section is about **hydration**
— building a source from scratch, 206 min → 108–113 min, 542 GiB →
183–208 GiB. #40189 is about **catch-up after a backlog**, a different
operation with its own motivating incident
([CPU-320](https://linear.app/materializeinc/issue/CPU-320): a source
that *"could not catch up on any replica size after a producer rate
increase"*). A customer who read the hydration section would learn
nothing about catch-up.
- **Suggested Improvements bullet, to add only if you confirm** — it was
*not* applied in the `final` commit:
> **Faster catch-up for upsert sources with a large backlog**: An upsert
source working through a backlog now drains one timestamp per round
instead of merging and re-pushing the whole backlog each round, so
catch-up time grows linearly rather than quadratically with the backlog;
draining a 100-timestamp tail fell from 6.5 to 0.15 seconds, and a
400-timestamp tail drains in 0.67 seconds.
- **Caveat that must travel with the decision**: the baseline in those
timings is the *paged* flavor on `main`, not the configuration Cloud ran
before this rollout — so they are not a clean before/after for a given
customer. Dropping the two timings and keeping the
linear-versus-quadratic claim holds regardless of baseline.
- **Two weaker links, stated rather than buried**: that the rolled-out
configuration is the one #40189 was measured on is *inferred from the
published section's prose* (its "buffer pool … serve sequential work
from disk" reads as chunked stash plus paged spill), not confirmed by
anyone. And the author declared "Release notes: None", naming
`enable_upsert_v2` — so publishing this would be an **override** of an
author signal, justified by your own later rollout statement.
- **Third consecutive train** to hold this flag: `v26.44.0-rc.1`
([#39041](#39041)),
`v26.45.0-rc.1`
([#39044](#39044)),
`v26.48.0-rc.2` (#40189). If the rollout statement is accurate, the
earlier two are worth revisiting rather than left to age out.

**The other two holds stand, corroborated.**
`enable_mcp_protocol_2026_07_28` still reads `false` at the ship tag and
the five MCP PRs rc.1 held appear nowhere in the section; neither does
the Console cluster-details page stack. In both cases you had the whole
train in view and did not promote them — the confirmation rc.1 asked
for, arriving in the negative.

**No duplicates, re-checked at ship.** The seven PRs rc.1 flagged as
already covered by v26.47.0 are still absent, the `## v26.47.1` section
is untouched, and rc.3's entry
([#40065](#40065))
appears exactly once.

**Two notes on human edits to this branch**, both recorded as fact
rather than objection:

- `524adb8` removed the rc.1 feature section **"Native Google Cloud
Storage for persist"**
([#39983](#39983))
outright. Flagging it only because a published entry disappearing is
easy to miss in a long branch — if that was deliberate, nothing to do.
- Two rc.1 Improvements were promoted to their own feature sections with
docs links and a code sample (Kafka sink headers
[#39908](#39908);
MySQL `EXCLUDE ALL NOT NULL CONSTRAINTS`
[#39873](#39873)), and
a **Guides** section was added that is not in the skill's template. The
lesson for next time: an option whose flag flipped to `true` in the same
release more likely warrants a feature section than the Improvements
bullet the rubric suggests by default.

**Carried forward from rc.3 and still open**: the `K8s recovery: envd on
failing node` nightly — the job that sank
[#40029](#40029), the
first attempt at what #40065 re-lands — **was not checked**; it needs
Buildkite, outside this run. The entry itself was verified correct in
source at both the rc.3 and ship tags, so this is a CI-confidence
question, not a notes-accuracy one.

### rc.3 increment

One Improvement, for #40065: a read-only `environmentd` that reboots to
pick up DDL changes during zero-downtime upgrade catch-up now restarts
in place rather than exiting its container, so Kubernetes no longer
applies crash-restart backoff. Not inherited from rc.1 — rc.1 correctly
omitted the first attempt (#40029) together with its revert (#40061) as
a no-op on `main`; rc.3 re-lands the behavior in narrowed form, verified
as a real change by comparing `entrypoint.sh` at the `v26.47.0` tag (no
retry loop) against `v26.48.0-rc.3` (re-exec on exit 75). The draft's
closing "so zero-downtime upgrades catch up faster" was cut: no cited PR
measures an upgrade duration, so the entry claims only the mechanism.

- **Not a Bug Fix.** 0dt upgrades completed on v26.47.0; the catch-up
reboots merely collected backoff delay. Existing behavior improved, so
Improvements.
- **Residual, deliberately unpublished.** A stale read-only process in a
replaced pod still restarts in place on these halts until the kubelet
kills its container, competing with its replacement for the generation's
replicas. The `MZ_RESTARTED_IN_PLACE` marker check keeps it from booting
writable, so it cannot fence the leader — the failure #40061 reverted.
Worth watching if 0dt reports look odd after this ships.

### rc.2 increment — empty, by design

rc.2's commit is empty: no entry was published and the provisional dates
did not move. It exists so later runs can see from the commit subject
that rc.2 was applied. Its single materialize PR, #40189, is the open
item discussed under the `final` increment above.

### rc.1 — standing warnings

⚠️ **Seven PRs in rc.1's tracking issue were already covered by the
v26.47.0 train, and none is republished here.** v26.47.0 took five RCs
whose later ones were cherry-picks onto the release branch; the same PRs
appear in this issue because they are also what landed on `main` for
v26.48.0. Four of the seven **shipped to Cloud in v26.47.0 on
2026-10-22** — #37653, #39910, #39927, #40036 — so republishing any of
them would put duplicate text in front of customers eight days later.
The other three (#39903, #39906, #40046) were held at Borderline in
v26.47.0 and are held again here, each reason re-checked rather than
inherited. The "snapshots are disjoint by construction" guarantee holds
*within* a train, not across trains, so this was caught mechanically by
grepping every PR number in the issue against the v26.47.0 snapshot
directories. All seven are listed under their own heading in
`rc.1/omitted.md`.

**Open questions for the release owner**

- **Upsert v2** (#40189, rc.2) — see the `final` increment above. The
rollout statement on this branch has changed the picture since rc.2;
this is the one item that needs a decision before merge.
- **MCP protocol revision 2026-07-28** (#39698, #39702, #39703, #39759,
#39761) — the stack is complete and `enable_mcp_protocol_2026_07_28`
still defaults to **`false`** at the ship tag. Omitted, with the absence
of any MCP text on this branch taken as confirmation. Drafted wording
remains in `rc.1/omitted.md` if the flag flips.
- **Redesigned cluster details page in the Console** (#39446, #39507,
#39508, #39510, #39607) — omitted for the same reason. Note that the
Improvements section does carry **"New cluster details page on
Self-Managed"**, added by hand, which links the v26.47 section rather
than re-describing the page.
- **Schema projection on the Console's Freshness page** (#40031) — a
genuine new way to diagnose lag, but the author states the whole
Freshness page "is currently behind a feature flag that is only enabled
for certain customers in impersonation mode". **Is the page generally
reachable yet?** This is the same page v26.47.0 held for the same
reason.
- **Time zone data 2026e timing** (#40045) — the author flags the
Canadian zone changes as effective **2026-11-01**, three days after the
Cloud date. Tight, but it holds.

*Resolved since rc.1: the Kafka sink headers framing question — the GA
section on this branch answers it.*

**Verification notes** — rc.1's Step 5 pass cut one drafted entry and
rewrote four:

- **Cut**: a Bug Fix drafted for #40032 ("halt with exit code 168 when a
live reader loses its lease"). The PR's own Problem section is about
alerting — `mz_persist_lease_timeout_read` cannot distinguish a dead
reader from a stuck one — and the follow-up it names is a Cloud alerting
change. Nothing a customer observes changes.
- **tzdata** (#40045): the draft said the Canadian zones move to
"permanent daylight saving time", the author's prose. The PR's data
table shows a permanent **−05/EST** and **−06/CST** — summer *offsets*
kept year-round, but with another zone's standard-time *abbreviations*,
which is what a user sees from `AT TIME ZONE`. Reworded to what the
table supports.
- **Hydration memory** (#39923): scoped to the consolidation step and
the measured outcome (~628 GiB peak, no restart, against every
pre-change run exiting on the 849 GiB limit). The PR explicitly says the
fix "covers this consolidation path, not the sink's whole footprint";
[CPU-317](https://linear.app/materializeinc/issue/CPU-317) tracks the
rest.
- **Kafka clients** (#40026): "55 to 24" is measured on a 4-worker,
11-source setup. The CPU throttling and degraded freshness are observed
symptoms of the *old* behavior at a few hundred sources, not a measured
post-change result at that scale, so the entry states the count as the
outcome and the throttling as what the count drove.
- **`environmentd` accept backlog** (#40035): the author's hedge is kept
— "less likely to be restarted", not eliminated, because the PR says
"This makes the queue fill later, not never."

**Judgment calls a reviewer may want to revisit** — #39605 is filed as
an **Improvement**, not a Bug Fix: nothing was incorrect, an unbounded
operation gained a bound and a knob. #40101 is filed as a **Bug Fix**
rather than omitted under the internal-logging rule, because credentials
reaching the logs is a security defect with a user-visible remedy; only
credentials a user inlined rather than wrapped in a `SECRET` were
affected.

Snapshot drafts (with PR links) in mz-skills:
- rc.1:
[release_notes.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.1/release_notes.md)
/
[omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.1/omitted.md)
- rc.2:
[release_notes.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.2/release_notes.md)
/
[omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.2/omitted.md)
- rc.3:
[release_notes.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.3/release_notes.md)
/
[omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.3/omitted.md)
- final:
[release_notes.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/final/release_notes.md)
/
[omitted.md](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/final/omitted.md)

**Borderline PRs omitted:** rc.1: 12 —
[omitted.md#borderline](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.1/omitted.md#borderline),
rc.2: 1 —
[omitted.md#borderline](https://github.com/MaterializeInc/mz-skills/blob/main/data/mz-release-notes/v26.48.0/rc.2/omitted.md#borderline),
rc.3: 0, final: 0

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

---------

Co-authored-by: Claude <[email protected]>
Co-authored-by: Valerie Chiang <[email protected]>
Co-authored-by: Pranshu Maheshwari <[email protected]>
Co-authored-by: Pranshu Maheshwari <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-CONSOLE Area: Console

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants