Skip to content

adapter: Align replica hydration history object counts - #38729

Open
aljoscha wants to merge 1 commit into
MaterializeInc:mainfrom
aljoscha:sql-692-hydration-object-count
Open

adapter: Align replica hydration history object counts#38729
aljoscha wants to merge 1 commit into
MaterializeInc:mainfrom
aljoscha:sql-692-hydration-object-count

Conversation

@aljoscha

@aljoscha aljoscha commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Motivation

Replica hydration history counts builtin introspection indexes that object hydration history excludes. This makes the two histories inconsistent.

Changes

Exclude introspection-index (si%) and transient (t%) exports from both object history and replica episode counts, without catalog lookups. Ordinary system and user exports are included.

Keep all non-transient exports in episode construction, so introspection hydration still determines episode boundaries and completion. Introspection-only episodes remain visible with object_count = 0.

Adjust hydration-status.td to require a zero-count introspection-only episode while retaining the exact count and timing assertion for an isolated user-index episode.

Closes: SQL-692

does not declare a key or index. Collection runs on the selected replica, so a
catalog-server index would not avoid importing and arranging the history there.

`object_count` counts only exports eligible for object hydration history: user

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.

let's cut back this change, keep it simple

| `started_at` | [`timestamp with time zone`] | The earliest maintained compute dataflow installation in the hydration episode. |
| `finished_at` | [`timestamp with time zone`] | The latest maintained compute dataflow hydration in the hydration episode. |
| `object_count` | [`uint8`] | The number of maintained compute dataflows in the hydration episode. |
| `object_count` | [`uint8`] | The number of user index and materialized view dataflows eligible for object hydration history in the episode. Builtin dataflows contribute to episode boundaries but not this count. |

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.

le'ts also cut this change, too complicated and we want to keep it simple

count(*)::uint8 AS object_count
count(*) FILTER (
WHERE object_id LIKE 'u%'
AND object_id IN (

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.

it's a bit heavy that we need an extra join here. Is there a local way of figuring out which objects to ignore. Which are the 33 objects we're seeing in there on the current version?

Comment thread src/catalog/src/builtin/mz_internal.rs Outdated
(
"object_count",
"The number of maintained compute dataflows in the hydration episode.",
"The number of user index and materialized view dataflows eligible for object hydration history in the episode. Builtin dataflows contribute to episode boundaries but not this count.",

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.

le'ts cut this change, keep it simple

@aljoscha
aljoscha force-pushed the sql-692-hydration-object-count branch from 9062977 to f3e4bd2 Compare September 10, 2026 11:01
@aljoscha
aljoscha marked this pull request as ready for review September 10, 2026 11:08
@aljoscha
aljoscha requested a review from a team as a code owner September 10, 2026 11:08
@def-

def- commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

QA LLM Review

1. MEDIUM -- object_count and object-history scope now contradict their published contracts

src/catalog/src/builtin/mz_internal.rs:5237

Both semantic changes in this diff leave the two tables' published column comments wrong. object_count is documented as "The number of maintained compute dataflows in the hydration episode", but it now excludes every builtin dataflow in the episode, so a builtin-only episode reports 0 over a real started_at/finished_at interval; and mz_object_hydration_history is documented as recording indexes and materialized views, but it now records every user compute export. Column comments ship in mz_columns.comment and are mirrored into the published docs page, so this is a user-facing contract error, not an internal note.

Details

object_count (hydration_history.rs:492) counts only u%, while started_at/finished_at still derive from all non-transient exports (hydration_history.rs:439-443). For a fresh replica the episode boundaries are therefore stamped by introspection dataflows that the count does not include, and for an empty cluster the only recorded episode reports zero objects. Affected surfaces: src/catalog/src/builtin/mz_internal.rs:5237 and doc/user/content/sql/system-catalog/mz_internal.md:771.

Dropping o.type IN ('index', 'materialized-view') (hydration_history.rs:391) widens object history to every u% compute export on the visited replica. On a user cluster that also covers continual tasks and metric sinks: ComputeSinkConnection::MetricSink renders with the sink's user GlobalId and reports hydration through the output probe like any other compute sink, so its rows now land in the table. Affected surfaces: src/catalog/src/builtin/mz_internal.rs:5126 (object_id comment), :5154 (ontology description), and doc/user/content/sql/system-catalog/mz_internal.md:726.

Suggested fix: reword both column comments plus the ontology description and the two docs paragraphs in terms of user compute dataflows/exports, and state explicitly that object_count can be 0 for an episode consisting only of builtin dataflows.

2. LOW -- test comment claims a property the rewritten assertion no longer checks

test/testdrive/hydration-status.td:65

The comment still says the zero-count episode "proves the collector excludes its own transient subscribe", but with the count filtered to u% a transient export contributes 0 either way, so the assertion passes whether or not the transient filter is in place.

Details

Concretely: if WHERE t.export_id NOT LIKE 't%' (hydration_history.rs:443) were dropped, the collector's own subscribe would enter the objects CTE and form its own episode, and that episode would still be the latest with object_count = 0, so SELECT h.status, h.object_count ... ORDER BY h.started_at DESC LIMIT 1 would still return hydrated 0. The pre-diff form compared against a live count that excluded t%, which is what made the claim hold. The later replica_episode_idx assertion would probably still catch such a regression, so this is a comment-accuracy issue rather than lost coverage: either drop the second sentence or add an assertion that no recorded episode starts at a transient export's install.

@ggevay ggevay 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.

LGTM, just nits

episode_started_at AS started_at,
max(hydrated_at) AS finished_at,
count(*)::uint8 AS object_count
count(*) FILTER (WHERE object_id LIKE 'u%')::uint8 AS object_count

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.

This makes object_count read 0 for the builtin-only episode, while the column comment (mz_internal.rs:5237) and the docs row still say "The number of maintained compute dataflows in the hydration episode", and that episode did hydrate 33 dataflows. You cut the longer rewording earlier; a one-word version might still be worth it: "The number of user-created compute dataflows in the hydration episode." Your call.

# A system-only replica hydration episode covers every visible non-transient
# export. Its existence also proves the collector excludes its own transient
# A system-only replica hydration episode has no objects eligible for object
# history. Its existence also proves the collector excludes its own transient

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.

With the assertion now hydrated 0, this sentence no longer follows: a spurious transient-only episode would also read hydrated 0, since t% is not u%. The old form compared against the live non-transient count, which did rule it out. Suggest dropping the sentence, or stating it as a fact about the NOT LIKE 't%' filter rather than as something this assertion proves. (Same point as item 2 of the QA review, independently confirmed.)

FROM mz_introspection.mz_compute_hydration_times_per_worker AS t
JOIN mz_internal.mz_object_global_ids AS ids ON ids.global_id = t.export_id
JOIN mz_catalog.mz_objects AS o ON o.id = ids.id
WHERE t.export_id LIKE 'u%'

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.

nit: collect_requires_every_worker could assert !sql.contains("mz_catalog.mz_objects") and !sql.contains("mz_object_global_ids") the way the replica test does, so the catalog imports (mz_objects alone is an 8-way union) do not creep back into the per-replica dataflow.

Exclude introspection-index and transient exports from both object history and replica episode counts, without catalog lookups. Include ordinary system and user exports while preserving replica episode boundaries and completion checks.

Closes: SQL-692
@aljoscha
aljoscha force-pushed the sql-692-hydration-object-count branch from f3e4bd2 to 8ac25e3 Compare September 11, 2026 06:27
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