Skip to content

Commit 4fc49af

Browse files
committed
catalog: add mz_object_hydration_history
Adds the durable table that hydration episodes are recorded into, along with the index that serves the question users ask of it, how long a given object took to hydrate. Nothing writes the table yet. 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 keyed by `(object_id, replica_id, installed_at)`, using the replica-stamped installation time because it is stable across an environmentd restart. 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. Adding a builtin index changes the fingerprint of the `mz_indexes` materialized view, which inlines the builtin index set as VALUES for exactly that reason, so this also declares the required replacement migration step. Ref: SQL-644
1 parent 0518a86 commit 4fc49af

14 files changed

Lines changed: 194 additions & 31 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
@@ -688,6 +688,28 @@ The `mz_object_history` view enriches the [`mz_catalog.mz_objects`](/reference/s
688688
| `created_at` | [`timestamp with time zone`] | Wall-clock timestamp of when the object was created. `NULL` for built in system objects. |
689689
| `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. |
690690

691+
## `mz_object_hydration_history`
692+
693+
The `mz_object_hydration_history` table records completed hydration of indexes and
694+
materialized views, with one row for each time an object hydrated on a replica. Rows
695+
are retained for 30 days, and `object_id`, `cluster_id`, and `replica_id` may name
696+
objects that no longer exist.
697+
698+
Recording is best effort. Only successful hydration is recorded, an episode can be
699+
missed if the object or its replica goes away before the episode is recorded, and a
700+
schema change to this table in a future release may clear its contents.
701+
702+
<!-- RELATION_SPEC mz_internal.mz_object_hydration_history -->
703+
| Field | Type | Meaning |
704+
| -------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
705+
| `object_id` | [`text`] | The ID of the index or materialized view. May name an object that no longer exists. |
706+
| `cluster_id` | [`text`] | The ID of the object's cluster. |
707+
| `replica_id` | [`text`] | The ID of the cluster replica. May name a replica that no longer exists. |
708+
| `installed_at` | [`timestamp with time zone`] | When the object's dataflow was installed on the replica. |
709+
| `started_at` | [`timestamp with time zone`] | When hydration work began, or `NULL` if the replica did not observe a start for this episode. |
710+
| `finished_at` | [`timestamp with time zone`] | When hydration finished. |
711+
| `status` | [`text`] | The terminal status. Currently always `hydrated`. |
712+
691713
## `mz_object_transitive_dependencies`
692714

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

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

Lines changed: 24 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;
@@ -725,6 +726,17 @@ impl Migration {
725726
"mz_object_arrangement_size_history cannot be migrated or else the table will be truncated"
726727
);
727728

729+
// Unlike the two tables above, there is no correctness hazard here, a
730+
// truncation would only lose history. This is a tripwire so that the
731+
// loss is chosen rather than stumbled into: if a schema change to
732+
// this table is worth clearing it for, remove this assert along with
733+
// the exemption in `plan_forced_migration`, and say in the release
734+
// notes that the history restarts.
735+
assert_ne!(
736+
&*MZ_OBJECT_HYDRATION_HISTORY_DESCRIPTION, object,
737+
"migrating mz_object_hydration_history clears it, see the comment above"
738+
);
739+
728740
// `mz_catalog_raw` cannot be migrated because it contains the durable catalog and it
729741
// wouldn't be very durable if we allowed it to be truncated.
730742
assert_ne!(
@@ -790,9 +802,17 @@ impl Migration {
790802
.filter(|(_, info)| {
791803
use Builtin::*;
792804
match info.builtin {
793-
// Filter out the 'mz_storage_usage_by_shard' table since we need to retain
794-
// that info for billing purposes.
795-
Table(table) => **table != *MZ_STORAGE_USAGE_BY_SHARD,
805+
// A forced migration allocates a fresh shard, which discards
806+
// the table's contents. Exclude the tables whose contents
807+
// are the point: storage usage is retained for billing, and
808+
// hydration history cannot be rebuilt from any other source.
809+
// The hydration exemption is best effort, not a guarantee.
810+
// See the tripwire in `plan_migration` for how to give it up
811+
// deliberately.
812+
Table(table) => {
813+
**table != *MZ_STORAGE_USAGE_BY_SHARD
814+
&& **table != *MZ_OBJECT_HYDRATION_HISTORY
815+
}
796816
MaterializedView(..) => true,
797817
Source(source) => **source != *MZ_CATALOG_RAW,
798818
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,
@@ -3064,29 +3065,19 @@ impl Coordinator {
30643065
debug!("coordinator init: resetting system tables");
30653066
let read_ts = self.get_local_read_ts().await;
30663067

3067-
// Filter out tables whose contents must survive restarts:
3068-
// 'mz_storage_usage_by_shard' for billing, and
3069-
// 'mz_object_arrangement_size_history', which accumulates history that
3070-
// is pruned by its own retention period instead.
3071-
let mz_storage_usage_by_shard_schema: SchemaSpecifier = self
3072-
.catalog()
3073-
.resolve_system_schema(MZ_STORAGE_USAGE_BY_SHARD.schema)
3074-
.into();
3075-
let arrangement_size_history_schema: SchemaSpecifier = self
3076-
.catalog()
3077-
.resolve_system_schema(MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY.schema)
3078-
.into();
3079-
let is_retained_across_restarts = |meta: &TableMetadata| -> bool {
3080-
(meta.name.item == MZ_STORAGE_USAGE_BY_SHARD.name
3081-
&& meta.name.qualifiers.schema_spec == mz_storage_usage_by_shard_schema)
3082-
|| (meta.name.item == MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY.name
3083-
&& meta.name.qualifiers.schema_spec == arrangement_size_history_schema)
3084-
};
3068+
let retained_across_restarts = BTreeSet::from([
3069+
self.catalog()
3070+
.resolve_builtin_table(&MZ_STORAGE_USAGE_BY_SHARD),
3071+
self.catalog()
3072+
.resolve_builtin_table(&MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY),
3073+
self.catalog()
3074+
.resolve_builtin_table(&MZ_OBJECT_HYDRATION_HISTORY),
3075+
]);
30853076

30863077
let mut retraction_tasks = Vec::new();
30873078
let system_tables: Vec<_> = table_metas
30883079
.iter()
3089-
.filter(|meta| meta.id.is_system() && !is_retained_across_restarts(meta))
3080+
.filter(|meta| meta.id.is_system() && !retained_across_restarts.contains(&meta.id))
30903081
.collect();
30913082

30923083
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,
@@ -1463,6 +1471,7 @@ pub static BUILTINS_STATIC: LazyLock<Vec<Builtin<NameReference>>> = LazyLock::ne
14631471
Builtin::View(&MZ_INDEX_ADVICE),
14641472
Builtin::View(&MZ_MCP_DATA_PRODUCTS),
14651473
Builtin::View(&MZ_MCP_DATA_PRODUCT_DETAILS),
1474+
Builtin::Table(&MZ_OBJECT_HYDRATION_HISTORY),
14661475
];
14671476

14681477
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
@@ -4965,6 +4965,106 @@ pub static MZ_OBJECT_ARRANGEMENT_SIZE_HISTORY_TS_IND: LazyLock<BuiltinIndex> =
49654965
is_retained_metrics_object: true,
49664966
});
49674967

4968+
/// Completed hydration episodes, one row per object, replica, and installation.
4969+
///
4970+
/// Exempt from the bootstrap reset and from forced schema migrations, since the
4971+
/// contents cannot be rebuilt from anything else. Clearing them for a schema
4972+
/// change is still allowed, see the tripwire in `plan_migration`.
4973+
pub static MZ_OBJECT_HYDRATION_HISTORY: LazyLock<BuiltinTable> = LazyLock::new(|| BuiltinTable {
4974+
name: "mz_object_hydration_history",
4975+
schema: MZ_INTERNAL_SCHEMA,
4976+
oid: oid::TABLE_MZ_OBJECT_HYDRATION_HISTORY_OID,
4977+
desc: RelationDesc::builder()
4978+
.with_column("object_id", SqlScalarType::String.nullable(false))
4979+
.with_column("cluster_id", SqlScalarType::String.nullable(false))
4980+
.with_column("replica_id", SqlScalarType::String.nullable(false))
4981+
.with_column(
4982+
"installed_at",
4983+
SqlScalarType::TimestampTz { precision: None }.nullable(false),
4984+
)
4985+
.with_column(
4986+
"started_at",
4987+
SqlScalarType::TimestampTz { precision: None }.nullable(true),
4988+
)
4989+
.with_column(
4990+
"finished_at",
4991+
SqlScalarType::TimestampTz { precision: None }.nullable(true),
4992+
)
4993+
.with_column("status", SqlScalarType::String.nullable(false))
4994+
.with_key(vec![0, 2, 3])
4995+
.finish(),
4996+
column_comments: BTreeMap::from_iter([
4997+
(
4998+
"object_id",
4999+
"The ID of the index or materialized view. May name an object that no longer exists.",
5000+
),
5001+
("cluster_id", "The ID of the object's cluster."),
5002+
(
5003+
"replica_id",
5004+
"The ID of the cluster replica. May name a replica that no longer exists.",
5005+
),
5006+
(
5007+
"installed_at",
5008+
"When the object's dataflow was installed on the replica.",
5009+
),
5010+
(
5011+
"started_at",
5012+
"When hydration work began, or `NULL` if the replica did not observe a start for this episode.",
5013+
),
5014+
("finished_at", "When hydration finished."),
5015+
(
5016+
"status",
5017+
"The terminal status. Currently always `hydrated`.",
5018+
),
5019+
]),
5020+
// Not a retained-metrics object: that would pin a 30 day compaction window,
5021+
// and our history lives in the rows, which the retention sweep retracts on
5022+
// its own schedule. Nothing reads this table at an old timestamp.
5023+
is_retained_metrics_object: false,
5024+
access: vec![PUBLIC_SELECT],
5025+
ontology: Some(Ontology {
5026+
entity_name: "object_hydration_event",
5027+
description: "Completed hydration of an index or materialized view on a replica",
5028+
// NOTE: These references outlive what they point at. A row deliberately
5029+
// survives the object and the replica it describes, so resolving one
5030+
// against the catalog can come up empty.
5031+
links: &const {
5032+
[
5033+
OntologyLink {
5034+
name: "hydration_of_object",
5035+
target: "object",
5036+
properties: LinkProperties::fk_typed(
5037+
"object_id",
5038+
"id",
5039+
Cardinality::ManyToOne,
5040+
mz_repr::SemanticType::CatalogItemId,
5041+
),
5042+
},
5043+
OntologyLink {
5044+
name: "hydrated_on_cluster",
5045+
target: "cluster",
5046+
properties: LinkProperties::fk("cluster_id", "id", Cardinality::ManyToOne),
5047+
},
5048+
OntologyLink {
5049+
name: "hydrated_on_replica",
5050+
target: "replica",
5051+
properties: LinkProperties::fk_typed(
5052+
"replica_id",
5053+
"id",
5054+
Cardinality::ManyToOne,
5055+
mz_repr::SemanticType::CatalogItemId,
5056+
),
5057+
},
5058+
]
5059+
},
5060+
column_semantic_types: &[
5061+
("object_id", SemanticType::CatalogItemId),
5062+
("cluster_id", SemanticType::ClusterId),
5063+
("replica_id", SemanticType::ReplicaId),
5064+
],
5065+
}),
5066+
});
5067+
49685068
pub static MZ_COMPUTE_HYDRATION_STATUSES: LazyLock<BuiltinView> = LazyLock::new(|| BuiltinView {
49695069
name: "mz_compute_hydration_statuses",
49705070
schema: MZ_INTERNAL_SCHEMA,

src/pgrepr-consts/src/oid.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,3 +822,4 @@ pub const FUNC_PARSE_CONNECTION_DETAILS_OID: u32 = 17112;
822822
pub const FUNC_MZ_AWS_ACCOUNT_ID_OID: u32 = 17113;
823823
pub const FUNC_MZ_AWS_EXTERNAL_ID_PREFIX_OID: u32 = 17114;
824824
pub const FUNC_MZ_AWS_CONNECTION_ROLE_ARN_OID: u32 = 17115;
825+
pub const TABLE_MZ_OBJECT_HYDRATION_HISTORY_OID: u32 = 17116;

test/sqllogictest/autogenerated/mz_internal.slt

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

385+
query TTT
386+
SELECT name, type, comment FROM objects WHERE schema = 'mz_internal' AND object = 'mz_object_hydration_history' ORDER BY position
387+
----
388+
object_id text The␠ID␠of␠the␠index␠or␠materialized␠view.␠May␠name␠an␠object␠that␠no␠longer␠exists.
389+
cluster_id text The␠ID␠of␠the␠object's␠cluster.
390+
replica_id text The␠ID␠of␠the␠cluster␠replica.␠May␠name␠a␠replica␠that␠no␠longer␠exists.
391+
installed_at timestamp␠with␠time␠zone When␠the␠object's␠dataflow␠was␠installed␠on␠the␠replica.
392+
started_at timestamp␠with␠time␠zone When␠hydration␠work␠began,␠or␠`NULL`␠if␠the␠replica␠did␠not␠observe␠a␠start␠for␠this␠episode.
393+
finished_at timestamp␠with␠time␠zone When␠hydration␠finished.
394+
status text The␠terminal␠status.␠Currently␠always␠`hydrated`.
395+
385396
query TTT
386397
SELECT name, type, comment FROM objects WHERE schema = 'mz_internal' AND object = 'mz_object_transitive_dependencies' ORDER BY position
387398
----
@@ -830,6 +841,7 @@ mz_object_dependencies
830841
mz_object_fully_qualified_names
831842
mz_object_global_ids
832843
mz_object_history
844+
mz_object_hydration_history
833845
mz_object_lifetimes
834846
mz_object_oid_alias
835847
mz_object_transitive_dependencies

test/sqllogictest/catalog_server_explain.slt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9969,7 +9969,7 @@ query T multiline
99699969
EXPLAIN SELECT * FROM "mz_internal"."mz_ontology_entity_types";
99709970
----
99719971
Explained Query (fast path):
9972-
→Constant (134 rows)
9972+
→Constant (135 rows)
99739973

99749974
Target cluster: mz_catalog_server
99759975

@@ -9979,7 +9979,7 @@ query T multiline
99799979
EXPLAIN SELECT * FROM "mz_internal"."mz_ontology_link_types";
99809980
----
99819981
Explained Query (fast path):
9982-
→Constant (173 rows)
9982+
→Constant (176 rows)
99839983

99849984
Target cluster: mz_catalog_server
99859985

@@ -9993,7 +9993,7 @@ Explained Query:
99939993
cte l0 =
99949994
→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}]
99959995
→Arrange (#0{schema_name}, #1{table_name})
9996-
→Constant (134 rows)
9996+
→Constant (135 rows)
99979997
→Arrange (#0{id})
99989998
→Fused with Child Map/Filter/Project
99999999
Project: #1, #3
@@ -10046,7 +10046,7 @@ Explained Query:
1004610046
→Differential Join %0:l4[#0{entity_name}, #1{name}] » %1[#0{entity_name}, #1{column_name}]
1004710047
→Arranged l4
1004810048
→Arrange (#0{entity_name}, #1{column_name})
10049-
→Constant (272 rows)
10049+
→Constant (275 rows)
1005010050
→Return
1005110051
→Union
1005210052
→Map/Filter/Project

test/sqllogictest/information_schema_tables.slt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@ mz_object_history
497497
VIEW
498498
materialize
499499
mz_internal
500+
mz_object_hydration_history
501+
BASE TABLE
502+
materialize
503+
mz_internal
500504
mz_object_lifetimes
501505
VIEW
502506
materialize

test/sqllogictest/mz_catalog_server_index_accounting.slt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ mz_message_batch_counts_received_raw_s2_primary_idx CREATE␠INDEX␠"mz_messag
8383
mz_message_batch_counts_sent_raw_s2_primary_idx CREATE␠INDEX␠"mz_message_batch_counts_sent_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_message_batch_counts_sent_raw"␠("channel_id",␠"from_worker_id",␠"to_worker_id")
8484
mz_message_counts_received_raw_s2_primary_idx CREATE␠INDEX␠"mz_message_counts_received_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_message_counts_received_raw"␠("channel_id",␠"from_worker_id",␠"to_worker_id")
8585
mz_message_counts_sent_raw_s2_primary_idx CREATE␠INDEX␠"mz_message_counts_sent_raw_s2_primary_idx"␠IN␠CLUSTER␠[s2]␠ON␠"mz_introspection"."mz_message_counts_sent_raw"␠("channel_id",␠"from_worker_id",␠"to_worker_id")
86-
mz_notices_ind CREATE␠INDEX␠"mz_notices_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s835␠AS␠"mz_internal"."mz_notices"]␠("id")
86+
mz_notices_ind CREATE␠INDEX␠"mz_notices_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s836␠AS␠"mz_internal"."mz_notices"]␠("id")
8787
mz_object_arrangement_size_history_object_ind CREATE␠INDEX␠"mz_object_arrangement_size_history_object_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s753␠AS␠"mz_internal"."mz_object_arrangement_size_history"]␠("object_id")
8888
mz_object_arrangement_size_history_ts_ind CREATE␠INDEX␠"mz_object_arrangement_size_history_ts_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s753␠AS␠"mz_internal"."mz_object_arrangement_size_history"]␠("collection_timestamp")
8989
mz_object_arrangement_sizes_ind CREATE␠INDEX␠"mz_object_arrangement_sizes_ind"␠IN␠CLUSTER␠[s2]␠ON␠[s751␠AS␠"mz_internal"."mz_object_arrangement_sizes"]␠("replica_id")

0 commit comments

Comments
 (0)