Skip to content

Commit da15f27

Browse files
committed
catalog: add mz_object_hydration_history
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
1 parent 35ae7a4 commit da15f27

14 files changed

Lines changed: 210 additions & 33 deletions

File tree

doc/user/content/reference/system-catalog/mz_internal.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,28 @@ The `mz_object_history` view enriches the [`mz_catalog.mz_objects`](/reference/s
704704
| `created_at` | [`timestamp with time zone`] | Wall-clock timestamp of when the object was created. `NULL` for built in system objects. |
705705
| `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. |
706706

707+
## `mz_object_hydration_history`
708+
709+
The `mz_object_hydration_history` table records completed hydration of indexes and
710+
materialized views, with one row for each time an object hydrated on a replica. Rows
711+
are retained for 30 days, and `object_id`, `cluster_id`, and `replica_id` may name
712+
objects that no longer exist.
713+
714+
Recording is best effort. Only successful hydration is recorded, an episode can be
715+
missed if the object or its replica goes away before the episode is recorded, and a
716+
schema change to this table in a future release may clear its contents.
717+
718+
<!-- RELATION_SPEC mz_internal.mz_object_hydration_history -->
719+
| Field | Type | Meaning |
720+
| -------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
721+
| `object_id` | [`text`] | The ID of the index or materialized view. May name an object that no longer exists. |
722+
| `cluster_id` | [`text`] | The ID of the object's cluster. |
723+
| `replica_id` | [`text`] | The ID of the cluster replica. May name a replica that no longer exists. |
724+
| `installed_at` | [`timestamp with time zone`] | When the object's dataflow was installed on the replica. |
725+
| `started_at` | [`timestamp with time zone`] | When hydration work began, or `NULL` if the replica reported none. A replica that observed no start reports the installation time instead, so a zero interval between the two does not mean the dataflow started immediately. |
726+
| `finished_at` | [`timestamp with time zone`] | When hydration finished. |
727+
| `status` | [`text`] | The terminal status. Currently always `hydrated`. |
728+
707729
## `mz_object_transitive_dependencies`
708730

709731
The `mz_object_transitive_dependencies` view describes the transitive dependency structure between

src/adapter/src/catalog/open/builtin_schema_migration.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ use futures::future::BoxFuture;
3737
use mz_build_info::{BuildInfo, DUMMY_BUILD_INFO};
3838
use mz_catalog::builtin::{
3939
BUILTIN_LOOKUP, Builtin, Fingerprint, MZ_CATALOG_RAW, MZ_CATALOG_RAW_DESCRIPTION,
40-
MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY_DESCRIPTION, MZ_STORAGE_USAGE_BY_SHARD,
40+
MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY_DESCRIPTION, MZ_OBJECT_HYDRATION_HISTORY,
41+
MZ_OBJECT_HYDRATION_HISTORY_DESCRIPTION, MZ_STORAGE_USAGE_BY_SHARD,
4142
MZ_STORAGE_USAGE_BY_SHARD_DESCRIPTION, RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL,
4243
};
4344
use mz_catalog::config::BuiltinItemMigrationConfig;
@@ -751,6 +752,23 @@ impl Migration {
751752
"mz_object_arrangement_size_history cannot be migrated or else the table will be truncated"
752753
);
753754

755+
// Unlike the two tables above, there is no correctness hazard here, a
756+
// truncation would only lose history. This is a tripwire so that the
757+
// loss is chosen rather than stumbled into: if a schema change to
758+
// this table is worth clearing it for, remove this assert along with
759+
// the exemption in `plan_forced_migration`, and say in the release
760+
// notes that the history restarts.
761+
//
762+
// Only `Replacement` loses the rows. An `Evolution` keeps the shard
763+
// and its contents, so it is the path a schema change to this table
764+
// should take and we let it through.
765+
if step.mechanism == Mechanism::Replacement {
766+
assert_ne!(
767+
&*MZ_OBJECT_HYDRATION_HISTORY_DESCRIPTION, object,
768+
"replacing mz_object_hydration_history clears it, see the comment above"
769+
);
770+
}
771+
754772
// `mz_catalog_raw` cannot be migrated because it contains the durable catalog and it
755773
// wouldn't be very durable if we allowed it to be truncated.
756774
assert_ne!(
@@ -816,9 +834,25 @@ impl Migration {
816834
.filter(|(_, info)| {
817835
use Builtin::*;
818836
match info.builtin {
819-
// Filter out the 'mz_storage_usage_by_shard' table since we need to retain
820-
// that info for billing purposes.
821-
Table(table) => **table != *MZ_STORAGE_USAGE_BY_SHARD,
837+
// A forced replacement allocates a fresh shard, which
838+
// discards the table's contents. Exclude the tables whose
839+
// contents are the point: storage usage is retained for
840+
// billing, and hydration history cannot be rebuilt from any
841+
// other source.
842+
//
843+
// The hydration history takes part in a forced `Evolution`,
844+
// which keeps the rows. It has to: dev upgrades force one for
845+
// every object, and a table left out of the plan never gets
846+
// its new schema registered, so `update_fingerprints` panics
847+
// at open as soon as the desc changes. The exemption is best
848+
// effort, not a guarantee. See the tripwire in
849+
// `validate_migration_steps` for how to give it up
850+
// deliberately.
851+
Table(table) => {
852+
**table != *MZ_STORAGE_USAGE_BY_SHARD
853+
&& (mechanism != Mechanism::Replacement
854+
|| **table != *MZ_OBJECT_HYDRATION_HISTORY)
855+
}
822856
MaterializedView(..) => true,
823857
Source(source) => **source != *MZ_CATALOG_RAW,
824858
Log(..) | View(..) | Type(..) | Func(..) | Index(..) | Connection(..) => false,

src/adapter/src/coord.rs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ use mz_adapter_types::dyncfgs::{
9898
use mz_auth::password::Password;
9999
use mz_build_info::BuildInfo;
100100
use mz_catalog::builtin::{
101-
BUILTINS, BUILTINS_STATIC, MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY, MZ_STORAGE_USAGE_BY_SHARD,
101+
BUILTINS, BUILTINS_STATIC, MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY, MZ_OBJECT_HYDRATION_HISTORY,
102+
MZ_STORAGE_USAGE_BY_SHARD,
102103
};
103104
use mz_catalog::config::{AwsPrincipalContext, BuiltinItemMigrationConfig, ClusterReplicaSizeMap};
104105
use mz_catalog::durable::OpenableDurableCatalogState;
@@ -149,7 +150,7 @@ use mz_secrets::cache::CachingSecretsReader;
149150
use mz_secrets::{SecretsController, SecretsReader};
150151
use mz_sql::ast::{Raw, Statement};
151152
use mz_sql::catalog::{CatalogCluster, EnvironmentId};
152-
use mz_sql::names::{QualifiedItemName, ResolvedIds, SchemaSpecifier};
153+
use mz_sql::names::{QualifiedItemName, ResolvedIds};
153154
use mz_sql::optimizer_metrics::OptimizerMetrics;
154155
use mz_sql::plan::{
155156
self, AlterSinkPlan, ConnectionDetails, CreateConnectionPlan, HirRelationExpr,
@@ -3120,29 +3121,19 @@ impl Coordinator {
31203121
debug!("coordinator init: resetting system tables");
31213122
let read_ts = self.get_local_read_ts().await;
31223123

3123-
// Filter out tables whose contents must survive restarts:
3124-
// 'mz_storage_usage_by_shard' for billing, and
3125-
// 'mz_object_arrangement_size_history', which accumulates history that
3126-
// is pruned by its own retention period instead.
3127-
let mz_storage_usage_by_shard_schema: SchemaSpecifier = self
3128-
.catalog()
3129-
.resolve_system_schema(MZ_STORAGE_USAGE_BY_SHARD.schema)
3130-
.into();
3131-
let arrangement_size_history_schema: SchemaSpecifier = self
3132-
.catalog()
3133-
.resolve_system_schema(MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY.schema)
3134-
.into();
3135-
let is_retained_across_restarts = |meta: &TableMetadata| -> bool {
3136-
(meta.name.item == MZ_STORAGE_USAGE_BY_SHARD.name
3137-
&& meta.name.qualifiers.schema_spec == mz_storage_usage_by_shard_schema)
3138-
|| (meta.name.item == MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY.name
3139-
&& meta.name.qualifiers.schema_spec == arrangement_size_history_schema)
3140-
};
3124+
let retained_across_restarts = BTreeSet::from([
3125+
self.catalog()
3126+
.resolve_builtin_table(&MZ_STORAGE_USAGE_BY_SHARD),
3127+
self.catalog()
3128+
.resolve_builtin_table(&MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY),
3129+
self.catalog()
3130+
.resolve_builtin_table(&MZ_OBJECT_HYDRATION_HISTORY),
3131+
]);
31413132

31423133
let mut retraction_tasks = Vec::new();
31433134
let system_tables: Vec<_> = table_metas
31443135
.iter()
3145-
.filter(|meta| meta.id.is_system() && !is_retained_across_restarts(meta))
3136+
.filter(|meta| meta.id.is_system() && !retained_across_restarts.contains(&meta.id))
31463137
.collect();
31473138

31483139
for system_table in system_tables {

src/catalog/src/builtin.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,14 @@ pub static MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY_DESCRIPTION: LazyLock<SystemObject
836836
object_type: CatalogItemType::Table,
837837
object_name: MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY.name.to_string(),
838838
});
839+
840+
/// Identifies [`MZ_OBJECT_HYDRATION_HISTORY`] for the schema-migration guard.
841+
pub static MZ_OBJECT_HYDRATION_HISTORY_DESCRIPTION: LazyLock<SystemObjectDescription> =
842+
LazyLock::new(|| SystemObjectDescription {
843+
schema_name: MZ_OBJECT_HYDRATION_HISTORY.schema.to_string(),
844+
object_type: CatalogItemType::Table,
845+
object_name: MZ_OBJECT_HYDRATION_HISTORY.name.to_string(),
846+
});
839847
pub const MZ_SYSTEM_ROLE: BuiltinRole = BuiltinRole {
840848
id: MZ_SYSTEM_ROLE_ID,
841849
name: SYSTEM_USER_NAME,
@@ -1465,6 +1473,7 @@ pub static BUILTINS_STATIC: LazyLock<Vec<Builtin<NameReference>>> = LazyLock::ne
14651473
Builtin::View(&MZ_INDEX_ADVICE),
14661474
Builtin::View(&MZ_MCP_DATA_PRODUCTS),
14671475
Builtin::View(&MZ_MCP_DATA_PRODUCT_DETAILS),
1476+
Builtin::Table(&MZ_OBJECT_HYDRATION_HISTORY),
14681477
];
14691478

14701479
builtin_items.extend(notice::builtins());

src/catalog/src/builtin/mz_internal.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5025,6 +5025,106 @@ pub static MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY_TS_IND: LazyLock<BuiltinIndex> =
50255025
is_retained_metrics_object: true,
50265026
});
50275027

5028+
/// Completed hydration episodes, one row per object, replica, and installation.
5029+
///
5030+
/// Exempt from the bootstrap reset and from forced shard replacement, since the
5031+
/// contents cannot be rebuilt from anything else. Schema evolution keeps them and
5032+
/// applies normally. Clearing them for a schema change is still allowed, see the
5033+
/// tripwire in `validate_migration_steps`.
5034+
pub static MZ_OBJECT_HYDRATION_HISTORY: LazyLock<BuiltinTable> = LazyLock::new(|| BuiltinTable {
5035+
name: "mz_object_hydration_history",
5036+
schema: MZ_INTERNAL_SCHEMA,
5037+
oid: oid::TABLE_MZ_OBJECT_HYDRATION_HISTORY_OID,
5038+
desc: RelationDesc::builder()
5039+
.with_column("object_id", SqlScalarType::String.nullable(false))
5040+
.with_column("cluster_id", SqlScalarType::String.nullable(false))
5041+
.with_column("replica_id", SqlScalarType::String.nullable(false))
5042+
.with_column(
5043+
"installed_at",
5044+
SqlScalarType::TimestampTz { precision: None }.nullable(false),
5045+
)
5046+
.with_column(
5047+
"started_at",
5048+
SqlScalarType::TimestampTz { precision: None }.nullable(true),
5049+
)
5050+
.with_column(
5051+
"finished_at",
5052+
SqlScalarType::TimestampTz { precision: None }.nullable(true),
5053+
)
5054+
.with_column("status", SqlScalarType::String.nullable(false))
5055+
.finish(),
5056+
column_comments: BTreeMap::from_iter([
5057+
(
5058+
"object_id",
5059+
"The ID of the index or materialized view. May name an object that no longer exists.",
5060+
),
5061+
("cluster_id", "The ID of the object's cluster."),
5062+
(
5063+
"replica_id",
5064+
"The ID of the cluster replica. May name a replica that no longer exists.",
5065+
),
5066+
(
5067+
"installed_at",
5068+
"When the object's dataflow was installed on the replica.",
5069+
),
5070+
(
5071+
"started_at",
5072+
"When hydration work began, or `NULL` if the replica reported none. A replica that observed no start reports the installation time instead, so a zero interval between the two does not mean the dataflow started immediately.",
5073+
),
5074+
("finished_at", "When hydration finished."),
5075+
(
5076+
"status",
5077+
"The terminal status. Currently always `hydrated`.",
5078+
),
5079+
]),
5080+
// Not a retained-metrics object: that would pin a 30 day compaction window,
5081+
// and our history lives in the rows, which the retention sweep retracts on
5082+
// its own schedule. Nothing reads this table at an old timestamp.
5083+
is_retained_metrics_object: false,
5084+
access: vec![PUBLIC_SELECT],
5085+
ontology: Some(Ontology {
5086+
entity_name: "object_hydration_event",
5087+
description: "Completed hydration of an index or materialized view on a replica",
5088+
// NOTE: These references outlive what they point at. A row deliberately
5089+
// survives the object and the replica it describes, so resolving one
5090+
// against the catalog can come up empty.
5091+
links: &const {
5092+
[
5093+
OntologyLink {
5094+
name: "hydration_of_object",
5095+
target: "object",
5096+
properties: LinkProperties::fk_typed(
5097+
"object_id",
5098+
"id",
5099+
Cardinality::ManyToOne,
5100+
mz_repr::SemanticType::CatalogItemId,
5101+
),
5102+
},
5103+
OntologyLink {
5104+
name: "hydrated_on_cluster",
5105+
target: "cluster",
5106+
properties: LinkProperties::fk("cluster_id", "id", Cardinality::ManyToOne),
5107+
},
5108+
OntologyLink {
5109+
name: "hydrated_on_replica",
5110+
target: "replica",
5111+
properties: LinkProperties::fk_typed(
5112+
"replica_id",
5113+
"id",
5114+
Cardinality::ManyToOne,
5115+
mz_repr::SemanticType::CatalogItemId,
5116+
),
5117+
},
5118+
]
5119+
},
5120+
column_semantic_types: &[
5121+
("object_id", SemanticType::CatalogItemId),
5122+
("cluster_id", SemanticType::ClusterId),
5123+
("replica_id", SemanticType::ReplicaId),
5124+
],
5125+
}),
5126+
});
5127+
50285128
pub static MZ_COMPUTE_HYDRATION_STATUSES: LazyLock<BuiltinView> = LazyLock::new(|| BuiltinView {
50295129
name: "mz_compute_hydration_statuses",
50305130
schema: MZ_INTERNAL_SCHEMA,

src/pgrepr-consts/src/oid.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,3 +826,4 @@ pub const VIEW_MZ_OBJECT_GRAPH_EDGES_OID: u32 = 17116;
826826
pub const INDEX_MZ_OBJECT_GRAPH_EDGES_IND_OID: u32 = 17117;
827827
pub const VIEW_MZ_BUILTIN_TABLES_OID: u32 = 17118;
828828
pub const VIEW_MZ_BUILTIN_VIEWS_OID: u32 = 17119;
829+
pub const TABLE_MZ_OBJECT_HYDRATION_HISTORY_OID: u32 = 17120;

test/sqllogictest/autogenerated/mz_internal.slt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,17 @@ object_type text The␠type␠of␠the␠object:␠one␠of␠`table`,␠`sour
388388
created_at timestamp␠with␠time␠zone Wall-clock␠timestamp␠of␠when␠the␠object␠was␠created.␠`NULL`␠for␠built␠in␠system␠objects.
389389
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.
390390

391+
query TTT
392+
SELECT name, type, comment FROM objects WHERE schema = 'mz_internal' AND object = 'mz_object_hydration_history' ORDER BY position
393+
----
394+
object_id text The␠ID␠of␠the␠index␠or␠materialized␠view.␠May␠name␠an␠object␠that␠no␠longer␠exists.
395+
cluster_id text The␠ID␠of␠the␠object's␠cluster.
396+
replica_id text The␠ID␠of␠the␠cluster␠replica.␠May␠name␠a␠replica␠that␠no␠longer␠exists.
397+
installed_at timestamp␠with␠time␠zone When␠the␠object's␠dataflow␠was␠installed␠on␠the␠replica.
398+
started_at timestamp␠with␠time␠zone When␠hydration␠work␠began,␠or␠`NULL`␠if␠the␠replica␠reported␠none.␠A␠replica␠that␠observed␠no␠start␠reports␠the␠installation␠time␠instead,␠so␠a␠zero␠interval␠between␠the␠two␠does␠not␠mean␠the␠dataflow␠started␠immediately.
399+
finished_at timestamp␠with␠time␠zone When␠hydration␠finished.
400+
status text The␠terminal␠status.␠Currently␠always␠`hydrated`.
401+
391402
query TTT
392403
SELECT name, type, comment FROM objects WHERE schema = 'mz_internal' AND object = 'mz_object_transitive_dependencies' ORDER BY position
393404
----
@@ -839,6 +850,7 @@ mz_object_fully_qualified_names
839850
mz_object_global_ids
840851
mz_object_graph_edges
841852
mz_object_history
853+
mz_object_hydration_history
842854
mz_object_lifetimes
843855
mz_object_oid_alias
844856
mz_object_transitive_dependencies

test/sqllogictest/catalog_server_explain.slt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5453,7 +5453,7 @@ mz_catalog.mz_tables:
54535453
Project: #3, #0, #4, #1, #5, #2, #6, #6, #6
54545454
Map: "s1", null
54555455
→Arrange (#1{schema_name}, #2{name})
5456-
→Constant (29 rows)
5456+
→Constant (30 rows)
54575457
→Arrange (#0{schema_name}) (#0{schema_name}, #1{name})
54585458
→Fused with Child Map/Filter/Project
54595459
Project: #5, #4, #6
@@ -8212,7 +8212,7 @@ query T multiline
82128212
EXPLAIN SELECT * FROM "mz_internal"."mz_builtin_tables";
82138213
----
82148214
Explained Query (fast path):
8215-
→Constant (29 rows)
8215+
→Constant (30 rows)
82168216

82178217
Target cluster: mz_catalog_server
82188218

@@ -10204,7 +10204,7 @@ query T multiline
1020410204
EXPLAIN SELECT * FROM "mz_internal"."mz_ontology_entity_types";
1020510205
----
1020610206
Explained Query (fast path):
10207-
→Constant (134 rows)
10207+
→Constant (135 rows)
1020810208

1020910209
Target cluster: mz_catalog_server
1021010210

@@ -10214,7 +10214,7 @@ query T multiline
1021410214
EXPLAIN SELECT * FROM "mz_internal"."mz_ontology_link_types";
1021510215
----
1021610216
Explained Query (fast path):
10217-
→Constant (173 rows)
10217+
→Constant (176 rows)
1021810218

1021910219
Target cluster: mz_catalog_server
1022010220

@@ -10228,7 +10228,7 @@ Explained Query:
1022810228
cte l0 =
1022910229
→Differential Join %1:mz_schemas[#0{id}] » %2:mz_objects[#2{schema_id}] » %0[#0{schema_name}, #1{table_name}] » %3:mz_columns[#0{id}]
1023010230
→Arrange (#0{schema_name}, #1{table_name})
10231-
→Constant (134 rows)
10231+
→Constant (135 rows)
1023210232
→Arrange (#0{id})
1023310233
→Fused with Child Map/Filter/Project
1023410234
Project: #1, #3
@@ -10281,7 +10281,7 @@ Explained Query:
1028110281
→Differential Join %0:l4[#0{entity_name}, #1{name}] » %1[#0{entity_name}, #1{column_name}]
1028210282
→Arranged l4
1028310283
→Arrange (#0{entity_name}, #1{column_name})
10284-
→Constant (272 rows)
10284+
→Constant (275 rows)
1028510285
→Return
1028610286
→Union
1028710287
→Map/Filter/Project

test/sqllogictest/information_schema_tables.slt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,10 @@ mz_object_history
509509
VIEW
510510
materialize
511511
mz_internal
512+
mz_object_hydration_history
513+
BASE TABLE
514+
materialize
515+
mz_internal
512516
mz_object_lifetimes
513517
VIEW
514518
materialize

0 commit comments

Comments
 (0)