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
12127b7 to
eca12d0
Compare
eca12d0 to
a95dd2d
Compare
a95dd2d to
54b2a8b
Compare
4e32b7b to
334cea5
Compare
334cea5 to
8baff32
Compare
8baff32 to
b1e5e12
Compare
82002e5 to
362151b
Compare
362151b to
ce60622
Compare
ce60622 to
44dba1e
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
Stacked on #38344.
Motivation
Adds the durable surface that hydration episodes are recorded into. The collector arrives in the next PR, so this rung can be reviewed as a catalog change.
Design doc:
20260817_durable_object_hydration_history.mdDescription
Adds
mz_internal.mz_object_hydration_history, with no index. The table starts inmz_internalto mark it unstable while its shape and semantics settle.An episode is identified operationally by
(object_id, replica_id, installed_at).object_idholds the dataflow id reported by the replica, withSemanticType::GlobalId, so consumers resolve it throughmz_internal.mz_object_global_idsrather than joiningmz_objectsdirectly.installed_atis replica-stamped and stable across environmentd restarts.The identity is not declared as a relation key. A key is an optimizer contract, while this best-effort sampler enforces uniqueness through its anti-join. A duplicate must remain visible rather than letting the optimizer silently elide it.
There is no index initially. The collector anti-joins by history identity, but its subscribe runs on the selected user replica and cannot use an index arranged on the catalog server. Such an index would pin the whole table without removing the collector's recurring import and arrangement cost. Adding one later also changes the
mz_indexesfingerprint, so it requires aMigrationStep::replacementformz_indexespinned to the then-current development version.Durability is best effort. The table is exempt from the bootstrap reset and from forced replacement, because either would discard sampled history that cannot be rebuilt. Schema evolution is allowed and keeps the shard and rows. A replacement step naming this table trips an assert in
validate_migration_steps; giving the exemption up is meant to be deliberate and release-noted. A focused policy test pins evolution participation and replacement exclusion.The bootstrap-reset filter is converted from name/schema comparisons to a set of resolved ids, which is clearer now that several history tables are exempt. The ontology link for
replica_idusesSemanticType::ReplicaId, matching both the column and its target. User documentation states that 30 days is the retention default, disabling collection pauses retention, and dropped dataflow IDs may no longer resolve throughmz_object_global_ids.Verification
Catalog goldens cover builtin counts, OIDs, index accounting,
information_schema, and the autogeneratedmz_internalrelation spec. The catalog migration tests exercise the migration path.This release will add the
mz_internal.mz_object_hydration_historytable.Ref: SQL-644