catalog: add mz_object_hydration_history - #38346
Conversation
fb0009d to
633e935
Compare
| | `created_at` | [`timestamp with time zone`] | Wall-clock timestamp of when the object was created. `NULL` for built in system objects. | | ||
| | `dropped_at` | [`timestamp with time zone`] | Wall-clock timestamp of when the object was dropped. `NULL` for built in system objects or if the object hasn't been dropped. | | ||
|
|
||
| ## `mz_object_hydration_history` |
There was a problem hiding this comment.
this needs to be cut down quite a bit and focus on the facts, no handwaving about support and whatnot, when we ship the feature this will be on. Also cut the stuff about surviving restarts, this is expected of tables.
There was a problem hiding this comment.
Cut to two paragraphs, matching the length of siblings like mz_cluster_replica_metrics_history. Dropped the restart sentence and the whole "contact support to enable" paragraph. What's left is what it records, 30 day retention, that ids may name dropped objects, and that recording is best effort with only successful hydration captured.
| is_retained_metrics_object: true, | ||
| }); | ||
|
|
||
| /// Completed hydration episodes, one row per object, replica, and installation. |
There was a problem hiding this comment.
cut this down to teh facts please
There was a problem hiding this comment.
Done, down to three lines: exempt from the bootstrap reset and forced migrations because the contents can't be rebuilt, and clearing them for a schema change is still allowed with a pointer to the tripwire. The reasoning about why that trade is acceptable lives in the design doc instead.
| ontology: None, | ||
| }); | ||
|
|
||
| pub static MZ_OBJECT_HYDRATION_HISTORY_IND: LazyLock<BuiltinIndex> = |
There was a problem hiding this comment.
are we sure we want the index? what do comparable internals have?
There was a problem hiding this comment.
Checked the closest comparable. mz_internal.mz_object_arrangement_size_history is the same kind of thing, a durable retention-bounded internal history table, and it carries two builtin indexes: mz_object_arrangement_size_history_object_ind on (object_id) and mz_object_arrangement_size_history_ts_ind on (collection_timestamp), both with is_retained_metrics_object: true.
So one index on (object_id) with is_retained_metrics_object: true is exactly the sibling's first index, and I'd keep it. I deliberately did not copy the sibling's second one: our retention deletes a bounded batch per sweep rather than being something users scan, so indexing finished_at would cost every environment an arrangement to speed up a background job that is already cheap.
This also settles a question I had flagged separately, whether is_retained_metrics_object should be false here. The sibling precedent is unambiguous, so true stays.
There was a problem hiding this comment.
Following up on my own answer: you are right, and my precedent argument was the wrong test. is_retained_metrics_object: true resolves to a 30 day logical compaction window (metrics_retention), which holds the since back so the shard, and for the index the arrangement in memory, retain 30 days of update history rather than just current state.
Our history lives in the rows, and the retention sweep retracts them on its own schedule, so nothing here needs to be readable at an old timestamp. The window would buy nothing and cost every environment memory on the catalog server. Set to false on both the table and the index, with the reasoning recorded at the definition.
That the sibling sets true while also pruning explicitly looks like the same redundancy, but I left it alone rather than widen this PR.
There was a problem hiding this comment.
ahh, I really think we shouldn't add the index by default, the history collection can grow quite a bit, so this index might grow out of hand, no?
| Builtin::View(&MZ_INDEX_ADVICE), | ||
| Builtin::View(&MZ_MCP_DATA_PRODUCTS), | ||
| Builtin::View(&MZ_MCP_DATA_PRODUCT_DETAILS), | ||
| // NOTE: This list is a dependency order, and in a fresh environment it |
There was a problem hiding this comment.
why did we add this comment? is it not clear from how things work so far? Might just want to drop this and spare the noise?
There was a problem hiding this comment.
Fair, dropped. It was explaining a property of the list that holds regardless of this change.
49e130d to
9783a3e
Compare
| ## `mz_object_hydration_history` | ||
|
|
||
| The `mz_object_hydration_history` table records completed hydration of indexes and | ||
| materialized views, one row per object, replica, and installation. Rows are retained |
There was a problem hiding this comment.
what's the "installation" here?
| // its own schedule. Nothing reads this table at an old timestamp. | ||
| is_retained_metrics_object: false, | ||
| access: vec![PUBLIC_SELECT], | ||
| // No ontology links: a history row deliberately outlives the object and the |
There was a problem hiding this comment.
are we sure about the ontology links? stll helpful to say what this points at, no?
| ontology: None, | ||
| }); | ||
|
|
||
| pub static MZ_OBJECT_HYDRATION_HISTORY_IND: LazyLock<BuiltinIndex> = |
There was a problem hiding this comment.
ahh, I really think we shouldn't add the index by default, the history collection can grow quite a bit, so this index might grow out of hand, no?
9783a3e to
950d1d0
Compare
950d1d0 to
4fc49af
Compare
4fc49af to
5dc9944
Compare
QA LLM Review1. MEDIUM -- promised
|
5dc9944 to
925bc3a
Compare
|
Both valid, thanks. Fixed. 1. Stale record. Correct, and the reason it matters is the reason I fixed it rather than shrugged. The index was dropped deliberately after review (an arrangement holding the whole table, growing with objects times replicas times re-hydrations, for a query nobody runs yet), and the Commit message and PR body now say there is no index and no step, and the commit message carries the forward-looking warning so it is attached to the thing someone will read when they add one:
2. The key. Dropped it. Your asymmetry argument is the decisive one: the key buys a I also checked what comparable tables do, and none of them declare a key: On whether a duplicate is actually possible: I believe it is prevented by construction, since the collector's subscribe reads the history table, so a second write for the same episode would have to be computed from a frontier that does not yet include the first, and the OCC write then fails with |
925bc3a to
025d91f
Compare
025d91f to
58e4a1e
Compare
58e4a1e to
4ab26a2
Compare
4ab26a2 to
2ba3fb3
Compare
QA LLM Review1. MEDIUM -- the migration guards block
|
2ba3fb3 to
32cbb0a
Compare
|
Both valid, thanks. Fixed in the latest push. 1, the guards blocking Both guards are now scoped to Also corrected the two comments that said 2, On |
32cbb0a to
f34d18e
Compare
f34d18e to
da15f27
Compare
da15f27 to
3f16731
Compare
3f16731 to
cfcedc3
Compare
Adds the durable table that hydration episodes are recorded into. Nothing writes it yet, the collector arrives separately. The table is in `mz_internal` because its contents are best effort and its `status` column will gain values as more hydration events become observable. An episode is identified by `(object_id, replica_id, installed_at)`, using the replica-stamped installation time because it is stable across an environmentd restart. That identity is not declared as a key on the relation: the collector's anti-join is what keeps it unique, and telling the optimizer a best-effort sampler's output is unique would turn any duplicate into a silently wrong query result. None of the comparable history tables declare one either. No index. An arrangement on the catalog server would hold the whole table, which grows with objects times replicas times re-hydrations, and nothing queries this table by key yet. NOTE: Adding one later is not only an index. `make_mz_indexes` inlines the builtin index set as VALUES, so a new index changes the `mz_indexes` fingerprint and needs a `MigrationStep::replacement` for it pinned to the then-current dev version. A step at a stale version is skipped and the fingerprint check panics at catalog open. Contents are exempt from the bootstrap reset and from forced schema migrations, since a sampled history cannot be rebuilt from anything else once it is gone. Durability is best effort in both directions, and the assert added here is a tripwire so that clearing the table is chosen rather than stumbled into. Ref: SQL-644
cfcedc3 to
64d8df3
Compare
Stacked on #38345.
Motivation
The durable surface that hydration episodes are recorded into. Nothing writes it yet, the collector arrives in the next PR, so this can be reviewed purely as a catalog change.
Design doc:
20260817_durable_object_hydration_history.mdDescription
Adds
mz_internal.mz_object_hydration_history. No index, see below.The table is in
mz_internalrather thanmz_catalogbecause its contents are best effort and itsstatuscolumn and nullable timestamps will gain meanings as more hydration events become observable. Its closest sibling,mz_internal.mz_object_arrangement_size_history, sits there for the same reason.An episode is identified by
(object_id, replica_id, installed_at). The replica-stamped installation time is used because it is stable across an environmentd restart, and becausestarted_atis null while an object waits to run.That identity is not declared as a key on the relation. A key is a promise to the optimizer, which may then elide a
DISTINCTor assume a join cardinality, so one duplicate from a best-effort sampler becomes a silently wrong answer rather than a duplicate row. The collector's anti-join keeps the identity unique, and none of the comparable history tables (mz_object_arrangement_size_history,mz_cluster_replica_metrics_history,mz_cluster_replica_status_history,mz_storage_usage_by_shard) declare a key either.There is also no index. An arrangement on the catalog server would hold the whole table, which grows with objects times replicas times re-hydrations, and nothing queries this table by key yet. A user query scans a table bounded by retention, and an index would not help the collector anyway, since its anti-join runs on the targeted user replica.
Adding one later is not only an index:
make_mz_indexesinlines the builtin index set as VALUES, so a new index changes themz_indexesfingerprint and needs aMigrationStep::replacementpinned to the then-current dev version, or the fingerprint check panics at catalog open. That is called out in the commit message for whoever does it.Two things about durability are worth pulling out of the diff:
plan_migrationis a tripwire, not a wall: there is no correctness hazard in truncating this table, so the comment there says how to give the exemption up deliberately and to note in the release notes that the history restarts. The user-facing docs say the matching thing, that this is a record to look at and not a data source to build on.The bootstrap-reset filter is also converted from comparing names and schema specifiers to a set of resolved ids, which is what adding a third table to it made worth doing.
Verification
Catalog goldens (builtin counts, OIDs, index accounting,
information_schema, the autogeneratedmz_internalrelation spec) are updated.test_builtin_schema_migrationexercises the migration path.This release will add the
mz_internal.mz_object_hydration_historytable.Ref: SQL-644