From 907a31b16928d2267e95f83927661dc5130dc380 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 21:36:48 +0000 Subject: [PATCH 01/36] agents.md: cut down AGENTS.md --- AGENTS.md | 157 +----------------------------------------------------- 1 file changed, 1 insertion(+), 156 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a7beb5751ec78..16def78e95123 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,45 +1,5 @@ # Materialize -## Skills - -Canonical agent skills in `.agents/skills/`. `.claude/skills` is compat symlink -for Claude Code. Check `mz-*` skill before tasks — encodes project conventions, -saves time. - -Use the `mz-test` skill before running ANY tests, even mid-task — the canonical -commands aren't the obvious ones (e.g. `bin/sqllogictest --optimized`, not -`cargo build --bin sqllogictest`). - -Use the `mz-run` skill before building, running, formatting, or linting. `bin/fmt` and `bin/lint` are the canonical entry points, NOT `cargo fmt`, `rustfmt`, or a bare `cargo clippy`. `bin/environmentd`, not `cargo build --bin environmentd`. - -Use the `mz-commit` skill before `git commit`, `git push`, or `gh pr create`. - -Use the `mz-debug-ci` skill before the first `bk` or `gh pr checks` command, or when handed a Buildkite URL. - -## Code navigation - -For operation flow tracing, read first: - -* `doc/developer/generated/flows.md` — maps operations (query lifecycle, source ingestion, MV creation, sink lifecycle, catalog DDL, timestamp selection, persist read/write, controller architecture) to `crate::module` paths in execution order. -* `doc/developer/generated//_crate.md` — per-crate overview: modules, key types, dependencies. -* `doc/developer/generated//.md` — per-file docs. - -> **READ-ONLY: `doc/developer/generated/` is generated, not authored.** -> The entire `doc/developer/generated/` tree is maintained exclusively by the -> recurring documentation agent, which runs the `update-docs` skill/command. -> That agent is the *only* session permitted to create, edit, or delete files -> under this directory. -> -> In any other session: **treat `doc/developer/generated/` as read-only.** Use -> it for navigation and context, but never edit, create, delete, or regenerate -> files there — not even to "fix" something you noticed, and not as part of an -> unrelated change. These files carry `source`/`revision` front-matter that the -> recurring agent manages; hand edits desync that bookkeeping. If a generated -> doc is wrong or stale, report it in your response rather than editing it, and -> leave the correction to the `update-docs` agent. Do not stage or commit any -> path under `doc/developer/generated/` unless you are explicitly running the -> `update-docs` workflow. - ## Dependency management ### Workspace dependencies @@ -62,122 +22,7 @@ Never regenerate full Cargo.lock. When changing deps: * **Never bare `cargo update`** — bumps every semver-compatible dep, causes unrelated breakage from transitive changes. * **If lock regenerated**, diff before commit (`git diff Cargo.lock | grep '^[+-]version'`) and pin back unintended bumps with `cargo update -p --precise `. -### Licensing +## Licensing Two files control license policy, **keep in sync**: `deny.toml` (`[licenses].allow`) and `about.toml` (`accepted`). New dep with new license not already allowed: add SPDX identifier to both. -## Guidance - -* When designing specs or implementing features, preserve the full scope and - capability of the solution. Do not substitute dynamic or generative - approaches with hardcoded data, skip automation that a reference - implementation provides, or simplify away the parts that make a feature - robust and maintainable. If a reference codebase generates data from an - authoritative source, our implementation should do the same, not ship a - static snapshot. When you feel tempted to reduce scope or take a shortcut, - flag it to the user and workshop an alternative together rather than silently - downgrading the design. -* When making code changes, run cheap checkers/linters and formatters before - reporting success and/or committing changes. For Rust, these would be - `bin/fmt` and `cargo check`. -* When debugging CI or lint failures, start by reproducing the exact failing - command locally and reading its output. Do not run generic checks (clippy, - fmt, grep) in a shotgun approach. -* We value simplicity and clear abstractions. We especially care about - designing the interfaces or boundaries between components well. This includes - components, traits, interfaces, modules, and crates. -* In code comments or inline documentation, we value clearly described - contracts and assumptions. -* In code comments, we don't like "fluff" comments, comments that describe what - code does when it is obvious from the code. Good code should be readable. It - _is_ okay to call out tricky parts of the code or "nota benes". -* In code comments and code documentation we value concise but complete - comments. -* In code comments and documentation, don't refer to potential previous states - of the code, or things like future PRs, try not to use chronology in there, - except when it's needed to explain why a certain thing behaves as it does and - we need to record that knowledge. In general comments need to stand on their - own and make sense from just looking at them and the code around it, not - previous changes. -* Avoid em-dashes for structuring sentences in any prose: code comments, specs, - design docs, all of it. Restructure with full stops and commas instead. The - same goes for semicolons, though one is fine where splitting would mangle the - sentence. -* Our guidance applies both when writing new code or designs, or when we notice - deviations in code or architecture that we are working on. At the same time, - we want to keep our changes minimal so it's good to call out deviations and - then we can decide together what to do about it. -* Never put a side effect inside `debug_assert!` (or `debug_assert_eq!`, etc.). - The macro body compiles out whenever debug-assertions are off, which includes - `[profile.optimized]` (what `bin/environmentd` and mzcompose use) and - `[profile.release]`. Only `[profile.ci]` turns them on. A side effect written - there, for example `debug_assert!(map.insert(k, v).is_none())`, runs under - `cargo test` but silently vanishes in optimized and release builds, leaving - the logic it powered inert and profiler-blind. Bind the effect to a `let` - outside the assert, then assert on the bound value. Clippy's - `debug_assert_with_mut_call` catches `&mut self` receivers but not `&self` or - free-function side effects. -* Never write vendor, customer, or account names into durable or user-facing - surfaces: committed code, comments, column comments, docs, specs, commit - messages, PR bodies, or test fixtures. Anything persisted to disk gets shared, - indexed, and outlives the context, so a name in it is a leak. Anonymize to the - technical pattern instead, for example "a wide unfiltered LEFT JOIN driving a - freshness incident" rather than who hit it. Names are fine in ephemeral chat, - not on disk. -* Never use `std::collections::HashMap`/`HashSet` directly, `clippy.toml`'s - `disallowed-types` blocks it. Use `BTreeMap`/`BTreeSet` when iteration order - matters, or `mz_ore::collections::HashMap` for keyed-only access with no - iteration. Hash-order iteration is a real nondeterminism source, for example - an unstable order reaching plan output or persisted state, not a style nit. -* A new feature flag should default off in production but default ON in the - test/CI configuration, so the new code path is exercised by sqllogictest, - testdrive, and optimizer goldens before it earns trust. Production safety and - test coverage are separate settings. Wire the override through - `system_parameter_default`: the `--system-parameter-default=NAME=VALUE` CLI - flag (env `SYSTEM_PARAMETER_DEFAULT`) for sqllogictest and environmentd - binaries, or `TestHarness::with_system_parameter_default` for Rust - integration tests. - -## Code comments - -Specifics for code comments and documentation: - -Spend comments on the non-obvious: concurrency and async hazards (races, -lease/handle expiry, values that must not be held across an await point), -ordering constraints ("X must happen before Y, else Z"), invariants whose -violation panics or corrupts data, restart/recovery semantics, the origin of -magic constants, and why the obvious alternative was not taken. Idiomatic code -(match arms, iterator chains, getters, logging) needs none. - -A doc comment is the caller's contract: a one-sentence summary, then only the -invariants and semantics a caller must know. A self-evident public item needs -only that one line. Don't narrate the body in rustdoc ("Phase 1 ... Phase -2 ...", or enumerating a flag's branches). Reasoning about how or why the code -works goes in an inline `//` at the decision point, or in a module-level `//!` -when it is about how the pieces fit together. Document a struct field only when -its meaning is subtle. - -Every fact gets exactly one owning comment: the decision point for reasoning, -the mechanism for mechanism docs, the public setter or constant for config -semantics. Everywhere else either points at the owner or says nothing. The -tell is the same clause appearing near-verbatim in a module doc, a struct doc, -and an inline comment; when that happens, pick the owner and cut the rest. In -particular: don't restate a callee's documented contract at its call sites, -don't narrate in one function's doc what a sibling's doc already owns, and -don't duplicate an assert message in a comment on the same line. - -Budget doc-comment paragraphs by decisions: each paragraph should carry a -distinct decision or invariant, not re-argue one documented elsewhere or -enumerate the item's callers or fields. Skip speculative comments about what -a future implementation could do (one `TODO` at the owning seam is enough), -and skip comments explaining an absence when the surrounding docs already -imply it. No ASCII section-divider banners (`// ----- helpers -----`); item -placement carries the structure. Performance claims in comments should state -a constraint or a measured number, not unverifiable color ("the compiler can -autovectorize this"). - -The same economy applies to tests: a test whose name and assert messages -state the property needs no doc comment. Keep test docs for non-obvious -setup, fixtures whose shape encodes the scenario, and multi-phase protocols. - -Mark counterintuitive gotchas with `NOTE:` and future work with `TODO:`. From 00557f56510eb564e91c46de622ae6c58528aa6e Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 3 Sep 2026 17:55:30 +0000 Subject: [PATCH 02/36] doc: design decoupled coordination Define catalog authority for maintained lifecycle and separate it from request-scoped query execution. Record the ownership and readability boundaries needed for independent clients, while leaving implementation mechanisms open and full multi-adapter operation outside this deliverable. Include an append-only implementation log for work across sessions. --- .../design/20260903_decoupled_coordination.md | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 doc/developer/design/20260903_decoupled_coordination.md diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md new file mode 100644 index 0000000000000..a433466b8fccb --- /dev/null +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -0,0 +1,130 @@ +# Decoupled coordination + +## Outcome and scope + +The catalog is the authority for maintained storage and compute lifecycle. +Cluster-side components subscribe to catalog changes and enact the desired +state, rather than depending on an adapter to send lifecycle commands. Adapters +write catalog changes and use a separate fast protocol for query execution. + +The goal is decoupling, with existing SQL behavior, consistency guarantees, and +performance preserved. Losing an adapter must not disrupt maintained dataflows +or other clients. Its own queries may fail. Transparent query or session +failover is out of scope. + +Working multi-adapter operation is the destination, not this deliverable. +Boundaries must support independent adapters becoming catalog writers and query +clients without another ownership redesign. Enabling concurrent catalog writers +and deploying multiple adapters are not required here. + +Query-local dataflows that do not go through the catalog, including slow-path +SELECTs, SUBSCRIBEs, and COPY TO, remain on the fast protocol. Their creation, +execution, responses, and cleanup are part of request-scoped execution, not +durable catalog lifecycle. + +## Approach + +Use catalog implications to derive lifecycle effects from committed catalog +state. Complete the relevant implication paths as needed for this work. Move +responsibility for enacting those effects to cluster-side components that can +recover and follow catalog state independently of the originating adapter. +Controllers can remain useful abstractions. Their exact placement and factoring +are implementation choices. + +Catalog authority covers creation, changes, compaction permission, and deletion +of maintained objects. It describes desired state, not a command history that +requires the initiating adapter to replay it. A recovering subscriber must be +able to establish the required state and continue following changes. + +The fast protocol supports independent query clients without giving a connecting +client ownership of the cluster's maintained lifecycle. Responses, cancellation, +and disconnect cleanup belong to the relevant client. This is a semantic split, +not simply another listener for the same controller protocol. + +## Boundary contracts + +### Readability and compaction + +A reader must secure protection before relying on a timestamp. Observing a +frontier in a catalog snapshot is not itself a read hold. While protection is +valid, compute and persist compaction must respect it, including through +dependencies, installation, and ownership handover. One client cannot release +another's protection. + +Catalog state carries the authority for advancing readability frontiers. The +representation and accounting of individual read requirements remain open. +An evolving per-object frontier is a candidate, not a prescribed schema. Keep +its meaning distinct from a dataflow's installation `as_of` and an MV's initial +storage visibility boundary unless the implementation establishes how they fit. + +Propagation to persist critical since handles must respect all valid read +requirements. Those handles are the durable backstop, not a substitute for +multi-client accounting. Stale owners must not advance compaction or destroy +data based on incomplete local knowledge. Abandoned client holds must be +reclaimable without allowing that client to resume using invalid protection. + +Avoid coupling ordinary query throughput to catalog writes for every hold +change. Coalescing or rate-limiting frontier advancement may retain extra +history, but must not delay protection until after it is needed. Choose cadence +and any configuration from measured catalog load and retention cost. + +### Visibility and execution + +Catalog commit, cluster application, and query readiness are different events. +Queries must not fail spuriously or execute against the wrong object state +because the fast protocol overtakes catalog application. Preserve existing +behavior for concurrent DDL, transactions, cancellation, and object drops. + +Query-client connections must not replace one another's desired state or reset +maintained dataflows. Lifecycle ownership and permission to perform external +writes must remain safe across restarts and handover, independently of query +connection lifetime. + +## Implementation and verification + +Implementers choose the smallest coherent mechanism and the order of work. +This document does not prescribe catalog fields, leases, protocol messages, +planning placement, or process topology. An incremental implementation is fine, +but an intermediate step is not completion of the outcome above. + +Flag implementation discoveries, tradeoffs, and scope growth to Aljoscha. Pause +affected work when guidance is needed, especially when correctness, user-visible +behavior, or an agreed boundary would change. Do not silently narrow capability +or expand the design to resolve a difficulty. Seek independent review where +the risk warrants it. + +Existing CI, nightly, and performance suites are the broad acceptance signal. +Ensure coverage actually exercises the changed boundaries, particularly +independent query clients, catalog/query ordering, and read protection during +compaction and recovery. Add targeted tests or experiments where existing +coverage leaves uncertainty. Measure catalog traffic and retained-history cost +as well as query performance. Record what ran, failures, and remaining gaps. + +### Starting points + +- [Catalog implications](../../../src/adapter/src/coord/catalog_implications.rs) + derive effects from committed changes. Index and MV creation also have + sequencer-side installation paths to account for. +- [Compute protocol](../../../src/compute-client/src/protocol/command.rs) mixes + lifecycle and query commands. [Transport](../../../src/service/src/transport.rs) + replaces the active client on a new connection. +- [StorageCollections](../../../src/storage-client/src/storage_collections.rs) + owns storage capability accounting and critical since handles. The fixed + critical-reader identity and epoch fencing support handover, not independent + owners aggregating their local holds. + +## Implementation log (append-only) + +Append dated entries with findings and evidence, decisions or pending questions, +validation results, and the next useful step. Clearly distinguish an +implementer's proposal from a decision reviewed with Aljoscha. Do not rewrite +earlier entries. Keep the main text concise and update its agreed boundaries +only following review. + +### 2026-09-03: Scope agreed with Aljoscha + +Multi-adapter operation is the end goal, not the immediate deliverable. +Query-local dataflows remain on the fast protocol. Adapter loss must not harm +other clients or maintained dataflows, but transparent failover is not required. +Completing relevant catalog implication paths is within scope. Implementation +has not started and runtime validation has not been performed. From dc2d5b371f39eefe4195414a286740d122ac645b Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 3 Sep 2026 18:09:37 +0000 Subject: [PATCH 03/36] doc: add prompt for implementing decoupled coordination --- .../20260903_decoupled_coordination_prompt.md | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 doc/developer/design/20260903_decoupled_coordination_prompt.md diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md new file mode 100644 index 0000000000000..3ebce787d42a7 --- /dev/null +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -0,0 +1,56 @@ +# Decoupled coordination: implementer session prompt + +```text +Work on doc/developer/design/20260903_decoupled_coordination.md. + +Working PR: https://github.com/MaterializeInc/materialize/pull/38696 +Bookmark: decoupled-coordination +Remote: origin, pointing to the contributor fork, not upstream + +Read the design and its implementation log, then inspect the current code, +worktree, and remote bookmark. Preserve existing work and account for progress +from other sessions. Choose the next coherent piece toward the full outcome, +briefly explain that choice, then implement and verify it. + +Treat the design as the agreed boundaries, not a prescribed mechanism. Prefer +the smallest coherent solution that preserves the full capability. Incremental +progress is fine, but do not mistake an intermediate step for completion. + +Bring discoveries, consequential tradeoffs, and scope growth to me, Aljoscha. +Pause affected work when guidance is needed rather than silently narrowing +scope, adding machinery, or changing an agreed boundary. + +Follow repository instructions and skills. Verify at the changed boundaries, +and seek independent review when the risk warrants it. + +Use the draft PR's CI as the default test loop instead of running test suites +locally. You may push changes and iterate on CI failures. Keep cheap local +formatting and checks, and run targeted local tests when useful, not as a +prerequisite for pushing. Follow the mz-debug-ci skill when investigating CI. +Report pending or failed checks explicitly rather than treating a push as +successful validation. + +Normally finish the session with one coherent change. Give it a clear commit +message and change description explaining the outcome, rationale, and validation +status, not the chronology of attempts. If blocked, report the blocker rather +than claiming completion. + +Append only a minimal dated handoff to the design's log: consequential findings +or decisions, validation status, unresolved questions, and the next useful step. +Link evidence rather than recording every CI attempt or duplicating the change +description. Distinguish proposals from decisions we reviewed together. Do not +rewrite earlier entries. Keep the main design focused on design, and change +its agreed boundaries only after discussing them with me. + +You may commit and push progress to this bookmark without asking again. +Prefer jj. In-progress commits and pushes are allowed while iterating. Before +finishing, squash your session's intermediate commits, including fixes and log +updates, into one coherent commit. +You may rewrite and repush your own session's work-in-progress commits for +this purpose. Check for remote changes before pushing. Do not overwrite +others' work, and ask before rewriting other sessions' commits or commits +that others have built on. Report validation failures honestly. + +Keep the existing PR as a draft. Do not merge, mark it ready, or push to +upstream or other branches without asking. +``` From 2e3158279e03b5a5d2755b8c63eb8d534b1d2bab Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 3 Sep 2026 20:20:02 +0000 Subject: [PATCH 04/36] doc: Defer nightly validation until the implementation works --- .../design/20260903_decoupled_coordination_prompt.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index 3ebce787d42a7..c67adcc3d3216 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -30,6 +30,10 @@ prerequisite for pushing. Follow the mz-debug-ci skill when investigating CI. Report pending or failed checks explicitly rather than treating a push as successful validation. +Focus on regular PR CI for now. Nightly intentionally does not run on this +draft PR, so do not treat its absence as a blocker or try to enable it. We will +start nightly validation once we have a working implementation. + Normally finish the session with one coherent change. Give it a clear commit message and change description explaining the outcome, rationale, and validation status, not the chronology of attempts. If blocked, report the blocker rather From eb90ed3aa008316df19620cbea0bbd6aea909fbf Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 3 Sep 2026 18:17:58 +0000 Subject: [PATCH 05/36] adapter: derive sink creation from committed catalog updates Make sink export installation an implication of the committed catalog diff, not a sequencer-side lifecycle call. Create exports after source and table inputs, then initialize their default read policies. Existing read holds protect inputs through installation, and SQL success still waits for it. Extend sink compatibility coverage with no-op creation and rename operations. Independent review found no blocking issue. CI Clippy and formatting passed before a documentation push superseded the run. Runtime validation remains pending, and local builds were blocked by missing artifacts and tools. This is an incremental ownership migration. Cluster-side catalog following, MV/index creation implications, and direct committed-update tests remain open. --- .../design/20260903_decoupled_coordination.md | 14 ++++++++++++++ src/adapter/src/coord/catalog_implications.rs | 11 ++++++++++- src/adapter/src/coord/sequencer/inner.rs | 10 +--------- test/testdrive/kafka-sinks.td | 11 +++++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index a433466b8fccb..cc999de2dd5c0 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -128,3 +128,17 @@ Query-local dataflows remain on the fast protocol. Adapter loss must not harm other clients or maintained dataflows, but transparent failover is not required. Completing relevant catalog implication paths is within scope. Implementation has not started and runtime validation has not been performed. + +### 2026-09-03: Sink creation from committed implications + +Sink additions now install exports through catalog implications. Independent +review found no blocking issue. [CI build 133862](https://buildkite.com/materialize/test/builds/133862) +passed both Clippy jobs and formatting before a documentation push superseded it. +Latest CI and runtime validation remain pending. Local builds were blocked by +missing artifacts and tools. Aljoscha confirmed CI as the default test loop. + +Controllers still live in the adapter. Direct committed-update tests and +same-batch MV/sink creation remain gaps. Next proposed slice: reuse index plan +reconstruction from bootstrap, preserving precommit optimization as a cache. +Index parsing leaves plans absent and the expression cache has no runtime read +API. No new boundary decision was made. diff --git a/src/adapter/src/coord/catalog_implications.rs b/src/adapter/src/coord/catalog_implications.rs index 1fab9a308d719..1df2b6e21a5d7 100644 --- a/src/adapter/src/coord/catalog_implications.rs +++ b/src/adapter/src/coord/catalog_implications.rs @@ -267,6 +267,7 @@ impl Coordinator { // sources (which don't). let mut table_collections_to_create = BTreeMap::new(); let mut source_collections_to_create = BTreeMap::new(); + let mut sinks_to_create = Vec::new(); let mut storage_policies_to_initialize = BTreeMap::new(); let mut execution_timestamps_to_set = BTreeSet::new(); let mut vpc_endpoints_to_create: Vec<(CatalogItemId, VpcEndpointConfig)> = vec![]; @@ -412,7 +413,11 @@ impl Coordinator { } } CatalogImplication::Sink(CatalogImplicationKind::Added(sink)) => { - tracing::debug!(?sink, "not handling AddSink in here yet"); + storage_policies_to_initialize + .entry(CompactionWindow::Default) + .or_default() + .insert(sink.global_id()); + sinks_to_create.push(sink); } CatalogImplication::Sink(CatalogImplicationKind::Altered { prev: prev_sink, @@ -804,6 +809,10 @@ impl Coordinator { self.create_table_collections(table_collections_to_create, execution_timestamps_to_set) .await?; } + // Sink inputs must exist before exports acquire their dependency read holds. + for sink in sinks_to_create { + self.create_storage_export(sink.global_id(), &sink).await?; + } // It is _very_ important that we only initialize read policies after we // have created all the sources/collections. Some of the sources created // in this collection might have dependencies on other sources, so the diff --git a/src/adapter/src/coord/sequencer/inner.rs b/src/adapter/src/coord/sequencer/inner.rs index 02513528370e6..e459276723d6e 100644 --- a/src/adapter/src/coord/sequencer/inner.rs +++ b/src/adapter/src/coord/sequencer/inner.rs @@ -19,7 +19,6 @@ use futures::future::{BoxFuture, FutureExt}; use futures::{Future, StreamExt, future}; use itertools::Itertools; use maplit::btreemap; -use mz_adapter_types::compaction::CompactionWindow; use mz_adapter_types::connection::ConnectionId; use mz_adapter_types::dyncfgs::{ ENABLE_EXPRESSION_CACHE, ENABLE_PASSWORD_AUTH, FRONTEND_READ_THEN_WRITE, @@ -1185,7 +1184,7 @@ impl Coordinator { let ops = vec![catalog::Op::CreateItem { id: item_id, name: name.clone(), - item: CatalogItem::Sink(catalog_sink.clone()), + item: CatalogItem::Sink(catalog_sink), owner_id: *ctx.session().current_role_id(), }]; @@ -1210,13 +1209,6 @@ impl Coordinator { } }; - self.create_storage_export(global_id, &catalog_sink) - .await - .unwrap_or_terminate("cannot fail to create exports"); - - self.initialize_storage_read_policies([item_id].into(), CompactionWindow::Default) - .await; - ctx.retire(Ok(ExecuteResponse::CreatedSink)) } diff --git a/test/testdrive/kafka-sinks.td b/test/testdrive/kafka-sinks.td index c22f048b83fe0..2bd896210937d 100644 --- a/test/testdrive/kafka-sinks.td +++ b/test/testdrive/kafka-sinks.td @@ -146,6 +146,17 @@ name type cluster comment FORMAT AVRO USING CONFLUENT SCHEMA REGISTRY CONNECTION csr_conn ENVELOPE DEBEZIUM +# Neither a no-op create nor a catalog-only alteration may reinstall the export. +> CREATE SINK IF NOT EXISTS snk2 + IN CLUSTER snk2_cluster + FROM src_materialized_tbl + INTO KAFKA CONNECTION kafka_conn (TOPIC 'testdrive-snk2-${testdrive.seed}') + FORMAT AVRO USING CONFLUENT SCHEMA REGISTRY CONNECTION csr_conn + ENVELOPE DEBEZIUM + +> ALTER SINK snk2 RENAME TO snk2_renamed +> ALTER SINK snk2_renamed RENAME TO snk2 + > CREATE CLUSTER snk3_cluster SIZE '${arg.default-storage-size}'; > CREATE SINK snk3 IN CLUSTER snk3_cluster From f37cd8f861f5133f5bba18e307a9a7c6a7eaab28 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 3 Sep 2026 19:26:02 +0000 Subject: [PATCH 06/36] adapter: extract catalog index plan reconstruction Separate index reconstruction from bootstrap orchestration so committed catalog implications can reuse it without inheriting bootstrap timestamp selection or installation. Keep cache validation, dependency ordering, and CREATE INDEX behavior unchanged. This is preparatory work, not index lifecycle decoupling. Runtime cache reads and committed index installation remain to be implemented. Validation: adapter cargo check and Rust formatting passed. Independent review found no issues. Full formatting and lint were blocked by local tooling and dependency build failures. Runtime tests and PR CI are pending. --- .../design/20260903_decoupled_coordination.md | 14 +++ src/adapter/src/coord.rs | 105 +++++++++--------- 2 files changed, 67 insertions(+), 52 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index cc999de2dd5c0..7de2dbd478879 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -142,3 +142,17 @@ same-batch MV/sink creation remain gaps. Next proposed slice: reuse index plan reconstruction from bootstrap, preserving precommit optimization as a cache. Index parsing leaves plans absent and the expression cache has no runtime read API. No new boundary decision was made. + +### 2026-09-03: Reusable index reconstruction + +Extracted bootstrap's uncached index planning and notice rendering without +changing cache policy, ordering, or installation. Independent review found no +issue. Local adapter `cargo check` and Rust formatting passed. Full formatting +and lint failed on missing tools and a Python-doctest dependency build. Runtime +validation and this commit's [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks) +remain pending. + +Next proposed slice: runtime expression-cache reads and index-add implications +using reconstruction on cache misses. Cache validity must account for committed +dependencies, and installation must follow same-batch prerequisites. Add direct +committed-update coverage without sequencer plans. No boundary decision changed. diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index 56f9a58e5d03b..a1612ee71984c 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -106,7 +106,7 @@ use mz_catalog::config::{AwsPrincipalContext, BuiltinItemMigrationConfig, Cluste use mz_catalog::durable::OpenableDurableCatalogState; use mz_catalog::expr_cache::{GlobalExpressions, LocalExpressions, latest_item_version}; use mz_catalog::memory::objects::{ - CatalogEntry, CatalogItem, ClusterReplicaProcessStatus, Connection, DataSourceDesc, + CatalogEntry, CatalogItem, ClusterReplicaProcessStatus, Connection, DataSourceDesc, Index, ReconfigurationTarget, Table, TableDataSource, }; use mz_cloud_resources::{CloudResourceController, VpcEndpointConfig, VpcEndpointEvent}; @@ -3716,6 +3716,46 @@ impl Coordinator { result } + /// Builds an index plan and rendered notices from its catalog definition. + /// + /// The snapshot must contain the compute collections available for imports. + /// This does not select an `as_of`, install the dataflow, or cache the result. + fn build_index_dataflow_plan( + &mut self, + name: &QualifiedItemName, + index: &Index, + compute_instance: ComputeInstanceSnapshot, + optimizer_config: OptimizerConfig, + ) -> Result { + let global_id = index.global_id(); + let mut optimizer = optimize::index::Optimizer::new( + self.owned_catalog(), + compute_instance, + global_id, + optimizer_config.clone(), + self.optimizer_metrics(), + ); + let index_plan = optimize::index::Index::new(name.clone(), index.on, index.keys.to_vec()); + let global_mir_plan = optimizer.optimize(index_plan)?; + let global_mir = global_mir_plan.df_desc().clone(); + let global_lir_plan = optimizer.optimize(global_mir_plan)?; + let (physical_plan, metainfo) = global_lir_plan.unapply(); + let notice_ids = std::iter::repeat_with(|| self.allocate_transient_id()) + .map(|(_item_id, gid)| gid) + .take(metainfo.optimizer_notices.len()) + .collect::>(); + let dataflow_metainfos = + self.catalog() + .render_notices(metainfo, notice_ids, Some(global_id)); + Ok(GlobalExpressions { + global_mir, + physical_plan, + dataflow_metainfos, + optimizer_features: optimizer_config.features, + item_version: RelationVersion::root(), + }) + } + /// Invokes the optimizer on all indexes and materialized views in the catalog and inserts the /// resulting dataflow plans into the catalog state. /// @@ -3787,57 +3827,18 @@ impl Coordinator { ) } Some(_) | None => { - let (optimized_plan, global_lir_plan) = { - // Build an optimizer for this INDEX. - let mut optimizer = optimize::index::Optimizer::new( - self.owned_catalog(), - compute_instance.clone(), - global_id, - optimizer_config.clone(), - self.optimizer_metrics(), - ); - - // MIR ⇒ MIR optimization (global) - let index_plan = optimize::index::Index::new( - entry.name().clone(), - idx.on, - idx.keys.to_vec(), - ); - let global_mir_plan = optimizer.optimize(index_plan)?; - let optimized_plan = global_mir_plan.df_desc().clone(); - - // MIR ⇒ LIR lowering and LIR ⇒ LIR optimization (global) - let global_lir_plan = optimizer.optimize(global_mir_plan)?; - - (optimized_plan, global_lir_plan) - }; - - let (physical_plan, metainfo) = global_lir_plan.unapply(); - let metainfo = { - // Pre-allocate a vector of transient GlobalIds for each notice. - let notice_ids = - std::iter::repeat_with(|| self.allocate_transient_id()) - .map(|(_item_id, gid)| gid) - .take(metainfo.optimizer_notices.len()) - .collect::>(); - // Return a metainfo with rendered notices. - self.catalog().render_notices( - metainfo, - notice_ids, - Some(idx.global_id()), - ) - }; - uncached_expressions.insert( - global_id, - GlobalExpressions { - global_mir: optimized_plan.clone(), - physical_plan: physical_plan.clone(), - dataflow_metainfos: metainfo.clone(), - optimizer_features: optimizer_config.features.clone(), - item_version: RelationVersion::root(), - }, - ); - (optimized_plan, physical_plan, metainfo) + let expressions = self.build_index_dataflow_plan( + entry.name(), + idx, + compute_instance.clone(), + optimizer_config, + )?; + uncached_expressions.insert(global_id, expressions.clone()); + ( + expressions.global_mir, + expressions.physical_plan, + expressions.dataflow_metainfos, + ) } }; From fbf3a82598b5daa969604245d2b3b313ef7a8801 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 3 Sep 2026 19:32:14 +0000 Subject: [PATCH 07/36] adapter: derive index creation from committed catalog updates Make index-add implications acquire compatible cached plans or reconstruct from catalog definitions, then install after cluster and storage creation. Retain precommit optimization and success-only session notices, without a sequencer installation closure. Reuse the existing read-hold handover. Expose best-effort expression-cache reads through its task and validate cached dependencies against committed state and available compute imports. Filter session notices for optimizer dependencies dropped during planning. Extend cache-handle tests and add cache-disabled SQL restart coverage for index use, EXPLAIN, notices, and drop cleanup. Same-batch external catalog application remains a test gap until a production subscriber exists. Adapter and cache-test compilation, Rust and Python formatting, and Ruff passed. Full local formatting and lint are blocked by missing tools. Runtime validation is delegated to the draft PR CI and remains pending. --- .../design/20260903_decoupled_coordination.md | 24 ++++++ src/adapter/src/catalog.rs | 8 ++ src/adapter/src/coord.rs | 69 +++++++++++++++- src/adapter/src/coord/catalog_implications.rs | 80 ++++++++++++++++++- src/adapter/src/coord/sequencer/inner.rs | 75 +---------------- .../src/coord/sequencer/inner/create_index.rs | 50 +++--------- src/adapter/src/optimize/index.rs | 6 -- src/catalog/src/expr_cache.rs | 63 ++++++++++++--- test/restart/mzcompose.py | 70 ++++++++++++++++ 9 files changed, 315 insertions(+), 130 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 7de2dbd478879..6a625ea217844 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -156,3 +156,27 @@ Next proposed slice: runtime expression-cache reads and index-add implications using reconstruction on cache misses. Cache validity must account for committed dependencies, and installation must follow same-batch prerequisites. Add direct committed-update coverage without sequencer plans. No boundary decision changed. + +### 2026-09-03: Runtime index implications, verification in progress + +Index additions now acquire cached or reconstructed plans and install through +implications. Runtime cache reads are best-effort and validated against committed +dependencies and compute availability. Review identified stale session notices +after cache rejection, addressed by filtering dropped dependencies. + +Adapter and cache-test compilation passed. Runtime tests remain pending. The +extraction's Clippy fix passed both [CI Clippy jobs](https://buildkite.com/materialize/test/builds/133870). +Full validation of this follow-up is pending. Proposed test-only adapter catalog +transaction request awaits Aljoscha's approval, because no existing harness can +exercise cluster/table/index creation in one committed batch. Same-batch MV/index +creation still depends on moving MV storage creation into implications. + +### 2026-09-03: System-boundary verification + +Agreed with Aljoscha to proceed with system-level coverage, without a test-only +coordinator command. Added cache-disabled SQL creation and restart coverage for +index use, EXPLAIN, notices, and drop cleanup. Python formatting and Ruff passed, +runtime validation remains in [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks). +The no-sequencer same-batch runtime case remains uncovered until a production +catalog subscriber exists. External catalog writers fence the adapter today, +and the existing read-only catalog harness does not apply controller effects. diff --git a/src/adapter/src/catalog.rs b/src/adapter/src/catalog.rs index 5e9986e6f24b1..a53d8db4f58e7 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -1613,6 +1613,14 @@ impl Catalog { self.update_expression_cache(local_exprs, global_exprs, Default::default()) } + /// Returns a best-effort cached plan, whose compatibility the caller must validate. + pub(crate) async fn cached_global_expressions( + &self, + id: GlobalId, + ) -> Option { + self.expr_cache_handle.as_ref()?.get_global(id).await + } + pub(crate) fn update_expression_cache<'a, 'b>( &'a self, new_local_expressions: Vec<(GlobalId, LocalExpressions)>, diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index a1612ee71984c..788838bf19211 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -174,6 +174,7 @@ use mz_storage_types::sources::kafka::KAFKA_PROGRESS_DESC; use mz_storage_types::sources::{IngestionDescription, SourceExport, Timeline}; use mz_timestamp_oracle::{TimestampOracleConfig, WriteTimestamp}; use mz_transform::dataflow::DataflowMetainfo; +use mz_transform::notice::OptimizerNotice; use opentelemetry::trace::TraceContextExt; use semver::Version; use serde::Serialize; @@ -3721,7 +3722,7 @@ impl Coordinator { /// The snapshot must contain the compute collections available for imports. /// This does not select an `as_of`, install the dataflow, or cache the result. fn build_index_dataflow_plan( - &mut self, + &self, name: &QualifiedItemName, index: &Index, compute_instance: ComputeInstanceSnapshot, @@ -4655,6 +4656,72 @@ impl Coordinator { .unwrap_or_terminate("allow_writes cannot fail"); } + /// Sets `df_desc`'s as-of from a read hold on `id_bundle`, ships the dataflow, and drops the + /// hold once compute has taken its own (compute puts in its own read holds during + /// `create_dataflow`, so it is safe to release this one right after shipping). + /// + /// The read hold across shipping keeps the since of `id_bundle` from advancing underneath the + /// as-of just picked. + async fn ship_new_dataflow( + &mut self, + id_bundle: &CollectionIdBundle, + mut df_desc: DataflowDescription, + instance: ComputeInstanceId, + notice_builtin_updates_fut: Option, + ) { + let read_holds = self.acquire_read_holds(id_bundle); + let since = read_holds.least_valid_read(); + df_desc.set_as_of(since); + + self.ship_dataflow_and_notice_builtin_table_updates( + df_desc, + instance, + notice_builtin_updates_fut, + None, + ) + .await; + + drop(read_holds); + } + + /// Persist already-rendered optimizer notices for a newly created + /// non-transient dataflow. + /// + /// This: + /// - packs builtin-table updates for `mz_optimizer_notices` (if enabled), + /// - stores the rendered metainfo on the catalog object via + /// `set_dataflow_metainfo`, + /// - and returns a future that resolves once the builtin-table append + /// has been observed, or `None` if nothing was appended. + fn persist_dataflow_metainfo( + &mut self, + df_meta: DataflowMetainfo>, + export_id: GlobalId, + ) -> Option { + // Attend to optimization notice builtin tables and save the metainfo in the catalog's + // in-memory state. + if self.catalog().state().system_config().enable_mz_notices() + && !df_meta.optimizer_notices.is_empty() + { + let mut builtin_table_updates = Vec::with_capacity(df_meta.optimizer_notices.len()); + self.catalog().state().pack_optimizer_notices( + &mut builtin_table_updates, + df_meta.optimizer_notices.iter(), + Diff::ONE, + ); + + // Save the metainfo. + self.catalog_mut().set_dataflow_metainfo(export_id, df_meta); + + Some(self.builtin_table_update().execute(builtin_table_updates)) + } else { + // Save the metainfo. + self.catalog_mut().set_dataflow_metainfo(export_id, df_meta); + + None + } + } + /// Like `ship_dataflow`, but also await on builtin table updates. pub(crate) async fn ship_dataflow_and_notice_builtin_table_updates( &mut self, diff --git a/src/adapter/src/coord/catalog_implications.rs b/src/adapter/src/coord/catalog_implications.rs index 1df2b6e21a5d7..e8be3df3e70a3 100644 --- a/src/adapter/src/coord/catalog_implications.rs +++ b/src/adapter/src/coord/catalog_implications.rs @@ -33,6 +33,7 @@ use std::time::{Duration, Instant}; use fail::fail_point; use itertools::Itertools; use mz_adapter_types::compaction::CompactionWindow; +use mz_catalog::expr_cache::GlobalExpressions; use mz_catalog::memory::objects::{ CatalogItem, Cluster, ClusterReplica, Connection, DataSourceDesc, Index, MaterializedView, MetricSink, Secret, Sink, Source, StateDiff, Table, TableDataSource, View, @@ -48,6 +49,7 @@ use mz_ore::future::InTask; use mz_ore::instrument; use mz_ore::retry::Retry; use mz_ore::task; +use mz_repr::optimize::OverrideFrom; use mz_repr::{CatalogItemId, GlobalId, RelationVersion, RelationVersionSelector}; use mz_sql::plan::ConnectionDetails; use mz_storage_client::controller::{CollectionDescription, DataSource}; @@ -66,6 +68,8 @@ use crate::coord::catalog_implications::parsed_state_updates::{ }; use crate::coord::peek::DroppedDependency; use crate::coord::timeline::TimelineState; +use crate::optimize::OptimizerConfig; +use crate::optimize::dataflows::dataflow_import_id_bundle; use crate::statement_logging::{StatementEndedExecutionReason, StatementLoggingId}; use crate::{AdapterError, CollectionIdBundle, ExecuteContext, ResultExt}; @@ -268,6 +272,7 @@ impl Coordinator { let mut table_collections_to_create = BTreeMap::new(); let mut source_collections_to_create = BTreeMap::new(); let mut sinks_to_create = Vec::new(); + let mut indexes_to_create = Vec::new(); let mut storage_policies_to_initialize = BTreeMap::new(); let mut execution_timestamps_to_set = BTreeSet::new(); let mut vpc_endpoints_to_create: Vec<(CatalogItemId, VpcEndpointConfig)> = vec![]; @@ -430,7 +435,7 @@ impl Coordinator { dropped_item_names.insert(sink.global_id(), full_name); } CatalogImplication::Index(CatalogImplicationKind::Added(index)) => { - tracing::debug!(?index, "not handling AddIndex in here yet"); + indexes_to_create.push((catalog_id, index)); } CatalogImplication::Index(CatalogImplicationKind::Altered { prev: prev_index, @@ -454,7 +459,7 @@ impl Coordinator { dropped_item_names.insert(index.global_id(), full_name); } CatalogImplication::MetricSink(CatalogImplicationKind::Added(_metric_sink)) => { - // Nothing to do, mirroring `Index`: shipping the dataflow at create time is + // Nothing to do: shipping the dataflow at create time is // the sequencer's job (`create_metric_sink_finish`), and re-rendering it after // a restart happens during bootstrap (`bootstrap_dataflow_plans`). } @@ -813,6 +818,10 @@ impl Coordinator { for sink in sinks_to_create { self.create_storage_export(sink.global_id(), &sink).await?; } + // Index imports need the cluster and storage collections from this batch. + for (catalog_id, index) in indexes_to_create { + self.create_index_from_catalog(catalog_id, &index).await?; + } // It is _very_ important that we only initialize read policies after we // have created all the sources/collections. Some of the sources created // in this collection might have dependencies on other sources, so the @@ -1187,6 +1196,73 @@ impl Coordinator { Ok(()) } + async fn create_index_from_catalog( + &mut self, + catalog_id: CatalogItemId, + index: &Index, + ) -> Result<(), AdapterError> { + let global_id = index.global_id(); + let compute_instance = self + .instance_snapshot(index.cluster_id) + .expect("index cluster must exist before installation"); + let optimizer_config = OptimizerConfig::from(self.catalog().system_config()) + .override_from( + &self + .catalog() + .get_cluster(index.cluster_id) + .config + .features(), + ) + .override_from(&self.cluster_scoped_optimizer_overrides(index.cluster_id)); + let cached = self.catalog().cached_global_expressions(global_id).await; + let cached = cached.filter(|expressions| { + expressions.item_version == RelationVersion::root() + && expressions.optimizer_features == optimizer_config.features + // A dropped index can still be in compute until this batch's drops run. + // Conversely, a committed index may not have been installed yet. + && expressions.global_mir.index_imports.keys() + .chain(expressions.physical_plan.index_imports.keys()) + .all(|id| { + self.catalog().try_get_entry_by_global_id(id).is_some() + && compute_instance.contains_collection(id) + }) + && expressions.dataflow_metainfos.optimizer_notices.iter().all(|notice| { + notice.dependencies.iter().all(|id| { + self.catalog().try_get_entry_by_global_id(id).is_some() + }) + }) + }); + let expressions = match cached { + Some(expressions) => expressions, + None => { + let name = self.catalog().get_entry(&catalog_id).name(); + self.build_index_dataflow_plan(name, index, compute_instance, optimizer_config)? + } + }; + let GlobalExpressions { + global_mir, + physical_plan, + dataflow_metainfos, + .. + } = expressions; + let id_bundle = dataflow_import_id_bundle(&physical_plan, index.cluster_id); + self.catalog_mut().set_optimized_plan(global_id, global_mir); + self.catalog_mut() + .set_physical_plan(global_id, physical_plan.clone()); + let notice_updates = self.persist_dataflow_metainfo(dataflow_metainfos, global_id); + self.ship_new_dataflow(&id_bundle, physical_plan, index.cluster_id, notice_updates) + .await; + self.update_compute_read_policy( + index.cluster_id, + catalog_id, + index + .custom_logical_compaction_window + .unwrap_or_default() + .into(), + ); + Ok(()) + } + #[instrument(level = "debug")] async fn create_table_collections( &mut self, diff --git a/src/adapter/src/coord/sequencer/inner.rs b/src/adapter/src/coord/sequencer/inner.rs index e459276723d6e..a91d4792ab98f 100644 --- a/src/adapter/src/coord/sequencer/inner.rs +++ b/src/adapter/src/coord/sequencer/inner.rs @@ -28,9 +28,6 @@ use mz_catalog::memory::error::ErrorKind; use mz_catalog::memory::objects::{ CatalogItem, Connection, DataSourceDesc, Sink, Source, Table, TableDataSource, Type, }; -use mz_compute_types::ComputeInstanceId; -use mz_compute_types::dataflows::DataflowDescription; -use mz_compute_types::plan::LirRelationExpr; use mz_expr::{ CollectionPlan, Eval, MapFilterProject, OptimizedMirRelationExpr, ResultSpec, RowSetFinishing, }; @@ -109,9 +106,7 @@ use crate::catalog::{ self, Catalog, CatalogState, ConnCatalog, DropObjectInfo, UpdatePrivilegeVariant, }; use crate::command::{ExecuteResponse, Response}; -use crate::coord::appends::{ - BuiltinTableAppendNotify, DeferredOp, DeferredPlan, PendingWriteTxn, UserWriteResponder, -}; +use crate::coord::appends::{DeferredOp, DeferredPlan, PendingWriteTxn, UserWriteResponder}; use crate::coord::read_then_write::{DependencyPolicy, validate_read_then_write_dependencies}; use crate::coord::sequencer::emit_optimizer_notices; use crate::coord::{ @@ -130,7 +125,7 @@ use crate::session::{ WriteLocks, WriteOp, }; use crate::util::{ClientTransmitter, ResultExt, viewable_variables}; -use crate::{CollectionIdBundle, PeekResponseUnary, ReadHolds}; +use crate::{PeekResponseUnary, ReadHolds}; /// A future that resolves to a real-time recency timestamp. type RtrTimestampFuture = BoxFuture<'static, Result>; @@ -5139,70 +5134,4 @@ impl Coordinator { Some(global_id), ) } - - /// Sets `df_desc`'s as-of from a read hold on `id_bundle`, ships the dataflow, and drops the - /// hold once compute has taken its own (compute puts in its own read holds during - /// `create_dataflow`, so it is safe to release this one right after shipping). - /// - /// The read hold across shipping keeps the since of `id_bundle` from advancing underneath the - /// as-of just picked. - async fn ship_new_dataflow( - &mut self, - id_bundle: &CollectionIdBundle, - mut df_desc: DataflowDescription, - instance: ComputeInstanceId, - notice_builtin_updates_fut: Option, - ) { - let read_holds = self.acquire_read_holds(id_bundle); - let since = read_holds.least_valid_read(); - df_desc.set_as_of(since); - - self.ship_dataflow_and_notice_builtin_table_updates( - df_desc, - instance, - notice_builtin_updates_fut, - None, - ) - .await; - - drop(read_holds); - } - - /// Persist already-rendered optimizer notices for a newly created - /// non-transient dataflow. - /// - /// This: - /// - packs builtin-table updates for `mz_optimizer_notices` (if enabled), - /// - stores the rendered metainfo on the catalog object via - /// `set_dataflow_metainfo`, - /// - and returns a future that resolves once the builtin-table append - /// has been observed, or `None` if nothing was appended. - fn persist_dataflow_metainfo( - &mut self, - df_meta: DataflowMetainfo>, - export_id: GlobalId, - ) -> Option { - // Attend to optimization notice builtin tables and save the metainfo in the catalog's - // in-memory state. - if self.catalog().state().system_config().enable_mz_notices() - && !df_meta.optimizer_notices.is_empty() - { - let mut builtin_table_updates = Vec::with_capacity(df_meta.optimizer_notices.len()); - self.catalog().state().pack_optimizer_notices( - &mut builtin_table_updates, - df_meta.optimizer_notices.iter(), - Diff::ONE, - ); - - // Save the metainfo. - self.catalog_mut().set_dataflow_metainfo(export_id, df_meta); - - Some(self.builtin_table_update().execute(builtin_table_updates)) - } else { - // Save the metainfo. - self.catalog_mut().set_dataflow_metainfo(export_id, df_meta); - - None - } - } } diff --git a/src/adapter/src/coord/sequencer/inner/create_index.rs b/src/adapter/src/coord/sequencer/inner/create_index.rs index be46743380721..02946743d27b5 100644 --- a/src/adapter/src/coord/sequencer/inner/create_index.rs +++ b/src/adapter/src/coord/sequencer/inner/create_index.rs @@ -21,6 +21,7 @@ use mz_sql::catalog::CatalogError; use mz_sql::names::ResolvedIds; use mz_sql::plan; use mz_sql::session::metadata::SessionMetadata; +use mz_transform::notice::OptimizerNoticeApi; use tracing::Span; use crate::command::ExecuteResponse; @@ -32,7 +33,6 @@ use crate::coord::{ use crate::error::AdapterError; use crate::explain::explain_dataflow; use crate::explain::optimizer_trace::OptimizerTrace; -use crate::optimize::dataflows::dataflow_import_id_bundle; use crate::optimize::{self, Optimize}; use crate::session::Session; use crate::{AdapterNotice, ExecuteContext, catalog}; @@ -452,7 +452,6 @@ impl Coordinator { optimizer_features, .. } = stage; - let id_bundle = dataflow_import_id_bundle(global_lir_plan.df_desc(), cluster_id); let on_entry = self.catalog().get_entry_by_global_id(&on); let owner_id = *on_entry.owner_id(); @@ -480,7 +479,7 @@ impl Coordinator { // We keep `raw_df_meta` live so that on success we can emit its raw // notices to the user session (rendered against the user's // session-aware humanizer). - let (df_desc, raw_df_meta) = global_lir_plan.unapply(); + let (df_desc, mut raw_df_meta) = global_lir_plan.unapply(); let on_desc = on_entry .relation_desc() .expect("can only create indexes on items with a valid description"); @@ -495,46 +494,14 @@ impl Coordinator { global_id, None, global_mir_plan.df_desc().clone(), - df_desc.clone(), - df_meta.clone(), + df_desc, + df_meta, optimizer_features, ) .await; let transact_result = self - .catalog_transact_with_side_effects(Some(ctx), ops, move |coord, _ctx| { - Box::pin(async move { - // Save plan structures. - coord - .catalog_mut() - .set_optimized_plan(global_id, global_mir_plan.df_desc().clone()); - coord - .catalog_mut() - .set_physical_plan(global_id, df_desc.clone()); - - let notice_builtin_updates_fut = - coord.persist_dataflow_metainfo(df_meta, global_id); - - // TODO: Maybe in the future, pass the read holds - // `ship_new_dataflow` takes on to compute, to hold on to them - // and downgrade when possible? - coord - .ship_new_dataflow( - &id_bundle, - df_desc, - cluster_id, - notice_builtin_updates_fut, - ) - .await; - // No `allow_writes` here because indexes do not modify external state. - - coord.update_compute_read_policy( - cluster_id, - item_id, - compaction_window.unwrap_or_default().into(), - ); - }) - }) + .catalog_transact_with_context(None, Some(ctx), ops) .await; match transact_result { @@ -543,6 +510,13 @@ impl Coordinator { // catalog transaction has succeeded. If the transaction had // failed, emitting notices would confuse the user with // information about an item that wasn't actually created. + // Optimizer-only dependencies are not covered by SQL plan validity. + raw_df_meta.optimizer_notices.retain(|notice| { + notice + .dependencies() + .iter() + .all(|id| self.catalog().try_get_entry_by_global_id(id).is_some()) + }); self.emit_raw_optimizer_notices_to_user(ctx, &raw_df_meta.optimizer_notices); Ok(StageResult::Response(ExecuteResponse::CreatedIndex)) } diff --git a/src/adapter/src/optimize/index.rs b/src/adapter/src/optimize/index.rs index 22b3b634ce3e7..a861ca1f776fe 100644 --- a/src/adapter/src/optimize/index.rs +++ b/src/adapter/src/optimize/index.rs @@ -124,12 +124,6 @@ pub struct GlobalLirPlan { df_meta: DataflowMetainfo, } -impl GlobalLirPlan { - pub fn df_desc(&self) -> &LirDataflowDescription { - &self.df_desc - } -} - impl Optimize for Optimizer { type To = GlobalMirPlan; diff --git a/src/catalog/src/expr_cache.rs b/src/catalog/src/expr_cache.rs index 320e2ed474104..070bbb95db738 100644 --- a/src/catalog/src/expr_cache.rs +++ b/src/catalog/src/expr_cache.rs @@ -33,7 +33,7 @@ use mz_transform::dataflow::DataflowMetainfo; use mz_transform::notice::OptimizerNotice; use semver::Version; use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc; +use tokio::sync::{mpsc, oneshot}; use tracing::{debug, warn}; #[derive( @@ -324,6 +324,24 @@ impl ExpressionCache { Ok((local_expressions, global_expressions)) } + fn get_global(&self, id: GlobalId) -> Option { + let key = CacheKey { + build_version: self.build_version.to_string(), + id, + expr_type: ExpressionType::Global, + }; + let expressions = self.durable_cache.get_local(&key)?; + match bincode::deserialize(expressions) { + Ok(expressions) => Some(expressions), + Err(err) => { + soft_panic_or_log!( + "unable to deserialize global expressions: ({key:?}, {expressions:?}): {err:?}" + ); + None + } + } + } + /// Durably removes all entries given by `invalidate_ids` and inserts `new_local_expressions` /// and `new_global_expressions` into current build version. /// @@ -405,6 +423,10 @@ impl ExpressionCache { /// Operations to perform on the cache. enum CacheOperation { + GetGlobal { + id: GlobalId, + tx: oneshot::Sender>, + }, /// See [`ExpressionCache::update`]. Update { new_local_expressions: Vec<(GlobalId, LocalExpressions)>, @@ -436,6 +458,9 @@ impl ExpressionCacheHandle { spawn(|| "expression-cache-task", async move { while let Some(op) = rx.recv().await { match op { + CacheOperation::GetGlobal { id, tx } => { + let _ = tx.send(cache.get_global(id)); + } CacheOperation::Update { new_local_expressions, new_global_expressions, @@ -457,6 +482,16 @@ impl ExpressionCacheHandle { (Self { tx }, local_expressions, global_expressions) } + /// Returns best-effort local cache contents for the current build version, or `None` + /// if absent or the cache task shuts down. Reads follow prior updates on this handle + /// without synchronizing with other cache owners. Callers must validate the item version, + /// optimizer features, and dependencies against their catalog/compute snapshot. + pub async fn get_global(&self, id: GlobalId) -> Option { + let (tx, rx) = oneshot::channel(); + self.tx.send(CacheOperation::GetGlobal { id, tx }).ok()?; + rx.await.ok().flatten() + } + pub fn update( &self, new_local_expressions: Vec<(GlobalId, LocalExpressions)>, @@ -527,6 +562,7 @@ mod tests { spawn(&first_build_version, ¤t_items, remove_prior_versions).await; assert_eq!(local_exprs, BTreeMap::new(), "new cache should be empty"); assert_eq!(global_exprs, BTreeMap::new(), "new cache should be empty"); + assert_eq!(cache.get_global(GlobalId::User(next_id)).await, None); // Insert some expressions into the cache. let mut local_exps = BTreeMap::new(); @@ -536,13 +572,13 @@ mod tests { let local_exp = gen_local_expressions(); let global_exp = gen_global_expressions(); - cache - .update( - vec![(id, local_exp.clone())], - vec![(id, global_exp.clone())], - BTreeSet::new(), - ) - .await; + // The read must observe the update even without awaiting its completion. + drop(cache.update( + vec![(id, local_exp.clone())], + vec![(id, global_exp.clone())], + BTreeSet::new(), + )); + assert_eq!(cache.get_global(id).await, Some(global_exp.clone())); current_items.insert(id, RelationVersion::root()); current_items.extend( @@ -706,8 +742,11 @@ mod tests { { // Re-open the cache at the first build version. - let (_cache, local_entries, global_entries) = + let (cache, local_entries, global_entries) = spawn(&first_build_version, ¤t_items, remove_prior_versions).await; + for id in new_gen_global_exps.keys() { + assert_eq!(cache.get_global(*id).await, None); + } assert_eq!( local_entries, local_exps, "Previous build version local expressions should still exist" @@ -721,7 +760,7 @@ mod tests { { // Open the cache at a new build version and clear previous build versions. remove_prior_versions = true; - let (_cache, local_entries, global_entries) = + let (cache, local_entries, global_entries) = spawn(&second_build_version, ¤t_items, remove_prior_versions).await; assert_eq!( local_entries, new_gen_local_exps, @@ -731,6 +770,10 @@ mod tests { global_entries, new_gen_global_exps, "new build version global expressions should be persisted" ); + let (&id, expressions) = new_gen_global_exps.first_key_value().expect("not empty"); + assert_eq!(cache.get_global(id).await, Some(expressions.clone())); + drop(cache.update(Vec::new(), Vec::new(), BTreeSet::from([id]))); + assert_eq!(cache.get_global(id).await, None); } { diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index 60a5ba99d358a..1722c6aea1db4 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -865,6 +865,76 @@ def workflow_bound_size_mz_cluster_replica_metrics_history(c: Composition) -> No ) +def workflow_index_without_expression_cache(c: Composition) -> None: + # Expression-cache enablement is sampled when the catalog opens. + with c.override( + Materialized( + additional_system_parameter_defaults={ + "enable_expression_cache": "false", + "enable_mz_notices": "true", + }, + ) + ): + c.up("materialized", Service("testdrive_no_reset", idle=True)) + c.testdrive( + service="testdrive_no_reset", + input=dedent(""" + > CREATE TABLE uncached_index_t (a int); + > INSERT INTO uncached_index_t VALUES (1), (2); + > CREATE VIEW uncached_index_v AS SELECT a + 1 AS b FROM uncached_index_t; + > CREATE INDEX uncached_arrangement ON uncached_index_v (); + > CREATE INDEX IF NOT EXISTS uncached_arrangement ON uncached_index_v (b); + """), + ) + index_id = c.sql_query( + "SELECT id FROM mz_indexes WHERE name = 'uncached_arrangement'", + reuse_connection=False, + )[0][0] + + def verify() -> None: + c.testdrive( + service="testdrive_no_reset", + input=dedent(""" + > SELECT b FROM uncached_index_v ORDER BY b; + 2 + 3 + """), + ) + notices = c.sql_query( + f"SELECT notice_type, count(*) FROM mz_internal.mz_optimizer_notices " + f"WHERE object_id = '{index_id}' GROUP BY notice_type", + user="mz_system", + port=6877, + reuse_connection=False, + ) + assert notices == [("Empty index key", 1)], notices + plan = c.sql_query( + "EXPLAIN SELECT b FROM uncached_index_v", reuse_connection=False + )[0][0] + assert "uncached_arrangement" in plan, plan + for stage in ("OPTIMIZED", "PHYSICAL"): + c.sql_query( + f"EXPLAIN {stage} PLAN FOR INDEX uncached_arrangement", + reuse_connection=False, + ) + + verify() + c.kill("materialized") + c.up("materialized") + verify() + + c.sql("DROP INDEX uncached_arrangement", reuse_connection=False) + notices = c.sql_query( + f"SELECT count(*) FROM mz_internal.mz_optimizer_notices " + f"WHERE object_id = '{index_id}'", + user="mz_system", + port=6877, + reuse_connection=False, + ) + assert notices == [(0,)], notices + c.sql("DROP TABLE uncached_index_t CASCADE", reuse_connection=False) + + def workflow_index_compute_dependencies(c: Composition) -> None: """ Assert that materialized views and index catalog items see and use only From 6c76f64016f7b78b5d74b8f17044b421d3e91add Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 3 Sep 2026 20:38:02 +0000 Subject: [PATCH 08/36] adapter: derive MV storage registration from catalog updates Register materialized view storage collections from committed implications before dependent sinks and indexes acquire read holds. Share descriptor construction with bootstrap to preserve initial visibility and replacement shard ownership. Apply read policies through the deferred batch. Keep compute installation and write enablement in the sequencer for this incremental step. Extend the existing MV test with a dependent index. Adapter cargo check and Rust/Python formatting pass. Independent review found no issue. Full local formatting and lint are blocked by missing tools and a doctest dependency build. Regular PR CI is pending. --- .../design/20260903_decoupled_coordination.md | 15 +++++++ src/adapter/src/coord.rs | 20 +--------- src/adapter/src/coord/catalog_implications.rs | 33 ++++++++++++++- src/adapter/src/coord/read_policy.rs | 25 ------------ .../inner/create_materialized_view.rs | 40 +------------------ test/testdrive/materialized-views.td | 2 + 6 files changed, 51 insertions(+), 84 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 6a625ea217844..c15d400dbfbfd 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -180,3 +180,18 @@ runtime validation remains in [PR CI](https://github.com/MaterializeInc/material The no-sequencer same-batch runtime case remains uncovered until a production catalog subscriber exists. External catalog writers fence the adapter today, and the existing read-only catalog harness does not apply controller effects. + +### 2026-09-03: MV storage registration from committed implications + +MV additions register storage before dependent sinks/indexes, then initialize +read policies through the deferred batch. Runtime and bootstrap share descriptor +construction, including replacement ownership and the initial storage frontier. +Compute installation remains sequencer-side. No boundary decision changed. + +Local adapter compilation and Rust/Python formatting passed. Full formatting and +lint are blocked by missing tools and a Python-doctest OpenSSL dependency build. +Independent review found no issue. This slice's [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks) +is pending. Prior regular [CI build 133879](https://buildkite.com/materialize/test/builds/133879) +passed. Next useful step: catalog-driven MV compute planning and installation, +preserving refresh timestamp selection and input protection. Same-batch runtime +coverage still awaits a production subscriber. diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index 788838bf19211..581ac73b2fb1f 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -3479,25 +3479,7 @@ impl Coordinator { }; } CatalogItem::MaterializedView(mv) => { - // Applying a replacement preserves the ownership link established when the - // replacement was created. The oldest collection owns the shard, each applied - // replacement points to its predecessor, and a pending replacement starts by - // pointing to its target's latest collection. - // - // NOTE: Versioned tables chain in the opposite direction because their latest - // version owns the shard. Each chain matches its runtime replacement path. - let mut primary = mv - .replacement_target - .map(|target_id| catalog.get_entry(&target_id).latest_global_id()); - let collection_descs = mv.collection_descs().map(|(gid, _version, desc)| { - let mut collection_desc = - CollectionDescription::for_other(desc, mv.initial_as_of.clone()); - collection_desc.primary = primary; - primary = Some(gid); - (gid, collection_desc) - }); - - collections.extend(collection_descs); + collections.extend(self.materialized_view_storage_collections(mv)); compute_collections.push((mv.global_id_writes(), mv.desc.latest())); } CatalogItem::Sink(sink) => { diff --git a/src/adapter/src/coord/catalog_implications.rs b/src/adapter/src/coord/catalog_implications.rs index e8be3df3e70a3..f8b0e9616dce4 100644 --- a/src/adapter/src/coord/catalog_implications.rs +++ b/src/adapter/src/coord/catalog_implications.rs @@ -476,7 +476,16 @@ impl Coordinator { dropped_item_names.insert(metric_sink.global_id, full_name); } CatalogImplication::MaterializedView(CatalogImplicationKind::Added(mv)) => { - tracing::debug!(?mv, "not handling AddMaterializedView in here yet"); + source_collections_to_create + .extend(self.materialized_view_storage_collections(&mv)); + storage_policies_to_initialize + .entry( + mv.custom_logical_compaction_window + .unwrap_or(CompactionWindow::Default), + ) + .or_default() + .extend(mv.global_ids()); + // TODO: Derive compute installation from the committed MV as well. } CatalogImplication::MaterializedView(CatalogImplicationKind::Altered { prev: prev_mv, @@ -1312,6 +1321,28 @@ impl Coordinator { Ok(()) } + /// Describe an MV's storage collections from committed catalog state. + pub(super) fn materialized_view_storage_collections( + &self, + mv: &MaterializedView, + ) -> Vec<(GlobalId, CollectionDescription)> { + // The oldest collection owns the shard. Applied replacements point to + // their predecessor, and pending replacements to their target's latest + // collection. NOTE: Versioned tables chain in the opposite direction. + let mut primary = mv + .replacement_target + .map(|target_id| self.catalog().get_entry(&target_id).latest_global_id()); + mv.collection_descs() + .map(|(gid, _version, desc)| { + let mut collection_desc = + CollectionDescription::for_other(desc, mv.initial_as_of.clone()); + collection_desc.primary = primary; + primary = Some(gid); + (gid, collection_desc) + }) + .collect() + } + #[instrument(level = "debug")] async fn create_source_collections( &mut self, diff --git a/src/adapter/src/coord/read_policy.rs b/src/adapter/src/coord/read_policy.rs index 29f9750966fc0..a4e23ca1b29d1 100644 --- a/src/adapter/src/coord/read_policy.rs +++ b/src/adapter/src/coord/read_policy.rs @@ -209,31 +209,6 @@ impl ReadHolds { } impl crate::coord::Coordinator { - /// Initialize the storage read policies. - /// - /// This should be called only after a storage collection is created, and - /// ideally very soon afterwards. The collection is otherwise initialized - /// with a read policy that allows no compaction. - pub(crate) async fn initialize_storage_read_policies( - &mut self, - ids: BTreeSet, - compaction_window: CompactionWindow, - ) { - let gids = ids - .into_iter() - .map(|item_id| self.catalog().get_entry(&item_id).global_ids()) - .flatten() - .collect(); - self.initialize_read_policies( - &CollectionIdBundle { - storage_ids: gids, - compute_ids: BTreeMap::new(), - }, - compaction_window, - ) - .await; - } - /// Initialize the compute read policies. /// /// This should be called only after a compute collection is created, and diff --git a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs index d986da63fd653..bc25b43fc9886 100644 --- a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs +++ b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs @@ -10,8 +10,6 @@ use anyhow::anyhow; use differential_dataflow::lattice::Lattice; use maplit::btreemap; -use maplit::btreeset; -use mz_adapter_types::compaction::CompactionWindow; use mz_catalog::memory::objects::{CatalogItem, MaterializedView}; use mz_expr::{CollectionPlan, ResultSpec}; use mz_ore::collections::CollectionExt; @@ -29,7 +27,6 @@ use mz_sql::plan; use mz_sql::session::metadata::SessionMetadata; use mz_sql_parser::ast; use mz_sql_parser::ast::display::AstDisplay; -use mz_storage_client::controller::CollectionDescription; use std::collections::BTreeMap; use timely::progress::Antichain; use tracing::Span; @@ -50,7 +47,6 @@ use crate::explain::optimizer_trace::OptimizerTrace; use crate::optimize::dataflows::dataflow_import_id_bundle; use crate::optimize::{self, Optimize}; use crate::session::Session; -use crate::util::ResultExt; use crate::{AdapterNotice, CollectionIdBundle, ExecuteContext, TimestampProvider, catalog}; impl Staged for CreateMaterializedViewStage { @@ -718,7 +714,6 @@ impl Coordinator { // here, so that if the catalog transaction below fails the user // isn't shown confusing notices about an item that wasn't actually // created. - let output_desc = global_lir_plan.desc().clone(); let (mut df_desc, raw_df_meta) = global_lir_plan.unapply(); let df_meta = { let system_catalog = self.catalog().for_system_session(); @@ -772,39 +767,6 @@ impl Coordinator { df_desc.set_initial_as_of(initial_as_of); df_desc.until = until; - let storage_metadata = coord.catalog.state().storage_metadata(); - - let mut collection_desc = - CollectionDescription::for_other(output_desc, Some(storage_as_of)); - let mut allow_writes = true; - - // If this MV is intended to replace another one, we need to start it in - // read-only mode, targeting the shard of the replacement target. - if let Some(target_id) = replacement_target { - let target_gid = coord.catalog.get_entry(&target_id).latest_global_id(); - collection_desc.primary = Some(target_gid); - allow_writes = false; - } - - // Announce the creation of the materialized view source. - coord - .controller - .storage - .create_collections( - storage_metadata, - None, - vec![(global_id, collection_desc)], - ) - .await - .unwrap_or_terminate("cannot fail to append"); - - coord - .initialize_storage_read_policies( - btreeset![item_id], - compaction_window.unwrap_or(CompactionWindow::Default), - ) - .await; - coord .ship_dataflow_and_notice_builtin_table_updates( df_desc, @@ -814,7 +776,7 @@ impl Coordinator { ) .await; - if allow_writes { + if replacement_target.is_none() { coord.allow_writes(cluster_id, global_id); } }) diff --git a/test/testdrive/materialized-views.td b/test/testdrive/materialized-views.td index 7332a108f318e..8876470f9871c 100644 --- a/test/testdrive/materialized-views.td +++ b/test/testdrive/materialized-views.td @@ -64,6 +64,8 @@ $ kafka-ingest format=avro topic=materialized-views schema=${materialized-views} > SELECT * FROM v1; 3 +> CREATE DEFAULT INDEX ON v1; + > CREATE SINK sink1 IN CLUSTER ${arg.single-replica-cluster} FROM v1 From 3285f5df356da8f5398273696fa113afd1210ed5 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 3 Sep 2026 21:16:56 +0000 Subject: [PATCH 09/36] adapter: extract catalog-based MV plan reconstruction Reuse bootstrap MV optimization and notice rendering as a catalog-based planning boundary for compute implications. Preserve cache policy, SQL typing, item versions, and timestamp selection. Compute installation and input read protection remain unchanged. Extend cache-disabled restart coverage to MV results, stored EXPLAIN plans, and continued maintenance after restart. Validation: adapter cargo check and Rust/Python formatting passed. Independent review found no issue. Full formatting and lint are blocked by missing tools and an OpenSSL dependency build. Runtime validation and regular PR CI are pending. --- .../design/20260903_decoupled_coordination.md | 16 ++ src/adapter/src/coord.rs | 157 +++++++++--------- test/restart/mzcompose.py | 20 ++- 3 files changed, 113 insertions(+), 80 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index c15d400dbfbfd..a1af98f3712e2 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -195,3 +195,19 @@ is pending. Prior regular [CI build 133879](https://buildkite.com/materialize/te passed. Next useful step: catalog-driven MV compute planning and installation, preserving refresh timestamp selection and input protection. Same-batch runtime coverage still awaits a production subscriber. + +### 2026-09-03: Reusable MV reconstruction + +Extracted bootstrap MV reconstruction without changing cache policy or timestamp +selection. Extended cache-disabled restart coverage to MV results, EXPLAIN, and +continued maintenance. Independent review found no issue. Adapter compilation +and Rust/Python formatting passed. +Full formatting and lint remain blocked by missing tools and an OpenSSL dependency +build. Prior [CI build 133882](https://buildkite.com/materialize/test/builds/133882) +passed. This slice's [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks) +and runtime coverage are pending. + +Next useful step: establish input protection for runtime MV reconstruction before +moving compute installation. A cache miss can choose imports outside the creator's +holds, and acquiring fresh holds after commit cannot recover history needed for +the committed first refresh. No protection mechanism or boundary change was agreed. diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index 581ac73b2fb1f..db67f4afda7c7 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -107,7 +107,7 @@ use mz_catalog::durable::OpenableDurableCatalogState; use mz_catalog::expr_cache::{GlobalExpressions, LocalExpressions, latest_item_version}; use mz_catalog::memory::objects::{ CatalogEntry, CatalogItem, ClusterReplicaProcessStatus, Connection, DataSourceDesc, Index, - ReconfigurationTarget, Table, TableDataSource, + MaterializedView, ReconfigurationTarget, Table, TableDataSource, }; use mz_cloud_resources::{CloudResourceController, VpcEndpointConfig, VpcEndpointEvent}; use mz_compute_client::as_of_selection; @@ -3739,6 +3739,57 @@ impl Coordinator { }) } + /// Builds a materialized view plan and rendered notices from its catalog definition. + /// + /// The snapshot must contain the compute collections available for imports. + /// This does not select an `as_of`, install the dataflow, or cache the result. + fn build_materialized_view_dataflow_plan( + &self, + name: &QualifiedItemName, + mv: &MaterializedView, + compute_instance: ComputeInstanceSnapshot, + optimizer_config: OptimizerConfig, + ) -> Result { + let global_id = mv.global_id_writes(); + let (_, internal_view_id) = self.allocate_transient_id(); + let debug_name = self.catalog().resolve_full_name(name, None).to_string(); + let mut optimizer = optimize::materialized_view::Optimizer::new( + self.owned_catalog().as_optimizer_catalog(), + compute_instance, + global_id, + internal_view_id, + mv.desc.latest().iter_names().cloned().collect(), + mv.non_null_assertions.clone(), + mv.refresh_schedule.clone(), + debug_name, + optimizer_config.clone(), + self.optimizer_metrics(), + ); + + // Use the HIR SQL type because MIR SQL types may not be coherent. + let typ = + infer_sql_type_for_catalog(&mv.raw_expr, &mv.locally_optimized_expr.as_ref().clone()); + let global_mir_plan = + optimizer.optimize((mv.locally_optimized_expr.as_ref().clone(), typ))?; + let global_mir = global_mir_plan.df_desc().clone(); + let global_lir_plan = optimizer.optimize(global_mir_plan)?; + let (physical_plan, metainfo) = global_lir_plan.unapply(); + let notice_ids = std::iter::repeat_with(|| self.allocate_transient_id()) + .map(|(_item_id, gid)| gid) + .take(metainfo.optimizer_notices.len()) + .collect::>(); + let dataflow_metainfos = + self.catalog() + .render_notices(metainfo, notice_ids, Some(global_id)); + Ok(GlobalExpressions { + global_mir, + physical_plan, + dataflow_metainfos, + optimizer_features: optimizer_config.features, + item_version: latest_item_version(&mv.collections), + }) + } + /// Invokes the optimizer on all indexes and materialized views in the catalog and inserts the /// resulting dataflow plans into the catalog state. /// @@ -3843,86 +3894,34 @@ impl Coordinator { let optimizer_config = optimizer_config(&self.catalog, mv.cluster_id); - let (optimized_plan, physical_plan, metainfo) = match cached_global_exprs - .remove(&global_id) - { - Some(global_expressions) - if global_expressions.optimizer_features - == optimizer_config.features => - { - debug!("global expression cache hit for {global_id:?}"); - ( - global_expressions.global_mir, - global_expressions.physical_plan, - global_expressions.dataflow_metainfos, - ) - } - Some(_) | None => { - let (_, internal_view_id) = self.allocate_transient_id(); - let debug_name = self - .catalog() - .resolve_full_name(entry.name(), None) - .to_string(); - - let (optimized_plan, global_lir_plan) = { - // Build an optimizer for this MATERIALIZED VIEW. - let mut optimizer = optimize::materialized_view::Optimizer::new( - self.owned_catalog().as_optimizer_catalog(), + let (optimized_plan, physical_plan, metainfo) = + match cached_global_exprs.remove(&global_id) { + Some(global_expressions) + if global_expressions.optimizer_features + == optimizer_config.features => + { + debug!("global expression cache hit for {global_id:?}"); + ( + global_expressions.global_mir, + global_expressions.physical_plan, + global_expressions.dataflow_metainfos, + ) + } + Some(_) | None => { + let expressions = self.build_materialized_view_dataflow_plan( + entry.name(), + mv, compute_instance.clone(), - global_id, - internal_view_id, - mv.desc.latest().iter_names().cloned().collect(), - mv.non_null_assertions.clone(), - mv.refresh_schedule.clone(), - debug_name, - optimizer_config.clone(), - self.optimizer_metrics(), - ); - - // MIR ⇒ MIR optimization (global) - // We make sure to use the HIR SQL type (since MIR SQL types may not be coherent). - let typ = infer_sql_type_for_catalog( - &mv.raw_expr, - &mv.locally_optimized_expr.as_ref().clone(), - ); - let global_mir_plan = optimizer - .optimize((mv.locally_optimized_expr.as_ref().clone(), typ))?; - let optimized_plan = global_mir_plan.df_desc().clone(); - - // MIR ⇒ LIR lowering and LIR ⇒ LIR optimization (global) - let global_lir_plan = optimizer.optimize(global_mir_plan)?; - - (optimized_plan, global_lir_plan) - }; - - let (physical_plan, metainfo) = global_lir_plan.unapply(); - let metainfo = { - // Pre-allocate a vector of transient GlobalIds for each notice. - let notice_ids = - std::iter::repeat_with(|| self.allocate_transient_id()) - .map(|(_item_id, global_id)| global_id) - .take(metainfo.optimizer_notices.len()) - .collect::>(); - // Return a metainfo with rendered notices. - self.catalog().render_notices( - metainfo, - notice_ids, - Some(mv.global_id_writes()), + optimizer_config, + )?; + uncached_expressions.insert(global_id, expressions.clone()); + ( + expressions.global_mir, + expressions.physical_plan, + expressions.dataflow_metainfos, ) - }; - uncached_expressions.insert( - global_id, - GlobalExpressions { - global_mir: optimized_plan.clone(), - physical_plan: physical_plan.clone(), - dataflow_metainfos: metainfo.clone(), - optimizer_features: optimizer_config.features.clone(), - item_version: latest_item_version(&mv.collections), - }, - ); - (optimized_plan, physical_plan, metainfo) - } - }; + } + }; let catalog = self.catalog_mut(); catalog.set_optimized_plan(mv.global_id_writes(), optimized_plan); diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index 1722c6aea1db4..780c856e9373b 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -865,7 +865,7 @@ def workflow_bound_size_mz_cluster_replica_metrics_history(c: Composition) -> No ) -def workflow_index_without_expression_cache(c: Composition) -> None: +def workflow_dataflows_without_expression_cache(c: Composition) -> None: # Expression-cache enablement is sampled when the catalog opens. with c.override( Materialized( @@ -884,6 +884,8 @@ def workflow_index_without_expression_cache(c: Composition) -> None: > CREATE VIEW uncached_index_v AS SELECT a + 1 AS b FROM uncached_index_t; > CREATE INDEX uncached_arrangement ON uncached_index_v (); > CREATE INDEX IF NOT EXISTS uncached_arrangement ON uncached_index_v (b); + > CREATE MATERIALIZED VIEW uncached_mv AS SELECT sum(b) AS total FROM uncached_index_v; + > CREATE DEFAULT INDEX ON uncached_mv; """), ) index_id = c.sql_query( @@ -898,6 +900,9 @@ def verify() -> None: > SELECT b FROM uncached_index_v ORDER BY b; 2 3 + + > SELECT total FROM uncached_mv; + 5 """), ) notices = c.sql_query( @@ -917,12 +922,25 @@ def verify() -> None: f"EXPLAIN {stage} PLAN FOR INDEX uncached_arrangement", reuse_connection=False, ) + c.sql_query( + f"EXPLAIN {stage} PLAN FOR MATERIALIZED VIEW uncached_mv", + reuse_connection=False, + ) verify() c.kill("materialized") c.up("materialized") verify() + c.testdrive( + service="testdrive_no_reset", + input=dedent(""" + > INSERT INTO uncached_index_t VALUES (3); + > SELECT total FROM uncached_mv; + 9 + """), + ) + c.sql("DROP INDEX uncached_arrangement", reuse_connection=False) notices = c.sql_query( f"SELECT count(*) FROM mz_internal.mz_optimizer_notices " From 5351ccc383c67f0ecf905a2f45820206cb85e2c3 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 3 Sep 2026 22:10:10 +0000 Subject: [PATCH 10/36] test: cover uncached recovery before an MV first refresh Keep a REFRESH AT CREATION materialized view unexecuted across restart, change its input after creation, and verify reconstruction uses the committed refresh timestamp rather than current input state. Extend the existing cache-disabled restart workflow without a test-only coordinator API. Record the input-protection obstacle to moving runtime MV installation. Broader creator holds can change historical readability and do not cover all access paths exposed by catalog changes. Validation: Python formatting and Ruff passed. Full local formatting and lint are blocked by missing tools and an OpenSSL dependency build. Runtime coverage is pending regular PR CI. --- .../design/20260903_decoupled_coordination.md | 18 +++++++++++++++++ test/restart/mzcompose.py | 20 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index a1af98f3712e2..d41bbe83c584d 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -211,3 +211,21 @@ Next useful step: establish input protection for runtime MV reconstruction befor moving compute installation. A cache miss can choose imports outside the creator's holds, and acquiring fresh holds after commit cannot recover history needed for the committed first refresh. No protection mechanism or boundary change was agreed. + +### 2026-09-03: Pending first-refresh recovery coverage + +Added cache-disabled restart coverage for an unexecuted first refresh after its +input changes. Python formatting and Ruff passed. Full formatting/lint remain +blocked by missing tools and an OpenSSL dependency build. Prior +[CI build 133887](https://buildkite.com/materialize/test/builds/133887) passed. +This slice's [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks) +and runtime verification are pending. + +Code inspection ruled out merely broadening creator holds: timestamp selection +joins every hold, changing historical MV readability, while +[`sufficient_collections`](../../../src/adapter/src/coord/indexes.rs) stops at +available indexes and does not cover sibling indexes exposed by same-batch drops. +Compute holds do protect actual transitive dependencies. Production MV installation +remains paused. Next proposed step: agree whether to establish lifecycle-owned +protection now or build a temporary protected-plan reconstruction bridge. No new +mechanism or boundary change was agreed. diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index 780c856e9373b..c2af3c4798ac1 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -886,6 +886,16 @@ def workflow_dataflows_without_expression_cache(c: Composition) -> None: > CREATE INDEX IF NOT EXISTS uncached_arrangement ON uncached_index_v (b); > CREATE MATERIALIZED VIEW uncached_mv AS SELECT sum(b) AS total FROM uncached_index_v; > CREATE DEFAULT INDEX ON uncached_mv; + + # Keep the first refresh pending until after catalog reconstruction. + > CREATE CLUSTER uncached_refresh SIZE 'scale=1,workers=1', REPLICATION FACTOR 0; + > CREATE TABLE uncached_refresh_t (a int); + > INSERT INTO uncached_refresh_t VALUES (1); + > CREATE MATERIALIZED VIEW uncached_refresh_mv + IN CLUSTER uncached_refresh + WITH (REFRESH AT CREATION) + AS SELECT a FROM uncached_refresh_t; + > UPDATE uncached_refresh_t SET a = 2; """), ) index_id = c.sql_query( @@ -938,6 +948,14 @@ def verify() -> None: > INSERT INTO uncached_index_t VALUES (3); > SELECT total FROM uncached_mv; 9 + + > ALTER CLUSTER uncached_refresh SET (REPLICATION FACTOR 1); + > SELECT a FROM uncached_refresh_t; + 2 + + # Recovery must use the committed first refresh, not the current input. + > SELECT a FROM uncached_refresh_mv; + 1 """), ) @@ -951,6 +969,8 @@ def verify() -> None: ) assert notices == [(0,)], notices c.sql("DROP TABLE uncached_index_t CASCADE", reuse_connection=False) + c.sql("DROP TABLE uncached_refresh_t CASCADE", reuse_connection=False) + c.sql("DROP CLUSTER uncached_refresh", reuse_connection=False) def workflow_index_compute_dependencies(c: Composition) -> None: From f302cddbe5b02d5185fcd2c7dc07e41631c7afa2 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 3 Sep 2026 22:28:47 +0000 Subject: [PATCH 11/36] doc: choose catalog-backed compaction bounds Record explicit catalog bounds as the compaction authority for maintained collections. Define the catalog transaction, application, and recovery boundaries while leaving representation, granularity, and publication cadence to implementation and measurement. Document delegated advancement as an alternative and explain the tradeoff between ongoing catalog publication and owner-backed DDL admission. Preserve the exploration log and append the agreed decision. Documentation checks passed, including preservation of earlier log entries. Full formatting and lint remain blocked by missing tools and the Python doctest OpenSSL dependency build. No runtime behavior changes. --- .../design/20260903_decoupled_coordination.md | 116 ++++++++++++++++-- 1 file changed, 107 insertions(+), 9 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index d41bbe83c584d..2336a265669d1 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -41,6 +41,37 @@ client ownership of the cluster's maintained lifecycle. Responses, cancellation, and disconnect cleanup belong to the relevant client. This is a semantic split, not simply another listener for the same controller protocol. +## Catalog-backed compaction bounds + +Store explicit compaction bounds for maintained collections in the catalog. +Storage and compute must not compact those collections beyond committed +permission. Applied compaction may lag, retaining extra history. Bounds advance +monotonically within a collection's lifetime. Their representation and granularity +remain implementation choices. + +Introducing or strengthening a maintained read requirement and advancing the +affected bounds must be coordinated at the catalog transaction boundary. A +transaction must not commit a requirement incompatible with already authorized +compaction, or authorize compaction that invalidates a committed requirement. +Protection must cover the interval between catalog commit and cluster application, +independently of the creating adapter's lifetime. + +This places maintained read requirements and permission to discard history under +the same durable authority. Cluster-side components have an explicit limit to +enforce and recover, without treating a live owner's local accounting as the +authority to advance beyond it. + +The cost is ongoing catalog traffic proportional to the number of changing bounds +and their publication cadence, together with catalog processing and subscriber +work. Publication competes with DDL, and further advancement depends on catalog +write availability. Delaying publication retains more history, with storage and +compute resource costs even when the metadata bandwidth is modest. + +Avoid coupling ordinary query throughput to catalog writes for every hold change. +Coalescing or rate-limiting bound advancement may retain extra history, but must +not delay protection until after it is needed. Choose cadence and batching from +measured catalog load, DDL latency, and retention cost. + ## Boundary contracts ### Readability and compaction @@ -51,11 +82,10 @@ valid, compute and persist compaction must respect it, including through dependencies, installation, and ownership handover. One client cannot release another's protection. -Catalog state carries the authority for advancing readability frontiers. The -representation and accounting of individual read requirements remain open. -An evolving per-object frontier is a candidate, not a prescribed schema. Keep -its meaning distinct from a dataflow's installation `as_of` and an MV's initial -storage visibility boundary unless the implementation establishes how they fit. +Keep compaction bounds distinct from a dataflow's installation `as_of` and an +MV's initial storage visibility boundary unless the implementation establishes +how they fit. The representation and accounting of individual read requirements +remain open. Propagation to persist critical since handles must respect all valid read requirements. Those handles are the durable backstop, not a substitute for @@ -63,10 +93,10 @@ multi-client accounting. Stale owners must not advance compaction or destroy data based on incomplete local knowledge. Abandoned client holds must be reclaimable without allowing that client to resume using invalid protection. -Avoid coupling ordinary query throughput to catalog writes for every hold -change. Coalescing or rate-limiting frontier advancement may retain extra -history, but must not delay protection until after it is needed. Choose cadence -and any configuration from measured catalog load and retention cost. +Recovery must establish actual readability and restore valid read requirements +before further advancement is authorized. Reconstructed plans must use inputs +readable at the protected timestamps, rather than assume equivalent access paths +have equivalent history. ### Visibility and execution @@ -80,6 +110,28 @@ maintained dataflows. Lifecycle ownership and permission to perform external writes must remain safe across restarts and handover, independently of query connection lifetime. +## Alternatives + +### Delegated compaction advancement + +The catalog could define maintained requirements and retention policies while a +fenced lifecycle owner accounts for client and maintained reads and advances +compaction directly. Persist critical handles would provide the durable storage +backstop, without publishing advancing bounds to the catalog. This avoids ongoing +frontier-publication traffic and its dependence on catalog write availability. + +Delegation still requires coordination when durable read requirements are +introduced or strengthened. Their admission must be tied to owner-held protection. +Reclaiming abandoned precommit protection must exclude a late commit that relies +on it. Observing an object's absence in a catalog snapshot is not sufficient. +Recovery of valid holds and enforcement against stale owners remain necessary +under either approach. + +We choose explicit bounds for the catalog-local permission boundary. Delegation +reduces ongoing catalog traffic but shifts coordination into maintained-DDL +admission and cleanup. We accept the publication and retention costs of explicit +bounds rather than this owner-backed admission and reclamation protocol. + ## Implementation and verification Implementers choose the smallest coherent mechanism and the order of work. @@ -229,3 +281,49 @@ Compute holds do protect actual transitive dependencies. Production MV installat remains paused. Next proposed step: agree whether to establish lifecycle-owned protection now or build a temporary protected-plan reconstruction bridge. No new mechanism or boundary change was agreed. + +### 2026-09-03: Lifecycle protection prioritized with Aljoscha + +Proceed with lifecycle-owned protection rather than a temporary adapter bridge. +Both restart jobs in [CI build 133889](https://buildkite.com/materialize/test/builds/133889) +passed, including the pending-first-refresh regression. Overall CI remains pending. + +The durable writer is already shared behind a mutex, so an independent lifecycle +component need not introduce concurrent catalog writers. Existing hold accounting +and epoch fencing are reusable, but neither recovers pending maintained read +requirements from catalog state. Before choosing a schema, clarify whether catalog +authority requires committed frontier bounds or can delegate advancement to a +fenced lifecycle owner under catalog-derived policies. The latter is a proposal, +not an agreed interpretation of the boundary. No production changes made. + +### 2026-09-03: Delegation failure-scenario exploration + +[CI build 133889](https://buildkite.com/materialize/test/builds/133889) passed. +Worked cases require atomic ordering of creation commit versus hold reclamation, +physical fencing, and a recovery barrier before compaction resumes. Snapshot absence +and catalog fencing alone are insufficient. Small abstract interleaving models +checked these orderings, not production behavior. The delegation proposal shifts +coordination to read-requiring DDL and owner handover rather than ongoing frontier +publication. Admission/revocation and recovery of valid client holds remain open. +The shared writer mutex only serializes one in-process handle, not independent +writers or subscriber delivery. No boundary decision or production change made. + +### 2026-09-03: Catalog-bound traffic estimate + +Aljoscha leans toward explicit bounds. For compact records advancing once per +minute, a planning budget of 300–500 bytes per retraction/insertion pair gives +0.5–0.83 MB/s at 100,000 changing bounds. This estimates uncompressed logical +updates, not measured persist traffic. Current structured JSONB encoding retains +whole-record JSON text. Whole-item rewrites, batch sizes, catalog CPU/DDL latency, +persist maintenance, subscriber fanout, and retained history need measurement. +No schema or publication cadence was agreed. + +### 2026-09-03: Explicit bounds agreed with Aljoscha + +Choose catalog-backed compaction bounds and document delegated advancement as an +alternative. Schema, granularity, batching, and publication cadence remain open. +The one-minute traffic estimate is not a chosen cadence. Next useful step: define +the catalog transaction boundary for maintained read requirements and bound +advancement. This is a design decision, with no production implementation change. +Documentation checks passed. Full formatting and lint remain blocked by missing +tools and the Python-doctest OpenSSL build. From 13c1821df670dc85fc72854c2d55d39b812f2ccf Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 11:08:10 +0000 Subject: [PATCH 12/36] doc: choose logical-input recovery protection Protect all logical collection inputs so recovery can reconstruct from catalog SQL without preserving optimizer decisions. Record the storage boundaries, progress-driven protection, and accepted retention cost. Automatic creation timestamps account for every logical input. Reject explicit historical refresh requests incompatible with committed compaction permission. Existing objects retain their promised results and convert only once their remaining recovery needs are protected. The conversion mechanism and rollout policy remain open. Document integrity checks passed, with independent review of the recovery boundary. Full formatting and lint are blocked by missing local tools and an OpenSSL dependency build. No runtime recovery experiments or retention measurements were run. --- .../design/20260903_decoupled_coordination.md | 116 +++++++++++++++++- 1 file changed, 112 insertions(+), 4 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 2336a265669d1..c6c6c16fd9ccb 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -7,10 +7,10 @@ Cluster-side components subscribe to catalog changes and enact the desired state, rather than depending on an adapter to send lifecycle commands. Adapters write catalog changes and use a separate fast protocol for query execution. -The goal is decoupling, with existing SQL behavior, consistency guarantees, and -performance preserved. Losing an adapter must not disrupt maintained dataflows -or other clients. Its own queries may fail. Transparent query or session -failover is out of scope. +The goal is decoupling, preserving consistency guarantees, performance, and +existing SQL behavior except for the [maintained-creation admission rules](#admission-and-conversion). +Losing an adapter must not disrupt maintained dataflows or other clients. Its own +queries may fail. Transparent query or session failover is out of scope. Working multi-adapter operation is the destination, not this deliverable. Boundaries must support independent adapters becoming catalog writers and query @@ -72,6 +72,23 @@ Coalescing or rate-limiting bound advancement may retain extra history, but must not delay protection until after it is needed. Choose cadence and batching from measured catalog load, DDL latency, and retention cost. +### Logical recovery dependencies + +Maintained recovery protection covers all logical collection inputs, including +those eliminated by optimization. Unmaterialized views do not form recovery +boundaries. Persisted inputs do: reading an upstream MV protects its output, +while that MV's maintenance separately protects its own inputs. Indexes remain +replaceable access paths, not the authority for recovery dependencies. + +Protection follows the history needed for installation and recovery, rather than +pinning creation-time history forever. For MVs it can advance with durable output +progress and cease when no further input reads are needed for recovery. Execution +holds may retain additional history. + +We accept extra retention on inputs the running plan does not read, especially +for slow or suspended consumers, to preserve reconstruction from catalog SQL +independently of optimizer choices. + ## Boundary contracts ### Readability and compaction @@ -98,6 +115,20 @@ before further advancement is authorized. Reconstructed plans must use inputs readable at the protected timestamps, rather than assume equivalent access paths have equivalent history. +### Admission and conversion + +Admission of maintained read requirements respects committed compaction permission +for all logical inputs, rather than relying on lagging physical compaction. +Automatically selected creation timestamps must be compatible with all those +inputs. Explicit historical refresh requests are rejected if any logical input +cannot support them, even when optimization removes that input. + +Existing objects retain their promised results. Their conversion to logical-input +protection becomes active only once their remaining recovery requirements are +protected across all logical inputs. Conversion must not skip pending results by +moving the recovery timestamp forward. The conversion mechanism and rollout policy +for objects that cannot yet satisfy this condition remain open. + ### Visibility and execution Catalog commit, cluster application, and query readiness are different events. @@ -112,6 +143,18 @@ connection lifetime. ## Alternatives +### Plan-specific recovery protection + +Protecting only physical inputs ties retention to optimizer output. Controller +dependency holds implicitly do this, with their aggregate effect preserved in +persist sinces. Bootstrap rebuilds holds from physical plans and durable storage +frontiers, but cannot restore discarded history for an input introduced by +replanning. + +A plan-specific design needs a recovery contract beyond an input-ID set, such as +durable logical recovery expressions with upgrade compatibility. We choose +logical-input protection rather than making optimization decisions authoritative. + ### Delegated compaction advancement The catalog could define maintained requirements and retention policies while a @@ -327,3 +370,68 @@ the catalog transaction boundary for maintained read requirements and bound advancement. This is a design decision, with no production implementation change. Documentation checks passed. Full formatting and lint remain blocked by missing tools and the Python-doctest OpenSSL build. + +### 2026-09-04: Admission and recovery scope exploration + +The proposed validation seam is durable `Transaction` state before +[`into_parts`](../../../src/catalog/src/durable/transaction.rs) extracts the batch. +MV creation protects physical imports but persists only the storage visibility +timestamp. Requirement identity must account for recovery and access-path changes, +not just creation admission. Protecting all logical dependencies risks retaining +unneeded history and rejecting historical creation on unused inputs. Protecting +selected imports requires a recoverable path through replanning and index drops. +This choice remains open, with schema work paused for discussion with Aljoscha. + +No production changes or local tests. Existing [CI build 133937](https://buildkite.com/materialize/test/builds/133937) +remains pending, with no reported failures at inspection. +Next useful step: agree the recovery protection scope before adding durable records. + +### 2026-09-04: Recovery cases traced + +Existing dependency holds retain dropped indexes and their storage inputs. Live +replanning lacks timestamp-aware access-path selection, while bootstrap can rebuild +indexes with different installation frontiers. Transitive storage protection handles +access-path loss but does not guarantee reconstruction from SQL: [optimizer goldens](../../../test/sqllogictest/transform/union_cancel.slt) +show whole logical inputs eliminated from nonconstant results. A reconstructed plan +can require their discarded history. The [expression cache](../../../src/catalog/src/expr_cache.rs) +explicitly has no cross-build representation compatibility contract. + +Pending proposal: retain a logical recovery computation over protected storage inputs, +without pinning physical indexes. This adds durable expression compatibility and +dependency-version obligations, not just frontier metadata. Independent scrutiny of +an IDs-only alternative using empty-input substitution found unestablished error and +semantic-assumption contracts. No approach was agreed. No production changes or runtime +experiments. Next useful step: review the recovery representation cost with Aljoscha. + +### 2026-09-04: Logical-input protection comparison + +Aljoscha leans toward protecting all logical inputs. The catalog already preserves +[logical dependencies for drop safety](../../../test/sqllogictest/materialized_views.slt), +but installed holds and [bootstrap recovery constraints](../../../src/compute-client/src/as_of_selection.rs) +follow physical imports. Bootstrap freezes recovered storage sinces, not missing +history. Conservative logical storage-input protection would strengthen that contract +while retaining SQL-based reconstruction, avoiding authoritative optimized expressions. +Extra retention, historical creation admission, and conversion of existing objects +need consideration. This remains a proposal. No production changes or runtime tests. + +### 2026-09-04: Logical-input protection agreed with Aljoscha + +Choose logical-input protection based on code inspection, accepting conservative +retention without making optimized expressions authoritative. Independent review +found no issue. Historical creation admission and conversion of existing objects +are the next design questions, not approved behavior changes. + +Document checks passed. Full formatting and lint remain blocked by missing tools +and the Python-doctest OpenSSL dependency build. No production changes, runtime +recovery experiments, or retention measurements were made. + +### 2026-09-04: Admission and conversion boundaries agreed with Aljoscha + +Automatic creation timestamps account for all logical inputs. Explicit historical +refresh requests incompatible with their committed compaction permission are +rejected. Existing objects keep their promised results, with conversion active only +once remaining recovery requirements are protected. The conversion mechanism and +rollout policy for objects unable to satisfy that condition remain open. + +Document checks passed. Full formatting and lint remain blocked by missing tools +and the OpenSSL dependency build. No production changes were made. From a4829fad49aaa7f274d0251db0790c54d03c2bb8 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 14:06:09 +0000 Subject: [PATCH 13/36] catalog: derive logical collection inputs Expand unmaterialized views through their relational name references and raw HIR reads, independently of optimization and available indexes. Stop at persisted collection outputs and logs, preserving exact version IDs. This establishes dependency discovery for logical recovery protection without changing admission, read holds, or runtime installation. Add catalog-boundary tests for eliminated and implicit inputs, shared view subgraphs, index independence, MV boundaries, and collection versions. Adapter library and test compilation pass. Rust formatting passes. Full formatting and lint are blocked by missing tools and an OpenSSL dependency build. Runtime validation remains pending in draft PR CI. --- .../design/20260903_decoupled_coordination.md | 22 ++ src/adapter/src/catalog/state.rs | 285 ++++++++++++++++++ 2 files changed, 307 insertions(+) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index c6c6c16fd9ccb..01c6f48bf962e 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -435,3 +435,25 @@ rollout policy for objects unable to satisfy that condition remain open. Document checks passed. Full formatting and lint remain blocked by missing tools and the OpenSSL dependency build. No production changes were made. + +### 2026-09-08: Logical collection input discovery + +Added catalog traversal and boundary tests for logical inputs, preserving collection +versions and stopping at upstream MV outputs without selecting indexes. Review found +that resolved collection IDs also include functions, addressed by filtering name +references to relations while retaining raw-HIR reads. This is discovery only, with +no runtime protection or admission change. + +Adapter library/test compilation and Rust formatting passed. Full formatting and +lint remain blocked by missing tools and the OpenSSL dependency build. Runtime +validation is pending in [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks). +Next useful step: derive maintained requirements from their query definitions and +enforce admission together with bound advancement at the durable transaction boundary. +Conversion policy remains open. No design boundary changed. + +### 2026-09-08: Test fixture Clippy fix + +Both Clippy jobs in [CI build 133949](https://buildkite.com/materialize/test/builds/133949) +rejected seven `unwrap()` calls in the new fixtures. Replaced them with descriptive +`expect()` messages without changing assertions or production behavior. Rust formatting +passed. Updated CI validation remains pending. diff --git a/src/adapter/src/catalog/state.rs b/src/adapter/src/catalog/state.rs index 91f9949bb29f7..a994a4c324e63 100644 --- a/src/adapter/src/catalog/state.rs +++ b/src/adapter/src/catalog/state.rs @@ -571,6 +571,55 @@ impl CatalogState { } } + /// Returns the logical collection inputs reachable through unmaterialized views. + /// + /// Tables, sources, materialized views, and logs are leaves. Collection versions + /// are preserved, and index availability does not affect the result. This only + /// discovers dependencies, it does not acquire read protection. + /// + /// Panics if an ID is absent or does not identify a queryable collection. + pub fn logical_collection_inputs( + &self, + ids: impl IntoIterator, + ) -> BTreeSet { + let mut pending: Vec<_> = ids.into_iter().collect(); + let mut seen = BTreeSet::new(); + let mut inputs = BTreeSet::new(); + while let Some(id) = pending.pop() { + if !seen.insert(id) { + continue; + } + match self.get_entry_by_global_id(&id).item() { + CatalogItem::View(view) => { + // Name resolution retains references eliminated by planning. + // Its IDs also include non-relations such as functions. + // Raw HIR also includes collection reads introduced by planning. + pending.extend( + view.resolved_ids + .collections() + .filter(|id| self.get_entry_by_global_id(id).is_relation()) + .copied(), + ); + pending.extend(view.raw_expr.depends_on()); + } + CatalogItem::Table(_) + | CatalogItem::Source(_) + | CatalogItem::MaterializedView(_) + | CatalogItem::Log(_) => { + inputs.insert(id); + } + CatalogItem::Index(_) + | CatalogItem::Sink(_) + | CatalogItem::Type(_) + | CatalogItem::Func(_) + | CatalogItem::Secret(_) + | CatalogItem::Connection(_) + | CatalogItem::MetricSink(_) => panic!("not a queryable collection: {id}"), + } + } + inputs + } + /// Computes the IDs of any log sources this catalog entry transitively /// depends on. pub fn introspection_dependencies(&self, id: CatalogItemId) -> Vec { @@ -3062,6 +3111,242 @@ impl Catalog { mod tests { use super::*; + async fn create_collection_input_item( + catalog: &mut Catalog, + name: &str, + create_sql: &str, + ) -> GlobalId { + let (id, global_id) = catalog + .allocate_user_id_for_test() + .await + .expect("can allocate test item IDs"); + let item = catalog + .state() + .parse_item( + global_id, + create_sql, + &BTreeMap::new(), + None, + false, + None, + &mut LocalExpressionCache::Closed, + None, + ) + .expect("can parse test item"); + let commit_ts = catalog.current_upper().await; + catalog + .transact( + None, + commit_ts, + None, + vec![crate::catalog::Op::CreateItem { + item, + name: QualifiedItemName { + qualifiers: mz_sql::names::ItemQualifiers { + database_spec: ResolvedDatabaseSpecifier::Id(DatabaseId::User(1)), + schema_spec: SchemaSpecifier::Id(SchemaId::User(3)), + }, + item: name.to_string(), + }, + id, + owner_id: MZ_SYSTEM_ROLE_ID, + }], + ) + .await + .expect("can create test item"); + global_id + } + + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` + async fn logical_collection_inputs_views_ignore_optimization_and_indexes() { + Catalog::with_debug(|mut catalog| async move { + let mut ids = Vec::new(); + for (name, sql) in [ + ("t", "CREATE TABLE materialize.public.t (a int)"), + ("v", "CREATE VIEW materialize.public.v AS SELECT * FROM materialize.public.t"), + ("l", "CREATE VIEW materialize.public.l AS SELECT * FROM materialize.public.v"), + ("r", "CREATE VIEW materialize.public.r AS SELECT * FROM materialize.public.v"), + ("diamond", "CREATE VIEW materialize.public.diamond AS SELECT * FROM materialize.public.l UNION ALL SELECT * FROM materialize.public.r"), + ("empty", "CREATE VIEW materialize.public.empty AS SELECT 1 FROM materialize.public.t WHERE false"), + ("typed", "CREATE VIEW materialize.public.typed AS SELECT pg_catalog.pg_typeof((SELECT a FROM materialize.public.t))"), + ] { + ids.push(create_collection_input_item(&mut catalog, name, sql).await); + } + let [table, _, _, _, diamond, empty, typed] = ids[..] else { + unreachable!() + }; + let entry = catalog.state.get_entry_by_global_id(&empty); + let CatalogItem::View(empty_view) = entry.item() else { + panic!("expected view") + }; + assert!(empty_view.locally_optimized_expr.depends_on().is_empty()); + assert!(empty_view.resolved_ids.collections().any(|id| *id == table)); + assert_eq!( + catalog.state.logical_collection_inputs([empty]), + BTreeSet::from([table]), + "name-resolution references survive optimization" + ); + + let entry = catalog.state.get_entry_by_global_id(&typed); + let CatalogItem::View(typed_view) = entry.item() else { + panic!("expected view") + }; + // pg_typeof discards its argument after determining its type. + assert!(typed_view.raw_expr.depends_on().is_empty()); + assert_eq!( + catalog.state.logical_collection_inputs([typed]), + BTreeSet::from([table]), + "name-resolution references survive elimination during SQL planning" + ); + + let roots = [diamond, diamond]; + assert_eq!( + catalog.state.logical_collection_inputs(roots), + BTreeSet::from([table]) + ); + for (name, sql) in [ + ("t_idx", "CREATE INDEX t_idx IN CLUSTER quickstart ON materialize.public.t (a)"), + ("v_idx", "CREATE INDEX v_idx IN CLUSTER quickstart ON materialize.public.v (a)"), + ] { + create_collection_input_item(&mut catalog, name, sql).await; + } + assert_eq!( + catalog.state.logical_collection_inputs(roots), + BTreeSet::from([table]), + "indexes on both leaves and intermediate views do not change logical inputs" + ); + catalog.expire().await; + }) + .await; + } + + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` + async fn logical_collection_inputs_implicit_sql_function_read() { + Catalog::with_debug(|mut catalog| async move { + let view = create_collection_input_item( + &mut catalog, + "implicit", + "CREATE VIEW materialize.public.implicit AS SELECT pg_catalog.pg_get_userbyid(1::oid)", + ) + .await; + let entry = catalog.state.get_entry_by_global_id(&view); + let CatalogItem::View(view_item) = entry.item() else { + panic!("expected view") + }; + assert!(view_item.resolved_ids.collections().any(|id| { + matches!(catalog.state.get_entry_by_global_id(id).item(), CatalogItem::Func(_)) + })); + // The SQL function reads mz_roles without naming it in the CREATE VIEW. + let roles = catalog + .state + .entry_by_id + .values() + .find(|entry| entry.name().item == "mz_roles") + .expect("debug catalog has mz_roles"); + assert!(matches!(roles.item(), CatalogItem::MaterializedView(_))); + let roles_id = roles.global_ids().next().expect("mz_roles has a collection"); + assert!(!view_item.resolved_ids.collections().any(|id| *id == roles_id)); + assert_eq!( + catalog.state.logical_collection_inputs([view]), + BTreeSet::from([roles_id]) + ); + catalog.expire().await; + }) + .await; + } + + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` + async fn logical_collection_inputs_leaf_boundaries_and_versions() { + Catalog::with_debug(|mut catalog| async move { + let mut ids = Vec::new(); + for (name, sql) in [ + ("t", "CREATE TABLE materialize.public.t (a int)"), + ("v", "CREATE VIEW materialize.public.v AS SELECT * FROM materialize.public.t"), + ("mv", "CREATE MATERIALIZED VIEW materialize.public.mv IN CLUSTER quickstart AS SELECT * FROM materialize.public.v"), + ("over_mv", "CREATE VIEW materialize.public.over_mv AS SELECT * FROM materialize.public.mv"), + ("replacement", "CREATE REPLACEMENT MATERIALIZED VIEW materialize.public.replacement FOR materialize.public.mv IN CLUSTER quickstart AS SELECT * FROM materialize.public.t"), + ] { + ids.push(create_collection_input_item(&mut catalog, name, sql).await); + } + let [table, view, mv, over_mv, replacement] = ids[..] else { + unreachable!() + }; + let mv_id = catalog.state.get_entry_by_global_id(&mv).id(); + let entry = catalog.state.get_entry_by_global_id(&replacement); + let CatalogItem::MaterializedView(replacement_item) = entry.item() else { + panic!("expected materialized view") + }; + assert_eq!(replacement_item.replacement_target, Some(mv_id)); + assert_eq!( + catalog.state.logical_collection_inputs([over_mv]), + BTreeSet::from([mv]) + ); + assert_eq!( + catalog.state.logical_collection_inputs([replacement]), + BTreeSet::from([replacement]), + "neither the MV definition nor its replacement target is traversed" + ); + + // Install a second version directly to isolate ID preservation from + // ALTER TABLE sequencing. The parsed view still refers to the root version. + let (_, latest) = catalog + .allocate_user_id_for_test() + .await + .expect("can allocate test collection version"); + let table_id = catalog.state.get_entry_by_global_id(&table).id(); + let entry = catalog + .state + .entry_by_id + .get_mut(&table_id) + .expect("test table exists"); + let CatalogItem::Table(table_item) = &mut entry.item else { + panic!("expected table") + }; + let version = table_item + .desc + .add_column("b", mz_repr::SqlScalarType::Int32.nullable(true)); + table_item.collections.insert(version, latest); + catalog.state.entry_by_global_id.insert(latest, table_id); + assert_eq!( + catalog.state.logical_collection_inputs([view]), + BTreeSet::from([table]), + "a view's table version is not remapped to the latest version" + ); + assert_eq!( + catalog.state.logical_collection_inputs([table, latest]), + BTreeSet::from([table, latest]), + "distinct versions of one catalog item remain distinct leaves" + ); + for is_source in [false, true] { + let entry = catalog + .state + .entry_by_id + .values() + .find(|entry| { + if is_source { + matches!(entry.item(), CatalogItem::Source(_)) + } else { + matches!(entry.item(), CatalogItem::Log(_)) + } + }) + .expect("debug catalog has sources and logs"); + let id = entry + .global_ids() + .next() + .expect("source or log has a collection"); + assert_eq!( + catalog.state.logical_collection_inputs([id]), + BTreeSet::from([id]) + ); + } + catalog.expire().await; + }) + .await; + } + /// A deep dependency chain (a long chain of stacked views) must not /// overflow the stack when computing its dependents, and the dependents /// must come out in reverse-dependency order (deepest dependent first, From de8b771c86a097453dff0409409096e1b2059544 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 14:41:29 +0000 Subject: [PATCH 14/36] doc, adapter: prune the decoupled coordination log and input-discovery tests Strip validation churn from the design's implementation log: pending CI, passed formatting checks, local tooling gaps, and review outcomes are all reconstructible from the PR and buried the findings and decisions. Update the session prompt so future handoffs record only findings, decisions, open questions, and the next step, and note which local checks work. Reduce the logical_collection_inputs tests to the promises that matter: name resolution survives planning-time elimination, raw-HIR reads count, indexes do not affect traversal, and upstream MV outputs are leaves. The dropped version test mutated catalog maps directly, and the replacement-MV case cannot run in the debug catalog because it has no shard metadata for the target. --- .../design/20260903_decoupled_coordination.md | 197 ++++++------------ .../20260903_decoupled_coordination_prompt.md | 16 +- src/adapter/src/catalog/state.rs | 154 +++----------- 3 files changed, 96 insertions(+), 271 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 01c6f48bf962e..f3e80802ed4f9 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -210,8 +210,8 @@ as well as query performance. Record what ran, failures, and remaining gaps. ## Implementation log (append-only) -Append dated entries with findings and evidence, decisions or pending questions, -validation results, and the next useful step. Clearly distinguish an +Append dated entries with findings, decisions or pending questions, and the next +useful step. Validation status lives in the PR, not here. Clearly distinguish an implementer's proposal from a decision reviewed with Aljoscha. Do not rewrite earlier entries. Keep the main text concise and update its agreed boundaries only following review. @@ -221,155 +221,110 @@ only following review. Multi-adapter operation is the end goal, not the immediate deliverable. Query-local dataflows remain on the fast protocol. Adapter loss must not harm other clients or maintained dataflows, but transparent failover is not required. -Completing relevant catalog implication paths is within scope. Implementation -has not started and runtime validation has not been performed. +Completing relevant catalog implication paths is within scope. ### 2026-09-03: Sink creation from committed implications -Sink additions now install exports through catalog implications. Independent -review found no blocking issue. [CI build 133862](https://buildkite.com/materialize/test/builds/133862) -passed both Clippy jobs and formatting before a documentation push superseded it. -Latest CI and runtime validation remain pending. Local builds were blocked by -missing artifacts and tools. Aljoscha confirmed CI as the default test loop. +Sink additions now install exports through catalog implications. Controllers still live in the adapter. Direct committed-update tests and -same-batch MV/sink creation remain gaps. Next proposed slice: reuse index plan -reconstruction from bootstrap, preserving precommit optimization as a cache. -Index parsing leaves plans absent and the expression cache has no runtime read -API. No new boundary decision was made. +same-batch MV/sink creation remain gaps. Index parsing leaves plans absent and +the expression cache has no runtime read API. ### 2026-09-03: Reusable index reconstruction Extracted bootstrap's uncached index planning and notice rendering without -changing cache policy, ordering, or installation. Independent review found no -issue. Local adapter `cargo check` and Rust formatting passed. Full formatting -and lint failed on missing tools and a Python-doctest dependency build. Runtime -validation and this commit's [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks) -remain pending. +changing cache policy, ordering, or installation. Next: runtime expression-cache +reads and index-add implications using reconstruction on cache misses. Cache +validity must account for committed dependencies, and installation must follow +same-batch prerequisites. -Next proposed slice: runtime expression-cache reads and index-add implications -using reconstruction on cache misses. Cache validity must account for committed -dependencies, and installation must follow same-batch prerequisites. Add direct -committed-update coverage without sequencer plans. No boundary decision changed. - -### 2026-09-03: Runtime index implications, verification in progress +### 2026-09-03: Runtime index implications Index additions now acquire cached or reconstructed plans and install through implications. Runtime cache reads are best-effort and validated against committed dependencies and compute availability. Review identified stale session notices after cache rejection, addressed by filtering dropped dependencies. -Adapter and cache-test compilation passed. Runtime tests remain pending. The -extraction's Clippy fix passed both [CI Clippy jobs](https://buildkite.com/materialize/test/builds/133870). -Full validation of this follow-up is pending. Proposed test-only adapter catalog -transaction request awaits Aljoscha's approval, because no existing harness can -exercise cluster/table/index creation in one committed batch. Same-batch MV/index -creation still depends on moving MV storage creation into implications. +No existing harness can exercise cluster/table/index creation in one committed +batch without the sequencer. Same-batch MV/index creation still depends on moving +MV storage creation into implications. ### 2026-09-03: System-boundary verification Agreed with Aljoscha to proceed with system-level coverage, without a test-only coordinator command. Added cache-disabled SQL creation and restart coverage for -index use, EXPLAIN, notices, and drop cleanup. Python formatting and Ruff passed, -runtime validation remains in [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks). -The no-sequencer same-batch runtime case remains uncovered until a production -catalog subscriber exists. External catalog writers fence the adapter today, -and the existing read-only catalog harness does not apply controller effects. +index use, EXPLAIN, notices, and drop cleanup. The no-sequencer same-batch runtime +case remains uncovered until a production catalog subscriber exists. External +catalog writers fence the adapter today, and the existing read-only catalog +harness does not apply controller effects. ### 2026-09-03: MV storage registration from committed implications MV additions register storage before dependent sinks/indexes, then initialize read policies through the deferred batch. Runtime and bootstrap share descriptor construction, including replacement ownership and the initial storage frontier. -Compute installation remains sequencer-side. No boundary decision changed. - -Local adapter compilation and Rust/Python formatting passed. Full formatting and -lint are blocked by missing tools and a Python-doctest OpenSSL dependency build. -Independent review found no issue. This slice's [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks) -is pending. Prior regular [CI build 133879](https://buildkite.com/materialize/test/builds/133879) -passed. Next useful step: catalog-driven MV compute planning and installation, -preserving refresh timestamp selection and input protection. Same-batch runtime -coverage still awaits a production subscriber. +Compute installation remains sequencer-side. ### 2026-09-03: Reusable MV reconstruction Extracted bootstrap MV reconstruction without changing cache policy or timestamp selection. Extended cache-disabled restart coverage to MV results, EXPLAIN, and -continued maintenance. Independent review found no issue. Adapter compilation -and Rust/Python formatting passed. -Full formatting and lint remain blocked by missing tools and an OpenSSL dependency -build. Prior [CI build 133882](https://buildkite.com/materialize/test/builds/133882) -passed. This slice's [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks) -and runtime coverage are pending. - -Next useful step: establish input protection for runtime MV reconstruction before -moving compute installation. A cache miss can choose imports outside the creator's -holds, and acquiring fresh holds after commit cannot recover history needed for -the committed first refresh. No protection mechanism or boundary change was agreed. +continued maintenance. + +Finding: a cache miss during runtime MV reconstruction can choose imports outside +the creator's holds, and acquiring fresh holds after commit cannot recover history +needed for the committed first refresh. Input protection must be established +before moving compute installation. ### 2026-09-03: Pending first-refresh recovery coverage Added cache-disabled restart coverage for an unexecuted first refresh after its -input changes. Python formatting and Ruff passed. Full formatting/lint remain -blocked by missing tools and an OpenSSL dependency build. Prior -[CI build 133887](https://buildkite.com/materialize/test/builds/133887) passed. -This slice's [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks) -and runtime verification are pending. +input changes. Code inspection ruled out merely broadening creator holds: timestamp selection joins every hold, changing historical MV readability, while [`sufficient_collections`](../../../src/adapter/src/coord/indexes.rs) stops at available indexes and does not cover sibling indexes exposed by same-batch drops. Compute holds do protect actual transitive dependencies. Production MV installation -remains paused. Next proposed step: agree whether to establish lifecycle-owned -protection now or build a temporary protected-plan reconstruction bridge. No new -mechanism or boundary change was agreed. +paused pending a decision between lifecycle-owned protection and a temporary +protected-plan reconstruction bridge. ### 2026-09-03: Lifecycle protection prioritized with Aljoscha Proceed with lifecycle-owned protection rather than a temporary adapter bridge. -Both restart jobs in [CI build 133889](https://buildkite.com/materialize/test/builds/133889) -passed, including the pending-first-refresh regression. Overall CI remains pending. The durable writer is already shared behind a mutex, so an independent lifecycle component need not introduce concurrent catalog writers. Existing hold accounting and epoch fencing are reusable, but neither recovers pending maintained read -requirements from catalog state. Before choosing a schema, clarify whether catalog -authority requires committed frontier bounds or can delegate advancement to a -fenced lifecycle owner under catalog-derived policies. The latter is a proposal, -not an agreed interpretation of the boundary. No production changes made. +requirements from catalog state. Open question before choosing a schema: does +catalog authority require committed frontier bounds, or can advancement be +delegated to a fenced lifecycle owner under catalog-derived policies? ### 2026-09-03: Delegation failure-scenario exploration -[CI build 133889](https://buildkite.com/materialize/test/builds/133889) passed. Worked cases require atomic ordering of creation commit versus hold reclamation, physical fencing, and a recovery barrier before compaction resumes. Snapshot absence and catalog fencing alone are insufficient. Small abstract interleaving models -checked these orderings, not production behavior. The delegation proposal shifts -coordination to read-requiring DDL and owner handover rather than ongoing frontier -publication. Admission/revocation and recovery of valid client holds remain open. +checked these orderings, not production behavior. Delegation shifts coordination +to read-requiring DDL and owner handover rather than ongoing frontier publication. The shared writer mutex only serializes one in-process handle, not independent -writers or subscriber delivery. No boundary decision or production change made. +writers or subscriber delivery. ### 2026-09-03: Catalog-bound traffic estimate -Aljoscha leans toward explicit bounds. For compact records advancing once per -minute, a planning budget of 300–500 bytes per retraction/insertion pair gives -0.5–0.83 MB/s at 100,000 changing bounds. This estimates uncompressed logical -updates, not measured persist traffic. Current structured JSONB encoding retains -whole-record JSON text. Whole-item rewrites, batch sizes, catalog CPU/DDL latency, -persist maintenance, subscriber fanout, and retained history need measurement. -No schema or publication cadence was agreed. +For compact records advancing once per minute, a planning budget of 300-500 bytes +per retraction/insertion pair gives 0.5-0.83 MB/s at 100,000 changing bounds. +This estimates uncompressed logical updates, not measured persist traffic. Current +structured JSONB encoding retains whole-record JSON text. Whole-item rewrites, +batch sizes, catalog CPU/DDL latency, persist maintenance, subscriber fanout, and +retained history need measurement. ### 2026-09-03: Explicit bounds agreed with Aljoscha Choose catalog-backed compaction bounds and document delegated advancement as an alternative. Schema, granularity, batching, and publication cadence remain open. -The one-minute traffic estimate is not a chosen cadence. Next useful step: define -the catalog transaction boundary for maintained read requirements and bound -advancement. This is a design decision, with no production implementation change. -Documentation checks passed. Full formatting and lint remain blocked by missing -tools and the Python-doctest OpenSSL build. +The one-minute traffic estimate is not a chosen cadence. ### 2026-09-04: Admission and recovery scope exploration @@ -380,11 +335,6 @@ timestamp. Requirement identity must account for recovery and access-path change not just creation admission. Protecting all logical dependencies risks retaining unneeded history and rejecting historical creation on unused inputs. Protecting selected imports requires a recoverable path through replanning and index drops. -This choice remains open, with schema work paused for discussion with Aljoscha. - -No production changes or local tests. Existing [CI build 133937](https://buildkite.com/materialize/test/builds/133937) -remains pending, with no reported failures at inspection. -Next useful step: agree the recovery protection scope before adding durable records. ### 2026-09-04: Recovery cases traced @@ -396,34 +346,20 @@ show whole logical inputs eliminated from nonconstant results. A reconstructed p can require their discarded history. The [expression cache](../../../src/catalog/src/expr_cache.rs) explicitly has no cross-build representation compatibility contract. -Pending proposal: retain a logical recovery computation over protected storage inputs, -without pinning physical indexes. This adds durable expression compatibility and -dependency-version obligations, not just frontier metadata. Independent scrutiny of -an IDs-only alternative using empty-input substitution found unestablished error and -semantic-assumption contracts. No approach was agreed. No production changes or runtime -experiments. Next useful step: review the recovery representation cost with Aljoscha. - -### 2026-09-04: Logical-input protection comparison - -Aljoscha leans toward protecting all logical inputs. The catalog already preserves -[logical dependencies for drop safety](../../../test/sqllogictest/materialized_views.slt), -but installed holds and [bootstrap recovery constraints](../../../src/compute-client/src/as_of_selection.rs) -follow physical imports. Bootstrap freezes recovered storage sinces, not missing -history. Conservative logical storage-input protection would strengthen that contract -while retaining SQL-based reconstruction, avoiding authoritative optimized expressions. -Extra retention, historical creation admission, and conversion of existing objects -need consideration. This remains a proposal. No production changes or runtime tests. +Retaining a logical recovery computation over protected storage inputs would add +durable expression compatibility and dependency-version obligations. An IDs-only +alternative using empty-input substitution has unestablished error and +semantic-assumption contracts. ### 2026-09-04: Logical-input protection agreed with Aljoscha Choose logical-input protection based on code inspection, accepting conservative -retention without making optimized expressions authoritative. Independent review -found no issue. Historical creation admission and conversion of existing objects -are the next design questions, not approved behavior changes. - -Document checks passed. Full formatting and lint remain blocked by missing tools -and the Python-doctest OpenSSL dependency build. No production changes, runtime -recovery experiments, or retention measurements were made. +retention without making optimized expressions authoritative. The catalog already +preserves [logical dependencies for drop safety](../../../test/sqllogictest/materialized_views.slt), +but installed holds and [bootstrap recovery constraints](../../../src/compute-client/src/as_of_selection.rs) +follow physical imports. Bootstrap freezes recovered storage sinces, not missing +history. Historical creation admission and +conversion of existing objects are the next design questions. ### 2026-09-04: Admission and conversion boundaries agreed with Aljoscha @@ -433,27 +369,14 @@ rejected. Existing objects keep their promised results, with conversion active o once remaining recovery requirements are protected. The conversion mechanism and rollout policy for objects unable to satisfy that condition remain open. -Document checks passed. Full formatting and lint remain blocked by missing tools -and the OpenSSL dependency build. No production changes were made. - ### 2026-09-08: Logical collection input discovery -Added catalog traversal and boundary tests for logical inputs, preserving collection -versions and stopping at upstream MV outputs without selecting indexes. Review found -that resolved collection IDs also include functions, addressed by filtering name -references to relations while retaining raw-HIR reads. This is discovery only, with -no runtime protection or admission change. - -Adapter library/test compilation and Rust formatting passed. Full formatting and -lint remain blocked by missing tools and the OpenSSL dependency build. Runtime -validation is pending in [PR CI](https://github.com/MaterializeInc/materialize/pull/38696/checks). -Next useful step: derive maintained requirements from their query definitions and -enforce admission together with bound advancement at the durable transaction boundary. -Conversion policy remains open. No design boundary changed. - -### 2026-09-08: Test fixture Clippy fix - -Both Clippy jobs in [CI build 133949](https://buildkite.com/materialize/test/builds/133949) -rejected seven `unwrap()` calls in the new fixtures. Replaced them with descriptive -`expect()` messages without changing assertions or production behavior. Rust formatting -passed. Updated CI validation remains pending. +Added catalog traversal and tests for logical inputs, preserving collection +versions and stopping at upstream MV outputs without selecting indexes. Review +found that resolved collection IDs also include functions, addressed by filtering +name references to relations while retaining raw-HIR reads. This is discovery only, +with no runtime protection or admission change. + +Next useful step: derive maintained requirements from their query definitions +and enforce admission together with bound advancement at the durable transaction +boundary. Conversion policy remains open. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index c67adcc3d3216..8ca7f46be2660 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -30,6 +30,11 @@ prerequisite for pushing. Follow the mz-debug-ci skill when investigating CI. Report pending or failed checks explicitly rather than treating a push as successful validation. +Locally, `bin/fmt`, `cargo check`, and the Rust parts of `bin/lint` +(check-cargo, check-formatting, check-python-docs) work. `bin/lint` also runs +checks whose tools are not installed here (npm, helm-docs, trufflehog, zizmor); +CI is the authority for those. Do not record local tooling gaps anywhere. + Focus on regular PR CI for now. Nightly intentionally does not run on this draft PR, so do not treat its absence as a blocker or try to enable it. We will start nightly validation once we have a working implementation. @@ -40,11 +45,12 @@ status, not the chronology of attempts. If blocked, report the blocker rather than claiming completion. Append only a minimal dated handoff to the design's log: consequential findings -or decisions, validation status, unresolved questions, and the next useful step. -Link evidence rather than recording every CI attempt or duplicating the change -description. Distinguish proposals from decisions we reviewed together. Do not -rewrite earlier entries. Keep the main design focused on design, and change -its agreed boundaries only after discussing them with me. +or decisions, unresolved questions, and the next useful step. Do not record +validation status there at all: CI results, pending checks, formatting or +compile checks, tool availability, and review outcomes are reconstructible from +the PR and are noise in the log. Distinguish proposals from decisions we +reviewed together. Do not rewrite earlier entries. Keep the main design focused +on design, and change its agreed boundaries only after discussing them with me. You may commit and push progress to this bookmark without asking again. Prefer jj. In-progress commits and pushes are allowed while iterating. Before diff --git a/src/adapter/src/catalog/state.rs b/src/adapter/src/catalog/state.rs index a994a4c324e63..ab96b18a97b9a 100644 --- a/src/adapter/src/catalog/state.rs +++ b/src/adapter/src/catalog/state.rs @@ -3159,52 +3159,48 @@ mod tests { #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` - async fn logical_collection_inputs_views_ignore_optimization_and_indexes() { + async fn logical_collection_inputs_follow_names_not_plans() { Catalog::with_debug(|mut catalog| async move { let mut ids = Vec::new(); for (name, sql) in [ ("t", "CREATE TABLE materialize.public.t (a int)"), ("v", "CREATE VIEW materialize.public.v AS SELECT * FROM materialize.public.t"), - ("l", "CREATE VIEW materialize.public.l AS SELECT * FROM materialize.public.v"), - ("r", "CREATE VIEW materialize.public.r AS SELECT * FROM materialize.public.v"), - ("diamond", "CREATE VIEW materialize.public.diamond AS SELECT * FROM materialize.public.l UNION ALL SELECT * FROM materialize.public.r"), - ("empty", "CREATE VIEW materialize.public.empty AS SELECT 1 FROM materialize.public.t WHERE false"), - ("typed", "CREATE VIEW materialize.public.typed AS SELECT pg_catalog.pg_typeof((SELECT a FROM materialize.public.t))"), + ("typed", "CREATE VIEW materialize.public.typed AS SELECT pg_catalog.pg_typeof((SELECT a FROM materialize.public.v))"), + ("implicit", "CREATE VIEW materialize.public.implicit AS SELECT pg_catalog.pg_get_userbyid(1::oid)"), ] { ids.push(create_collection_input_item(&mut catalog, name, sql).await); } - let [table, _, _, _, diamond, empty, typed] = ids[..] else { + let [table, view, typed, implicit] = ids[..] else { unreachable!() }; - let entry = catalog.state.get_entry_by_global_id(&empty); - let CatalogItem::View(empty_view) = entry.item() else { - panic!("expected view") - }; - assert!(empty_view.locally_optimized_expr.depends_on().is_empty()); - assert!(empty_view.resolved_ids.collections().any(|id| *id == table)); - assert_eq!( - catalog.state.logical_collection_inputs([empty]), - BTreeSet::from([table]), - "name-resolution references survive optimization" - ); + // pg_typeof discards its argument during planning, so the read of `v` + // survives only in name resolution. let entry = catalog.state.get_entry_by_global_id(&typed); let CatalogItem::View(typed_view) = entry.item() else { panic!("expected view") }; - // pg_typeof discards its argument after determining its type. assert!(typed_view.raw_expr.depends_on().is_empty()); assert_eq!( catalog.state.logical_collection_inputs([typed]), BTreeSet::from([table]), - "name-resolution references survive elimination during SQL planning" + "name-resolution references survive elimination during planning" ); - let roots = [diamond, diamond]; + // The SQL function body reads mz_roles without the view naming it. + let roles_id = catalog + .state + .entry_by_id + .values() + .find(|entry| entry.name().item == "mz_roles") + .and_then(|entry| entry.global_ids().next()) + .expect("debug catalog has mz_roles"); assert_eq!( - catalog.state.logical_collection_inputs(roots), - BTreeSet::from([table]) + catalog.state.logical_collection_inputs([implicit]), + BTreeSet::from([roles_id]), + "collection reads introduced by planning are included" ); + for (name, sql) in [ ("t_idx", "CREATE INDEX t_idx IN CLUSTER quickstart ON materialize.public.t (a)"), ("v_idx", "CREATE INDEX v_idx IN CLUSTER quickstart ON materialize.public.v (a)"), @@ -3212,9 +3208,9 @@ mod tests { create_collection_input_item(&mut catalog, name, sql).await; } assert_eq!( - catalog.state.logical_collection_inputs(roots), + catalog.state.logical_collection_inputs([view, typed]), BTreeSet::from([table]), - "indexes on both leaves and intermediate views do not change logical inputs" + "indexes are not inputs and do not change traversal" ); catalog.expire().await; }) @@ -3223,43 +3219,7 @@ mod tests { #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` - async fn logical_collection_inputs_implicit_sql_function_read() { - Catalog::with_debug(|mut catalog| async move { - let view = create_collection_input_item( - &mut catalog, - "implicit", - "CREATE VIEW materialize.public.implicit AS SELECT pg_catalog.pg_get_userbyid(1::oid)", - ) - .await; - let entry = catalog.state.get_entry_by_global_id(&view); - let CatalogItem::View(view_item) = entry.item() else { - panic!("expected view") - }; - assert!(view_item.resolved_ids.collections().any(|id| { - matches!(catalog.state.get_entry_by_global_id(id).item(), CatalogItem::Func(_)) - })); - // The SQL function reads mz_roles without naming it in the CREATE VIEW. - let roles = catalog - .state - .entry_by_id - .values() - .find(|entry| entry.name().item == "mz_roles") - .expect("debug catalog has mz_roles"); - assert!(matches!(roles.item(), CatalogItem::MaterializedView(_))); - let roles_id = roles.global_ids().next().expect("mz_roles has a collection"); - assert!(!view_item.resolved_ids.collections().any(|id| *id == roles_id)); - assert_eq!( - catalog.state.logical_collection_inputs([view]), - BTreeSet::from([roles_id]) - ); - catalog.expire().await; - }) - .await; - } - - #[mz_ore::test(tokio::test)] - #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` - async fn logical_collection_inputs_leaf_boundaries_and_versions() { + async fn logical_collection_inputs_stop_at_materialized_views() { Catalog::with_debug(|mut catalog| async move { let mut ids = Vec::new(); for (name, sql) in [ @@ -3267,81 +3227,17 @@ mod tests { ("v", "CREATE VIEW materialize.public.v AS SELECT * FROM materialize.public.t"), ("mv", "CREATE MATERIALIZED VIEW materialize.public.mv IN CLUSTER quickstart AS SELECT * FROM materialize.public.v"), ("over_mv", "CREATE VIEW materialize.public.over_mv AS SELECT * FROM materialize.public.mv"), - ("replacement", "CREATE REPLACEMENT MATERIALIZED VIEW materialize.public.replacement FOR materialize.public.mv IN CLUSTER quickstart AS SELECT * FROM materialize.public.t"), ] { ids.push(create_collection_input_item(&mut catalog, name, sql).await); } - let [table, view, mv, over_mv, replacement] = ids[..] else { + let [_table, _view, mv, over_mv] = ids[..] else { unreachable!() }; - let mv_id = catalog.state.get_entry_by_global_id(&mv).id(); - let entry = catalog.state.get_entry_by_global_id(&replacement); - let CatalogItem::MaterializedView(replacement_item) = entry.item() else { - panic!("expected materialized view") - }; - assert_eq!(replacement_item.replacement_target, Some(mv_id)); assert_eq!( catalog.state.logical_collection_inputs([over_mv]), - BTreeSet::from([mv]) - ); - assert_eq!( - catalog.state.logical_collection_inputs([replacement]), - BTreeSet::from([replacement]), - "neither the MV definition nor its replacement target is traversed" - ); - - // Install a second version directly to isolate ID preservation from - // ALTER TABLE sequencing. The parsed view still refers to the root version. - let (_, latest) = catalog - .allocate_user_id_for_test() - .await - .expect("can allocate test collection version"); - let table_id = catalog.state.get_entry_by_global_id(&table).id(); - let entry = catalog - .state - .entry_by_id - .get_mut(&table_id) - .expect("test table exists"); - let CatalogItem::Table(table_item) = &mut entry.item else { - panic!("expected table") - }; - let version = table_item - .desc - .add_column("b", mz_repr::SqlScalarType::Int32.nullable(true)); - table_item.collections.insert(version, latest); - catalog.state.entry_by_global_id.insert(latest, table_id); - assert_eq!( - catalog.state.logical_collection_inputs([view]), - BTreeSet::from([table]), - "a view's table version is not remapped to the latest version" + BTreeSet::from([mv]), + "an upstream MV output is a leaf, its definition is not traversed" ); - assert_eq!( - catalog.state.logical_collection_inputs([table, latest]), - BTreeSet::from([table, latest]), - "distinct versions of one catalog item remain distinct leaves" - ); - for is_source in [false, true] { - let entry = catalog - .state - .entry_by_id - .values() - .find(|entry| { - if is_source { - matches!(entry.item(), CatalogItem::Source(_)) - } else { - matches!(entry.item(), CatalogItem::Log(_)) - } - }) - .expect("debug catalog has sources and logs"); - let id = entry - .global_ids() - .next() - .expect("source or log has a collection"); - assert_eq!( - catalog.state.logical_collection_inputs([id]), - BTreeSet::from([id]) - ); - } catalog.expire().await; }) .await; From 8bdefbf1f968cc6ef7caadc047e285646678af27 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 15:01:18 +0000 Subject: [PATCH 15/36] doc: define fresh-environment coordination milestones Focus implementation on observable recovery, lifecycle, and query-client outcomes in environments initialized under the new protection rules. Defer existing-environment conversion without weakening its result preservation contract. Direct implementers to the active milestone and retain rejected alternatives as decision context rather than pending work. --- .../design/20260903_decoupled_coordination.md | 74 ++++++++++++++++++- .../20260903_decoupled_coordination_prompt.md | 9 ++- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index f3e80802ed4f9..56550939127a4 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -17,6 +17,13 @@ Boundaries must support independent adapters becoming catalog writers and query clients without another ownership redesign. Enabling concurrent catalog writers and deploying multiple adapters are not required here. +Initial implementation and validation target environments initialized under the +new protection rules. Conversion of existing environments is deferred and is not +a prerequisite for the [milestones](#milestones). Enabling the new ownership model +for existing environments requires a separate conversion and rollout decision +that preserves their promised results. Fresh environments must still survive +restarts, replanning, and ownership handover. + Query-local dataflows that do not go through the catalog, including slow-path SELECTs, SUBSCRIBEs, and COPY TO, remain on the fast protocol. Their creation, execution, responses, and cleanup are part of request-scoped execution, not @@ -126,8 +133,8 @@ cannot support them, even when optimization removes that input. Existing objects retain their promised results. Their conversion to logical-input protection becomes active only once their remaining recovery requirements are protected across all logical inputs. Conversion must not skip pending results by -moving the recovery timestamp forward. The conversion mechanism and rollout policy -for objects that cannot yet satisfy this condition remain open. +moving the recovery timestamp forward. Conversion is deferred under the +[initial scope](#outcome-and-scope). ### Visibility and execution @@ -195,11 +202,52 @@ compaction and recovery. Add targeted tests or experiments where existing coverage leaves uncertainty. Measure catalog traffic and retained-history cost as well as query performance. Record what ran, failures, and remaining gaps. +### Milestones + +These are outcome checkpoints, not a rigid implementation sequence or one-commit +tasks. Work can cross their boundaries when necessary. The implementation log +records the active milestone and progress toward it. + +#### 1. Catalog-backed recovery protection + +Maintained requirements and compaction permission are coordinated through the +catalog. Use an MV as the first complete example: logical-input protection is +established before installation depends on it, survives uncached recovery, +preserves a pending first refresh, and advances with durable output progress +rather than retaining creation-time history forever. + +Evidence includes actual compaction and an input eliminated by optimization. +Measure publication and retained-history costs as the real path becomes available. +This milestone can use the current single-writer arrangement. + +#### 2. Catalog-driven maintained lifecycle + +Cluster-side lifecycle components establish and follow maintained state from the +catalog without sequencer installation closures or the originating adapter. +Creation, changes, deletion, and compaction work across recovery. Losing the +adapter does not interrupt maintained work. + +Demonstrate a production subscriber applying committed changes without +creator-local plans, including same-batch dependencies. + +#### 3. Independent query execution + +Query clients use the fast protocol without acquiring ownership of maintained +lifecycle. Catalog application and query readiness remain correctly ordered. +Responses, cancellation, query-local dataflows, and disconnect cleanup are +isolated between clients. + +Demonstrate independent clients without requiring concurrent catalog writers or +a multi-adapter deployment. Together, these milestones complete the +fresh-environment decoupling outcome, subject to the correctness and performance +acceptance criteria above. + ### Starting points - [Catalog implications](../../../src/adapter/src/coord/catalog_implications.rs) - derive effects from committed changes. Index and MV creation also have - sequencer-side installation paths to account for. + derive effects from committed changes, including sink creation, index + installation, and MV storage registration. MV and metric-sink compute + installation remain sequencer-side. - [Compute protocol](../../../src/compute-client/src/protocol/command.rs) mixes lifecycle and query commands. [Transport](../../../src/service/src/transport.rs) replaces the active client on a new connection. @@ -380,3 +428,21 @@ with no runtime protection or admission change. Next useful step: derive maintained requirements from their query definitions and enforce admission together with bound advancement at the durable transaction boundary. Conversion policy remains open. + +### 2026-09-08: Fresh-environment milestones agreed with Aljoscha + +Focus initial implementation on fresh environments and defer existing-environment +conversion and rollout. Preserve the rejected alternatives as decision context. +Milestone 1 is active. Sink/index implications, MV storage registration, reusable +reconstruction, and logical-input discovery are implemented. Admission and +catalog-backed protection remain unwired, and independent lifecycle following +and query clients remain unimplemented. + +Next useful step: connect maintained requirements, admission, and bound enforcement +toward the first protected MV creation and recovery example. Conversion is not a +blocker for this work. + +Implementation finding: the [runtime index cache-miss path](../../../src/adapter/src/coord/catalog_implications.rs) +optimizes synchronously after commit, following precommit optimization. Account +for coordinator blocking and the postcommit failure boundary before extending +that pattern to MVs, without making a broad cache redesign a prerequisite. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index 8ca7f46be2660..e7e0fdd558ead 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -9,12 +9,17 @@ Remote: origin, pointing to the contributor fork, not upstream Read the design and its implementation log, then inspect the current code, worktree, and remote bookmark. Preserve existing work and account for progress -from other sessions. Choose the next coherent piece toward the full outcome, -briefly explain that choice, then implement and verify it. +from other sessions. Use the latest handoff and current code to identify the +active milestone in the design's Implementation and verification section. Choose +the next coherent piece toward its observable outcome, briefly explain that +choice, then implement and verify it. Historical proposals and next steps are +context, not a cumulative task list. Treat the design as the agreed boundaries, not a prescribed mechanism. Prefer the smallest coherent solution that preserves the full capability. Incremental progress is fine, but do not mistake an intermediate step for completion. +Implementation choices within the agreed boundaries do not require renewed +design approval. Bring discoveries, consequential tradeoffs, and scope growth to me, Aljoscha. Pause affected work when guidance is needed rather than silently narrowing From efbfcf8366b3f337eedf74b83f60e0c3a202b0d7 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 15:16:21 +0000 Subject: [PATCH 16/36] storage: enforce explicit collection compaction bounds Account for committed compaction permission independently of retention policies and execution holds. Preserve it during initialization, shared shard version changes, and drops, and apply monotonic advances in batches. Recovery uses protected readability rather than policy frontiers, and initialization stops if a fencing compare fails. This is the storage consumer boundary for decoupled coordination. Durable catalog production, maintained admission, and MV recovery wiring remain unimplemented, so SQL-created collections do not yet use these bounds. Five focused storage tests cover persist advancement, recovery, aliases, and drops. Recovery regressions were verified with the fixes removed. Formatting, cheap lint checks, and all-target checks through adapter and compute-client pass locally. Broad validation remains with draft PR CI. --- .../design/20260903_decoupled_coordination.md | 12 + src/adapter/src/coord/catalog_implications.rs | 8 +- src/compute-client/src/as_of_selection.rs | 8 + src/storage-client/src/controller.rs | 6 + src/storage-client/src/storage_collections.rs | 710 +++++++++++++++++- src/storage-controller/src/lib.rs | 2 + 6 files changed, 719 insertions(+), 27 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 56550939127a4..a86b2eae96b92 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -446,3 +446,15 @@ Implementation finding: the [runtime index cache-miss path](../../../src/adapter optimizes synchronously after commit, following precommit optimization. Account for coordinator blocking and the postcommit failure boundary before extending that pattern to MVs, without making a broad cache redesign a prerequisite. + +### 2026-09-08: Storage-side compaction permission + +Storage consumes explicit bounds independently of retention policies and execution +holds, including initialization, shared-shard versions, and drops. Recovery must +use protected readability rather than the policy frontier. Initialization must +not reuse a handle after a failed fencing compare refreshes its owner token. + +Milestone 1 remains active. There is no durable producer for these bounds yet. +Next useful step: persist bounds and maintained requirements, enforce their +compatibility at the catalog transaction boundary, and supply committed bounds +to storage before relying on them for MV installation and recovery. diff --git a/src/adapter/src/coord/catalog_implications.rs b/src/adapter/src/coord/catalog_implications.rs index f8b0e9616dce4..d20d69ed492f1 100644 --- a/src/adapter/src/coord/catalog_implications.rs +++ b/src/adapter/src/coord/catalog_implications.rs @@ -1575,7 +1575,13 @@ impl Coordinator { self.controller .storage - .alter_table_desc(existing_gid, new_gid, new_desc, expected_version) + .alter_table_desc( + self.catalog.state().storage_metadata(), + existing_gid, + new_gid, + new_desc, + expected_version, + ) .await .unwrap_or_terminate("failed to alter desc of table"); diff --git a/src/compute-client/src/as_of_selection.rs b/src/compute-client/src/as_of_selection.rs index 9e5b499d956b5..d9cca3c6d5cb2 100644 --- a/src/compute-client/src/as_of_selection.rs +++ b/src/compute-client/src/as_of_selection.rs @@ -1011,6 +1011,7 @@ mod tests { async fn alter_table_desc( &self, + _storage_metadata: &StorageMetadata, _existing_collection: GlobalId, _new_collection: GlobalId, _new_desc: RelationDesc, @@ -1031,6 +1032,13 @@ mod tests { unimplemented!() } + fn apply_compaction_bounds( + &self, + _bounds: BTreeMap>, + ) -> Result<(), StorageError> { + unimplemented!() + } + fn acquire_read_holds( &self, desired_holds: Vec, diff --git a/src/storage-client/src/controller.rs b/src/storage-client/src/controller.rs index b0909c9aec717..3935b724ca2a2 100644 --- a/src/storage-client/src/controller.rs +++ b/src/storage-client/src/controller.rs @@ -213,6 +213,11 @@ pub struct StorageMetadata { #[serde(serialize_with = "mz_ore::serde::map_key_to_string")] pub collection_metadata: BTreeMap, pub unfinalized_shards: BTreeSet, + /// Committed permission to compact each governed collection through this frontier. + /// Absence identifies collections not governed by this API, not permission to + /// release an existing bound. Callers must supply complete committed metadata. + #[serde(serialize_with = "mz_ore::serde::map_key_to_string")] + pub compaction_bounds: BTreeMap>, } impl StorageMetadata { @@ -572,6 +577,7 @@ pub trait StorageController: Debug { /// Runtime registration must go through the adapter's group committer. async fn alter_table_desc( &mut self, + storage_metadata: &StorageMetadata, existing_collection: GlobalId, new_collection: GlobalId, new_desc: RelationDesc, diff --git a/src/storage-client/src/storage_collections.rs b/src/storage-client/src/storage_collections.rs index 2a484f501c9d4..3d5998acbeafd 100644 --- a/src/storage-client/src/storage_collections.rs +++ b/src/storage-client/src/storage_collections.rs @@ -247,6 +247,10 @@ pub trait StorageCollections: Debug + Sync { /// /// `migrated_storage_collections` is a set of migrated storage collections to be excluded /// from the txn-wal sub-system. + /// + /// Committed bounds must permit both the recovered since and requested initial + /// since. All governed aliases of a shard being created must be present in + /// this batch or already registered. async fn create_collections_for_bootstrap( &self, storage_metadata: &StorageMetadata, @@ -256,8 +260,10 @@ pub trait StorageCollections: Debug + Sync { ) -> Result<(), StorageError>; /// Updates the [`RelationDesc`] for the specified table. + /// A governed table requires committed bounds for both versions in `storage_metadata`. async fn alter_table_desc( &self, + storage_metadata: &StorageMetadata, existing_collection: GlobalId, new_collection: GlobalId, new_desc: RelationDesc, @@ -266,6 +272,8 @@ pub trait StorageCollections: Debug + Sync { /// Drops the read capability for the sources and allows their resources to /// be reclaimed. + /// The caller must have committed the drop. This releases compaction permission + /// but retains outstanding execution read holds. /// /// TODO(jkosh44): This method does not validate the provided identifiers. /// Currently when the controller starts/restarts it has no durable state. @@ -296,6 +304,14 @@ pub trait StorageCollections: Debug + Sync { /// policies. fn set_read_policies(&self, policies: Vec<(GlobalId, ReadPolicy)>); + /// Applies committed, monotonic compaction permissions to already-governed collections. + /// Validates the whole batch before applying it. Omitted IDs retain their bounds. + /// The caller must commit these permissions durably before calling this method. + fn apply_compaction_bounds( + &self, + bounds: BTreeMap>, + ) -> Result<(), StorageError>; + /// Acquires and returns the earliest possible read holds for the specified /// collections. fn acquire_read_holds( @@ -814,12 +830,11 @@ impl StorageCollectionsImpl { self_collections: &mut BTreeMap, id: GlobalId, ) -> Result<(), StorageError> { - let (deps, collection_implied_capability) = match self_collections.get(&id) { - Some(CollectionState { - storage_dependencies: deps, - implied_capability, - .. - }) => (deps.clone(), implied_capability), + let (deps, collection_since) = match self_collections.get(&id) { + Some(collection) => ( + collection.storage_dependencies.clone(), + collection.read_capabilities.frontier().to_owned(), + ), _ => return Ok(()), }; @@ -830,21 +845,16 @@ impl StorageCollectionsImpl { mz_ore::soft_assert_or_log!( PartialOrder::less_equal( - &dep_collection.implied_capability, - collection_implied_capability + &dep_collection.read_capabilities.frontier().to_owned(), + &collection_since ), "dependency since ({dep}@{:?}) cannot be in advance of dependent's since ({id}@{:?})", - dep_collection.implied_capability, - collection_implied_capability, + dep_collection.read_capabilities.frontier(), + collection_since, ); } - self.install_read_capabilities_inner( - self_collections, - id, - &deps, - collection_implied_capability.clone(), - )?; + self.install_read_capabilities_inner(self_collections, id, &deps, collection_since)?; Ok(()) } @@ -1769,6 +1779,59 @@ impl StorageCollections for StorageCollectionsImpl { } } + // Every alias constrains the shared shard, including aliases whose handles + // have not opened yet. Validate requested initialization before any downgrade. + let mut shard_bounds: BTreeMap> = BTreeMap::new(); + for (id, bound) in &storage_metadata.compaction_bounds { + let shard = storage_metadata.get_collection_shard(*id)?; + shard_bounds + .entry(shard) + .or_default() + .extend(bound.iter().copied()); + } + { + let existing = self.collections.lock().expect("lock poisoned"); + let creating: BTreeSet<_> = collections.iter().map(|(id, _)| *id).collect(); + let creating_shards: BTreeSet<_> = creating + .iter() + .map(|id| storage_metadata.get_collection_shard(*id)) + .collect::>()?; + for id in storage_metadata.compaction_bounds.keys() { + if creating_shards.contains(&storage_metadata.get_collection_shard(*id)?) + && !creating.contains(id) + && !existing.contains_key(id) + { + return Err(StorageError::IdentifierMissing(*id)); + } + } + for (id, collection) in existing.iter() { + if let Some(bound) = &collection.compaction_bound { + if !storage_metadata.compaction_bounds.contains_key(id) { + return Err(StorageError::InvalidUsage(format!( + "missing compaction bound for {id}" + ))); + } + shard_bounds + .entry(collection.collection_metadata.data_shard) + .or_default() + .extend(bound.iter().copied()); + } + } + } + for (id, description) in &collections { + let shard = storage_metadata.get_collection_shard(*id)?; + if let Some(bound) = shard_bounds.get(&shard) { + if description + .since + .as_ref() + .is_some_and(|since| !PartialOrder::less_equal(since, bound)) + { + return Err(StorageError::ReadBeforeSince(*id)); + } + } + } + let shard_bounds = &shard_bounds; + // We first enrich each collection description with some additional // metadata... let enriched_with_metadata = collections @@ -1836,6 +1899,12 @@ impl StorageCollections for StorageCollectionsImpl { ) .await; + if let Some(bound) = shard_bounds.get(&metadata.data_shard) { + if !PartialOrder::less_equal(since_handle.since(), bound) { + return Err(StorageError::ReadBeforeSince(id)); + } + } + // Present tables as springing into existence at the register_ts // by advancing the since. Otherwise, we could end up in a // situation where a table with a long compaction window appears @@ -1860,13 +1929,21 @@ impl StorageCollections for StorageCollectionsImpl { && !migrated_storage_collections.contains(&id) { debug!("advancing {} to initial since of {:?}", id, register_ts); + let initial_since = Antichain::from_elem(register_ts); + if shard_bounds.get(&metadata.data_shard).is_some_and(|bound| { + !PartialOrder::less_equal(&initial_since, bound) + }) { + return Err(StorageError::ReadBeforeSince(id)); + } let token = since_handle.opaque(); + // NOTE: A failed compare refreshes the handle's cached + // token. It must not be reused as the other owner. let _ = since_handle - .compare_and_downgrade_since( - &token, - (&token, &Antichain::from_elem(register_ts)), - ) - .await; + .compare_and_downgrade_since(&token, (&token, &initial_since)) + .await + .unwrap_or_else(|epoch| { + mz_ore::halt!("fenced by envd @ {epoch:?}"); + }); } } } @@ -1930,7 +2007,10 @@ impl StorageCollections for StorageCollectionsImpl { let dependency_collection = self_collections .get(dep) .ok_or(StorageError::IdentifierMissing(*dep))?; - let dependency_since = dependency_collection.implied_capability.clone(); + let dependency_since = dependency_collection + .read_capabilities + .frontier() + .to_owned(); // If an item has a dependency, its initial since must be // advanced as far as its dependency, i.e. a dependency's @@ -2037,6 +2117,15 @@ impl StorageCollections for StorageCollectionsImpl { storage_dependencies, metadata.clone(), ); + if let Some(bound) = storage_metadata.compaction_bounds.get(&id) { + if !PartialOrder::less_equal(&collection_state.implied_capability, bound) { + return Err(StorageError::ReadBeforeSince(id)); + } + collection_state + .read_capabilities + .update_iter(bound.iter().map(|t| (*t, 1))); + collection_state.compaction_bound = Some(bound.clone()); + } // Install the collection state in the appropriate spot. match &description.data_source { @@ -2095,17 +2184,50 @@ impl StorageCollections for StorageCollectionsImpl { async fn alter_table_desc( &self, + storage_metadata: &StorageMetadata, existing_collection: GlobalId, new_collection: GlobalId, new_desc: RelationDesc, expected_version: RelationVersion, ) -> Result<(), StorageError> { + // Keep execution permission stable across schema evolution and handle opening. + // Otherwise the background task could compact past the new version's bound. + let _hold = self + .acquire_read_holds(vec![existing_collection]) + .map_err(|_| StorageError::IdentifierMissing(existing_collection))?; + let new_bound = storage_metadata.compaction_bounds.get(&new_collection); let data_shard = { let self_collections = self.collections.lock().expect("lock poisoned"); let existing = self_collections .get(&existing_collection) .ok_or_else(|| StorageError::IdentifierMissing(existing_collection))?; + if existing.compaction_bound.is_some() + && (!storage_metadata + .compaction_bounds + .contains_key(&existing_collection) + || new_bound.is_none()) + { + return Err(StorageError::InvalidUsage(format!( + "missing compaction bound for {new_collection}" + ))); + } + if let Some(bound) = new_bound { + if !PartialOrder::less_equal( + &existing.read_capabilities.frontier().to_owned(), + bound, + ) { + return Err(StorageError::ReadBeforeSince(new_collection)); + } + } + if storage_metadata.get_collection_shard(new_collection)? + != existing.collection_metadata.data_shard + { + return Err(StorageError::InvalidUsage(format!( + "shard mismatch for {new_collection}" + ))); + } + existing.collection_metadata.data_shard }; @@ -2222,7 +2344,7 @@ impl StorageCollections for StorageCollectionsImpl { data_shard, txns_shard: Some(self.txns_read.txns_id().clone()), }; - let collection_state = CollectionState::new( + let mut collection_state = CollectionState::new( None, existing.time_dependence.clone(), existing.ingestion_remap_collection_id.clone(), @@ -2231,6 +2353,12 @@ impl StorageCollections for StorageCollectionsImpl { Vec::new(), collection_meta, ); + if let Some(bound) = new_bound { + collection_state + .read_capabilities + .update_iter(bound.iter().map(|t| (*t, 1))); + collection_state.compaction_bound = Some(bound.clone()); + } // Add a record of the new collection. self_collections.insert(new_collection, collection_state); @@ -2282,6 +2410,7 @@ impl StorageCollections for StorageCollectionsImpl { // frontier of the source to `[]` (the empty Antichain), will propagate // to the storage dependencies. let mut finalized_policies = Vec::new(); + let mut bound_releases = BTreeMap::new(); for id in identifiers { // Make sure it's still there, might already have been deleted. @@ -2313,9 +2442,20 @@ impl StorageCollections for StorageCollectionsImpl { } finalized_policies.push((id, ReadPolicy::ValidFrom(Antichain::new()))); + let collection = self_collections.get_mut(&id).expect("checked above"); + if let Some(bound) = collection.compaction_bound.take() { + let mut changes = ChangeBatch::new(); + changes.extend(bound.iter().map(|t| (*t, -1))); + bound_releases.insert(id, changes); + } } self.set_read_policies_inner(&mut self_collections, finalized_policies); + Self::update_read_capabilities_inner( + &self.cmd_tx, + &mut self_collections, + &mut bound_releases, + ); drop(self_collections); @@ -2354,6 +2494,42 @@ impl StorageCollections for StorageCollectionsImpl { } } + fn apply_compaction_bounds( + &self, + bounds: BTreeMap>, + ) -> Result<(), StorageError> { + let mut collections = self.collections.lock().expect("lock poisoned"); + for (id, bound) in &bounds { + let collection = collections + .get(id) + .ok_or(StorageError::IdentifierMissing(*id))?; + let old = collection.compaction_bound.as_ref().ok_or_else(|| { + StorageError::InvalidUsage(format!( + "collection {id} has no committed compaction bound" + )) + })?; + if !PartialOrder::less_equal(old, bound) { + return Err(StorageError::InvalidUsage(format!( + "compaction bound regression for {id}" + ))); + } + } + let mut updates = BTreeMap::new(); + for (id, bound) in bounds { + let collection = collections.get_mut(&id).expect("validated above"); + let old = collection + .compaction_bound + .replace(bound.clone()) + .expect("validated above"); + let mut changes = ChangeBatch::new(); + changes.extend(old.iter().map(|t| (*t, -1))); + changes.extend(bound.iter().map(|t| (*t, 1))); + updates.insert(id, changes); + } + Self::update_read_capabilities_inner(&self.cmd_tx, &mut collections, &mut updates); + Ok(()) + } + fn acquire_read_holds( &self, desired_holds: Vec, @@ -2605,9 +2781,8 @@ struct CollectionState { /// Accumulation of read capabilities for the collection. /// - /// This accumulation will always contain `self.implied_capability`, but may - /// also contain capabilities held by others who have read dependencies on - /// this collection. + /// This accumulation contains `self.implied_capability`, any committed + /// compaction bound, and capabilities held by readers of this collection. pub read_capabilities: MutableAntichain, /// The implicit capability associated with collection creation. This @@ -2618,6 +2793,9 @@ struct CollectionState { /// The policy to use to downgrade `self.implied_capability`. pub read_policy: ReadPolicy, + /// Independent permission capability, released only by an authorized drop. + pub compaction_bound: Option>, + /// Storage identifiers on which this collection depends. pub storage_dependencies: Vec, @@ -2647,6 +2825,7 @@ impl CollectionState { ingestion_remap_collection_id, read_capabilities, implied_capability: since.clone(), + compaction_bound: None, read_policy: ReadPolicy::NoPolicy { initial_since: since, }, @@ -3289,6 +3468,485 @@ mod tests { use super::*; + struct TestTxn(ShardId); + + #[async_trait] + impl StorageTxn for TestTxn { + fn get_collection_metadata(&self) -> BTreeMap { + BTreeMap::new() + } + fn insert_collection_metadata( + &mut self, + _: BTreeMap, + ) -> Result<(), StorageError> { + unimplemented!() + } + fn delete_collection_metadata( + &mut self, + _: BTreeSet, + ) -> Vec<(GlobalId, ShardId)> { + unimplemented!() + } + fn get_unfinalized_shards(&self) -> BTreeSet { + BTreeSet::new() + } + fn insert_unfinalized_shards(&mut self, _: BTreeSet) -> Result<(), StorageError> { + unimplemented!() + } + fn remove_unfinalized_shards(&mut self, _: BTreeSet) { + unimplemented!() + } + fn get_txn_wal_shard(&self) -> Option { + Some(self.0) + } + fn write_txn_wal_shard(&mut self, _: ShardId) -> Result<(), StorageError> { + unimplemented!() + } + } + + async fn bound_test_controller() -> (StorageCollectionsImpl, PersistClient) { + let location = PersistLocation { + blob_uri: SensitiveUrl::from_str("mem://").unwrap(), + consensus_uri: SensitiveUrl::from_str("mem://").unwrap(), + }; + let registry = MetricsRegistry::new(); + let mut config = PersistConfig::new_default_configs(&DUMMY_BUILD_INFO, SYSTEM_TIME.clone()); + // Exercise actual since downgrades without waiting for persist's rate limit. + config.critical_downgrade_interval = Duration::ZERO; + let cache = Arc::new(PersistClientCache::new(config, ®istry, |_, _| { + PubSubClientConnection::noop() + })); + let persist = cache.open(location.clone()).await.unwrap(); + let controller = StorageCollectionsImpl::new( + location, + cache, + ®istry, + SYSTEM_TIME.clone(), + Arc::new(TxnMetrics::new(®istry)), + NonZeroI64::new(1).unwrap(), + false, + ConnectionContext::for_tests(Arc::new(InMemorySecretsController::new())), + &TestTxn(ShardId::new()), + ) + .await; + (controller, persist) + } + + fn frontier(ts: u64) -> Antichain { + Antichain::from_elem(ts.into()) + } + + async fn await_frontier( + controller: &StorageCollectionsImpl, + id: GlobalId, + expected: Antichain, + ) { + tokio::time::timeout(Duration::from_secs(10), async { + loop { + if controller + .collection_frontiers(id) + .unwrap() + .read_capabilities + == expected + { + break; + } + tokio::task::yield_now().await; + } + }) + .await + .expect("read frontier did not converge"); + } + + #[mz_ore::test(tokio::test)] + async fn test_committed_compaction_bound_caps_progress() { + let (controller, persist) = bound_test_controller().await; + let id = GlobalId::User(1); + let shard = ShardId::new(); + let metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(id, shard)]), + compaction_bounds: BTreeMap::from([(id, frontier(5))]), + ..Default::default() + }; + controller + .create_collections_for_bootstrap( + &metadata, + None, + vec![( + id, + CollectionDescription::for_other(RelationDesc::empty(), None), + )], + &BTreeSet::new(), + ) + .await + .unwrap(); + let holds = controller.acquire_read_holds(vec![id]).unwrap(); + controller.set_read_policies(vec![(id, ReadPolicy::step_back())]); + let mut writer = persist + .open_writer::( + shard, + Arc::new(RelationDesc::empty()), + Arc::new(UnitSchema), + Diagnostics::for_tests(), + ) + .await + .unwrap(); + let updates: Vec<((SourceData, ()), Timestamp, StorageDiff)> = Vec::new(); + writer + .compare_and_append(&updates, frontier(0), frontier(20)) + .await + .unwrap() + .unwrap(); + drop(holds); + await_frontier(&controller, id, frontier(5)).await; + await_persist_since(&persist, shard, frontier(5)).await; + let holds = controller.acquire_read_holds(vec![id]).unwrap(); + controller.set_read_policies(vec![(id, ReadPolicy::ValidFrom(frontier(30)))]); + controller + .apply_compaction_bounds(BTreeMap::from([(id, frontier(10))])) + .unwrap(); + assert_eq!( + controller + .collection_frontiers(id) + .unwrap() + .read_capabilities, + frontier(5) + ); + drop(holds); + await_frontier(&controller, id, frontier(10)).await; + await_persist_since(&persist, shard, frontier(10)).await; + assert_err!(controller.apply_compaction_bounds(BTreeMap::from([(id, frontier(9))]))); + assert_err!(controller.apply_compaction_bounds(BTreeMap::from([ + (id, frontier(15)), + (GlobalId::User(2), frontier(15)) + ]))); + assert_eq!( + controller + .collection_frontiers(id) + .unwrap() + .read_capabilities, + frontier(10) + ); + controller + .apply_compaction_bounds(BTreeMap::from([(id, Antichain::new())])) + .unwrap(); + await_frontier(&controller, id, frontier(30)).await; + await_persist_since(&persist, shard, frontier(30)).await; + writer.expire().await; + } + + async fn await_persist_since( + persist: &PersistClient, + shard: ShardId, + expected: Antichain, + ) { + tokio::time::timeout(Duration::from_secs(10), async { + loop { + let since = persist + .open_critical_since::( + shard, + PersistClient::CONTROLLER_CRITICAL_SINCE, + Opaque::encode(&PersistEpoch::default()), + Diagnostics::for_tests(), + ) + .await + .unwrap(); + assert!( + PartialOrder::less_equal(since.since(), &expected), + "persist compacted beyond permission" + ); + if since.since() == &expected { + break; + } + tokio::task::yield_now().await; + } + }) + .await + .expect("persist since did not converge"); + } + + #[mz_ore::test(tokio::test)] + async fn test_committed_compaction_bound_recovery_validation() { + for (description, register_ts, recovered_since) in [ + ( + CollectionDescription::for_other(RelationDesc::empty(), Some(frontier(6))), + None, + None, + ), + ( + CollectionDescription::for_table(RelationDesc::empty()), + Some(6.into()), + None, + ), + ( + CollectionDescription::for_other(RelationDesc::empty(), None), + None, + Some(frontier(6)), + ), + ] { + let (controller, persist) = bound_test_controller().await; + let id = GlobalId::User(1); + let shard = ShardId::new(); + let metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(id, shard)]), + compaction_bounds: BTreeMap::from([(id, frontier(5))]), + ..Default::default() + }; + if let Some(recovered_since) = recovered_since { + let mut since = persist + .open_critical_since::( + shard, + PersistClient::CONTROLLER_CRITICAL_SINCE, + Opaque::encode(&PersistEpoch::default()), + Diagnostics::for_tests(), + ) + .await + .unwrap(); + let token = since.opaque().clone(); + since + .compare_and_downgrade_since(&token, (&token, &recovered_since)) + .await + .unwrap(); + } + let result = controller + .create_collections_for_bootstrap( + &metadata, + register_ts, + vec![(id, description)], + &BTreeSet::new(), + ) + .await; + assert!(matches!(result, Err(StorageError::ReadBeforeSince(found)) if found == id)); + } + } + + #[mz_ore::test(tokio::test)] + async fn test_committed_compaction_bound_recovers_table_history() { + let (controller, persist) = bound_test_controller().await; + let id = GlobalId::User(1); + let alias = GlobalId::User(2); + let shard = ShardId::new(); + let mut metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(id, shard)]), + compaction_bounds: BTreeMap::from([(id, frontier(5))]), + ..Default::default() + }; + let mut since = persist + .open_critical_since::( + shard, + PersistClient::CONTROLLER_CRITICAL_SINCE, + Opaque::encode(&PersistEpoch::default()), + Diagnostics::for_tests(), + ) + .await + .unwrap(); + let token = since.opaque().clone(); + since + .compare_and_downgrade_since(&token, (&token, &frontier(3))) + .await + .unwrap(); + controller + .create_collections_for_bootstrap( + &metadata, + Some(100.into()), + vec![(id, CollectionDescription::for_table(RelationDesc::empty()))], + &BTreeSet::new(), + ) + .await + .unwrap(); + controller.set_read_policies(vec![(id, ReadPolicy::ValidFrom(frontier(20)))]); + await_frontier(&controller, id, frontier(5)).await; + await_persist_since(&persist, shard, frontier(5)).await; + + metadata.collection_metadata.insert(alias, shard); + metadata.compaction_bounds.insert(alias, frontier(5)); + let mut description = CollectionDescription::for_table(RelationDesc::empty()); + description.primary = Some(id); + controller + .create_collections_for_bootstrap( + &metadata, + Some(100.into()), + vec![(alias, description)], + &BTreeSet::new(), + ) + .await + .unwrap(); + controller.set_read_policies(vec![(alias, ReadPolicy::ValidFrom(frontier(20)))]); + controller + .apply_compaction_bounds(BTreeMap::from([(id, frontier(10))])) + .unwrap(); + assert_eq!( + controller + .collection_frontiers(id) + .unwrap() + .read_capabilities, + frontier(5) + ); + controller + .apply_compaction_bounds(BTreeMap::from([(alias, frontier(10))])) + .unwrap(); + await_frontier(&controller, id, frontier(10)).await; + await_persist_since(&persist, shard, frontier(10)).await; + } + + #[mz_ore::test(tokio::test)] + async fn test_committed_compaction_bound_versions_and_drop() { + let (controller, persist) = bound_test_controller().await; + let old = GlobalId::User(1); + let new = GlobalId::User(2); + let shard = ShardId::new(); + persist + .register_schema::( + shard, + &RelationDesc::empty(), + &UnitSchema, + Diagnostics::for_tests(), + ) + .await + .unwrap() + .expect("initial schema"); + let mut metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(old, shard)]), + compaction_bounds: BTreeMap::from([(old, frontier(5))]), + ..Default::default() + }; + controller + .create_collections_for_bootstrap( + &metadata, + Some(0.into()), + vec![(old, CollectionDescription::for_table(RelationDesc::empty()))], + &BTreeSet::new(), + ) + .await + .unwrap(); + metadata.collection_metadata.insert(new, shard); + assert_err!( + controller + .alter_table_desc( + &metadata, + old, + new, + RelationDesc::empty(), + RelationVersion::root() + ) + .await + ); + metadata.compaction_bounds.insert(new, frontier(10)); + controller + .alter_table_desc( + &metadata, + old, + new, + RelationDesc::empty(), + RelationVersion::root(), + ) + .await + .unwrap(); + controller.set_read_policies(vec![ + (old, ReadPolicy::ValidFrom(frontier(30))), + (new, ReadPolicy::ValidFrom(frontier(30))), + ]); + await_frontier(&controller, new, frontier(5)).await; + await_persist_since(&persist, shard, frontier(5)).await; + controller + .apply_compaction_bounds(BTreeMap::from([(new, frontier(15))])) + .unwrap(); + let holds = controller.acquire_read_holds(vec![old]).unwrap(); + metadata.collection_metadata.remove(&old); + metadata.compaction_bounds.remove(&old); + metadata.compaction_bounds.insert(new, frontier(15)); + controller.drop_collections_unvalidated(&metadata, vec![old]); + assert_eq!( + controller + .collection_frontiers(new) + .unwrap() + .read_capabilities, + frontier(5) + ); + drop(holds); + await_frontier(&controller, new, frontier(15)).await; + await_persist_since(&persist, shard, frontier(15)).await; + let holds = controller.acquire_read_holds(vec![new]).unwrap(); + metadata.collection_metadata.remove(&new); + metadata.compaction_bounds.remove(&new); + controller.drop_collections_unvalidated(&metadata, vec![new]); + assert_eq!( + controller + .collection_frontiers(new) + .unwrap() + .read_capabilities, + frontier(15) + ); + drop(holds); + tokio::time::timeout(Duration::from_secs(10), async { + while controller.collection_frontiers(new).is_ok() { + tokio::task::yield_now().await; + } + }) + .await + .unwrap(); + await_persist_since(&persist, shard, Antichain::new()).await; + } + + #[mz_ore::test(tokio::test)] + async fn test_committed_compaction_bound_bootstrap_aliases() { + let (controller, _) = bound_test_controller().await; + let old = GlobalId::User(1); + let primary = GlobalId::User(2); + let shard = ShardId::new(); + let metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(old, shard), (primary, shard)]), + compaction_bounds: BTreeMap::from([(old, frontier(5)), (primary, frontier(10))]), + ..Default::default() + }; + let mut old_desc = CollectionDescription::for_table(RelationDesc::empty()); + old_desc.primary = Some(primary); + let mut primary_desc = CollectionDescription::for_table(RelationDesc::empty()); + primary_desc.since = Some(frontier(6)); + let result = controller + .create_collections_for_bootstrap( + &metadata, + Some(0.into()), + vec![(old, old_desc.clone()), (primary, primary_desc.clone())], + &BTreeSet::new(), + ) + .await; + assert!(matches!(result, Err(StorageError::ReadBeforeSince(id)) if id == primary)); + primary_desc.since = Some(frontier(5)); + let (controller, _) = bound_test_controller().await; + let result = controller + .create_collections_for_bootstrap( + &metadata, + Some(0.into()), + vec![(primary, primary_desc.clone())], + &BTreeSet::new(), + ) + .await; + assert!(matches!(result, Err(StorageError::IdentifierMissing(id)) if id == old)); + let (controller, persist) = bound_test_controller().await; + controller + .create_collections_for_bootstrap( + &metadata, + Some(0.into()), + vec![(old, old_desc), (primary, primary_desc)], + &BTreeSet::new(), + ) + .await + .unwrap(); + controller.set_read_policies(vec![ + (old, ReadPolicy::ValidFrom(frontier(30))), + (primary, ReadPolicy::ValidFrom(frontier(30))), + ]); + assert_eq!( + controller + .collection_frontiers(primary) + .unwrap() + .read_capabilities, + frontier(5) + ); + await_persist_since(&persist, shard, frontier(5)).await; + } + #[mz_ore::test] fn test_partition_finalizable_shards() { let active_shard = ShardId::new(); diff --git a/src/storage-controller/src/lib.rs b/src/storage-controller/src/lib.rs index d790f581c1206..846a3e932635b 100644 --- a/src/storage-controller/src/lib.rs +++ b/src/storage-controller/src/lib.rs @@ -1429,6 +1429,7 @@ impl StorageController for Controller { async fn alter_table_desc( &mut self, + storage_metadata: &StorageMetadata, existing_collection: GlobalId, new_collection: GlobalId, new_desc: RelationDesc, @@ -1451,6 +1452,7 @@ impl StorageController for Controller { // Let StorageCollections know! storage_collections .alter_table_desc( + storage_metadata, existing_collection, new_collection, new_desc.clone(), From 9f70ff4d3c0d44cdabb903747e099671766a1dac Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 15:52:20 +0000 Subject: [PATCH 17/36] doc: prioritize production integration in coordination work --- .../design/20260903_decoupled_coordination_prompt.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index e7e0fdd558ead..302687fe3040b 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -15,6 +15,11 @@ the next coherent piece toward its observable outcome, briefly explain that choice, then implement and verify it. Historical proposals and next steps are context, not a cumulative task list. +Prefer connecting existing pieces through the active milestone's production path +over adding further standalone APIs. Preparatory work is appropriate when it +unblocks that path. Let integration evidence refine intermediate interfaces +rather than adding machinery to preserve them. + Treat the design as the agreed boundaries, not a prescribed mechanism. Prefer the smallest coherent solution that preserves the full capability. Incremental progress is fine, but do not mistake an intermediate step for completion. From 1154ec0f13e3e7fd1c15f4c3c32a5f2258b7300a Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 16:12:09 +0000 Subject: [PATCH 18/36] adapter: admit materialized views against logical inputs Include logical collection inputs in MV refresh preparation and creation timestamp selection, even when planning or optimization removes their reads. Preserve early holds and acquire missing planner-introduced inputs before installation, rejecting explicit refreshes that those inputs cannot support. Carry query-scoped name references separately from statement dependencies so replacement targets do not constrain input readability. Add SQL coverage for eliminated inputs, automatic timestamps, refresh-at-creation, replacement targets, and implicit SQL-function reads. This prepares catalog-backed recovery protection but does not implement durable bounds or maintained requirements. Admission currently uses held readability. Validation: bin/fmt, cargo check for mz-adapter and mz-sqllogictest with all targets, and selected Cargo, formatting, and Python-doc lint checks passed. SQL execution and broader validation are delegated to draft PR CI. --- .../design/20260903_decoupled_coordination.md | 12 +++ src/adapter/src/coord/command_handler.rs | 21 ++--- .../inner/create_materialized_view.rs | 73 ++++++++++++++-- src/sql/src/plan.rs | 2 + src/sql/src/plan/statement/ddl.rs | 2 + test/sqllogictest/materialized_views.slt | 86 +++++++++++++++++++ 6 files changed, 176 insertions(+), 20 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index a86b2eae96b92..4758066a7bb27 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -458,3 +458,15 @@ Milestone 1 remains active. There is no durable producer for these bounds yet. Next useful step: persist bounds and maintained requirements, enforce their compatibility at the catalog transaction boundary, and supply committed bounds to storage before relying on them for MV installation and recovery. + +### 2026-09-08: Logical-input MV admission + +MV creation accounts for logical inputs in refresh preparation and timestamp +selection, including eliminated references and planner-introduced reads. Query +references are kept separate from statement dependencies because a replacement's +`FOR` target is not an input read. Existing early holds are preserved when adding +inputs discovered during planning. + +This constrains live admission using held readability, not committed permission. +Milestone 1 still needs a durable producer for bounds and maintained requirements, +transaction-boundary compatibility checks, and committed delivery to storage. diff --git a/src/adapter/src/coord/command_handler.rs b/src/adapter/src/coord/command_handler.rs index c5380e42693e8..aa7c8249f6ada 100644 --- a/src/adapter/src/coord/command_handler.rs +++ b/src/adapter/src/coord/command_handler.rs @@ -1665,12 +1665,7 @@ impl Coordinator { } let mz_now = match self - .resolve_mz_now_for_create_materialized_view( - &cmvs, - &resolved_ids, - ctx.session_mut(), - true, - ) + .resolve_mz_now_for_create_materialized_view(&cmvs, ctx.session_mut(), true) .await { Ok(mz_now) => mz_now, @@ -1712,12 +1707,7 @@ impl Coordinator { }) => { let mut cmvs = *box_cmvs; let mz_now = match self - .resolve_mz_now_for_create_materialized_view( - &cmvs, - &resolved_ids, - ctx.session_mut(), - false, - ) + .resolve_mz_now_for_create_materialized_view(&cmvs, ctx.session_mut(), false) .await { Ok(mz_now) => mz_now, @@ -1875,7 +1865,6 @@ impl Coordinator { async fn resolve_mz_now_for_create_materialized_view( &mut self, cmvs: &CreateMaterializedViewStatement, - resolved_ids: &ResolvedIds, session: &Session, acquire_read_holds: bool, ) -> Result, AdapterError> { @@ -1886,9 +1875,13 @@ impl Coordinator { { let catalog = self.catalog().for_session(session); let cluster = mz_sql::plan::resolve_cluster_for_materialized_view(&catalog, cmvs)?; - let ids = self + let resolved_ids = mz_sql::names::visit_dependencies(&catalog, &cmvs.query); + let mut ids = self .index_oracle(cluster) .sufficient_collections(resolved_ids.collections().copied()); + ids.extend( + &self.materialized_view_logical_inputs(resolved_ids.collections().copied())?, + ); // If there is any REFRESH option, then acquire read holds. (Strictly speaking, we'd // need this only if there is a `REFRESH AT`, not for `REFRESH EVERY`, because later diff --git a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs index bc25b43fc9886..0287e34236886 100644 --- a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs +++ b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs @@ -19,7 +19,7 @@ use mz_repr::explain::{ExprHumanizerExt, TransientItem}; use mz_repr::optimize::OptimizerFeatures; use mz_repr::optimize::OverrideFrom; use mz_repr::refresh_schedule::RefreshSchedule; -use mz_repr::{CatalogItemId, Datum, RelationVersion, Row, VersionedRelationDesc}; +use mz_repr::{CatalogItemId, Datum, GlobalId, RelationVersion, Row, VersionedRelationDesc}; use mz_sql::ast::ExplainStage; use mz_sql::catalog::CatalogError; use mz_sql::names::ResolvedIds; @@ -94,6 +94,33 @@ impl Staged for CreateMaterializedViewStage { } impl Coordinator { + /// Discovers storage inputs required to reconstruct an MV from its definition. + pub(crate) fn materialized_view_logical_inputs( + &self, + ids: impl IntoIterator, + ) -> Result { + let inputs = self.catalog().state().logical_collection_inputs( + ids.into_iter() + .filter(|id| self.catalog().get_entry_by_global_id(id).is_relation()), + ); + let log_names: Vec<_> = inputs + .iter() + .map(|id| self.catalog().get_entry_by_global_id(id)) + .filter(|entry| matches!(entry.item(), CatalogItem::Log(_))) + .map(|entry| entry.name().item.clone()) + .collect(); + if !log_names.is_empty() { + return Err(AdapterError::InvalidLogDependency { + object_type: "materialized view".into(), + log_names, + }); + } + Ok(CollectionIdBundle { + storage_ids: inputs, + compute_ids: BTreeMap::new(), + }) + } + #[instrument] pub(crate) async fn sequence_create_materialized_view( &mut self, @@ -325,6 +352,7 @@ impl Coordinator { materialized_view: plan::MaterializedView { expr, + query_ids, cluster_id, target_replica, refresh_schedule, @@ -392,7 +420,7 @@ impl Coordinator { // index), otherwise we might be missing some read holds. let ids = self .index_oracle(*cluster_id) - .sufficient_collections(resolved_ids.collections().copied()); + .sufficient_collections(query_ids.collections().copied()); if !ids.difference(&read_holds.id_bundle()).is_empty() { return Err(AdapterError::ChangedPlan( "the set of possible inputs changed during the creation of the \ @@ -570,6 +598,7 @@ impl Coordinator { materialized_view: plan::MaterializedView { mut create_sql, + query_ids, expr: raw_expr, column_names, dependencies, @@ -611,6 +640,12 @@ impl Coordinator { // Timestamp selection let id_bundle = dataflow_import_id_bundle(global_lir_plan.df_desc(), cluster_id); + let logical_inputs = self.materialized_view_logical_inputs( + query_ids + .collections() + .copied() + .chain(raw_expr.depends_on()), + )?; let read_holds_owned; let read_holds = if let Some(txn_reads) = self.txn_read_holds.get(ctx.session().conn_id()) { @@ -625,8 +660,18 @@ impl Coordinator { &read_holds_owned }; - let (dataflow_as_of, storage_as_of, until) = - self.select_timestamps(id_bundle, refresh_schedule.as_ref(), read_holds)?; + // Reuse purification's holds, whose timestamps may already be named by + // REFRESH AT. Planning can introduce reads absent from name resolution. + let mut additional_inputs = id_bundle.clone(); + additional_inputs.extend(&logical_inputs); + let additional_read_holds = + self.acquire_read_holds(&additional_inputs.difference(&read_holds.id_bundle())); + let (dataflow_as_of, storage_as_of, until) = self.select_timestamps( + id_bundle, + refresh_schedule.as_ref(), + read_holds, + &additional_read_holds, + )?; tracing::info!( dataflow_as_of = ?dataflow_as_of, @@ -817,6 +862,7 @@ impl Coordinator { id_bundle: CollectionIdBundle, refresh_schedule: Option<&RefreshSchedule>, read_holds: &ReadHolds, + additional_read_holds: &ReadHolds, ) -> Result< ( Antichain, @@ -826,13 +872,18 @@ impl Coordinator { AdapterError, > { assert!( - id_bundle.difference(&read_holds.id_bundle()).is_empty(), + id_bundle + .difference(&read_holds.id_bundle()) + .difference(&additional_read_holds.id_bundle()) + .is_empty(), "we must have read holds for all involved collections" ); // For non-REFRESH MVs both the `dataflow_as_of` and the `storage_as_of` should be simply // `least_valid_read`. - let least_valid_read = read_holds.least_valid_read(); + let least_valid_read = read_holds + .least_valid_read() + .join(&additional_read_holds.least_valid_read()); let mut dataflow_as_of = least_valid_read.clone(); let mut storage_as_of = least_valid_read.clone(); @@ -846,6 +897,16 @@ impl Coordinator { // the first refresh time. Also note that simply moving the `dataflow_as_of` forward to the // first refresh time would prevent warmup before the first refresh. if let Some(refresh_schedule) = &refresh_schedule { + // Planning can introduce logical reads absent from name resolution. + // Do not let rounding skip a requested refresh on those inputs. + for refresh_at_ts in &refresh_schedule.ats { + if !least_valid_read.less_equal(refresh_at_ts) { + return Err(AdapterError::InputNotReadableAtRefreshAtTime( + *refresh_at_ts, + least_valid_read, + )); + } + } if let Some(least_valid_read_ts) = least_valid_read.as_option() { if let Some(first_refresh_ts) = refresh_schedule.round_up_timestamp(*least_valid_read_ts) diff --git a/src/sql/src/plan.rs b/src/sql/src/plan.rs index f883199ac6204..c2412a20fbbbb 100644 --- a/src/sql/src/plan.rs +++ b/src/sql/src/plan.rs @@ -1964,6 +1964,8 @@ pub struct View { pub struct MaterializedView { /// Parse-able SQL that is stored durably and defines this materialized view. pub create_sql: String, + /// Query name references, including those eliminated by planning, rather than all statement references. + pub query_ids: ResolvedIds, /// Unoptimized high-level expression from parsing the `create_sql`. pub expr: HirRelationExpr, /// All of the catalog objects that are referenced by this materialized view, according to the `expr`. diff --git a/src/sql/src/plan/statement/ddl.rs b/src/sql/src/plan/statement/ddl.rs index 3c79d12cb003b..04c63db24fd77 100644 --- a/src/sql/src/plan/statement/ddl.rs +++ b/src/sql/src/plan/statement/ddl.rs @@ -2874,6 +2874,7 @@ pub fn plan_create_materialized_view( let partial_name = normalize::unresolved_item_name(stmt.name)?; let name = scx.allocate_qualified_name(partial_name.clone())?; + let query_ids = crate::names::visit_dependencies(scx.catalog, &stmt.query); let query::PlannedRootQuery { expr, mut desc, @@ -3190,6 +3191,7 @@ pub fn plan_create_materialized_view( name, materialized_view: MaterializedView { create_sql, + query_ids, expr, dependencies: DependencyIds(dependencies), column_names, diff --git a/test/sqllogictest/materialized_views.slt b/test/sqllogictest/materialized_views.slt index fe022f5982c4b..752ec684b99f0 100644 --- a/test/sqllogictest/materialized_views.slt +++ b/test/sqllogictest/materialized_views.slt @@ -993,6 +993,92 @@ CREATE MATERIALIZED VIEW mv_no_refresh WITH (REFRESH AT '2000-01-01 10:00') AS SELECT * FROM t2; +## Logical inputs constrain admission even when an unmaterialized view optimizes to a constant. +# t2 is not readable at 0. Keep a nonempty result to also check constant data survives timestamp selection. +statement ok +CREATE VIEW v_eliminated_input AS +SELECT x FROM t2 WHERE false UNION ALL SELECT 7; + +query error REFRESH AT requested for a time where not all the inputs are readable +CREATE MATERIALIZED VIEW mv_eliminated_past +WITH (REFRESH AT 0) +AS SELECT * FROM v_eliminated_input; + +statement ok +CREATE MATERIALIZED VIEW mv_eliminated_auto AS SELECT * FROM v_eliminated_input; + +# Inspect the creation boundary, not a since that compaction can advance independently. +query B +SELECT (regexp_match(create_sql, 'AS OF ([0-9]+)'))[1]::numeric > 0 +FROM mz_materialized_views WHERE name = 'mv_eliminated_auto'; +---- +true + +query I +SELECT * FROM mv_eliminated_auto; +---- +7 + +statement ok +CREATE MATERIALIZED VIEW mv_eliminated_creation +WITH (REFRESH AT CREATION) +AS SELECT * FROM v_eliminated_input; + +query I +SELECT * FROM mv_eliminated_creation; +---- +7 + +# An unused CTE names a logical input even though planning removes its read. +statement ok +CREATE VIEW v_unused_input AS WITH unused AS (SELECT * FROM t2) SELECT 7; + +query error REFRESH AT requested for a time where not all the inputs are readable +CREATE MATERIALIZED VIEW mv_unused_past +WITH (REFRESH AT 0) +AS SELECT * FROM v_unused_input; + +statement ok +DROP MATERIALIZED VIEW mv_eliminated_auto, mv_eliminated_creation; + +statement ok +DROP VIEW v_eliminated_input, v_unused_input; + +# The FOR target is a catalog dependency, not a read of its output. +statement ok +CREATE MATERIALIZED VIEW mv_future_target WITH (REFRESH AT '3000-01-01') AS SELECT 7; + +statement ok +CREATE REPLACEMENT MATERIALIZED VIEW mv_replacement FOR mv_future_target AS SELECT 8; + +query B +SELECT (regexp_match(create_sql, 'AS OF ([0-9]+)'))[1]::numeric = 0 +FROM mz_materialized_views WHERE name = 'mv_replacement'; +---- +true + +statement ok +DROP MATERIALIZED VIEW mv_replacement; + +statement ok +CREATE REPLACEMENT MATERIALIZED VIEW mv_replacement FOR mv_future_target +WITH (REFRESH AT 0) AS SELECT 8; + +statement ok +DROP MATERIALIZED VIEW mv_future_target CASCADE; + +# The SQL function body introduces a collection read absent from query name resolution. +query error REFRESH AT requested for a time where not all the inputs are readable +CREATE MATERIALIZED VIEW mv_implicit_past WITH (REFRESH AT 0) +AS SELECT pg_get_userbyid(1::oid); + +statement ok +CREATE MATERIALIZED VIEW mv_implicit_input WITH (REFRESH AT '3000-01-01') +AS SELECT pg_get_userbyid(1::oid); + +statement ok +DROP MATERIALIZED VIEW mv_implicit_input; + ## Query MV after the last refresh statement ok CREATE MATERIALIZED VIEW mv3 From 509287bc4af3c5cf6e8a02f42bdbd1a215f42914 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 18:13:45 +0000 Subject: [PATCH 19/36] doc: prioritize catalog-boundary integration handoff --- .../design/20260903_decoupled_coordination.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 4758066a7bb27..fe8dc996b5f04 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -470,3 +470,19 @@ inputs discovered during planning. This constrains live admission using held readability, not committed permission. Milestone 1 still needs a durable producer for bounds and maintained requirements, transaction-boundary compatibility checks, and committed delivery to storage. + +### 2026-09-08: Catalog integration prioritized with Aljoscha + +Milestone 1's next target is the catalog authority connecting admission to storage: +maintained requirements and compaction permission are checked together in the +catalog transaction, and committed permission reaches storage. This can take +several commits. If blocked, identify the concrete obstacle to this integration. + +The decisive check must reject an incompatible maintained requirement even when +the history remains physically readable. Creator-held readability is not durable +protection for logical inputs eliminated from the installed plan. + +Account for mixed drop/create batches: [storage creation](../../../src/storage-client/src/storage_collections.rs) +requires metadata for every still-governed collection, but [catalog implications](../../../src/adapter/src/coord/catalog_implications.rs) +create collections before processing drops whose metadata is already removed. +Resolve this mismatch as part of integration, preserving valid read protection. From 21f525d2185e33881f29d4dd21bc421f097ba602 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 18:16:32 +0000 Subject: [PATCH 20/36] storage: preserve bounds across mixed create/drop batches Accept committed removal of collection metadata during creation without releasing the installed compaction bound. Catalog implications create collections before applying drops, so requiring the removed bound in the committed snapshot rejects valid mixed batches. Keep requiring bounds for collections that remain cataloged and retain shared-shard protection until explicit drop application. Extend the version/drop boundary test to cover create-before-drop and rejection of missing bounds for still-cataloged collections. The regression failed before the fix. Targeted storage-bound tests, cargo check, formatting, and selected repository lint checks passed. Regular PR CI remains pending. This unblocks catalog-bound delivery but does not add its durable producer. --- .../design/20260903_decoupled_coordination.md | 10 ++++++ src/storage-client/src/storage_collections.rs | 36 +++++++++++++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index fe8dc996b5f04..8ce3d58a171da 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -486,3 +486,13 @@ Account for mixed drop/create batches: [storage creation](../../../src/storage-c requires metadata for every still-governed collection, but [catalog implications](../../../src/adapter/src/coord/catalog_implications.rs) create collections before processing drops whose metadata is already removed. Resolve this mismatch as part of integration, preserving valid read protection. + +### 2026-09-08: Mixed-batch storage protection + +Storage creation accepts committed removal of collection metadata while retaining +the installed bound until the drop is applied. Still-cataloged collections must +continue supplying bounds. This preserves create-before-drop implication ordering +and shared-shard protection without another lifecycle API. + +Milestone 1 remains active. Next: persist requirements and bounds, check their +compatibility in the catalog transaction, and deliver committed bounds to storage. diff --git a/src/storage-client/src/storage_collections.rs b/src/storage-client/src/storage_collections.rs index 3d5998acbeafd..2d4953787780a 100644 --- a/src/storage-client/src/storage_collections.rs +++ b/src/storage-client/src/storage_collections.rs @@ -1806,11 +1806,16 @@ impl StorageCollections for StorageCollectionsImpl { } for (id, collection) in existing.iter() { if let Some(bound) = &collection.compaction_bound { - if !storage_metadata.compaction_bounds.contains_key(id) { + if storage_metadata.collection_metadata.contains_key(id) + && !storage_metadata.compaction_bounds.contains_key(id) + { return Err(StorageError::InvalidUsage(format!( "missing compaction bound for {id}" ))); } + // A committed drop can precede its application here. Retain its + // bound, including shared-shard initialization protection, until + // drop_collections_unvalidated releases it. shard_bounds .entry(collection.collection_metadata.data_shard) .or_default() @@ -3851,10 +3856,35 @@ mod tests { controller .apply_compaction_bounds(BTreeMap::from([(new, frontier(15))])) .unwrap(); - let holds = controller.acquire_read_holds(vec![old]).unwrap(); - metadata.collection_metadata.remove(&old); metadata.compaction_bounds.remove(&old); metadata.compaction_bounds.insert(new, frontier(15)); + // Catalog implications apply creates before drops from the same commit. + let unrelated = GlobalId::User(3); + metadata + .collection_metadata + .insert(unrelated, ShardId::new()); + metadata.compaction_bounds.insert(unrelated, frontier(20)); + let creates = vec![( + unrelated, + CollectionDescription::for_other(RelationDesc::empty(), None), + )]; + let result = controller + .create_collections_for_bootstrap(&metadata, None, creates.clone(), &BTreeSet::new()) + .await; + assert!(matches!(result, Err(StorageError::InvalidUsage(_)))); + metadata.collection_metadata.remove(&old); + controller + .create_collections_for_bootstrap(&metadata, None, creates, &BTreeSet::new()) + .await + .unwrap(); + assert_eq!( + controller + .collection_frontiers(old) + .unwrap() + .read_capabilities, + frontier(5) + ); + let holds = controller.acquire_read_holds(vec![old]).unwrap(); controller.drop_collections_unvalidated(&metadata, vec![old]); assert_eq!( controller From daf03127934736d8a818c86434d01f8a74c8d358 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 19:25:48 +0000 Subject: [PATCH 21/36] catalog: coordinate read protection with compaction permission Persist storage-backed compaction bounds separately from maintained logical read requirements. Validate final transaction state before batch extraction and return admission failures before the adapter fatal commit boundary. Deliver committed bounds through catalog implications and preserve collection lifetimes in dry runs and drops. This connects catalog authority to storage enforcement without enabling SQL production of the records. Creation, recovery, and progress publication remain work toward the first protected MV. Add durable reopen and admission coverage, plus adapter coverage for committed updates and staged collection lifetimes. --- .../design/20260903_decoupled_coordination.md | 13 + src/adapter/src/catalog/apply.rs | 15 + src/adapter/src/catalog/migrate.rs | 14 +- src/adapter/src/catalog/open.rs | 2 + src/adapter/src/catalog/transact.rs | 214 +- src/adapter/src/coord/catalog_implications.rs | 23 +- .../parsed_state_updates.rs | 4 + src/adapter/src/coord/ddl.rs | 2 + src/adapter/src/util.rs | 1 + src/catalog-debug/src/main.rs | 38 +- src/catalog-protos/objects_hashes.json | 6 +- src/catalog-protos/src/lib.rs | 3 +- src/catalog-protos/src/objects.rs | 41 + src/catalog-protos/src/objects_v93.rs | 2331 +++++++++++++++++ src/catalog/src/durable/debug.rs | 26 + src/catalog/src/durable/error.rs | 3 + src/catalog/src/durable/objects.rs | 98 +- .../src/durable/objects/serialization.rs | 75 +- .../src/durable/objects/state_update.rs | 54 + src/catalog/src/durable/persist.rs | 14 + src/catalog/src/durable/transaction.rs | 210 +- src/catalog/src/durable/upgrade.rs | 12 +- .../durable/upgrade/snapshots/objects_v93.txt | 100 + src/catalog/src/durable/upgrade/v92_to_v93.rs | 19 + src/catalog/src/memory/objects.rs | 2 + src/catalog/tests/read-write.rs | 245 ++ 26 files changed, 3507 insertions(+), 58 deletions(-) create mode 100644 src/catalog-protos/src/objects_v93.rs create mode 100644 src/catalog/src/durable/upgrade/snapshots/objects_v93.txt create mode 100644 src/catalog/src/durable/upgrade/v92_to_v93.rs diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 8ce3d58a171da..b7b41e0979677 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -496,3 +496,16 @@ and shared-shard protection without another lifecycle API. Milestone 1 remains active. Next: persist requirements and bounds, check their compatibility in the catalog transaction, and deliver committed bounds to storage. + +### 2026-09-08: Durable protection and committed delivery + +Storage-backed bounds and maintained requirements have separate durable records. +Final transaction state checks their compatibility and bound monotonicity, with +recoverable adapter admission and committed delivery to storage. Governance begins +at collection birth, not adoption of a live ungoverned collection. + +Milestone 1 remains active. SQL creation does not yet produce these records. Next: +wire initial bounds and MV logical-input requirements into creation, accounting for +registration timestamps and actual readability when reusing shards. Recovery must +consume the requirements, and durable output progress must advance them and permit +bound publication before this becomes the complete protected-MV path. diff --git a/src/adapter/src/catalog/apply.rs b/src/adapter/src/catalog/apply.rs index a7c3721f3b7e8..d919404588159 100644 --- a/src/adapter/src/catalog/apply.rs +++ b/src/adapter/src/catalog/apply.rs @@ -384,6 +384,15 @@ impl CatalogState { retractions, ); } + StateUpdateKind::CollectionCompactionBound(bound) => { + apply_inverted_lookup( + &mut Arc::make_mut(&mut self.storage_metadata).compaction_bounds, + &bound.id, + bound.frontier.into_iter().collect(), + diff, + ); + } + StateUpdateKind::MaintainedReadRequirement(_) => {} StateUpdateKind::UnfinalizedShard(unfinalized_shard) => { self.apply_unfinalized_shard_update(unfinalized_shard, diff, retractions); } @@ -1492,6 +1501,8 @@ impl CatalogState { StateUpdateKind::Database(_) | StateUpdateKind::Schema(_) | StateUpdateKind::NetworkPolicy(_) + | StateUpdateKind::CollectionCompactionBound(_) + | StateUpdateKind::MaintainedReadRequirement(_) | StateUpdateKind::StorageCollectionMetadata(_) | StateUpdateKind::UnfinalizedShard(_) => Vec::new(), } @@ -2255,6 +2266,8 @@ fn sort_updates(updates: Vec) -> Vec { StateUpdateKind::Comment(_) | StateUpdateKind::SourceReferences(_) | StateUpdateKind::AuditLog(_) + | StateUpdateKind::CollectionCompactionBound(_) + | StateUpdateKind::MaintainedReadRequirement(_) | StateUpdateKind::StorageCollectionMetadata(_) | StateUpdateKind::UnfinalizedShard(_) => push_update( update, @@ -2489,6 +2502,8 @@ impl ApplyState { | SourceReferences(_) | Comment(_) | AuditLog(_) + | CollectionCompactionBound(_) + | MaintainedReadRequirement(_) | StorageCollectionMetadata(_) | UnfinalizedShard(_) => Self::Updates(vec![update]), } diff --git a/src/adapter/src/catalog/migrate.rs b/src/adapter/src/catalog/migrate.rs index e5b0014066c55..8bb9277f7c049 100644 --- a/src/adapter/src/catalog/migrate.rs +++ b/src/adapter/src/catalog/migrate.rs @@ -172,11 +172,15 @@ pub(crate) async fn migrate( // Since some migrations might introduce non-item 'post-item' updates, we sequester those // so they can be applied with other post-item updates after migrations to avoid // accumulating negative diffs. - let (post_item_updates, item_updates): (Vec<_>, Vec<_>) = item_updates - .into_iter() - // The only post-item update kind we currently generate is to - // update storage collection metadata. - .partition(|(kind, _, _)| matches!(kind, StateUpdateKind::StorageCollectionMetadata(_))); + let (post_item_updates, item_updates): (Vec<_>, Vec<_>) = + item_updates.into_iter().partition(|(kind, _, _)| { + matches!( + kind, + StateUpdateKind::StorageCollectionMetadata(_) + | StateUpdateKind::CollectionCompactionBound(_) + | StateUpdateKind::MaintainedReadRequirement(_) + ) + }); let item_updates = item_updates .into_iter() diff --git a/src/adapter/src/catalog/open.rs b/src/adapter/src/catalog/open.rs index 9f9c96e28865e..41204f71779ce 100644 --- a/src/adapter/src/catalog/open.rs +++ b/src/adapter/src/catalog/open.rs @@ -313,6 +313,8 @@ impl Catalog { diff: diff.try_into().expect("valid diff"), }), StateUpdateKind::Comment(_) + | StateUpdateKind::CollectionCompactionBound(_) + | StateUpdateKind::MaintainedReadRequirement(_) | StateUpdateKind::StorageCollectionMetadata(_) | StateUpdateKind::SourceReferences(_) | StateUpdateKind::UnfinalizedShard(_) => { diff --git a/src/adapter/src/catalog/transact.rs b/src/adapter/src/catalog/transact.rs index acb4b80e51ab3..2d0dc92d45c93 100644 --- a/src/adapter/src/catalog/transact.rs +++ b/src/adapter/src/catalog/transact.rs @@ -70,6 +70,7 @@ use mz_sql::session::vars::OwnedVarInput; use mz_sql::session::vars::{Value as VarValue, VarInput}; use mz_sql::{DEFAULT_SCHEMA, rbac}; use mz_sql_parser::ast::{QualifiedReplica, Value}; +use mz_storage_client::controller::StorageTxn; use mz_storage_client::storage_collections::StorageCollections; use serde::{Deserialize, Serialize}; use tracing::{info, trace}; @@ -273,6 +274,15 @@ pub enum Op { scoped: ScopedParameters, prune_scope: ScopedParametersScope, }, + SetCollectionCompactionBound { + id: GlobalId, + frontier: Option, + }, + SetMaintainedReadRequirement { + id: GlobalId, + inputs: BTreeSet, + frontier: Option, + }, /// Injects audit events into the catalog. /// /// This is a nonstandard path used for manually appending audit events at the current time. @@ -1037,9 +1047,23 @@ impl Catalog { storage_collections.is_none(), "dry-run mode must not prepare storage state" ); + // Model collection lifetimes for admission without controller + // effects. These shard IDs stay in the noncommittable snapshot. + tx.insert_collection_metadata( + storage_collections_to_create + .into_iter() + .map(|id| (id, ShardId::new())) + .collect(), + )?; + tx.insert_collection_metadata(storage_collections_to_register)?; + tx.delete_collection_metadata(storage_collections_to_drop); } } + // Admission failures must return before entering the fatal commit path. + // Batch extraction repeats this check for other durable callers. + tx.validate_read_protection()?; + let updates = tx.get_and_commit_op_updates(); if !updates.is_empty() { let mut local_expr_cache = LocalExpressionCache::new(cached_exprs.clone()); @@ -3131,6 +3155,16 @@ impl Catalog { } } } + Op::SetCollectionCompactionBound { id, frontier } => { + tx.set_collection_compaction_bound(id, frontier)?; + } + Op::SetMaintainedReadRequirement { + id, + inputs, + frontier, + } => { + tx.set_maintained_read_requirement(id, inputs, frontier)?; + } Op::InjectAuditEvents { events } => { for event in events { let id = tx.allocate_audit_log_id()?; @@ -3564,6 +3598,131 @@ mod tests { use crate::catalog::{Catalog, Op}; use crate::session::DEFAULT_DATABASE_NAME; + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] + async fn test_read_protection_admission_and_committed_updates() { + use std::collections::{BTreeMap, BTreeSet}; + + use mz_catalog::memory::objects::StateDiff; + use mz_persist_client::ShardId; + use mz_repr::{GlobalId, Timestamp}; + use mz_storage_client::controller::StorageTxn; + use timely::progress::Antichain; + + use crate::catalog::state::LocalExpressionCache; + use crate::coord::catalog_implications::parsed_state_updates::ParsedStateUpdateKind; + + Catalog::with_debug(|mut catalog| async move { + let input = GlobalId::User(100_000); + let output = GlobalId::User(100_001); + let ts = |value| Timestamp::new(value); + // Seed collection birth through the durable transaction. This harness + // does not run storage shard allocation or controller implications. + let updates = { + let mut storage = catalog.storage().await; + let mut tx = storage.transaction().await.unwrap(); + tx.insert_collection_metadata(BTreeMap::from([ + (input, ShardId::new()), + (output, ShardId::new()), + ])) + .unwrap(); + tx.set_collection_compaction_bound(input, Some(ts(10))) + .unwrap(); + let updates = tx.get_and_commit_op_updates(); + let commit_ts = tx.upper(); + tx.commit(commit_ts).await.unwrap(); + updates + }; + let _ = catalog + .state + .apply_updates(updates, &mut LocalExpressionCache::Closed) + .await; + + let requirement = |frontier| Op::SetMaintainedReadRequirement { + id: output, + inputs: BTreeSet::from([input]), + frontier, + }; + let bound = |frontier| Op::SetCollectionCompactionBound { + id: input, + frontier, + }; + let before = catalog.state().storage_metadata().compaction_bounds.clone(); + let oracle_write_ts = catalog.current_upper().await; + let result = catalog + .transact(None, oracle_write_ts, None, vec![requirement(Some(ts(9)))]) + .await; + assert!( + result.is_err(), + "admission must return an error, not terminate" + ); + assert_eq!(catalog.state().storage_metadata().compaction_bounds, before); + let base_state = catalog.state().clone(); + assert!( + catalog + .transact_incremental_dry_run( + &base_state, + vec![requirement(Some(ts(9)))], + None, + None, + oracle_write_ts, + ) + .await + .is_err() + ); + + let result = catalog + .transact( + None, + oracle_write_ts, + None, + vec![bound(Some(ts(20))), requirement(Some(ts(20)))], + ) + .await + .unwrap(); + assert_eq!( + catalog.state().storage_metadata().compaction_bounds[&input], + Antichain::from_elem(ts(20)) + ); + assert!(result.catalog_updates.iter().any(|update| { + matches!( + &update.kind, + ParsedStateUpdateKind::CollectionCompactionBound(bound) + if bound.id == input && bound.frontier == Some(ts(20)) + && update.diff == StateDiff::Addition + ) + })); + + let oracle_write_ts = catalog.current_upper().await; + assert!( + catalog + .transact(None, oracle_write_ts, None, vec![bound(Some(ts(21)))]) + .await + .is_err() + ); + catalog + .transact( + None, + oracle_write_ts, + None, + vec![bound(None), requirement(None)], + ) + .await + .unwrap(); + assert_eq!( + catalog + .state() + .storage_metadata() + .compaction_bounds + .get(&input), + Some(&Antichain::new()), + "completed permission remains governed" + ); + catalog.expire().await; + }) + .await + } + #[mz_ore::test] fn test_reconfiguration_audit_details() { use std::time::Duration; @@ -4084,12 +4243,7 @@ mod tests { ); } - /// Verifies that `transact_incremental_dry_run` processes only new ops - /// against the accumulated state, not all ops from scratch. Two paths are - /// compared: - /// - Incremental: two separate calls, each with one op - /// - All-at-once: one call with both ops - /// Both must produce equivalent catalog state. + /// Staged dry runs carry collection lifetimes and protection across statements. #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` async fn test_transact_incremental_dry_run_processes_only_new_ops() { @@ -4145,16 +4299,22 @@ mod tests { let op_t1 = make_table_op(id_t1, global_id_t1, "t1"); let op_t2 = make_table_op(id_t2, global_id_t2, "t2"); + let bound = Op::SetCollectionCompactionBound { + id: global_id_t1, + frontier: Some(10.into()), + }; + let requirement = Op::SetMaintainedReadRequirement { + id: global_id_t2, + inputs: [global_id_t1].into_iter().collect(), + frontier: Some(10.into()), + }; let base_state = catalog.state().clone(); - // --- Path A: Incremental (two separate dry-run calls) --- - - // First call: only op_t1, no previous snapshot. let (state_after_t1, snapshot_after_t1) = catalog .transact_incremental_dry_run( &base_state, - vec![op_t1.clone()], + vec![op_t1.clone(), bound.clone()], None, None, oracle_write_ts, @@ -4180,11 +4340,10 @@ mod tests { "t2 should NOT exist after first dry run" ); - // Second call: only op_t2, using state/snapshot from first call. - let (state_incremental, _) = catalog + let (state_incremental, snapshot_incremental) = catalog .transact_incremental_dry_run( &state_after_t1, - vec![op_t2.clone()], + vec![op_t2.clone(), requirement.clone()], None, Some(snapshot_after_t1), oracle_write_ts, @@ -4202,12 +4361,10 @@ mod tests { "t2 should exist in incremental result" ); - // --- Path B: All-at-once (single dry-run call with both ops) --- - let (state_all_at_once, _) = catalog .transact_incremental_dry_run( &base_state, - vec![op_t1.clone(), op_t2.clone()], + vec![op_t1.clone(), bound, op_t2.clone(), requirement], None, None, oracle_write_ts, @@ -4224,8 +4381,6 @@ mod tests { "t2 should exist in all-at-once result" ); - // --- Compare: both paths produce equivalent items --- - let inc_t1 = state_incremental.try_get_entry(&id_t1).expect("inc t1"); let all_t1 = state_all_at_once.try_get_entry(&id_t1).expect("all t1"); assert_eq!(inc_t1.name(), all_t1.name()); @@ -4236,6 +4391,29 @@ mod tests { assert_eq!(inc_t2.name(), all_t2.name()); assert_eq!(inc_t2.owner_id, all_t2.owner_id); + let (dropped_state, dropped_snapshot) = catalog + .transact_incremental_dry_run( + &state_incremental, + vec![ + Op::DropObjects(vec![crate::catalog::DropObjectInfo::Item(id_t2)]), + Op::SetCollectionCompactionBound { + id: global_id_t1, + frontier: Some(20.into()), + }, + ], + None, + Some(snapshot_incremental), + oracle_write_ts, + ) + .await + .expect("dropping a consumer releases its requirement in the dry run"); + assert!(dropped_state.try_get_entry(&id_t2).is_none()); + assert!(dropped_snapshot.maintained_read_requirements.is_empty()); + assert_eq!( + dropped_state.storage_metadata().compaction_bounds[&global_id_t1], + timely::progress::Antichain::from_elem(20.into()) + ); + catalog.expire().await; }) .await diff --git a/src/adapter/src/coord/catalog_implications.rs b/src/adapter/src/coord/catalog_implications.rs index d20d69ed492f1..54ce8842774b2 100644 --- a/src/adapter/src/coord/catalog_implications.rs +++ b/src/adapter/src/coord/catalog_implications.rs @@ -50,7 +50,7 @@ use mz_ore::instrument; use mz_ore::retry::Retry; use mz_ore::task; use mz_repr::optimize::OverrideFrom; -use mz_repr::{CatalogItemId, GlobalId, RelationVersion, RelationVersionSelector}; +use mz_repr::{CatalogItemId, GlobalId, RelationVersion, RelationVersionSelector, Timestamp}; use mz_sql::plan::ConnectionDetails; use mz_storage_client::controller::{CollectionDescription, DataSource}; use mz_storage_types::connections::PostgresConnection; @@ -59,6 +59,7 @@ use mz_storage_types::sinks::StorageSinkConnection; use mz_storage_types::sources::{ GenericSourceConnection, SourceDesc, SourceExport, SourceExportDataConfig, }; +use timely::progress::Antichain; use tracing::{Instrument, info_span, warn}; use crate::active_compute_sink::ActiveComputeSinkRetireReason; @@ -110,6 +111,7 @@ impl Coordinator { // We re-run all `SystemVars` callbacks against the committed values, so // we only track that a change happened, not the individual vars. let mut system_config_changed = false; + let mut compaction_bounds = BTreeMap::new(); // Whether to wake the cluster controller once the implications below are // applied. Decided from the committed diff, see the method. @@ -176,6 +178,12 @@ impl Coordinator { // matter here. system_config_changed = true; } + ParsedStateUpdateKind::CollectionCompactionBound(bound) => { + if update.diff == StateDiff::Addition { + compaction_bounds.insert(bound.id, bound.frontier.into_iter().collect()); + } + // Collection drops release installed bounds, not record retractions. + } } } @@ -187,6 +195,7 @@ impl Coordinator { introspection_source_indexes, replica_scoped_config_changed, system_config_changed, + compaction_bounds, ) .await?; @@ -237,6 +246,7 @@ impl Coordinator { mut introspection_source_indexes: BTreeMap>, replica_scoped_config_changed: bool, system_config_changed: bool, + compaction_bounds: BTreeMap>, ) -> Result<(), AdapterError> { // Re-run the `SystemVars` callbacks against the committed values. // Deriving this from the committed diff, rather than the input ops, is @@ -839,6 +849,14 @@ impl Coordinator { self.initialize_storage_collections(storage_policies_to_initialize) .await?; + // New collections already enforce their committed bounds during creation. + // Deliver advancements after same-batch creates, before drops release protection. + if !compaction_bounds.is_empty() { + self.controller + .storage_collections + .apply_compaction_bounds(compaction_bounds)?; + } + // Create VPC endpoints for AWS PrivateLink connections if !vpc_endpoints_to_create.is_empty() { if let Some(cloud_resource_controller) = self.cloud_resource_controller.as_ref() { @@ -2079,6 +2097,9 @@ impl CatalogImplication { // apply_catalog_implications and not routed through absorb. unreachable!("SystemConfiguration should not be passed to absorb"); } + ParsedStateUpdateKind::CollectionCompactionBound(_) => { + unreachable!("CollectionCompactionBound should not be passed to absorb"); + } } } diff --git a/src/adapter/src/coord/catalog_implications/parsed_state_updates.rs b/src/adapter/src/coord/catalog_implications/parsed_state_updates.rs index 77d87180a04e4..95d8ea76de1d3 100644 --- a/src/adapter/src/coord/catalog_implications/parsed_state_updates.rs +++ b/src/adapter/src/coord/catalog_implications/parsed_state_updates.rs @@ -71,6 +71,7 @@ pub enum ParsedStateUpdateKind { SystemConfiguration { durable: durable::objects::SystemConfiguration, }, + CollectionCompactionBound(durable::objects::CollectionCompactionBound), } /// Potentially generate a [ParsedStateUpdate] that corresponds to the given @@ -110,6 +111,9 @@ pub fn parse_state_update( StateUpdateKind::SystemConfiguration(durable) => { Some(ParsedStateUpdateKind::SystemConfiguration { durable }) } + StateUpdateKind::CollectionCompactionBound(bound) => { + Some(ParsedStateUpdateKind::CollectionCompactionBound(bound)) + } _ => { // The controllers are currently not interested in other kinds of // changes to the catalog. Cluster-scoped system-parameter overrides diff --git a/src/adapter/src/coord/ddl.rs b/src/adapter/src/coord/ddl.rs index c39b8f082384b..599c9465293b4 100644 --- a/src/adapter/src/coord/ddl.rs +++ b/src/adapter/src/coord/ddl.rs @@ -1397,6 +1397,8 @@ impl Coordinator { | Op::ResetSystemConfiguration { .. } | Op::ResetAllSystemConfiguration { .. } | Op::UpdateScopedSystemParameters { .. } + | Op::SetCollectionCompactionBound { .. } + | Op::SetMaintainedReadRequirement { .. } | Op::Comment { .. } | Op::CheckClusterState { .. } | Op::InjectAuditEvents { .. } => {} diff --git a/src/adapter/src/util.rs b/src/adapter/src/util.rs index ecdbdfde510d9..c9c237d1e8222 100644 --- a/src/adapter/src/util.rs +++ b/src/adapter/src/util.rs @@ -326,6 +326,7 @@ impl ShouldTerminateGracefully for DurableCatalogError { | DurableCatalogError::Uninitialized | DurableCatalogError::NotWritable(_) | DurableCatalogError::DryRunTransaction + | DurableCatalogError::InvalidReadProtection(_) | DurableCatalogError::DuplicateKey | DurableCatalogError::UniquenessViolation | DurableCatalogError::Storage(_) diff --git a/src/catalog-debug/src/main.rs b/src/catalog-debug/src/main.rs index 48fa96d4eacd6..7329f93543ab0 100644 --- a/src/catalog-debug/src/main.rs +++ b/src/catalog-debug/src/main.rs @@ -32,13 +32,15 @@ use mz_build_info::{BuildInfo, build_info}; use mz_catalog::config::{BuiltinItemMigrationConfig, ClusterReplicaSizeMap, StateConfig}; use mz_catalog::durable::debug::{ AuditLogCollection, ClusterCollection, ClusterIntrospectionSourceIndexCollection, - ClusterReplicaCollection, ClusterSystemConfigurationCollection, Collection, CollectionTrace, - CollectionType, CommentCollection, ConfigCollection, DatabaseCollection, DebugCatalogState, - DefaultPrivilegeCollection, IdAllocatorCollection, ItemCollection, NetworkPolicyCollection, - ReplicaSystemConfigurationCollection, RoleAuthCollection, RoleCollection, SchemaCollection, - SettingCollection, SourceReferencesCollection, StorageCollectionMetadataCollection, - SystemConfigurationCollection, SystemItemMappingCollection, SystemPrivilegeCollection, Trace, - TxnWalShardCollection, UnfinalizedShardsCollection, + ClusterReplicaCollection, ClusterSystemConfigurationCollection, Collection, + CollectionCompactionBoundCollection, CollectionTrace, CollectionType, CommentCollection, + ConfigCollection, DatabaseCollection, DebugCatalogState, DefaultPrivilegeCollection, + IdAllocatorCollection, ItemCollection, MaintainedReadRequirementCollection, + NetworkPolicyCollection, ReplicaSystemConfigurationCollection, RoleAuthCollection, + RoleCollection, SchemaCollection, SettingCollection, SourceReferencesCollection, + StorageCollectionMetadataCollection, SystemConfigurationCollection, + SystemItemMappingCollection, SystemPrivilegeCollection, Trace, TxnWalShardCollection, + UnfinalizedShardsCollection, }; use mz_catalog::durable::{ BootstrapArgs, OpenableDurableCatalogState, persist_backed_catalog_state, @@ -321,6 +323,12 @@ macro_rules! for_collection { CollectionType::SystemPrivileges => { $fn::($($arg),*).await? } + CollectionType::CollectionCompactionBound => { + $fn::($($arg),*).await? + } + CollectionType::MaintainedReadRequirement => { + $fn::($($arg),*).await? + } CollectionType::StorageCollectionMetadata => { $fn::($($arg),*).await? } @@ -473,6 +481,8 @@ async fn dump( cluster_system_configurations, replica_system_configurations, system_privileges, + collection_compaction_bounds, + maintained_read_requirements, storage_collection_metadata, unfinalized_shards, txn_wal_shard, @@ -565,6 +575,20 @@ async fn dump( stats_only, consolidate, ); + dump_col( + &mut data, + collection_compaction_bounds, + &ignore, + stats_only, + consolidate, + ); + dump_col( + &mut data, + maintained_read_requirements, + &ignore, + stats_only, + consolidate, + ); dump_col( &mut data, storage_collection_metadata, diff --git a/src/catalog-protos/objects_hashes.json b/src/catalog-protos/objects_hashes.json index 6eda5343ff044..61d00f085193e 100644 --- a/src/catalog-protos/objects_hashes.json +++ b/src/catalog-protos/objects_hashes.json @@ -1,7 +1,7 @@ [ { "name": "objects.rs", - "md5": "033aea36ed61e9181489faffbf9de51d" + "md5": "5b31bed07809b8c18c30f04ac09bfc7c" }, { "name": "objects_v74.rs", @@ -78,5 +78,9 @@ { "name": "objects_v92.rs", "md5": "033aea36ed61e9181489faffbf9de51d" + }, + { + "name": "objects_v93.rs", + "md5": "5b31bed07809b8c18c30f04ac09bfc7c" } ] diff --git a/src/catalog-protos/src/lib.rs b/src/catalog-protos/src/lib.rs index 19cc741e98602..dea651e46f3a1 100644 --- a/src/catalog-protos/src/lib.rs +++ b/src/catalog-protos/src/lib.rs @@ -30,6 +30,7 @@ pub mod objects_v89; pub mod objects_v90; pub mod objects_v91; pub mod objects_v92; +pub mod objects_v93; pub mod serialization; /// The current version of the `Catalog`. @@ -37,7 +38,7 @@ pub mod serialization; /// We will initialize new `Catalog`s with this version, and migrate existing `Catalog`s to this /// version. Whenever the `Catalog` changes, e.g. the types we serialize in the `Catalog` /// change, we need to bump this version. -pub const CATALOG_VERSION: u64 = 92; +pub const CATALOG_VERSION: u64 = 93; /// The minimum `Catalog` version number that we support migrating from. /// diff --git a/src/catalog-protos/src/objects.rs b/src/catalog-protos/src/objects.rs index 3e679ff6d0b59..c38308e040cfe 100644 --- a/src/catalog-protos/src/objects.rs +++ b/src/catalog-protos/src/objects.rs @@ -520,6 +520,31 @@ pub struct StorageCollectionMetadataValue { pub shard: String, } +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct CollectionCompactionBoundKey { + pub id: GlobalId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct CollectionCompactionBoundValue { + pub frontier: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct MaintainedReadRequirementKey { + pub id: GlobalId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct MaintainedReadRequirementValue { + pub inputs: Vec, + pub frontier: Option, +} + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] pub struct UnfinalizedShardKey { @@ -2017,6 +2042,8 @@ pub enum StateUpdateKind { ReplicaSystemConfiguration(ReplicaSystemConfiguration), Setting(Setting), SourceReferences(SourceReferences), + CollectionCompactionBound(CollectionCompactionBound), + MaintainedReadRequirement(MaintainedReadRequirement), StorageCollectionMetadata(StorageCollectionMetadata), SystemPrivileges(SystemPrivileges), TxnWalShard(TxnWalShard), @@ -2213,6 +2240,20 @@ pub struct StorageCollectionMetadata { pub value: StorageCollectionMetadataValue, } +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct CollectionCompactionBound { + pub key: CollectionCompactionBoundKey, + pub value: CollectionCompactionBoundValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct MaintainedReadRequirement { + pub key: MaintainedReadRequirementKey, + pub value: MaintainedReadRequirementValue, +} + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] pub struct UnfinalizedShard { diff --git a/src/catalog-protos/src/objects_v93.rs b/src/catalog-protos/src/objects_v93.rs new file mode 100644 index 0000000000000..c38308e040cfe --- /dev/null +++ b/src/catalog-protos/src/objects_v93.rs @@ -0,0 +1,2331 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +#[cfg(any(test, feature = "proptest"))] +use proptest_derive::Arbitrary; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; +use uuid::Uuid; + +/// A proptest strategy for [`Uuid`]s, which don't implement `Arbitrary`. +#[cfg(any(test, feature = "proptest"))] +fn any_uuid() -> impl proptest::strategy::Strategy { + use proptest::strategy::Strategy; + proptest::arbitrary::any::().prop_map(Uuid::from_u128) +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ConfigKey { + pub key: String, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ConfigValue { + pub value: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SettingKey { + pub name: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SettingValue { + pub value: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct IdAllocKey { + pub name: String, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct IdAllocValue { + pub next_id: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct GidMappingKey { + pub schema_name: String, + pub object_type: CatalogItemType, + pub object_name: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct GidMappingValue { + pub catalog_id: SystemCatalogItemId, + pub global_id: SystemGlobalId, + pub fingerprint: String, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterKey { + pub id: ClusterId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterValue { + pub name: String, + pub owner_id: RoleId, + pub privileges: Vec, + pub config: ClusterConfig, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterIntrospectionSourceIndexKey { + pub cluster_id: ClusterId, + pub name: String, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterIntrospectionSourceIndexValue { + pub catalog_id: IntrospectionSourceIndexCatalogItemId, + pub global_id: IntrospectionSourceIndexGlobalId, + pub oid: u32, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterReplicaKey { + pub id: ReplicaId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterReplicaValue { + pub cluster_id: ClusterId, + pub name: String, + pub config: ReplicaConfig, + pub owner_id: RoleId, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct DatabaseKey { + pub id: DatabaseId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct DatabaseValue { + pub name: String, + pub owner_id: RoleId, + pub privileges: Vec, + pub oid: u32, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SchemaKey { + pub id: SchemaId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SchemaValue { + pub database_id: Option, + pub name: String, + pub owner_id: RoleId, + pub privileges: Vec, + pub oid: u32, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ItemKey { + pub gid: CatalogItemId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ItemValue { + pub schema_id: SchemaId, + pub name: String, + pub definition: CatalogItem, + pub owner_id: RoleId, + pub privileges: Vec, + pub oid: u32, + pub global_id: GlobalId, + pub extra_versions: Vec, + /// `Some(uuid)` marks a temporary item owned by, and only visible to, the + /// session with that UUID. `None` is a normal durable item. + #[cfg_attr( + any(test, feature = "proptest"), + proptest(strategy = "proptest::option::of(any_uuid())") + )] + pub ephemeral_owner_session: Option, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ItemVersion { + pub global_id: GlobalId, + pub version: Version, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct RoleKey { + pub id: RoleId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct RoleValue { + pub name: String, + pub attributes: RoleAttributes, + pub membership: RoleMembership, + pub vars: RoleVars, + pub oid: u32, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct RoleAuthKey { + pub id: RoleId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct RoleAuthValue { + pub password_hash: Option, + pub updated_at: EpochMillis, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct NetworkPolicyKey { + pub id: NetworkPolicyId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct NetworkPolicyValue { + pub name: String, + pub rules: Vec, + pub owner_id: RoleId, + pub privileges: Vec, + pub oid: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ServerConfigurationKey { + pub name: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ServerConfigurationValue { + pub value: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterSystemConfigurationKey { + pub cluster_id: ClusterId, + pub name: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterSystemConfigurationValue { + pub value: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ReplicaSystemConfigurationKey { + pub replica_id: ReplicaId, + pub name: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ReplicaSystemConfigurationValue { + pub value: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct AuditLogKey { + pub event: AuditLogEvent, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum AuditLogEvent { + V1(AuditLogEventV1), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct CommentKey { + pub object: CommentObject, + pub sub_component: Option, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum CommentObject { + Table(CatalogItemId), + View(CatalogItemId), + MaterializedView(CatalogItemId), + Source(CatalogItemId), + Sink(CatalogItemId), + MetricSink(CatalogItemId), + Index(CatalogItemId), + Func(CatalogItemId), + Connection(CatalogItemId), + Type(CatalogItemId), + Secret(CatalogItemId), + Role(RoleId), + Database(DatabaseId), + Schema(ResolvedSchema), + Cluster(ClusterId), + ClusterReplica(ClusterReplicaId), + NetworkPolicy(NetworkPolicyId), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum CommentSubComponent { + ColumnPos(u64), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct CommentValue { + pub comment: String, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SourceReferencesKey { + pub source: CatalogItemId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SourceReferencesValue { + pub references: Vec, + pub updated_at: EpochMillis, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SourceReference { + pub name: String, + pub namespace: Option, + pub columns: Vec, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct StorageCollectionMetadataKey { + pub id: GlobalId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct StorageCollectionMetadataValue { + pub shard: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct CollectionCompactionBoundKey { + pub id: GlobalId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct CollectionCompactionBoundValue { + pub frontier: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct MaintainedReadRequirementKey { + pub id: GlobalId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct MaintainedReadRequirementValue { + pub inputs: Vec, + pub frontier: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct UnfinalizedShardKey { + pub shard: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct TxnWalShardValue { + pub shard: String, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Empty {} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct StringWrapper { + pub inner: String, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Duration { + pub secs: u64, + pub nanos: u32, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct EpochMillis { + pub millis: u64, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Version { + pub value: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum CatalogItem { + V1(CatalogItemV1), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct CatalogItemV1 { + pub create_sql: String, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum CatalogItemId { + System(u64), + User(u64), + Transient(u64), + IntrospectionSourceIndex(u64), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SystemCatalogItemId(pub u64); + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct IntrospectionSourceIndexCatalogItemId(pub u64); + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum GlobalId { + System(u64), + User(u64), + Transient(u64), + Explain, + IntrospectionSourceIndex(u64), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SystemGlobalId(pub u64); + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct IntrospectionSourceIndexGlobalId(pub u64); + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum ClusterId { + System(u64), + User(u64), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum DatabaseId { + System(u64), + User(u64), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum ResolvedDatabaseSpecifier { + Ambient, + Id(DatabaseId), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum SchemaId { + System(u64), + User(u64), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum SchemaSpecifier { + Temporary, + Id(SchemaId), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ResolvedSchema { + pub database: ResolvedDatabaseSpecifier, + pub schema: SchemaSpecifier, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum ReplicaId { + System(u64), + User(u64), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterReplicaId { + pub cluster_id: ClusterId, + pub replica_id: ReplicaId, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum NetworkPolicyId { + System(u64), + User(u64), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ReplicaLogging { + pub log_logging: bool, + pub interval: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct OptimizerFeatureOverride { + pub name: String, + pub value: String, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterScheduleRefreshOptions { + pub rehydration_time_estimate: Duration, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum ClusterSchedule { + Manual, + Refresh(ClusterScheduleRefreshOptions), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterConfig { + pub workload_class: Option, + pub variant: ClusterVariant, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum ClusterVariant { + Unmanaged, + Managed(ManagedCluster), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ManagedCluster { + pub size: String, + pub replication_factor: u32, + pub availability_zones: Vec, + pub logging: ReplicaLogging, + pub arrangement_compression: bool, + pub optimizer_feature_overrides: Vec, + pub schedule: ClusterSchedule, + /// User-configured autoscaling policy, distinct from the in-flight runtime + /// records below. + pub auto_scaling_strategy: Option, + /// Latest graceful reconfiguration record, if one has been written. + pub reconfiguration: Option, + /// In-flight hydration burst the controller is running. + pub burst: Option, +} + +/// The user-configured autoscaling policy of a managed cluster. +/// +/// Extensible: future strategies are added as additional optional sub-policies. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct AutoScalingStrategy { + pub on_hydration: Option, +} + +/// The `ON HYDRATION` autoscaling sub-policy. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct OnHydration { + pub hydration_size: String, + pub linger_duration: Option, +} + +/// Latest graceful reconfiguration record, including the target shape, deadline, +/// timeout action, and lifecycle status. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ReconfigurationState { + pub target: ReconfigurationTarget, + /// Deadline as an `mz_repr::Timestamp`. + pub deadline: u64, + /// What to do if the deadline passes before the target hydrates. + pub on_timeout: OnTimeoutAction, + pub status: ReconfigurationStatus, +} + +/// The lifecycle status of the latest graceful reconfiguration. +/// +/// NOTE: the serde serialization of these variant names is what the +/// `mz_internal.mz_cluster_reconfigurations` builtin view matches on. When +/// adding a variant, extend that view's `status` CASE mapping, or the new +/// variant surfaces verbatim (`SomeNewStatus`) instead of snake_case. +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum ReconfigurationStatus { + InProgress, + Finalized, + TimedOut, + Cancelled, + ResourceExhausted, +} + +/// The action a graceful reconfiguration applies if its deadline passes before +/// the target replicas hydrate. Mirrors `mz_sql::plan::OnTimeoutAction`. +/// +/// NOTE: like `ReconfigurationStatus`, the serde variant names feed the +/// `on_timeout` CASE mapping in `mz_internal.mz_cluster_reconfigurations`. +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum OnTimeoutAction { + /// Cut over to the (not-yet-hydrated) target anyway. + Commit, + /// Drop the target replica set, reverting to the pre-reconfiguration shape. + Rollback, +} + +/// The full config shape a reconfiguration is moving the cluster to, so a +/// combined size + replication-factor + availability-zone change is one record. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ReconfigurationTarget { + pub size: String, + pub replication_factor: u32, + pub availability_zones: Vec, + pub logging: ReplicaLogging, + pub arrangement_compression: bool, +} + +/// An active hydration burst. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct BurstState { + pub burst_size: String, + pub linger_duration: Duration, + /// When the steady-state replicas were first observed hydrated, as an + /// `mz_repr::Timestamp`. Absent until that observation. + pub steady_hydrated_at: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ReplicaConfig { + pub logging: ReplicaLogging, + pub location: ReplicaLocation, + pub arrangement_compression: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct UnmanagedLocation { + pub storagectl_addrs: Vec, + pub computectl_addrs: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ManagedLocation { + pub size: String, + /// The availability zones the replica was provisioned under. + /// + /// For a replica of a managed cluster this is the cluster's + /// `AVAILABILITY ZONES` pool at provision time; the cluster controller + /// compares it against a cluster's target `availability_zones` to tell + /// realized- from target-shape replicas (including an `AVAILABILITY ZONES` + /// divergence). For a replica of an unmanaged cluster it is the user-pinned + /// `AVAILABILITY ZONE`, as a zero- or one-element list. Empty when no zones + /// constrain placement. + pub availability_zones: Vec, + pub internal: bool, + pub billed_as: Option, + pub pending: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum ReplicaLocation { + Unmanaged(UnmanagedLocation), + Managed(ManagedLocation), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum RoleId { + System(u64), + User(u64), + Public, + Predefined(u64), +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum AutoProvisionSource { + Oidc = 0, + Frontegg = 1, + None = 2, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct RoleAttributes { + pub inherit: bool, + pub superuser: Option, + pub login: Option, + pub auto_provision_source: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct RoleMembership { + pub map: Vec, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct RoleMembershipEntry { + pub key: RoleId, + pub value: RoleId, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct RoleVars { + pub entries: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct RoleVarsEntry { + pub key: String, + pub val: RoleVar, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum RoleVar { + Flat(String), + SqlSet(Vec), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct NetworkPolicyRule { + pub name: String, + pub address: String, + pub action: NetworkPolicyRuleAction, + pub direction: NetworkPolicyRuleDirection, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum NetworkPolicyRuleAction { + Allow, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub enum NetworkPolicyRuleDirection { + Ingress, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct AclMode { + pub bitflags: u64, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct MzAclItem { + pub grantee: RoleId, + pub grantor: RoleId, + pub acl_mode: AclMode, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct DefaultPrivilegesKey { + pub role_id: RoleId, + pub database_id: Option, + pub schema_id: Option, + pub object_type: ObjectType, + pub grantee: RoleId, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct DefaultPrivilegesValue { + pub privileges: AclMode, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SystemPrivilegesKey { + pub grantee: RoleId, + pub grantor: RoleId, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SystemPrivilegesValue { + pub acl_mode: AclMode, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct AuditLogEventV1 { + pub id: u64, + pub event_type: audit_log_event_v1::EventType, + pub object_type: audit_log_event_v1::ObjectType, + pub user: Option, + pub occurred_at: EpochMillis, + pub details: audit_log_event_v1::Details, +} + +pub mod audit_log_event_v1 { + use super::*; + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct IdFullNameV1 { + pub id: String, + pub name: FullNameV1, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct FullNameV1 { + pub database: String, + pub schema: String, + pub item: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct IdNameV1 { + pub id: String, + pub name: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct RenameClusterV1 { + pub id: String, + pub old_name: String, + pub new_name: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct RenameClusterReplicaV1 { + pub cluster_id: String, + pub replica_id: String, + pub old_name: String, + pub new_name: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct AlterClusterReconfigurationV1 { + pub cluster_id: String, + pub cluster_name: String, + pub transition: ReconfigurationLifecycleV1, + /// On a `finalized` transition: whether the cut-over was forced by `ON + /// TIMEOUT COMMIT` at the deadline rather than reached by hydration. + #[serde(default)] + pub forced: Option, + pub target_size: String, + pub target_replication_factor: u32, + pub target_availability_zones: Vec, + pub target_logging: ClusterReplicaLoggingV1, + pub deadline: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct ClusterReplicaLoggingV1 { + pub log_logging: bool, + pub interval: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct ReconfigurationLifecycleV1 { + pub transition: reconfiguration_lifecycle_v1::Transition, + } + + pub mod reconfiguration_lifecycle_v1 { + use super::*; + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub enum Transition { + Started(Empty), + Finalized(Empty), + TimedOut(Empty), + Cancelled(Empty), + ResourceExhausted(Empty), + } + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct ClusterHydrationBurstV1 { + pub cluster_id: String, + pub cluster_name: String, + pub transition: HydrationBurstLifecycleV1, + /// On a `finished` transition: why the burst tore down. + #[serde(default)] + pub finish_cause: Option, + pub burst_size: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct HydrationBurstLifecycleV1 { + pub transition: hydration_burst_lifecycle_v1::Transition, + } + + pub mod hydration_burst_lifecycle_v1 { + use super::*; + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub enum Transition { + Started(Empty), + Finished(Empty), + } + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct BurstFinishCauseV1 { + pub cause: burst_finish_cause_v1::Cause, + } + + pub mod burst_finish_cause_v1 { + use super::*; + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub enum Cause { + LingerElapsed(Empty), + NoLongerWarranted(Empty), + } + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct RenameItemV1 { + pub id: String, + pub old_name: FullNameV1, + pub new_name: FullNameV1, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateClusterReplicaV1 { + pub cluster_id: String, + pub cluster_name: String, + pub replica_id: Option, + pub replica_name: String, + pub logical_size: String, + pub disk: bool, + pub billed_as: Option, + pub internal: bool, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateClusterReplicaV2 { + pub cluster_id: String, + pub cluster_name: String, + pub replica_id: Option, + pub replica_name: String, + pub logical_size: String, + pub disk: bool, + pub billed_as: Option, + pub internal: bool, + pub reason: CreateOrDropClusterReplicaReasonV1, + pub scheduling_policies: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateClusterReplicaV3 { + pub cluster_id: String, + pub cluster_name: String, + pub replica_id: Option, + pub replica_name: String, + pub logical_size: String, + pub disk: bool, + pub billed_as: Option, + pub internal: bool, + pub reason: CreateOrDropClusterReplicaReasonV1, + pub scheduling_policies: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateClusterReplicaV4 { + pub cluster_id: String, + pub cluster_name: String, + pub replica_id: Option, + pub replica_name: String, + pub logical_size: String, + pub billed_as: Option, + pub internal: bool, + pub reason: CreateOrDropClusterReplicaReasonV1, + pub scheduling_policies: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct DropClusterReplicaV1 { + pub cluster_id: String, + pub cluster_name: String, + pub replica_id: Option, + pub replica_name: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct DropClusterReplicaV2 { + pub cluster_id: String, + pub cluster_name: String, + pub replica_id: Option, + pub replica_name: String, + pub reason: CreateOrDropClusterReplicaReasonV1, + pub scheduling_policies: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct DropClusterReplicaV3 { + pub cluster_id: String, + pub cluster_name: String, + pub replica_id: Option, + pub replica_name: String, + pub reason: CreateOrDropClusterReplicaReasonV1, + pub scheduling_policies: Option, + } + + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize + )] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateOrDropClusterReplicaReasonV1 { + pub reason: CreateOrDropClusterReplicaReasonV1Reason, + } + + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize + )] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub enum CreateOrDropClusterReplicaReasonV1Reason { + Manual(Empty), + Schedule(Empty), + System(Empty), + Reconfiguration(Empty), + HydrationBurst(Empty), + Retired(Empty), + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct SchedulingDecisionsWithReasonsV1 { + pub on_refresh: RefreshDecisionWithReasonV1, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct SchedulingDecisionsWithReasonsV2 { + pub on_refresh: RefreshDecisionWithReasonV2, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub enum RefreshDecision { + On(Empty), + Off(Empty), + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct RefreshDecisionWithReasonV1 { + pub objects_needing_refresh: Vec, + pub rehydration_time_estimate: String, + pub decision: RefreshDecision, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct RefreshDecisionWithReasonV2 { + pub objects_needing_refresh: Vec, + pub objects_needing_compaction: Vec, + pub rehydration_time_estimate: String, + pub decision: RefreshDecision, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateSourceSinkV1 { + pub id: String, + pub name: FullNameV1, + pub size: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateSourceSinkV2 { + pub id: String, + pub name: FullNameV1, + pub size: Option, + pub external_type: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateSourceSinkV3 { + pub id: String, + pub name: FullNameV1, + pub external_type: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateSourceSinkV4 { + pub id: String, + pub cluster_id: Option, + pub name: FullNameV1, + pub external_type: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateIndexV1 { + pub id: String, + pub cluster_id: String, + pub name: FullNameV1, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateMaterializedViewV1 { + pub id: String, + pub cluster_id: String, + pub name: FullNameV1, + pub replacement_target_id: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct AlterApplyReplacementV1 { + pub target: IdFullNameV1, + pub replacement: IdFullNameV1, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct AlterSourceSinkV1 { + pub id: String, + pub name: FullNameV1, + pub old_size: Option, + pub new_size: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct AlterSetClusterV1 { + pub id: String, + pub name: FullNameV1, + pub old_cluster_id: String, + pub new_cluster_id: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct GrantRoleV1 { + pub role_id: String, + pub member_id: String, + pub grantor_id: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct GrantRoleV2 { + pub role_id: String, + pub member_id: String, + pub grantor_id: String, + pub executed_by: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct RevokeRoleV1 { + pub role_id: String, + pub member_id: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct RevokeRoleV2 { + pub role_id: String, + pub member_id: String, + pub grantor_id: String, + pub executed_by: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct UpdatePrivilegeV1 { + pub object_id: String, + pub grantee_id: String, + pub grantor_id: String, + pub privileges: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct AlterDefaultPrivilegeV1 { + pub role_id: String, + pub database_id: Option, + pub schema_id: Option, + pub grantee_id: String, + pub privileges: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct UpdateOwnerV1 { + pub object_id: String, + pub old_owner_id: String, + pub new_owner_id: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct SchemaV1 { + pub id: String, + pub name: String, + pub database_name: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct SchemaV2 { + pub id: String, + pub name: String, + pub database_name: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct RenameSchemaV1 { + pub id: String, + pub database_name: Option, + pub old_name: String, + pub new_name: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct UpdateItemV1 { + pub id: String, + pub name: FullNameV1, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct AlterRetainHistoryV1 { + pub id: String, + pub old_history: Option, + pub new_history: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct AlterAddColumnV1 { + pub id: String, + pub column: String, + pub column_type: String, + pub nullable: bool, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct AlterSourceTimestampIntervalV1 { + pub id: String, + pub old_interval: Option, + pub new_interval: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct ToNewIdV1 { + pub id: String, + pub new_id: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct FromPreviousIdV1 { + pub id: String, + pub previous_id: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct SetV1 { + pub name: String, + pub value: Option, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct RotateKeysV1 { + pub id: String, + pub name: String, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub struct CreateRoleV1 { + pub id: String, + pub name: String, + pub auto_provision_source: Option, + } + + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + Serialize_repr, + Deserialize_repr + )] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + #[repr(u8)] + pub enum EventType { + Unknown = 0, + Create = 1, + Drop = 2, + Alter = 3, + Grant = 4, + Revoke = 5, + Comment = 6, + } + + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + Serialize_repr, + Deserialize_repr + )] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + #[repr(u8)] + pub enum ObjectType { + Unknown = 0, + Cluster = 1, + ClusterReplica = 2, + Connection = 3, + Database = 4, + Func = 5, + Index = 6, + MaterializedView = 7, + Role = 8, + Secret = 9, + Schema = 10, + Sink = 11, + Source = 12, + Table = 13, + Type = 14, + View = 15, + System = 16, + ContinualTask = 17, + NetworkPolicy = 18, + MetricSink = 19, + } + + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] + #[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] + pub enum Details { + CreateClusterReplicaV1(CreateClusterReplicaV1), + CreateClusterReplicaV2(CreateClusterReplicaV2), + CreateClusterReplicaV3(CreateClusterReplicaV3), + CreateClusterReplicaV4(CreateClusterReplicaV4), + DropClusterReplicaV1(DropClusterReplicaV1), + DropClusterReplicaV2(DropClusterReplicaV2), + DropClusterReplicaV3(DropClusterReplicaV3), + CreateSourceSinkV1(CreateSourceSinkV1), + CreateSourceSinkV2(CreateSourceSinkV2), + AlterSourceSinkV1(AlterSourceSinkV1), + AlterSetClusterV1(AlterSetClusterV1), + GrantRoleV1(GrantRoleV1), + GrantRoleV2(GrantRoleV2), + RevokeRoleV1(RevokeRoleV1), + RevokeRoleV2(RevokeRoleV2), + UpdatePrivilegeV1(UpdatePrivilegeV1), + AlterDefaultPrivilegeV1(AlterDefaultPrivilegeV1), + UpdateOwnerV1(UpdateOwnerV1), + IdFullNameV1(IdFullNameV1), + RenameClusterV1(RenameClusterV1), + RenameClusterReplicaV1(RenameClusterReplicaV1), + RenameItemV1(RenameItemV1), + IdNameV1(IdNameV1), + SchemaV1(SchemaV1), + SchemaV2(SchemaV2), + RenameSchemaV1(RenameSchemaV1), + UpdateItemV1(UpdateItemV1), + CreateSourceSinkV3(CreateSourceSinkV3), + AlterRetainHistoryV1(AlterRetainHistoryV1), + ToNewIdV1(ToNewIdV1), + FromPreviousIdV1(FromPreviousIdV1), + SetV1(SetV1), + ResetAllV1(Empty), + RotateKeysV1(RotateKeysV1), + CreateSourceSinkV4(CreateSourceSinkV4), + CreateIndexV1(CreateIndexV1), + CreateMaterializedViewV1(CreateMaterializedViewV1), + AlterApplyReplacementV1(AlterApplyReplacementV1), + CreateRoleV1(CreateRoleV1), + AlterAddColumnV1(AlterAddColumnV1), + AlterSourceTimestampIntervalV1(AlterSourceTimestampIntervalV1), + AlterClusterReconfigurationV1(AlterClusterReconfigurationV1), + ClusterHydrationBurstV1(ClusterHydrationBurstV1), + } +} + +/// The contents of a single state update. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +// Serialize the top-level enum in the persist-backed catalog as internally tagged to set up +// persist pushdown statistics for success. +#[serde(tag = "kind")] +pub enum StateUpdateKind { + AuditLog(AuditLog), + Cluster(Cluster), + ClusterIntrospectionSourceIndex(ClusterIntrospectionSourceIndex), + ClusterReplica(ClusterReplica), + Comment(Comment), + Config(Config), + Database(Database), + DefaultPrivileges(DefaultPrivileges), + FenceToken(FenceToken), + GidMapping(GidMapping), + IdAlloc(IdAlloc), + Item(Item), + NetworkPolicy(NetworkPolicy), + Role(Role), + RoleAuth(RoleAuth), + Schema(Schema), + ServerConfiguration(ServerConfiguration), + ClusterSystemConfiguration(ClusterSystemConfiguration), + ReplicaSystemConfiguration(ReplicaSystemConfiguration), + Setting(Setting), + SourceReferences(SourceReferences), + CollectionCompactionBound(CollectionCompactionBound), + MaintainedReadRequirement(MaintainedReadRequirement), + StorageCollectionMetadata(StorageCollectionMetadata), + SystemPrivileges(SystemPrivileges), + TxnWalShard(TxnWalShard), + UnfinalizedShard(UnfinalizedShard), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct AuditLog { + pub key: AuditLogKey, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Cluster { + pub key: ClusterKey, + pub value: ClusterValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterReplica { + pub key: ClusterReplicaKey, + pub value: ClusterReplicaValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Comment { + pub key: CommentKey, + pub value: CommentValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Config { + pub key: ConfigKey, + pub value: ConfigValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Database { + pub key: DatabaseKey, + pub value: DatabaseValue, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct DefaultPrivileges { + pub key: DefaultPrivilegesKey, + pub value: DefaultPrivilegesValue, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct FenceToken { + pub deploy_generation: u64, + pub epoch: i64, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct IdAlloc { + pub key: IdAllocKey, + pub value: IdAllocValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterIntrospectionSourceIndex { + pub key: ClusterIntrospectionSourceIndexKey, + pub value: ClusterIntrospectionSourceIndexValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Item { + pub key: ItemKey, + pub value: ItemValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Role { + pub key: RoleKey, + pub value: RoleValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct RoleAuth { + pub key: RoleAuthKey, + pub value: RoleAuthValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct NetworkPolicy { + pub key: NetworkPolicyKey, + pub value: NetworkPolicyValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Schema { + pub key: SchemaKey, + pub value: SchemaValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct Setting { + pub key: SettingKey, + pub value: SettingValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ServerConfiguration { + pub key: ServerConfigurationKey, + pub value: ServerConfigurationValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ClusterSystemConfiguration { + pub key: ClusterSystemConfigurationKey, + pub value: ClusterSystemConfigurationValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct ReplicaSystemConfiguration { + pub key: ReplicaSystemConfigurationKey, + pub value: ReplicaSystemConfigurationValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SourceReferences { + pub key: SourceReferencesKey, + pub value: SourceReferencesValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct GidMapping { + pub key: GidMappingKey, + pub value: GidMappingValue, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct SystemPrivileges { + pub key: SystemPrivilegesKey, + pub value: SystemPrivilegesValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct StorageCollectionMetadata { + pub key: StorageCollectionMetadataKey, + pub value: StorageCollectionMetadataValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct CollectionCompactionBound { + pub key: CollectionCompactionBoundKey, + pub value: CollectionCompactionBoundValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct MaintainedReadRequirement { + pub key: MaintainedReadRequirementKey, + pub value: MaintainedReadRequirementValue, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct UnfinalizedShard { + pub key: UnfinalizedShardKey, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +pub struct TxnWalShard { + pub value: TxnWalShardValue, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + Serialize_repr, + Deserialize_repr +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +#[repr(u8)] +pub enum CatalogItemType { + Unknown = 0, + Table = 1, + Source = 2, + Sink = 3, + View = 4, + MaterializedView = 5, + Index = 6, + Type = 7, + Func = 8, + Secret = 9, + Connection = 10, + MetricSink = 11, +} + +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + Serialize_repr, + Deserialize_repr +)] +#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))] +#[repr(u8)] +pub enum ObjectType { + Unknown = 0, + Table = 1, + View = 2, + MaterializedView = 3, + Source = 4, + Sink = 5, + Index = 6, + Type = 7, + Role = 8, + Cluster = 9, + ClusterReplica = 10, + Secret = 11, + Connection = 12, + Database = 13, + Schema = 14, + Func = 15, + NetworkPolicy = 17, + MetricSink = 18, +} diff --git a/src/catalog/src/durable/debug.rs b/src/catalog/src/durable/debug.rs index 3330c80e5289e..bfafdbb0cb79d 100644 --- a/src/catalog/src/durable/debug.rs +++ b/src/catalog/src/durable/debug.rs @@ -74,6 +74,8 @@ pub enum CollectionType { ReplicaSystemConfiguration, SystemGidMapping, SystemPrivileges, + CollectionCompactionBound, + MaintainedReadRequirement, StorageCollectionMetadata, UnfinalizedShard, TxnWalShard, @@ -291,6 +293,22 @@ collection_impl!({ update: StateUpdateKind::SystemPrivilege, }); +collection_impl!({ + name: CollectionCompactionBoundCollection, + key: proto::CollectionCompactionBoundKey, + value: proto::CollectionCompactionBoundValue, + collection_type: CollectionType::CollectionCompactionBound, + trace_field: collection_compaction_bounds, + update: StateUpdateKind::CollectionCompactionBound, +}); +collection_impl!({ + name: MaintainedReadRequirementCollection, + key: proto::MaintainedReadRequirementKey, + value: proto::MaintainedReadRequirementValue, + collection_type: CollectionType::MaintainedReadRequirement, + trace_field: maintained_read_requirements, + update: StateUpdateKind::MaintainedReadRequirement, +}); collection_impl!({ name: StorageCollectionMetadataCollection, key: proto::StorageCollectionMetadataKey, @@ -367,6 +385,8 @@ pub struct Trace { pub cluster_system_configurations: CollectionTrace, pub replica_system_configurations: CollectionTrace, pub system_privileges: CollectionTrace, + pub collection_compaction_bounds: CollectionTrace, + pub maintained_read_requirements: CollectionTrace, pub storage_collection_metadata: CollectionTrace, pub unfinalized_shards: CollectionTrace, pub txn_wal_shard: CollectionTrace, @@ -396,6 +416,8 @@ impl Trace { cluster_system_configurations: CollectionTrace::new(), replica_system_configurations: CollectionTrace::new(), system_privileges: CollectionTrace::new(), + collection_compaction_bounds: CollectionTrace::new(), + maintained_read_requirements: CollectionTrace::new(), storage_collection_metadata: CollectionTrace::new(), unfinalized_shards: CollectionTrace::new(), txn_wal_shard: CollectionTrace::new(), @@ -425,6 +447,8 @@ impl Trace { cluster_system_configurations, replica_system_configurations, system_privileges, + collection_compaction_bounds, + maintained_read_requirements, storage_collection_metadata, unfinalized_shards, txn_wal_shard, @@ -450,6 +474,8 @@ impl Trace { cluster_system_configurations.sort(); replica_system_configurations.sort(); system_privileges.sort(); + collection_compaction_bounds.sort(); + maintained_read_requirements.sort(); storage_collection_metadata.sort(); unfinalized_shards.sort(); txn_wal_shard.sort(); diff --git a/src/catalog/src/durable/error.rs b/src/catalog/src/durable/error.rs index f2a9cc3dadb07..3ae0144d2f640 100644 --- a/src/catalog/src/durable/error.rs +++ b/src/catalog/src/durable/error.rs @@ -71,6 +71,9 @@ pub enum DurableCatalogError { /// A dry-run transaction reached a commit path. #[error("cannot commit a dry-run catalog transaction")] DryRunTransaction, + /// The proposed transaction violates durable read protection. + #[error("invalid read protection: {0}")] + InvalidReadProtection(String), /// Unable to serialize/deserialize Protobuf message. #[error("proto: {0}")] Proto(TryFromProtoError), diff --git a/src/catalog/src/durable/objects.rs b/src/catalog/src/durable/objects.rs index 2c0c2487d2d38..607f1e59fe3a4 100644 --- a/src/catalog/src/durable/objects.rs +++ b/src/catalog/src/durable/objects.rs @@ -29,7 +29,7 @@ pub mod serialization; pub(crate) mod state_update; use std::cmp::Ordering; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::time::Duration; use mz_audit_log::VersionedEvent; @@ -39,7 +39,7 @@ use mz_persist_types::ShardId; use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem}; use mz_repr::network_policy_id::NetworkPolicyId; use mz_repr::role_id::RoleId; -use mz_repr::{CatalogItemId, GlobalId, RelationVersion}; +use mz_repr::{CatalogItemId, GlobalId, RelationVersion, Timestamp}; use mz_sql::catalog::{ CatalogItemType, DefaultPrivilegeAclItem, DefaultPrivilegeObject, ObjectType, RoleAttributes, RoleMembership, RoleVars, @@ -1302,6 +1302,75 @@ impl DurableType for StorageCollectionMetadata { } } +/// A collection compaction bound. `frontier: None` is the empty/top frontier. +/// A missing record leaves the collection ungoverned. +#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)] +pub struct CollectionCompactionBound { + pub id: GlobalId, + pub frontier: Option, +} + +impl DurableType for CollectionCompactionBound { + type Key = CollectionCompactionBoundKey; + type Value = CollectionCompactionBoundValue; + + fn into_key_value(self) -> (Self::Key, Self::Value) { + ( + CollectionCompactionBoundKey { id: self.id }, + CollectionCompactionBoundValue { + frontier: self.frontier, + }, + ) + } + + fn from_key_value(key: Self::Key, value: Self::Value) -> Self { + Self { + id: key.id, + frontier: value.frontier, + } + } + + fn key(&self) -> Self::Key { + CollectionCompactionBoundKey { id: self.id } + } +} + +/// Remaining reads of a maintained output. `frontier: None` means no remaining reads. +#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)] +pub struct MaintainedReadRequirement { + pub id: GlobalId, + /// Logical collection inputs, including reads eliminated by optimization. + pub inputs: BTreeSet, + pub frontier: Option, +} + +impl DurableType for MaintainedReadRequirement { + type Key = MaintainedReadRequirementKey; + type Value = MaintainedReadRequirementValue; + + fn into_key_value(self) -> (Self::Key, Self::Value) { + ( + MaintainedReadRequirementKey { id: self.id }, + MaintainedReadRequirementValue { + inputs: self.inputs, + frontier: self.frontier, + }, + ) + } + + fn from_key_value(key: Self::Key, value: Self::Value) -> Self { + Self { + id: key.id, + inputs: value.inputs, + frontier: value.frontier, + } + } + + fn key(&self) -> Self::Key { + MaintainedReadRequirementKey { id: self.id } + } +} + #[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)] pub struct UnfinalizedShard { pub shard: ShardId, @@ -1359,6 +1428,10 @@ pub struct Snapshot { pub system_privileges: BTreeMap, pub storage_collection_metadata: BTreeMap, + pub collection_compaction_bounds: + BTreeMap, + pub maintained_read_requirements: + BTreeMap, pub unfinalized_shards: BTreeMap, pub txn_wal_shard: BTreeMap<(), proto::TxnWalShardValue>, } @@ -1628,6 +1701,27 @@ pub struct AuditLogKey { pub(crate) event: VersionedEvent, } +#[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)] +pub struct CollectionCompactionBoundKey { + pub(crate) id: GlobalId, +} + +#[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Ord)] +pub struct CollectionCompactionBoundValue { + pub(crate) frontier: Option, +} + +#[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)] +pub struct MaintainedReadRequirementKey { + pub(crate) id: GlobalId, +} + +#[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Ord)] +pub struct MaintainedReadRequirementValue { + pub(crate) inputs: BTreeSet, + pub(crate) frontier: Option, +} + #[derive(Debug, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)] pub struct StorageCollectionMetadataKey { pub(crate) id: GlobalId, diff --git a/src/catalog/src/durable/objects/serialization.rs b/src/catalog/src/durable/objects/serialization.rs index d735d1999cba2..e537f768e96ca 100644 --- a/src/catalog/src/durable/objects/serialization.rs +++ b/src/catalog/src/durable/objects/serialization.rs @@ -16,14 +16,15 @@ use crate::durable::objects::state_update::StateUpdateKindJson; use crate::durable::objects::{ AuditLogKey, ClusterIntrospectionSourceIndexKey, ClusterIntrospectionSourceIndexValue, ClusterKey, ClusterReplicaKey, ClusterReplicaValue, ClusterSystemConfigurationKey, - ClusterSystemConfigurationValue, ClusterValue, CommentKey, CommentValue, ConfigKey, - ConfigValue, DatabaseKey, DatabaseValue, DefaultPrivilegesKey, DefaultPrivilegesValue, - GidMappingKey, GidMappingValue, IdAllocKey, IdAllocValue, - IntrospectionSourceIndexCatalogItemId, IntrospectionSourceIndexGlobalId, ItemKey, ItemValue, - NetworkPolicyKey, NetworkPolicyValue, ReplicaSystemConfigurationKey, - ReplicaSystemConfigurationValue, RoleKey, RoleValue, SchemaKey, SchemaValue, - ServerConfigurationKey, ServerConfigurationValue, SettingKey, SettingValue, SourceReference, - SourceReferencesKey, SourceReferencesValue, StorageCollectionMetadataKey, + ClusterSystemConfigurationValue, ClusterValue, CollectionCompactionBoundKey, + CollectionCompactionBoundValue, CommentKey, CommentValue, ConfigKey, ConfigValue, DatabaseKey, + DatabaseValue, DefaultPrivilegesKey, DefaultPrivilegesValue, GidMappingKey, GidMappingValue, + IdAllocKey, IdAllocValue, IntrospectionSourceIndexCatalogItemId, + IntrospectionSourceIndexGlobalId, ItemKey, ItemValue, MaintainedReadRequirementKey, + MaintainedReadRequirementValue, NetworkPolicyKey, NetworkPolicyValue, + ReplicaSystemConfigurationKey, ReplicaSystemConfigurationValue, RoleKey, RoleValue, SchemaKey, + SchemaValue, ServerConfigurationKey, ServerConfigurationValue, SettingKey, SettingValue, + SourceReference, SourceReferencesKey, SourceReferencesValue, StorageCollectionMetadataKey, StorageCollectionMetadataValue, SystemCatalogItemId, SystemGlobalId, SystemPrivilegesKey, SystemPrivilegesValue, TxnWalShardValue, UnfinalizedShardKey, }; @@ -774,6 +775,64 @@ impl RustType for AuditLogKey { } } +impl RustType for CollectionCompactionBoundKey { + fn into_proto(&self) -> proto::CollectionCompactionBoundKey { + proto::CollectionCompactionBoundKey { + id: self.id.into_proto(), + } + } + + fn from_proto(proto: proto::CollectionCompactionBoundKey) -> Result { + Ok(Self { + id: proto.id.into_rust()?, + }) + } +} + +impl RustType for CollectionCompactionBoundValue { + fn into_proto(&self) -> proto::CollectionCompactionBoundValue { + proto::CollectionCompactionBoundValue { + frontier: self.frontier.map(u64::from), + } + } + + fn from_proto(proto: proto::CollectionCompactionBoundValue) -> Result { + Ok(Self { + frontier: proto.frontier.map(mz_repr::Timestamp::new), + }) + } +} + +impl RustType for MaintainedReadRequirementKey { + fn into_proto(&self) -> proto::MaintainedReadRequirementKey { + proto::MaintainedReadRequirementKey { + id: self.id.into_proto(), + } + } + + fn from_proto(proto: proto::MaintainedReadRequirementKey) -> Result { + Ok(Self { + id: proto.id.into_rust()?, + }) + } +} + +impl RustType for MaintainedReadRequirementValue { + fn into_proto(&self) -> proto::MaintainedReadRequirementValue { + proto::MaintainedReadRequirementValue { + inputs: self.inputs.into_proto(), + frontier: self.frontier.map(u64::from), + } + } + + fn from_proto(proto: proto::MaintainedReadRequirementValue) -> Result { + Ok(Self { + inputs: proto.inputs.into_rust()?, + frontier: proto.frontier.map(mz_repr::Timestamp::new), + }) + } +} + impl RustType for StorageCollectionMetadataKey { fn into_proto(&self) -> proto::StorageCollectionMetadataKey { proto::StorageCollectionMetadataKey { diff --git a/src/catalog/src/durable/objects/state_update.rs b/src/catalog/src/durable/objects/state_update.rs index d53783d724350..fd50063049475 100644 --- a/src/catalog/src/durable/objects/state_update.rs +++ b/src/catalog/src/durable/objects/state_update.rs @@ -152,6 +152,8 @@ impl StateUpdate { replica_system_configurations, default_privileges, system_privileges, + collection_compaction_bounds, + maintained_read_requirements, storage_collection_metadata, unfinalized_shards, txn_wal_shard, @@ -190,6 +192,14 @@ impl StateUpdate { let default_privileges = from_batch(default_privileges, StateUpdateKind::DefaultPrivilege); let source_references = from_batch(source_references, StateUpdateKind::SourceReferences); let system_privileges = from_batch(system_privileges, StateUpdateKind::SystemPrivilege); + let collection_compaction_bounds = from_batch( + collection_compaction_bounds, + StateUpdateKind::CollectionCompactionBound, + ); + let maintained_read_requirements = from_batch( + maintained_read_requirements, + StateUpdateKind::MaintainedReadRequirement, + ); let storage_collection_metadata = from_batch( storage_collection_metadata, StateUpdateKind::StorageCollectionMetadata, @@ -218,6 +228,8 @@ impl StateUpdate { .chain(replica_system_configurations) .chain(default_privileges) .chain(system_privileges) + .chain(collection_compaction_bounds) + .chain(maintained_read_requirements) .chain(storage_collection_metadata) .chain(unfinalized_shards) .chain(txn_wal_shard) @@ -266,6 +278,14 @@ pub enum StateUpdateKind { ), SystemObjectMapping(proto::GidMappingKey, proto::GidMappingValue), SystemPrivilege(proto::SystemPrivilegesKey, proto::SystemPrivilegesValue), + CollectionCompactionBound( + proto::CollectionCompactionBoundKey, + proto::CollectionCompactionBoundValue, + ), + MaintainedReadRequirement( + proto::MaintainedReadRequirementKey, + proto::MaintainedReadRequirementValue, + ), StorageCollectionMetadata( proto::StorageCollectionMetadataKey, proto::StorageCollectionMetadataValue, @@ -305,6 +325,12 @@ impl StateUpdateKind { } StateUpdateKind::SystemObjectMapping(_, _) => Some(CollectionType::SystemGidMapping), StateUpdateKind::SystemPrivilege(_, _) => Some(CollectionType::SystemPrivileges), + StateUpdateKind::CollectionCompactionBound(_, _) => { + Some(CollectionType::CollectionCompactionBound) + } + StateUpdateKind::MaintainedReadRequirement(_, _) => { + Some(CollectionType::MaintainedReadRequirement) + } StateUpdateKind::StorageCollectionMetadata(_, _) => { Some(CollectionType::StorageCollectionMetadata) } @@ -513,6 +539,18 @@ impl TryFrom<&StateUpdateKind> for Option { source_references, )) } + StateUpdateKind::CollectionCompactionBound(key, value) => { + let collection_compaction_bounds = into_durable(key, value)?; + Some(memory::objects::StateUpdateKind::CollectionCompactionBound( + collection_compaction_bounds, + )) + } + StateUpdateKind::MaintainedReadRequirement(key, value) => { + let maintained_read_requirements = into_durable(key, value)?; + Some(memory::objects::StateUpdateKind::MaintainedReadRequirement( + maintained_read_requirements, + )) + } StateUpdateKind::StorageCollectionMetadata(key, value) => { let storage_collection_metadata = into_durable(key, value)?; Some(memory::objects::StateUpdateKind::StorageCollectionMetadata( @@ -695,6 +733,16 @@ impl RustType for StateUpdateKind { StateUpdateKind::SystemPrivilege(key, value) => { proto::StateUpdateKind::SystemPrivileges(proto::SystemPrivileges { key, value }) } + StateUpdateKind::CollectionCompactionBound(key, value) => { + proto::StateUpdateKind::CollectionCompactionBound( + proto::CollectionCompactionBound { key, value }, + ) + } + StateUpdateKind::MaintainedReadRequirement(key, value) => { + proto::StateUpdateKind::MaintainedReadRequirement( + proto::MaintainedReadRequirement { key, value }, + ) + } StateUpdateKind::StorageCollectionMetadata(key, value) => { proto::StateUpdateKind::StorageCollectionMetadata( proto::StorageCollectionMetadata { key, value }, @@ -778,6 +826,12 @@ impl RustType for StateUpdateKind { proto::StateUpdateKind::SystemPrivileges(proto::SystemPrivileges { key, value }) => { StateUpdateKind::SystemPrivilege(key, value) } + proto::StateUpdateKind::CollectionCompactionBound( + proto::CollectionCompactionBound { key, value }, + ) => StateUpdateKind::CollectionCompactionBound(key, value), + proto::StateUpdateKind::MaintainedReadRequirement( + proto::MaintainedReadRequirement { key, value }, + ) => StateUpdateKind::MaintainedReadRequirement(key, value), proto::StateUpdateKind::StorageCollectionMetadata( proto::StorageCollectionMetadata { key, value }, ) => StateUpdateKind::StorageCollectionMetadata(key, value), diff --git a/src/catalog/src/durable/persist.rs b/src/catalog/src/durable/persist.rs index bd550ff1e44c1..5b825fd3cb78b 100644 --- a/src/catalog/src/durable/persist.rs +++ b/src/catalog/src/durable/persist.rs @@ -863,6 +863,12 @@ impl> PersistHandle { StateUpdateKind::SystemPrivilege(key, value) => { apply(&mut snapshot.system_privileges, key, value, diff); } + StateUpdateKind::CollectionCompactionBound(key, value) => { + apply(&mut snapshot.collection_compaction_bounds, key, value, diff); + } + StateUpdateKind::MaintainedReadRequirement(key, value) => { + apply(&mut snapshot.maintained_read_requirements, key, value, diff); + } StateUpdateKind::StorageCollectionMetadata(key, value) => { apply(&mut snapshot.storage_collection_metadata, key, value, diff); } @@ -2149,6 +2155,14 @@ impl Trace { StateUpdateKind::SystemPrivilege(k, v) => { trace.system_privileges.values.push(((k, v), ts, diff)) } + StateUpdateKind::CollectionCompactionBound(k, v) => trace + .collection_compaction_bounds + .values + .push(((k, v), ts, diff)), + StateUpdateKind::MaintainedReadRequirement(k, v) => trace + .maintained_read_requirements + .values + .push(((k, v), ts, diff)), StateUpdateKind::StorageCollectionMetadata(k, v) => trace .storage_collection_metadata .values diff --git a/src/catalog/src/durable/transaction.rs b/src/catalog/src/durable/transaction.rs index a83d5dd6def40..f2fd8c32149d4 100644 --- a/src/catalog/src/durable/transaction.rs +++ b/src/catalog/src/durable/transaction.rs @@ -52,16 +52,18 @@ use crate::durable::objects::{ AuditLogKey, Cluster, ClusterConfig, ClusterIntrospectionSourceIndexKey, ClusterIntrospectionSourceIndexValue, ClusterKey, ClusterReplica, ClusterReplicaKey, ClusterReplicaValue, ClusterSystemConfiguration, ClusterSystemConfigurationKey, - ClusterSystemConfigurationValue, ClusterValue, CommentKey, CommentValue, Config, ConfigKey, - ConfigValue, Database, DatabaseKey, DatabaseValue, DefaultPrivilegesKey, - DefaultPrivilegesValue, DurableType, GidMappingKey, GidMappingValue, IdAllocKey, IdAllocValue, - IntrospectionSourceIndex, Item, ItemKey, ItemValue, NetworkPolicyKey, NetworkPolicyValue, - ReplicaConfig, ReplicaSystemConfiguration, ReplicaSystemConfigurationKey, - ReplicaSystemConfigurationValue, Role, RoleKey, RoleValue, Schema, SchemaKey, SchemaValue, - ServerConfigurationKey, ServerConfigurationValue, SettingKey, SettingValue, SourceReference, - SourceReferencesKey, SourceReferencesValue, StorageCollectionMetadataKey, - StorageCollectionMetadataValue, SystemObjectDescription, SystemObjectMapping, - SystemPrivilegesKey, SystemPrivilegesValue, TxnWalShardValue, UnfinalizedShardKey, + ClusterSystemConfigurationValue, ClusterValue, CollectionCompactionBoundKey, + CollectionCompactionBoundValue, CommentKey, CommentValue, Config, ConfigKey, ConfigValue, + Database, DatabaseKey, DatabaseValue, DefaultPrivilegesKey, DefaultPrivilegesValue, + DurableType, GidMappingKey, GidMappingValue, IdAllocKey, IdAllocValue, + IntrospectionSourceIndex, Item, ItemKey, ItemValue, MaintainedReadRequirementKey, + MaintainedReadRequirementValue, NetworkPolicyKey, NetworkPolicyValue, ReplicaConfig, + ReplicaSystemConfiguration, ReplicaSystemConfigurationKey, ReplicaSystemConfigurationValue, + Role, RoleKey, RoleValue, Schema, SchemaKey, SchemaValue, ServerConfigurationKey, + ServerConfigurationValue, SettingKey, SettingValue, SourceReference, SourceReferencesKey, + SourceReferencesValue, StorageCollectionMetadataKey, StorageCollectionMetadataValue, + SystemObjectDescription, SystemObjectMapping, SystemPrivilegesKey, SystemPrivilegesValue, + TxnWalShardValue, UnfinalizedShardKey, }; use crate::durable::{ AUDIT_LOG_ID_ALLOC_KEY, BUILTIN_MIGRATION_SHARD_KEY, CATALOG_CONTENT_VERSION_KEY, CatalogError, @@ -111,6 +113,10 @@ pub struct Transaction<'a> { network_policies: TableTransaction, storage_collection_metadata: TableTransaction, + collection_compaction_bounds: + TableTransaction, + maintained_read_requirements: + TableTransaction, unfinalized_shards: TableTransaction, txn_wal_shard: TableTransaction<(), TxnWalShardValue>, // Don't make this a table transaction so that it's not read into the @@ -175,6 +181,8 @@ impl<'a> Transaction<'a> { default_privileges, system_privileges, storage_collection_metadata, + collection_compaction_bounds, + maintained_read_requirements, unfinalized_shards, txn_wal_shard, }: Snapshot, @@ -262,6 +270,8 @@ impl<'a> Transaction<'a> { default_privileges: TableTransaction::new(default_privileges)?, system_privileges: TableTransaction::new(system_privileges)?, storage_collection_metadata: TableTransaction::new(storage_collection_metadata)?, + collection_compaction_bounds: TableTransaction::new(collection_compaction_bounds)?, + maintained_read_requirements: TableTransaction::new(maintained_read_requirements)?, unfinalized_shards: TableTransaction::new(unfinalized_shards)?, // Uniqueness violations for this value occur at the key rather than // the value (the key is the unit struct `()` so this is a singleton @@ -1201,6 +1211,8 @@ impl<'a> Transaction<'a> { source_references: self.source_references.current_items_proto(), system_privileges: self.system_privileges.current_items_proto(), storage_collection_metadata: self.storage_collection_metadata.current_items_proto(), + collection_compaction_bounds: self.collection_compaction_bounds.current_items_proto(), + maintained_read_requirements: self.maintained_read_requirements.current_items_proto(), unfinalized_shards: self.unfinalized_shards.current_items_proto(), txn_wal_shard: self.txn_wal_shard.current_items_proto(), } @@ -2576,6 +2588,8 @@ impl<'a> Transaction<'a> { system_privileges, audit_log_updates, storage_collection_metadata, + collection_compaction_bounds, + maintained_read_requirements, unfinalized_shards, // Not representable as a `StateUpdate`. id_allocator: _, @@ -2678,6 +2692,16 @@ impl<'a> Transaction<'a> { StateUpdateKind::StorageCollectionMetadata, self.op_id, )) + .chain(get_collection_op_updates( + collection_compaction_bounds, + StateUpdateKind::CollectionCompactionBound, + self.op_id, + )) + .chain(get_collection_op_updates( + maintained_read_requirements, + StateUpdateKind::MaintainedReadRequirement, + self.op_id, + )) .chain(get_collection_op_updates( unfinalized_shards, StateUpdateKind::UnfinalizedShard, @@ -2714,6 +2738,129 @@ impl<'a> Transaction<'a> { self.upper } + /// Stages a storage collection's compaction permission. + /// + /// A first bound must accompany collection birth. The caller must secure actual + /// readability at that bound, including when reusing a shard. Validation checks + /// committed permission and read requirements, not physical storage frontiers. + /// `None` denotes the empty frontier, not an ungoverned collection. + pub fn set_collection_compaction_bound( + &mut self, + id: GlobalId, + frontier: Option, + ) -> Result<(), CatalogError> { + self.collection_compaction_bounds.set( + CollectionCompactionBoundKey { id }, + Some(CollectionCompactionBoundValue { frontier }), + self.op_id, + )?; + Ok(()) + } + + /// Stages a maintained read requirement, checked against permission at commit. + /// + /// The caller supplies all logical inputs and may advance the required frontier + /// only when recovery no longer needs earlier history. `None` marks completion. + pub fn set_maintained_read_requirement( + &mut self, + id: GlobalId, + inputs: BTreeSet, + frontier: Option, + ) -> Result<(), CatalogError> { + self.maintained_read_requirements.set( + MaintainedReadRequirementKey { id }, + Some(MaintainedReadRequirementValue { inputs, frontier }), + self.op_id, + )?; + Ok(()) + } + + /// Validates the final storage-backed read protection state without committing it. + /// Call before entering a commit path that treats errors as fatal. + pub fn validate_read_protection(&self) -> Result<(), CatalogError> { + if self.storage_collection_metadata.pending.is_empty() + && self.collection_compaction_bounds.pending.is_empty() + && self.maintained_read_requirements.pending.is_empty() + { + return Ok(()); + } + let invalid = + |message| CatalogError::from(DurableCatalogError::InvalidReadProtection(message)); + let storage = self.storage_collection_metadata.items(); + let bounds = self.collection_compaction_bounds.items(); + let requirements = self.maintained_read_requirements.items(); + + // Compare against the initial snapshot, not the preceding setter, so a + // delete/reinsert of a surviving GlobalId cannot erase its bound history. + for (key, initial) in &self.collection_compaction_bounds.initial { + if !storage.contains_key(&StorageCollectionMetadataKey { id: key.id }) { + continue; + } + let Some(bound) = bounds.get(key) else { + return Err(invalid(format!( + "collection {} lost its compaction bound", + key.id + ))); + }; + let monotonic = match (initial.frontier, bound.frontier) { + (_, None) => true, + (Some(initial), Some(final_frontier)) => initial <= final_frontier, + (None, Some(_)) => false, + }; + if !monotonic { + return Err(invalid(format!( + "collection {} compaction bound regressed", + key.id + ))); + } + } + for key in bounds.keys() { + // An already-live ungoverned collection has no durable readability + // guarantee and storage cannot adopt a bound without conversion. + if self + .storage_collection_metadata + .initial + .contains_key(&StorageCollectionMetadataKey { id: key.id }) + && !self.collection_compaction_bounds.initial.contains_key(key) + { + return Err(invalid(format!( + "cannot introduce a compaction bound for existing collection {}", + key.id + ))); + } + if !storage.contains_key(&StorageCollectionMetadataKey { id: key.id }) { + return Err(invalid(format!( + "compaction bound owner {} has no storage metadata", + key.id + ))); + } + } + for (key, requirement) in requirements { + if !storage.contains_key(&StorageCollectionMetadataKey { id: key.id }) { + return Err(invalid(format!( + "read requirement owner {} has no storage metadata", + key.id + ))); + } + let Some(required) = requirement.frontier else { + continue; + }; + for input in &requirement.inputs { + let readable = bounds + .get(&CollectionCompactionBoundKey { id: *input }) + .and_then(|bound| bound.frontier) + .is_some_and(|bound| bound <= required); + if !readable { + return Err(invalid(format!( + "read requirement {} needs input {input} readable at {required}", + key.id + ))); + } + } + } + Ok(()) + } + fn ensure_committable(&self) -> Result<(), CatalogError> { match self.commit_capability { Some(_) => Ok(()), @@ -2731,6 +2878,7 @@ impl<'a> Transaction<'a> { pub(crate) fn into_parts( self, ) -> Result<(TransactionBatch, &'a mut dyn DurableCatalogState), CatalogError> { + self.validate_read_protection()?; let commit_capability = self .commit_capability .ok_or(DurableCatalogError::DryRunTransaction)?; @@ -2762,6 +2910,8 @@ impl<'a> Transaction<'a> { default_privileges: self.default_privileges.pending(), system_privileges: self.system_privileges.pending(), storage_collection_metadata: self.storage_collection_metadata.pending(), + collection_compaction_bounds: self.collection_compaction_bounds.pending(), + maintained_read_requirements: self.maintained_read_requirements.pending(), unfinalized_shards: self.unfinalized_shards.pending(), txn_wal_shard: self.txn_wal_shard.pending(), audit_log_updates, @@ -2773,8 +2923,9 @@ impl<'a> Transaction<'a> { /// Commits the storage transaction to durable storage. /// - /// [`DurableCatalogError::DryRunTransaction`] is a pre-effect - /// programming error that leaves durable state unchanged. Any other error + /// [`DurableCatalogError::DryRunTransaction`] and + /// [`DurableCatalogError::InvalidReadProtection`] leave durable state unchanged. + /// Read protection validation fails before any commit effects. Any other error /// outside read-only mode indicates the catalog may be in an indeterminate /// state and needs to be fully re-read before proceeding. In general, such /// errors must be fatal to the calling process. We do not panic/halt here so @@ -2815,6 +2966,8 @@ impl<'a> Transaction<'a> { default_privileges, system_privileges, storage_collection_metadata, + collection_compaction_bounds, + maintained_read_requirements, unfinalized_shards, txn_wal_shard, audit_log_updates, @@ -2844,6 +2997,8 @@ impl<'a> Transaction<'a> { differential_dataflow::consolidation::consolidate_updates(default_privileges); differential_dataflow::consolidation::consolidate_updates(system_privileges); differential_dataflow::consolidation::consolidate_updates(storage_collection_metadata); + differential_dataflow::consolidation::consolidate_updates(collection_compaction_bounds); + differential_dataflow::consolidation::consolidate_updates(maintained_read_requirements); differential_dataflow::consolidation::consolidate_updates(unfinalized_shards); differential_dataflow::consolidation::consolidate_updates(txn_wal_shard); differential_dataflow::consolidation::consolidate_updates(audit_log_updates); @@ -2856,8 +3011,9 @@ impl<'a> Transaction<'a> { /// Commits the storage transaction to durable storage. /// - /// [`DurableCatalogError::DryRunTransaction`] is a pre-effect - /// programming error that leaves durable state unchanged. Any other error + /// [`DurableCatalogError::DryRunTransaction`] and + /// [`DurableCatalogError::InvalidReadProtection`] leave durable state unchanged. + /// Read protection validation fails before any commit effects. Any other error /// outside read-only mode indicates the catalog may be in an indeterminate /// state and needs to be fully re-read before proceeding. In general, such /// errors must be fatal to the calling process. We do not panic/halt here so @@ -2951,6 +3107,16 @@ impl StorageTxn for Transaction<'_> { } fn delete_collection_metadata(&mut self, ids: BTreeSet) -> Vec<(GlobalId, ShardId)> { + self.collection_compaction_bounds.delete_by_keys( + ids.iter() + .map(|id| CollectionCompactionBoundKey { id: *id }), + self.op_id, + ); + self.maintained_read_requirements.delete_by_keys( + ids.iter() + .map(|id| MaintainedReadRequirementKey { id: *id }), + self.op_id, + ); let ks: Vec<_> = ids .into_iter() .map(|id| StorageCollectionMetadataKey { id }) @@ -3071,6 +3237,16 @@ pub struct TransactionBatch { proto::StorageCollectionMetadataValue, Diff, )>, + pub(crate) collection_compaction_bounds: Vec<( + proto::CollectionCompactionBoundKey, + proto::CollectionCompactionBoundValue, + Diff, + )>, + pub(crate) maintained_read_requirements: Vec<( + proto::MaintainedReadRequirementKey, + proto::MaintainedReadRequirementValue, + Diff, + )>, pub(crate) unfinalized_shards: Vec<(proto::UnfinalizedShardKey, (), Diff)>, pub(crate) txn_wal_shard: Vec<((), proto::TxnWalShardValue, Diff)>, pub(crate) audit_log_updates: Vec<(proto::AuditLogKey, (), Diff)>, @@ -3105,6 +3281,8 @@ impl TransactionBatch { default_privileges, system_privileges, storage_collection_metadata, + collection_compaction_bounds, + maintained_read_requirements, unfinalized_shards, txn_wal_shard, audit_log_updates, @@ -3132,6 +3310,8 @@ impl TransactionBatch { && default_privileges.is_empty() && system_privileges.is_empty() && storage_collection_metadata.is_empty() + && collection_compaction_bounds.is_empty() + && maintained_read_requirements.is_empty() && unfinalized_shards.is_empty() && txn_wal_shard.is_empty() && audit_log_updates.is_empty() @@ -3207,6 +3387,8 @@ mod unique_name { SettingValue, SourceReferencesValue, StorageCollectionMetadataValue, + CollectionCompactionBoundValue, + MaintainedReadRequirementValue, SystemPrivilegesValue, TxnWalShardValue, RoleAuthValue, diff --git a/src/catalog/src/durable/upgrade.rs b/src/catalog/src/durable/upgrade.rs index bb18a5980a3bd..27661aa1bd285 100644 --- a/src/catalog/src/durable/upgrade.rs +++ b/src/catalog/src/durable/upgrade.rs @@ -243,7 +243,7 @@ macro_rules! objects { objects!( [v74, v75, v76, v77, v78], [ - v79, v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92 + v79, v80, v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92, v93 ] ); @@ -275,6 +275,7 @@ mod v88_to_v89; mod v89_to_v90; mod v90_to_v91; mod v91_to_v92; +mod v92_to_v93; /// Describes a single action to take during a migration from `V1` to `V2`. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -506,6 +507,15 @@ async fn run_upgrade( ) .await } + 92 => { + run_versioned_upgrade( + unopened_catalog_state, + version, + commit_ts, + v92_to_v93::upgrade, + ) + .await + } // Up-to-date, no migration needed! CATALOG_VERSION => Ok((CATALOG_VERSION, commit_ts)), FUTURE_VERSION.. => Err(incompatible), diff --git a/src/catalog/src/durable/upgrade/snapshots/objects_v93.txt b/src/catalog/src/durable/upgrade/snapshots/objects_v93.txt new file mode 100644 index 0000000000000..4185fb0bd7b4e --- /dev/null +++ b/src/catalog/src/durable/upgrade/snapshots/objects_v93.txt @@ -0,0 +1,100 @@ +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/4KayVPCdkq/ikKEq8J+VtOK8lgo2QjTgqrgqJD068J+Jgi/grLNgOybgq4hgafCbhLIl1rTvvb1x4K2dXC0l0ajgsZpGYPCRhYBRCjVCMyXhr4bhqpJ8YNGo4KuQ4Z2yWCLCpSouXWUlJT3wkLClcyhzJuG+jTpPLlzwnqWQ8JCysQojQiF6yLov8J2MjiLCpT3wkaam77akP8KjTOCuk+CukMKlyLoKF0IV36klYHJJddGoS8OYUkPwq7SrOSEhCjpCOMO64LWPJypcR/Ctp7PwnoSiTEHwkbWo4LedXeqgjPCRtZAn8J2Sq3rwkYyK8JCphFxjcT4m4YmLCkBCPlkl4oK+4LCOffCeuqPqoLRg6p+VPXvDk+C7jnHwn5W08JGPkk0tLj98yLrgpopSY/CRir0mLvCQlpnwnp+tCiBCHiZK4KmeOmfwkYyM0ahUYFYv8J2Mv8i6PTR70agqJQo5QjfisrXRqCc9S++oge+/vTjig54iwr3OhiTwn4Od8J+VtF/gqLPgtqEvYOCzsSTwn6aEJ+GgjFw5Cj9CPeqfkfCRtInwkYu2IvCflbTCpfCRjILhiZ1g0aha4K+X4Z+gyLo7fj/hgIDwkKaqQHs6IvCRgbElyLrgp50KKEIm8JCAhMK1PyLwnL2U4LemISQkyLp7PMi6aeCwqsi6asi68J65vCIKH0IdYCbwnLOj8J2EhfCfqrBd4K2f4Kede+C0juqpmFsKJUIjOvCRjK1c8J6AguCqgnvwn5W08J+VtFIn0agqyLo86qGCIicKNEIy8JGPl/CeuqLvv73vu4ci4K+4bz9NdmJoKuCug/CQnYpqXXTwn5W08J64pzon77+9Oj0KP0I9ey7hs4JVw6HwnL2qL8OGOyQ/4reFTuGmjvCRjIPwnZS+WvCWqofwn5W08J6kg/CflbTwkpGjJCXwnYaIJAo5Qjfhqongt4QiLy7wnrmkSNGoJuCmj8K28J65ovCRtbXwkY2HRSowcTws6pq9XfCeuZ9KcT7goKkqChNCEfCQnoRd8JCApSI8ez08d8KlCjVCM/CQrpzwkLy+PSEmdfCRso3wnriQ4rai44eAOnHwsbOLXDbwnrmHVDzgq6g9TsK98J+VtApBQj/wkLWBL27qrIwnQ8O9bEbwn6u4QD3Iumwu8J6yr9Go77+9LiV78JCiri5R4b2s4b2NViT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yewoRQg/gvIw/XPCflbQ/77+91YgKK0Ip8JGOjiQ6Pzx74LaKJlsnQ+CrkDw3JFzwnrmkZE1oTmnwnrmRZPCbsoEKJ0IlJOCoisi6K+CxoCrwkbSIJ1cq0ahcOT8vei7wkYej6q2Q8J2fqgpAQj4u0agn8JGKiDkn4Z+p77+9PCXhpYpC8JGjgfChm4skwqXwkZqcfnDigoDwkYOzXkfwnruwRPCav7054KuIbQo2QjQq8J2UjSQ8w5DwkKC3w5bqrJQmJ+Cugy7wkaah8JGPl+GsrSrwkY+CQCHwkby6JjLvv70+CihCJsi68J2LqT9EP3sqaeGdpiYi6qeUW/CQkL/wn6q84KqIWfCQu4IuChBCDn464b64ZzTgrIrwkLOeCi5CLCRERC468J28i0jwkZmAaS7vrL4m8JGykibitoBc4YyUXGlxw5RB4KuhbyUvCjxCOvCfqoQl8JGGmCXRqEZiYMOZ8J+tke++iMi6dd+rw5Ba8J6Av3pvYGYm8J2CiWDirofwnZSuMWHCoS8KDEIK762D8JCDpTFvZApBQj9MyLrhoKXvv71L8JGji/CQnoTgqKHCpfCRu6Zc8JCWjTprKu+/vSd7KvCeurHwkbGZJfCflbQvbE5sXOCuqTQKQ0JBc2vvv73wlqqh4KKZ4KqDU+qfmdGoyLpOPPCRsIMv8J2VgO+5qSXDry9lVvCrl5cn8JGkgdeqU++5quGupPCeuaQKO0I5KvCflbQt4LGGLvCRpJhn44CPKj1V4ra+NS7irozhi4ngpp8l4KusJCo8e+CrsPCflbTwkL+KOjxtCi5CLPCRm6DitYPwkJ2jP/Cmpbnwn5W0yLrwnZK/PG908JCmvSZy8KaFtS9Fa8K9ChxCGvCQhYRu77+9JvCdiLR74b++Reqpl/CRpIlxChhCFuCmvnrigJzvv73wkLOnPyBmP+K6jlwKM0IxXvCRm4jwk5GVKmDvv6tU4KyD77azevCRpZcgXMi6ICF7NVzzoIas8J64u2B70ajCpQovQi174LC88JCSp/CRhJJ38JCor1w9wqvwn5yU77+9Ki7wn5W0yLrhj7tY8J+VtGQKLkIs76in0ahZYPCfgrjwkYWALiY6NOCwiljgrp5GJybwm4Wl8JC8hDzRqPCeuYkKK0IpLu+/veCxiCrwnriDP/CQpLjwn5W0OidjcuGKsiXgs4j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3sicq4KiQ77+9Ins577+90agl8JCOhlQ8Ch5CHD/wn5W0w4k/4Ki877+98J2LgD9gIW3Iulriur0KQkJA77ePYlzwn6GSd/CfmLbRqPCRjLbhvZkn4L6Rw5NaIDp7Ku+/vSnwkIC8JvCQopLvv71WwqVefO+/jlvwkI6KPQoXQhXwn5SHIPCQtLDgprZVwqUqXiI20agKI0IhLvCegJjCpXYv4KmeKnJWKDnCpSLvv70uJfCfn6ty77+9CihCJibwkYqhwqUnXC/wnoCmIVzhipHwnZS74Z+k4KmIXFrwn5W0yLouCjtCOe+/vT4q6q2pOfCQlq9gJcKlLT3it50/JDl24LmN4aSn8J+it1pjWDwicfCQlozwkaahJifwnY23LgoiQiBFJC8kYz1EJe+/ve+/vT09ICrwnrmo4LOA8J65vipsJQoKQgg9LyTwkLyEKgpFQkPwnrmkybQ88JGLj/Cfopwl8JGMhypgPPCcsKpzJjs5J8Kl6q+54KeIRMOj4auO0ajvv73Wv1zwnoqo4K64LvCWpZI/CjFCLy7CpeG/iCpG8J+bndGoauGNh8OO4oabPVzguofIuibwkJSaPz7DmPCQuq1sP8KlCiRCIsKl4Y6A8J+VtC7gp4jwkZqz8JOEm01J8JGgheCuvsOIWiUKL0It0aglOmMvLGB1w4s/S/CQlqrgp5/wkJaUXGEi8KugjcKlPsKlTeC8lirwkICtChBCDvCfiLpc8JCwifCeuac6CjBCLuK8uHsy8JGPjixLR1xgW1vwkKycLiIwKiLIuiwnLeGkmEIm8JGMiPCQk5rhjbgKM0Ix8JCiju+thMKqeuCokD3gsrnCpWDRqFwq8J+VtD0x77+96qOS8J+VtMi6UsOmPMi6PQobQhkywqXwn5W08JaplnRcOjw1MvCRpLdj4ra1CidCJeCroV9PUfCepLA/PX3wkY2MP8OhzpMiOvCRqIzgsbhk7Z6xJC8KLEIq8JGkifCQjIV18JCWvCpcL2Ai8JCzsWjwnZKm4Kqz8JGNoNyhOPCfiKQkCi5CLCXckSYvRk3wlryaQ3vgoaNW8J64ovCcsanhnLUiZj8jXl3IuvCQlqB76qytCjtCOeGJpSA9OvCQhJLRqCVHYO+5nOCxhibwlq6IJUU6L/CRj4Iw4YmYcO2fnD8uSfCfgq5z4K6f8JG8uAoiQiDCpU4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RmZAtP1oKIEIe77+96p++NfCUk6RvIPCRj4Lwlr2R4aCZMkbRqHtcCitCKfCRpZBc76yT8J65tcOi4aCYYOqZpeC3lsKlL28y8J6jlfCdkqJeyLo/CihCJjwu4KmMcTpW8JGZkS4uej0uLmA0V+Cnh+CqtWDwnY2uJzU9RjZsCiVCI3JyKu+/veG9m/CeoIQ9PGxP76y+8JOCu3HwkI+OJlxfLsKlCj5CPPCfopHgrrhqJSHOliJcXGokOuCxqOG8sC9X4KqV8J+btC7Cp/CRnKXwm7KV4K+u77+9fcKl8JCelkjCoAo6QjhO0ajCpeGJnD7wmr+5yLrwkJK9yLrwkY6Qcjrgp4jwkbybOuqfv+K0tmBM8JGZpfCfiZBF8JGOjgoHQgVL8JGRoAooQiYlOWAsyLrvubTPtSJge014fOKRh+GtlH5gR/CeuLk64b2V4LGWLApIQkZgKVvDniXwn5W04LSp8J65m++/vfCQhpRcMT3hiI8k8JGEuWzwnoSz8JG/hD0lIjB28JCEuu+/vT9g4LeY8J+rsGDwkbyDCiFCH/CeoZzwkbWF0ag9yLom8JCWqdGow5DvrYQq8JCtvioKQkJAbfCflbTDoy9Z776z4KmR4La5J+CupPCel7/gqavwkZuDYC936qCD4K6+e8i6JC/wkICH4Ki2Q8OEfSTwrauZPApGQkQl4aWz4L2E8JCMsCXqr6His7sq77iJLvCQhqB58Ja1i/CfgqHgtIQq8JGxsfCQpqU/SSXRqHgqYPCflbQ677+8btiXLAoUQhLhiZjgprnwnZWA8JCAvWAqw4AKM0Ix0ag9ImwmOuKDrCt1LeGJlCfwkY+HwqVga1fwnpe/4YmR4oKNJkx04KmcIy7hsqVgeAoaQhho0ajRqMOvInEke+K3kyfqqZck77+9yLoKPkI88JGmte+/vfCRtIk/8JCngSIu8JGOjvCflbTwqqCqTsKl4by88JGcjWB78JarsD17YTcr8JCPi+C0g1w6ChBCDuGfsdGoKvCWhKkqwqUoCgJCAAohQh/qkpkmPCfwkbWEYS9F8JGNrHvit4lX6qyqLzPwlqquCiJCIPCxk5Hgu4EkWsi6yLo/JH1oPSolVy4vViTwnoWI4bK1CkRCQvCYtIF3cyXwn5W0w77wkLmz77+9w4bgp7t18J2Ehi8v8JCmivOghKo0VyNn8J6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KIgoEVXNlchIawgEXCgoDmWJFgiNFdVdsEP///////////wEKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgpXF4ZFJ1SUQlEcCpsBugGXAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCnUyh5c0URFZliwKOAoHZ3JhbnRlZRItugEqCigKClByZWRlZmluZWQSGsIBFwoKAxAnYlUEghcgbBD///////////8BCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCjcklUhzd1cheUwKkQG6AY0BCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLASJHkoV0YIRVN0wKLQoHZ3JhbnRlZRIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKI3mFeEchKERQHAotCgdncmFudG9yEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgoHCFiQKANwIUhcCnK6AW8KLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBCGaBF1NBFAhTnAoTCgdncmFudGVlEghCBlB1YmxpYwopCgdncmFudG9yEh66ARsKGQoGU3lzdGVtEg/CAQwKCogkZwCDkgFHmHwKXLoBWQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwExOAdpGZFHAyMsChMKB2dyYW50ZWUSCEIGUHVibGljChMKB2dyYW50b3ISCEIGUHVibGljCpYBugGSAQo3CghhY2xfbW9kZRIrugEoCiYKCGJpdGZsYWdzEhrCARcKCgaUGGByV5USRlwQ////////////AQotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpAk4dDVRI5dVIsCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwEyJichCAaTNCWcCnG6AW4KLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBOAaQgJCVkxJpfAoTCgdncmFudGVlEghCBlB1YmxpYwooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBKVgFdVCJKTkBfAqMAboBiAEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxI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u4LGH77+9Z+GFjS4q8J2BmS49CjJCMGkl8JCVvOCssvCcvaLDiSZq8JCumeK3nmDDkiog8J+hkdebyLrIutu+Jid78JGZpwo0QjLwrqGDw4/CpcKlP2A/LMODXPCRpJNTOuCsjEEkJXsv77+94Z2zQyUlMFHwkJ608J6yggo5QjfRqHfwkY2XOuGJsXtd4LqH4L+PX2vitLPhvrfwkb6w8JCglPCWqovIujnCpSrwn5W0PvCei78lChBCDi7CpS/wnLy9e9qEL8KuCiJCICJn8J28kOGpuyQuKvCfhIPRqEvRqCLwkaeQ4LuSOmhpCkBCPuCkvicmcvCflbRe4K6ZUiLwkYysPSR6IuC/iPCflbTCpcK4JSTwnbyncSd74L2n0ozwn4ml0ajwkZC3PT8lCilCJzwzKsKl8J6hnsKlOmFgJVzLkyI7JPCWta3wnZO/JHvwn6mzde+/ogoMQgoi4KayLvCRvJ9UCj1CO3UwczDwkIC9T/CQtLElOjLqppHvv5zhibPwkYqI8JCCnibwkKiFIi55ZPCRgr4uPCd74K6DLjonwqU8CgtCCfCav7Xgt6hnRwolQiPCpV7RqPCRtYY877+9JUjhp5BcVNWkXOqfl0lF6q6MyLrIugoxQi94L+G9mSfqqa7DmvCeuKTvv5vwlq2nPDopP/CQo7Qn4YGD4Ki48JCXsC578JiohwoiQiDDidGo4LuOTVJF4aKSXcKlPSpbdeC7hl0u4Y+68JCGoAoLQgl1JiUmKvCTkYIKOUI38J+AhvCflbRB4LWNN/CRl4Vh8J6lnvCQurHwn5W0UO+/vWBD4b+SwqVa4LK2cPCetIh7didFLgorQilzPeGNuO+tkvCQvbM/e+Csq/CfrpAlcSUk4b2Z8J6AluCsslzwkY2xRQohQh8u4Y62JkrwkbS84LWVRzVTyLrDhuCruyQ977u5L9GoCjdCNfCRh7HwnpOmcnAn8JC9gfCRgorRqPCflbRs8Ky5tPCRoJM88J+VtCTgrLY98JallPCQg5QlCjlCN/CRj5VJ8JKUnS46fSpl8JG1o2jigogiOM6e6qq3w5Zj77qcLlPCpeK/uD0nwrc677+9w5Hvv70KK0Ipw70oYe2evyJ0L1jwn5W04La9QjxA0ajwkaSgJ2EmPS/Iuiom77+NIWAKOEI277+9e+GKiH3wkJaeXGt2LVbhv7o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98J+VtDLwnrqiyLoq8J2Vg3PgsJPwkIaY7Z+SOlxxYC8iYPCeuYvgqZpDXCc98J2LkSAKNUIzPCzwm4WnL3BD8Jars/CTkY3vv70qYD9O4K6V8JGMr/CQlIzqppQm8JuygfCflbQlXD8uCjhCNsKlfvCQoIXRqGBgYD/wn6KF6qCw8JuyntGo8JC7hPCeuZdm8JG2oVQ6KnR38Jy8vuqomjwvewo7QjldV3A88JCeteCoiXtXw6144Kmc8J+CsvCWvJ/wn5W0PPCfh73hsKLgqIM4JyLCpUsvKvCdkrHvv70KM0Ix4rap8J66p+GlstaIXeGLjTp9Z/CRmI9tZHvhoKVs4recIyNcUOCmpS57ZvCflbQlIwoaQhjhg4fwkY2MKvCRsp0kLidkQe+svvCQo6EKI0IhMlw6TD3wkaSJ77+9LuGKvtGoPTUiIsKl77+WyLrqqLUnCjJCMC48JCLwnrmS77qFLyzIuvCeuJvwkbao0ajwkLSyd/CQgoFb4LGgyLpc8J+VtOC3swokQiJw77+977+9PD/wn5W0JfCflbR94Yu9U8Oq0ag8J+CmqsKlCiFCH1w3Jz/wmr+78JCKjPCRhLlPJE8v8JGFmDxi4LeFIi4KNEIy4KGmPyLgtoFR8JCkiOCovCzhnJPguq9cfXc3fuCyrPCWqYjhvKPCojw/8JGMgeCniCoKEkIQ8JC0tOK2izB9wqU88J2SqgoRQg/wq5WYL2Dwn4mgTfCRnYMKOEI24L6uNOqfluGjq21l8JG7rXUu4ra0JyQ/4b2bPGgv4oGKQid6Kifhi7wu0ajvv7088JGGpi88CgxCCk0t8J+upvCdkrgKL0ItT/CflbQlPeCumVzvv70h8J2Un8i6yLrwkJ2SesOuTSJdIm3wn5+Xfj/wnou/CkJCQDDRqMOp3J4uwrLwkJaQ8J6Apz/wkICeJ37wnYuh8JGwiOC7hnlPJyTCpfCeuZ3DhConP/CQuppK8JuFleCriz0KFEISdj8/TCfgsZg84KCL8JOFjMKgCj5CPHlQPe+3gnvdmPCeuqfwkYyeeiXitqtee3fDpfCbsoY5bOG9jPCfiJYlID0iSPCfqbA9yLrwnZKf8J+VtAoIQgZJbkjhnJMKOUI3L/CWqqHhp5Uv4LOVaSJi8JGEkO+thOCqgUJoWfCforvwkaWU76Si4by2SsKz8JCng8i6JuGnlAogQh7grqpg8J2Dm/CRjJXwk5SZKvCRjZfCuSI94biDPWAKHEIa4LWN4K2NM3w6LuGKjGgm4K2gbSJc8J+VtF8KI0Ih4KmCJ2ct8J6KofCfn4cvLuC1j3tdZvCflbTwkZOSYC4nCjtCOSdcYjHwmr+x8JGcnfCcs4smJ/CSvr3wkY6LOCdu4reS8J6frWHivLvvv70l8J+Vs++slzpj8Ky6oQoqQihbRWpKyLrhoqTitqvwkICGKvCQrpEieE7wkbKxe0Q74K6TXPCfn7AiCiVCIyrwkbyO8Ja/seK0nHbhnak6w67RqHdgP1wlQvCRiog54KGQCilCJzrwn6264LK84Yqh4K6c4rSt8Kq0pfCQqJXwm4mW8JCWuyIn8JGwhwouQiwufGgi8JGMuTpHJeKtvfCflbQhWfCQtJwu6qO+8JGxgvCQoJMk8JGsge+/vQoQQg4kL3rgtpZh4KmAPOqpkAo4QjY68J64u/CflbQ84LSv77+90aho8J65n1Hwm4SyImBCPy4q4LmAXHfwkJWeXOCth++/vSZtP2AKE0IR8J6EvXDwnp+rRFgvV/CeoYQKCkII4b2d4aCyOk4KMEIu4K2de2hcPXtcWPCQlo/wnrikUDhx8JCouX0kO+CnjuK3hT1HJSo2J+GfojolXAo2QjROey0q8JGLodGoJmFtwqVf4bOn6p+Qcj7vv70vMMOU8JCeoPCQj5ElSeGJpfCehLMu4LaOCjtCOfCRjYI+PFzqoLXwnYup8J28heqsjXXgr4pPdCLwkLOULsKq8J+thD0sJ00t4K2h4KmR4K2wIuK1kAo5Qjd18JC6sSXwnZKeYMOQ8J65gvCdlJ/wk5GP8JCVt/Cfm7DwnaqbJ/Cbh5Xhjangp41n8JG0ui8uCipCKCXgor/wpo68QDhFJvCeubt68JC/hPCQgLzguoHvv73grpLgrpJrLkMKM0Ix4YqMeCrgqLPwkYSbZSZrPkgq8J66kyrgp5fwsompyLpo8JGAhPCWravIuicv8JCilQorQiniuIAx8JiilWVnY+Cgu/CQhqBZLlsoIkM64pGE8J+VtPCeurJ+8KudvgonQiU8Pzwv77+9IvCQi4JYL1FwIlIvJuGfoeqjleC2sUwlKsi6ZEtmCjNCMXtKSDrIujoi4b2LL0ZuyLrwkLqrQSc8TU7wkaSW8JCCtyrvv70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wkL6GPQogQh7hg4dO8J2Spijwn6uUP1zgroPwkKShVcKl8J64uU0KKEImOOC+o8Kl8JCmrM6K4K+C77+EIjooLyUl4K+LL3k88JCsv3vhqKEKKEImUvCdlJs6WyROJjzhv6LwnZiqYOqdmvCav7NcKsKl8JORh+G/lj8KIEIe4LqCJO+/vXdb8LCbovCWvZHgtIxY4Z+34oGw4KmeCktCSeC3kj3gqIrIujDgtbk13LHgqLZgJ/CQnaDgs6Mne8O3J/CQuoTgs5XwkKeX8JCdjU/IuvCfm7zvv713QvCWq7HvuIkqIvCfq7AKMEIu8JC5qXvwlqWvwrLgq41+8JCujzx8PEPhrK9y8J+VtGbigbElO8i64Ymc4YmNXQoVQhNzVvCegKPwkYOy4LK88J+VtFwuCg5CDOK0p10m4K+I4KyiLgojQiEg4Z2w4LqE8J65vCoiJiYz0ahyMDritLdaMu+/vfCWuZYKIkIg8JGPoXsm8J2dmMKlPFPhn7Ei8J6jh/Cfn6RcTPCeo44KMUIv8Je8t/CQh6bRqCdfJMi6LFfwlqmc4Kef8Jatmyp1w7Ai0ajwkY68a+Ctn/CeuZIKN0I1OtGoLtaye1JEP0YnXn1f4K6v4bOu76yVMe+/veC0q0YyLi8q8J+qhXvIun3hg40v8J+gg2wKOUI3Jn466qyE4aWa8J65nSJywqUnXOC1ufCflbTwkZGgLip6csi68JGKimBgYPCfn4QlJNGoPyZ4PAoHQgXwkLyxPwoyQjBN4LuK77+9KuCssuGumOC0gSU/w7ouL9GoyLrgoaUveynCpWAu8JCpuFgi4Lq7TyQKJ0IlJMKlZ3IlKSl7dzot6qC5MTvvv7068JCgt2ly8J2LiX4mSOKBjAoLQgk9JTpWRuGJjD8KDkIMSiY/V8KoP2oqwqU/CiNCISUkLmRP44CBb8KlJO+6kT7IuuGdg/CdkrTwkLyD8J6LvwoyQjAi4YqNXOGJmCoiJfCRpJ488J6Bl+GqsPCeuYd6UT814KGi4amk4LSE8JCsvvCQurAKK0IpYOK3jOCwrycmYCQ86qCB4b2ZMmcvQmDwkbyMJz0jPMKl8JCWkiXigpoKGkIYYOC1jHvwn5W0XuC4ofCdhYPwqoq0Ll0lCjVCM2DiuLI98J6llEA68J+VtCUv0ah+Wyrgsqvgp7E14KGe4o6ROuCzjGDRqCUmJMOxJiopXAojQiF7YG5XJOGKvPCflbRcOvCdkrngrqlu8JGkifCQlrR4XCsKM0Ix4K2gUeC7mS/wkL2N8J+DivCRv5EtfSjwkbSAXMKlJsi68JCpl+KBsCwiw64lLsi6YAo5Qjc68JC2jtGo8J6fofCav7F78JCUp11gUuKwq/Cfn4Pwnrmu8J6AuMOIT0pw4KyKJeCriSLwkaC4CkRCQl88wqVuUSYu8JGLtyrwkYyA4q6Q4KytPfCRio0lLeGmq/Crv50/J8i64reD8J2Ul+CngvCQjozio5JAP/CRpqdoVApNQkvgroLwkY6O8J+Fn++4jOChqvCeuqfwn5W04Kav8J+DnsOHLuCnnPCeuIB2PSbgp4jvrLQiLuCusPCmq77wkJaUJfCegKl5JkUoRzcKNUIzfsOGwrEn8Ja+j3sk8J+VtOCxi3vguIPDoCXwm4WmUeCsontgbSrwkaSB4KC3LuK0syI9CilCJ2Ve4LuDP++/hiMk8JCmiNWAwqVC4Ki4OvCWvocq8JGRoSfwn5W0YwokQiLqrKzCtHfCpT9b4LeyIvCdkp/wnri2OCRGLvCQj5Na77mbCglCB/CQvJzDuFwKPkI80ag877+9P++/vU5owrnDjsOy8J67sSpLXPCfiYBD4Kmn77+94K+GPPCel5UlJuCupGAuLj7vspcr4K+CCjFCL0RP4K6VSDoiRn4m4KyPL2094KeNXMi68J65kSfwkYK+4KecP+G/tPCQjIPhs6JLCgpCCFjRqEB8Isi6Ch1CG3zhnK5cLPCQjKHDmSbwn5W08J2LseCwlM28SAoTQhFze1LDi0jwnrm36qyC8JGkpAoJQgd7XCTvrYQiCjxCOuK2ufCSiKY9fSUn0ag6KvCQlqUkViTwnoCjbuCmj++/vXDwnZKlXOqklV/wkK2HwqXwkIy0duCxhycKA0IBfQobQhnwlqmm4aKzIiLRqDomc8OY8J+VtPCRjpdnCjNCMW4m8Ja9sCYww7ZKXG7hoJjwnbynYD0v4bGJ4K+MVEjwlq22QuGXoPCQipgoKsi6T2cKG0IZ8JGqgO+6jjjvv73qn5HwkKC3eyImS1lTPwo4Qjbhv5Z90ajwn5W04KawUfCRioo9PO+/vV494rSn4Ymu8J6ApD9o8JiomcKl8JGIhXvIuvCRr7AKNkI04Z+2QFzwn6mle9GowrP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Ji1vwlqmO4aC4VuGtonbgs4rwlq2W0ahFJu+5tApCQkAnP/CeuY3qrIvhio3ZhHXwkaej8Juyn8O1PO+/vVTwsKKAyLo94Kiu0agvUPCRgaxsPScuwqVCLsOwLirwkbu4CiFCHztYauCjldu94LKO8J2qqPCWvpzgqYsk8JGwgOGxgFYKH0IdRnsq4K6DwqVLL/CdjKfhppDqvqRrZteZ8J2Sn1YKL0ItTfCRm4lp8KmHjE/hpbHwkJaP0agx77+98JGysM6RwqQ88Ja5geqSuypcbj1KCjRCMi3wkY2yYFDqn5Dwkaia8JaqpuGMiPCRioJg8JGZk/CQnoQmKsi68J+VtOCnoMi6J9GoChVCEyQ/JfCWvanRqPCRsLPvubDDujoKPkI876q48J6Cj28jMyZcwqXqn5PgsJDCu8i6zozgtqHhsYla8JKRskIqOiIqe/CeuLtc0ajgr5cu8J6fqFMvCjxCOvCeuYsjOnvgqqrwkr+CUOqfmy5H8JC6rVfvv70677it8J+rsn0u8JGPouCtnfCRgrQ9P21YJ/Ceip0KEUIPwr7grofRqFUm4Kay2bBCCi1CK/Ceua95JPCflbRg8JGMufCflbRpZuK0pz9LK+Czil1nwqXwnY2tWeK2qn4KIkIgSfCQoLjwn5W0MvCQnpjCt2/wlr+i0ag6XNGoyLpoY1kKM0IxVjoqJ/CRsII8LickV+CovFkkwqXgtbvIuj0v6qiWZ1zgrqjgrLU5ImxH8JGbglYiPAojQiElPfCRpqV0Jzrwm4Sy8JGKjOGPoW5oPyY68J65tDx4OmEKRUJD8JGphMO3Ju+shjbXpOG8m++/vXnwn5W0dVDwn5W04LGj4raO4KayWzLhoInwn4GWJNGo0ajqqYzwkKSm8JGOouCogQo2QjTgvKY64Kuq8J2Nsi3hjpY9JPCQnoPhnJFyJ++/vVzvv6Lvv706yLol4bOBPGXwkaqX4LuGCiFCHyRfRSY2e/CesoM64LGZ8JCOkF9FJ+C0hyIkS1pUOkgKQkJAJSdzyLp7S1zhnapHe/Cfm6pi8JGitEt28J+JovCflbQ8w7Z78JGjv++/gtGo8J2EjvCflbTwnrqQL+KAgci6Lgo0QjLwkbGFe1rwkb2G8JGPhUzwnrqn4Keg8J+JgfCflbTwkY+h4LKyOGjwkL+p4bOwUsKlcgpGQkTwkYO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Qo7XCpeK1rwodQhsmLlAu8JCeuOCyrGPvv73wn6OA4ZWTLvCfhYkKFUIT8JCtjuCuj+GzoD3wn4mj77+9ewoMQgol4b+y4rWDV3RYCi1CK3bwn5W0YOCsvCbwkJ6BR+CroPCRio088JCpgCR48J6XvzTwkbS84oCcJS8KKEImIuCqs8i66pmtwrjgtIbvv705RuGktWdK8JuxmfCflbQhWfCfoZEKIEIe8J66hG3graAm77+98JarmDQ68JGwg/CeuYvjiIYuCiFCH1zwnri54LGYJENuKnpTPfCQoIgr4YqL8J6fou+/vVAKG0IZWjzwkYWD5KGl8JaujSR74YqMUSkg4bK+QQosQiov8J6Lv/CRtqDRqCU6PGDgqZtjKjbwn5W0OuC3kuOIm306OmBgSyZ7Oj0KLEIqJi8qeyrwkICD8JauhnZ5In7wkZmXSvCdk5FcVeK6nCZv8J2VgjvwnYWwCjNCMfCQqbjhqoUlJivgqLwkIteY8JuymSoucWE6NyQte/CdlLxU4LeewqPCqPCflbTRqHwKPEI6Se+/vSTwn5W08Jy8mk/wnoSl8JGmo+GKuifRqC4+KvCQi7RcXci6LzImZPCflbTgqr5jYOC6lEZdRQoCQgAKMkIw8JKFku+/vfCQh7RCeyp74Ky9JPCQoLgnaXskOVzgqIHDtvCflbTwmr+y4b+yYEY5CjpCOGcz8JCWlSoydtaN8JC0hCYi4YmMezNIO+CzqFPwkKSw0ajwkLqt4oGZbEXvv6AxKvCRiI92XMi6CjRCMvCcsafwmr+7w6oq77+S4YmlJeC0nuqfk+GlgHtGOSFjdzrwkJak0ahDOifwkaOtLiIuCkVCQ1TDj/CeiqFV4aSwL/CflbTwkKCEwrQv4ra+XeGLmUDIujrwkb+mJuKAt3fwnqOIJX7grpwiOvCeuqht0ajwkY6i0agKM0IxJ/CeiqFTw5k68J+QjFzirqQ68Ja1i3Jx76y+4LK4JuGqqEzDomFoez87KvCQioo9dQofQh068J+VtCQuaeCouGnvrYE84aiN8J+hk/CdkqLRqAo2QjRswqU/8JCqlj5g77+9LiIx8JCGlzHig5VZ4Kqy0ajwnrm+wqXwkLyzPDlcUCVEOuCuoyApCixCKiZcOkA6YPCRsK/wlq2h4LuD7Z614Ku78J+toHVb8JG2oUTvv71CPOCrqgo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wnZiiJ/CQoYbwnLymTuCnhwo1QjM9w5Y88JCysPCcvInXsG8yVGxH8JG2iy0815TwnZKp8JCgkiVUPeC0jvCepZ83OuG9uHcKGkIYJu+3j2vIuvCRi4YkIeG0jGxl6q2j76yGCkBCPl7wkY2CUVThqJDgrYjwm7KfPzR7JTcpP++5qiouVWDwn5W0VCTwkKmSIvCrnbLwm7CY8JCKl++7uPCeu7FhCjVCM+C9vdGo6q6NJTjDkeCuo+CwtMOsOlcx8J65lCjgsaDwnoWFJfCRjIDVhVzgto7np7AiYQoIQgbDlO+/vT0KB0IF4Ly2TjoKJ0IleyXgqrI/efCflbQmOtGowrMq0ah4wqU/P0dg8JGwp8Kl4raLWwo/Qj0l8Japo29NJe+/ve+/vO+uqD/wkKiG4K+XcD3RqHMkYGDwsKmeP+Cxity/77+98J+VtDoyYFNqTtGo6qyqChRCEis/w4/DqCclXltVXDoi8JCKlQoPQg1cKvCdiI7igbDwn6m8CiZCJGJgwq8u8JG1mD9yVTrhoJbIuvCfp5g8wqHwkI2F8JuygeGJiwozQjFpJ+CxrMK8YfCQurHvv71GJu+/veC6hO+/ve+3jy8k4b280ajwkKGSJ/CflbRE4LuDCglCB3NmTfCflbQKKkIo4Ky24KeB4Z2iImfwn5W0Q8i6JFvCpUsiyLrwn5W0JOCvi2Hwn5W0KQpCQkDwnaid76yTwqUjKvCdi4UlPUzIui7gpIvisqDvv73CqCTgprLwn46U8JGgme+/vS4qIiZGIuGNluK2pfCQmrJ7CgZCBPCQgIsKN0I1L+CulfCQi6kn77+94LuO8J64q+GdkyczafCav7N8ejo9L/CeuLtdJOKvqfCRj4J+RiTDukwKOEI28J+VtMOp8Ja5gmhNV8OsST0yLifwkJ6y4Y+5eyfwn6uc76SO8JC6sNy18Jy8lS5N8JChkMKlCkBCPtGoNGRgd/CQvI/wkpKCL3sq77+98JG2kDUq8J+VtOCnje+3g/CRtL0v0ajwn5W04Ki5KvCdlYZK4LGIa8K3CkxCSi824aWe8Ja1rOGzoSI9VO+/vfCRjYtD4K+XU/CWqrcrUvCRv6zwnrmC8J+rtnwq8J+VtF7hgacl8J2TgPCQkrnjgYPvv7094LeKCilCJ+CyueCmj0ftn6ch8JGklmLgt5bwnoS9PfCRhK5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74Ka3TOC5lvCforFtOvCQoq7wkbS9PEXIumNmwqXigoBgCkJCQCIpXyLgrojgv4nwkaunJyQ6wqVQ8JGwgeCnh/CRtIRU8JGMuPCQloN8ey/IujrCpT/wkKO08JGxpVzCpT3gs6AKI0Ih8JG8r9GoOibcvl/gq6vwkY+R8J2VkFzhirI54LGMe2M9CjZCNGwvPyfwn4e68J64ofCSkbQ1OvCRj4ovecK1yLom0ahc4Z2yw6LigIRjJybdoCYlP/CfgqgKK0Ip4b+aJi8l8JC6rfCYh4g1OShcJD3wkKmAUiXDl++/vSTgu4TwnoGoPC4KFUITPDpB8JC7hHvgoafwnLmALOCtiwo2QjQnZD094Y288JCAj/CWpL7wkIyhai/crz3DqT008JGPiTpgJSfwn4+5P/CflbQmPT7wkoOeCjNCMfCflbTvv73wnY2jPOKBsXvwn6KSP2hgWPCdgKsmOi8vaOGglljwkLqr8JC/pfCQlZoKRkJE0ahX0ajwnoCKw5/wn5W0Kz7RqDw8fEHDkiouLyfwn4mi4bOH4K+QL+CmjMOU8JGXguCrkMKl8JGkt8Kme/CQp4DhiZ0KJEIiIipiwrY/K/CRiLNgeTPPhe+/qvCRjI8qbmU/Ljll77+9Mgo8Qjov4Ky5P8KlYDHgrpw/8JGTkPCQj43vv71rM+C6iCLCoSXgt4Vf8Ja5nnAt8JGkuE89bPCeuLkm4rqRCi9CLe+4rTxywqzwn5umcz8vJyYvPcK4K+G9mfCRipDIunXwkLKo8JGckcKlJ++sggofQh3itYUgOmDwnpeV3L7CpfCRtaLwkYqM8JGIiyJYOgo2QjRrQ/Cdi4c9w7btnrJeTT8lLzwnbSUvSiTwm4G9QCbqrI4m8JCWlGVdOuCmsmbqoqLwkaeVChdCFS3grqQyyLou77+98J6FjuOGtXzIugo3QjXRqMKmPylB8JCtuT9cKuGclD7wkKCI8J+VtO+/vULhpbJUQPCei4Dqoqjgr4DwkY2w6qWyIQoOQgzwn5W0U3RGJfCQs4YKIEIe3K9fwqXgq5BaKSVJL8i6YUR08JGkiUJ7ZO+stUVlCjVCM2Pjg5fwl56SPfCRkaAiP+CqkOChnk54w43wkZqS8JGmo+GytcK277+9XC46e0XwkKmRRgoUQhJ+JfCflbThoYJ18JCpgMKlKkUKMUIvdSI877+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4wq9JP+GJi3N7Ci9CLfCRj5ThqqDwkpG08JuFlfCfoInWpfCRloYzKjBAJGDIusi6P8ODc+Ctr2olOApHQkXwkYy5bDol4by5e/CfqbgiVe+/vfCQhqBi8JCegjPwkbaVR/CflbTgr4bgrYcuwqVD4LGdJeC3k/CfqbU0PmfhvaXgtY4KNkI0yLrhpJdc0ahyJMi6JdGo77+9d00m4Y+88J+kvSJwYTPRqDVl8J64giTwrKGrJCbwn4GsMgoYQhYkfuGZli4u8J2eqDo9c/CQvrB14aCYCj9CPeqlozzDlPCSkbLwnrmoZ+KAgivwkLqwJfCRj4rqqJDCpVwn8J+VtPCRhYImUy9O8J+VtD13asOU77+9LyoKJ0IlKuCnnHMsei/wkbWn8JCtnuK0hi08LvCRqaZN8JG8pDlUPVLDhwo/Qj3IumxV4b2bJOCzsuC6sVxvN/CRpJjwkYWCfC7wkIGaPCY68JCwpeqqr1B34KqK8J+busOl6qaYJ/CWtZIuChtCGWZLKMKlPcO08JGcnvCutonwnbyp4aSjbi8KMEIu4KyFJ1Dwn5W08J2HgC8qc1U8aMKlJEnNv0nhiZbgp4TitKdo4LebyLpgeypkLgoeQhxKPzsl8J6fre+/vSvwkKSyPOGMkuCuoyThoIN1Ci1CK/CQoITwn4mD4KiiOvCQtLg/JcKlLnLwkZyeJi89IuCviCV7YuGlmiLhs4UKRkJE8J6ln+qskWc7fi9+Im4zLvCQs4jjg4LCtOCyt9W9JS3qoJXwkIeYPyDwkIun4KmH8J2Lq/CfiYI98J+qtlRfw5PitrIKFEISMWXwkZyaJEbCoPCQq43wn5W0ChFCD+Cun0Vxw6PwkI6C4rmZIgoPQg3wnrmhP+K6l2BL77+9CgdCBfCflbRcCjZCNMi6a2DwkK6sJy1oWWo+Oz878JCjtW5j8JC8mCJc4YmT8Ja9sS4gXuCno+Cmhj5u8K60jzwKNEIy8JGKj+K1r/CfoJTgrIN74K6KKvCfr6k9QeOImPCdkp/wn5W0S+GJi+CtnU9BQSXhq4MKJUIjw67vv73wkLWcPMKl77+9RkLippjgrYsl4KedP++/pvCbgocKLEIq8JG1pSLvpLEvWVxGYDrRqOKzvEImKicmPH53R+GxqT141YQlevCfn6AmCjZCNPCRtJwv8JCDk/CRjIB9V+Gglty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D4r+H8JGNgmZje/CRkZ97O+C3igoKQgjwn6Kk4KqwOQoxQi/wnri0Kirwn5W0e2BxYvCflbTgqKguPTHwkI2yPfCSkJl6KnrMsk/hio0hTsi6eAo/Qj3wkJ6BIy5a4K6FcvCQqJ7gsrJg8JCpgmfvt4/wkY2dbSonzozwm4KpJypgICTwkKyv0ajwnLin4Zq216AvChpCGCQq6p+Q8JKRotGo8JC0nD094Ymuw5UkZQorQinwn6um4Yi31o7Ymz178JGymvCcvL5zJ++/vTvwlr2S8J6An3vgqLI8OgotQivwnZWG4KaQJOCyq/CeuqHwn5u58J6ElfCfnorwkpGy77iq8J+RqS/hiphKCgtCCSrhv4BcwqPRqAoXQhXwnLy/8J64iVDwnp+lO+C+hOC7imQKDEIKO9Go4LK377u1SAoaQhhS8J2VjuC2uiF0Ml57Iu+/ve+/vSQ/LTUKOEI2JOC3kD/hval8ZOqjsvCdkoglInTwn4Kr8JCKiVXgs5Xgp448wqU8Mjbit5FK77+9e/Ceo5BVCjhCNvCav7JP8J28mFp7dT8vOvCdjIdnPD3wn5yheyLqnJzvv5tg4KyvP+Czii3wkKGGJC9gVeGJnQocQhovamDgq786L++/rV0rJPCegKNw6qCTe+G8nQo1QjNvP1xg8J64nzpVSPCeuqzwnrmfW+qskWDwkL644aCM4reD8JGcuEtKO3fDkCTvqqThp5IKOUI3Znbqo5Q/XX1mOvCflbRzeuK2rT3wn5yxIvCRjqxpPWlQ8JG8jPCQoJThvJgu4Yq44YmU8JaugwpDQkF2XvCeuKHhirXvv73guoFE4LuGwqgm4LKPL20kwqUh8JCgofCQqJcqP/CbgILCu/CRj5fCsyo8bCdgw4fwlqm0KAozQjHwnp+oY3fIuibwnrm1PfCvpK/DnCpEWeG/m3JcJWkvJDzCuT7dpMK28KuHtPCrjq8vCipCKPCQsJBs8J2SomTwn5W01ZzCpT/wn5W08J2SrPCflbTwlq6Fb/CQlbAKK0IpyLo6JvCdhIzwka+fL+CovCXwnrmJP++/vUnqo5PIunxc4aKwVm4mIjMKPkI8WGMjY/CWqYF6ce+/ve+/vci6PPCbsbQ/d/CRiqRH8JCNn34l8J+IqSBuLsi6SW7guJnwn5W08Jars8KlCidCJSQjJfCeuIoz8JGOuiU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Mhu2fmT946qmWYWlL4b+74KmHPTE/wqVWJz/wnrikKj/wkKiVCilCJ20i8JGNl/CehY49Jj/tnrPgpqvwn4GO0ahdR3VbOuC6mUXwn5+wWgoFQgNeL3QKI0Ih0ah74KiBP3tBIuCqnS4yJT3wkbCUP+GlnNqR8JGKgz1cCjlCN/CdkrvwnZS1Ny/gqI/hoIXwnriyWvCQvLTwlqmn8J+ggPCXgqniuKzwkKCI4YuDJfCRtZhsUmAKOEI2JDoiViHhnbBLXPCRpLjIui/gqIM/Lns88JCgvMKl8JC2jntO8JGkt8i6L/CeuZ0me07gup80CgpCCDVl8JCWmTRPCiRCIvCQpL9ZYOqloPCRjLLwkIC68JGHmTrwnrmt8JGTkvCeuqEKQEI+4LeKPSfgu54mP/CfnqU/wqXwkYqIeyXRqCfhooow8JCWoz/Xn/CfhKE8eO+/vWDgsrXRqPCeuLlgZChvyLoKNEIy8JaplfCQv6UyVuCnnz49buK0p+G9nTzgqLbvu4HgrprDjzTwnrmbPFIid++/vXzgt7IKPEI68J2ZijvwkZOSKu+/vfCusZzwnp+wJPCQlqxVYlBJJOC1lOCpnC7hsJjgrac94LGj8J65vsKl8JG+sAoxQi8uwrnwkKmWb/CflbRwPSTwkb+wJU01JfCehJco4aa+4aKE4pGJ8J2NsPCflbRYSAoZQhfDt1vIujIiOzrwkaSJ4ayaWzo64oOAewoLQgl24bOGPeqgtWQKCEIGLvCQqJYwChZCFPCWqYB7XHHgsYtqNHRSe1zRqNGoCiJCIPCQvbAlJ3fgs6/wm7KCZ0okJ+Gzkj0m8J6AiOCxiyF4ChZCFCpGw7/hqrLwn6GXPeCskPCdlL0nCihCJuCoj/CQoIPwkISBQCUvOiYw8J65j3vhrpN6Ju+/vXhQ8JCMtC5ICiFCHyUuUPCRj5gv4YuQ0ajwnZWG8J64pEUs8JGNiDp1wqUKMkIwaEjwm4WQKjMlSllm3o3wkZeb4YqN3a5Yfu+3jybgsaFcwqUpPCXwkKKq8JCHlHYiCi5CLPCepZLRqFzCpTwswqUkajLvv7174q254LeKwqXgtr0qUvCQpr9j4bWdyLo/CjRCMj97OvCflbRSP8OyJe+/gu+/vV3VkPCRjLPqnLciJPCbsJQy4oGxePCen6XgrLXIun5XCjtCOW5+XPCRtLr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gdOCygDcnTuGJkWXIugo3QjVt4rSnLnM0IvCflbROYOC7nPCepYjvv70/76q6fiwm8J+VtPCdlL59enfwnZK+8J+VtGE6dgowQi7iuLDwnp+qXC7hrYHtn6Uq8JGGjy7qmbIiPCbgqoIqZPCetKnwkY2xM0fwkJatChVCEz3wkbS64b2R4Z+z8JGppCvhlKcKQ0JBJiTwkYSx8JGPheGDje+/vW4/4LCB4LGLP++vm/CQlqvvv71uP27vv4dkWzcm4ay48JatpuCzjXvIuiIuJ/CRqoQKMkIwey7hsazqr7XwkJaS8J+fsEt6XCrIunk/8J+UkuK3lmHtn5gx8JGPl+CthPCYsahFCg9CDTl4XmLCseC5juG/nzkKCkII4Y+LU/CQgYsKO0I5Insm4oWV8J64pz5s8JCRmfCdlYYnWdGo8J+VtD3hnbMk8J6fvVMq8JC6sOqvsD/Cpci68J65qWAlCixCKjxkyLrCpeCqqu+/vSRnPfCQgZbqn5Hwnrmp8J65pCLwkISe8JCWmeGPjQofQh3gtYcmJ+GLgMOGyLozyLpoPPCQi6/gs6bwkKelMAo/Qj3wn4in8K6NrvCflbTwn5W0O/Ceub7gsr4uwqU9KiRBacOvci9S8J64gOCzlS1V4q6CKmXqn5HRqC9UZiJTCkFCP3vwkYyw4L2k8JCgiPCdkrvgtIvgrZUqP/CXpr/ito3CseCunOG/pu+/vfCQtZTgt6fvv7094rmOQci64aOMLworQinRqPCYtIU84Ka2cT3guqXgtr068JG8h+GOgWBRYGDIutGo4LerLu+/vQotQisw4KqyR1Zc4LqldjrvrYDwnp+t8J+hmfCQtJ0/Jzkl4Ke+8JCWnvCeuaQmCjlCN/CRprE8aGo98JGYtzzgqLYiOvCflbQjK0/wn6m5PT126p+a8J2AgvCeub5K8J+bs0zwlqqaISQKFkIU8JCgvEvCpUbjhoXCpSJww6/DslUKEkIQJ8Or8JGNsElELip3WUdhIwozQjEiLuGfo/CQhpN5RfCflbQ/8JC8keGzgz3vv73gp5/hvpxqc+GknH7wn5+w8JCetDpgCglCBz3CpTo9JV4KIUIfw5bhv7Lvv71v6p+Xek7RqCovIn7wkYqlYTkncS80NQo5QjfwnrmJLlY8KuCrvSngroPigbDDo+CxlvCRv64k4K2IQPCbion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0QjJUSS/wn6OA4K2cYO+/vWjwkYiBZ1jwn6KwwqXIuvCfq7hN0ajvv73IuuqpmPCRjLN7fQpLQkkl8JCukcOk4Kme4LaWJeCxrD064KuL8J2Sv/CflbTwloSM4aWy4LKPwrbwkKiCXPCRjYt2w493VOK6ssi644aE4K2cPigtW8KlCiVCI/CTn5XwkISCIlfwkJ64P2cva+Gzt+Ghm/CdlLXwkY+RZ8OVChRCEn7CpSrhh7nhvLjhp7/wkYS7QwoVQhNe8JOQmHI6WSdU4K2MJPCQnoInChNCETbvv71C8J+gijM/4aS7Ysi6CjFCLydU8JGTkiLqnbThqpN1RSp7JjzwnZKe4LGL3Iou4K6fa8Ks8J65vNGoIjgiL1l7Cj1CO/CdlLkgRvCehLrwkYer4LaCVeGql/CeuLtr44Ky8J6LqeOEr8KwL3zwnrqT8J6Lv0QvPHpewqXwkY2hChZCFDon8J60niDYluGMqPCRjIbCpS4qCjNCMfCflbQkdjxW1KDgsZ1eyLrhpLbRqMi64LGH4b+scXPwkKmH8JGMhiVg8J+DkiQqVEEKBUIDJs6MCiBCHvCdnL3wkayA8J+VtFw6w4okSMKiI0/CpS5E4L6NJApIQkbwnoKP4K6pXPCRgrg1WS7hsq9pPCV7JHw8OvCei4/gv448wr448JCAreCossi6L+GigPCdhpwl8J+VtOCqqvCbhZXwn6KRCghCBmMm8J65nwo8QjonbfCWrZRU4KiD4q26cuC4rCjDom/hibPgp4fwmKesJ0QlOl8uyLrwkZiQ4YmT77+9w4rCrvCRjLI8CiNCITovLzrwn5W04YKlwqvIun7hsYk877OKyLonUD3htrVORwpDQkE88JCVvOChni/wnrmdVCcm4YuD8J+fqMKl8J+CtuqioirwkIGAYSXgvr7wkIGWLmBTXfCRm5h7YEzwkLSeJ1LCpQo6QjjgqLxFOHzhuLbqqJjwn5W0762E8JCkoirhvaBLe/CuqJEnPXs/Y/CQuq1pLlbwkZ2C8J65vifRqAokQiLwn4KnXPCRjosh6qOX4rWv4rmP8JGxs1XIuvCRlpTwn5W0CkNCQTPgurd48JG0k+KAluC2jvCeuqtf77+98J+VtCXgu55NLlzwkby1L15vdSfhjbnwmKCRL+C1iCR8KvCRsIhdwqVcCilCJ0QiwqXbhvCcuK7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/CgRCAj9cCkJCQMKlL/CQrIIiMj0lSsi68JGrqMK5PzZPVeqmvHx88J64g0Lwn5W0KvCQlrTIuiJh8J65gvCQjpDwnri5w6zvuasKD0IN8JG1qzrwlq2T8JCBkgoVQhPCpXFbL3zCqHA68JGwu9GoKCd7CjVCM34k8JG1mDzRqH1j8JGMuC920ag6IuK1r/CQoZVt8J2Up/CRpLc0wqwk8J65oiMq4oGEYApAQj7wnp+qdzx7PtWTKiXgqoErfkJlXCXrh4jihZzwnrmX8JCkgeC7hOCouDJdyLrwkK6p4Ki2XHLgtYzwnZSxYgo7Qjl78J64p/Cdkb3wn5W0XPCQvoFce/CWp7nwkbW7IuC6hDw2wqIle8KgYCdcYvCRpJnDp03CpeGFuyAKREJCKjRT8JCKsCXwkZyx8Jaqge+2rVwzN/CfnZXwkZGdOnts8J+mvfCetJ7guoImOlzwkJKhP1zwlq2ke+C6hvCQqJY0CgJCAAolQiMkdntw0ahx4K2H8JG1pzrguqXgv5ciN/CRjI/wlquwLuC9mQoPQg3wnrm34K+L4KiCSz1bChJCEPCQlbbwnZSO4KytL++/vTwKEEIOZmDhuo108J2Skjk6eTwKDUILVSbCpVJLKjXhvZ0KOkI4TvCQk4NK8J2VhtGo4Kmb8Jy9gVcq8J+DmfCfoILgqoJGOD0qP/CQpL9c8Ja+nifwmr++XPCepZIKKUIn4LqG8JCWg++/vfCRo7/wnou/4LWVbeGtryU9JCfwkK2u8JG2kVgqCjVCM3fIuvCQjIjhvYgi8JGakOK0rXwmLOG9mUt78JuFkvCxvqLhva09ffCdlZAv6qWvLio8bgo3QjVWUVkgKWDwnpO344GWRCJV8JCKpOK8tO+4sSrwn5W0QipZ8J2Sn8OLPSbDnOCsivCQurB4XAoyQjA977+9KvCRvKfhoZ/wnZS18JC7g8OGMT1f8J+ihn17bzfhiZY6Z1zwkKi4PfCRjagKJEIiPPCflbTqn5Phm4w/JF3RqMORw6EqYCbhj6Y18JGCvk48Jwo7Qjk18J+VtOC7nPCeuZQ94Zy24LadQS1I8JuFpCMuQlh7OifwkaS38JCtru+/vXt74Kec8JGPgj3itroKI0Ih8JGEiuG/o++5sfCQpL/wlrmGJTzwn6uoKtGo8J+VtNGoCiVCIyrgprbhvrZFadGoP/C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jNfCfg5YvKfCQlIQ8Lz1c8J+VtOGAieGgjeCulcOT8JGxgCYKMEIuLlvwnriBPfCeuIJcyLov8J+VtOKCjvCfnYAnaEIq8JG7qfCRhKUvZlQuLyc9KgozQjF7Ju+2kyo376yf8J2Qnsi6WVTwkI6T8JaqjOCroWHRqCrvv4zgrpkqM2/hg4fwkY+VCjxCOi7Cpe+5qC7wn5W0XvCRoKIn8J+vs/CQqKUvbeCunid0LsKnMUcuPPCQs5J2PfCRjYTwkbCDZj3hqrwKN0I1JsKle9WV4Kq/TiLwnZKb4aqTUfCRr7bhirJZ4oKRw7DCuO+/vci6Rj/jhofwnruxYPCusbgKPkI88J+tgsi6PC7Dknt74KaUJe+/lOCstcKxVi49JPCegI068JGNl+Cmssi68Jy+pHUy8J2EnPCegaXCoV57ChtCGeG9nfCUka0v4KaP0ahXIC/wnqSp8J6kiz8KKUIn4KmaJeGuguqfkz/hvo8h8J+VtDzSvPCSkbM/PSY/8J64j3vwn5W0CkRCQk/wkZuSYC7qqZh78JCQu++/vTTwkYK+YOC0qmE84K6z4Z+04b6R4aKle2Dwrq6aw5bwrrG06p+TeeC7nUzwnrqiLwo2QjQ/4K2h77+9JnM/JG/wkKim4Z2ib+CtoemAjDZgb23hsr3gqI8o8J65tdGoXOGciTrRqMKlChFCD3DqrIQ/J+Cmueqnk3gqUQoDQgE9CjlCNzkhPGI/IlvwkY2QOuqhmOG9nT3CpeC/l2Av8JGsicOQKnvgtZXgprc6XMKhwqVe8JC2jvCegIQKAkIACi5CLCV91bclwqUkPVXwkKi48Ja/pCoi8J2SmSTgsLI6XHU98J2UvvCRkY7wn5W0CgpCCGvDoFci6qesCjRCMnngrpngooDwkZqEXD9cOEFE4LCH8Jy9huG+s1zig5Fgw7gi8JGvty8k8J+VtOK8o8ObChJCEOG/jSpSXERf8J+VtMKnwqUKOUI3RmrIutaNbvCRioonaltnUe+9klV78JCeuSJRYC058JGZqnvhj7rwkK6uW8KleNGo8JKRscK9JwoqQigq8JCbnXPwnZSKbFUqJdeQKvCeuKcnJPCdhZ89aEFrUSBZQsi6LNGoCgNCASYKP0I9NyU876eA8J+VtGBfwqVm4LuY8J+VtPCTkYdI4K6DSzvDmuG8o+Cog9GowqXwkautUeKRhC0q8JCWtuqpjQo5QjfwnaifN/CRmZU/PWJEInTCpcKlbnfqrKYuYHrhoaEk8J65kVMkJ8i60ajwn5W04LCE0ajRqGwlCiRCIiQmd/CRjazwkYyAOsK/8JuEsjrXgyVR4q6lXfCRpJbvv70KIkIgL3tV4oKQ4LOVPPCSjagvYyZc4aqQ8J+VtMOhwqXigpYKNEIya2XwkY6ddvCfq7jhpZTgv5fwkLW+44GsLOCmtvCdk4Aqd3HhiY3wkZmRIT7DrXR7VW0KGkIYZVzwm4G6J/CflbQs4Kun8JGvlfCQrIsuCjJCMGNkNFzwkaSe8JCir8OkyLpgKmB30ajwkYy44b2dZuCrq+Gys/CRoqghL/CbsaLCpQpCQkAqKT9URCh3YPCdlI5E8J+VtF7wkY+VKcKr8JC6sVw+77+h8JGyiCYvwqU/8J65tfCdkbjCtOGzuVkl4Y+74KuAChlCF31bSDxgR++7szt2P+G9m11mTfCQoLdnChhCFibwnZWG8JCtvu+/vVdUZyRUP3VgKjwKO0I58Ji0huG/vHnRqHxH44a5J/CeuYk8XDLIum/hvJrCpXvwkISy8JGBluCvguGqmCVB8JC7hDDgvpYmCitCKfCQqIZIOu+/vTwqOmAn4aWhOvCQrq7ZmTrwkKiXSj1N8JOhse+/lWA8ChVCE8i6P0ovYCIhWX7gu5nwn5W0P0cKKEImXOCriElgKuCqh/CRtpYsX23gqJxV8JCMvjrwm4WS8JGxpeCosy4KK0IpaWh9OiTvv64hWD8qUvCWvbM88JCiqe+/vfCQmLPwkKi48J2SovCQkqQKMEIuRGDigbEm8JKSp3x1w6bgqrLwkLO+8JCZhSDIump06pmb8J2QrCY8YGDDmOGqkgoQQg7CpT3jhIbijpfvrITRqAoqQijhjJUqQ+qXqUjhv4Tfl/CdlYZV8JCAnuqUvlI9JuCwueKvhXsk77+9ChxCGsOGKvCfiYjIuuCmiNGo8JCOlci6YPCdkp8+Cj9CPeqsgSXRqFsiLfCegY3wm4WkL/CRipzwkZmm4KuJ76y+8J+VtCIiJiXIuuGwtCJgPTritrrCpfCQqYVg0agKF0IVM/CdkrYl8JGWncKl77+9ePCdkqI8Cj1CO+C7jXJ3JVwp77+FNmhEJyoq8J+VtPCRjLLqmKTqmbfqrKnwnoCcNyI88JCHnfCQgYvit5jigZHRqDpcChZCFDA6alhp8Jq/s/CQrptFXOGop3wuCh9CHfCRjLbRqOG8pfCQoIgkJeG9mfCfnLRz8JGPh35kCi9CLVwm8JGyi/CflbTwkL29JuC7hmngp4dIw65Q4KC5YeK0rSrgsrnwkISIe3s0JAovQi1QXGU/e/CRp40nyLrvv73vv73wnrinRfCdlJTwkKCAJ/CQsqPwlr6SP/CYpJwKQUI/8JChhS1b8J+VtGkl4K+x77mzw6LwlquxJmLwsJ2X4LOKJWEmVCxcOeOGvfCRmZIi4K6j4oKg8Jait35y6pqBCipCKDTRqDFcejvwkL27OvCQlpE6Pda5LvCSv6vwkbSN8JCNlS/hvZvWpHIKGUIXJSnwkZiZ8J+VtPCRtqjwkKiX0ah9KjoKF0IV8JKRqDnwkLS08J64gjTCpyImaXUuCgZCBCIiai4KOAoEbmFtZRIwQi4mXPCdlJBHPsOgKiR74LCP8JuyhkPwlryJw4ck1axQ8KeGmDzwn5W0ey/jhr4vCiYKCW5hbWVzcGFjZRIZQhfwkL+FLeCnneqsq++/vcK34Y2qdeCmnQqLB7oBhwcKygYKB2NvbHVtbnMSvgayAboGCh5CHGBdZvCQurA0VvCdjJjwn5W04LezJ0DgqYxsOicKB0IFXzzgs4wKOEI24L6mKvCRnJ/wlq6CL+qsg+K3nCQ6POC8tWpuwqpLVSTgtrRWfCYl8JCKkuaKlGjwk5KH77+cCjVCM/CbsoZKeycu8J+VtGvCpfCRj5di4YOH4YuA8J+VtPCRkq5fRvCRpJHwn5W04KmRVeGJjQoMQgoqUE/wn6GUIyYmCj5CPCTqrJLgt5QmJfCflbQ84LuGKvCRip3wkZGhPSYn8J+ht8Kv44Op4KGociU84Ku+RyAwIMOj8JuEsj1gLgoZQhfhnbNi8JuFkGDqrJXRqGY9PCYr4K2hQQo8QjrDtiRT6qa0PSrDheCunu+/vVjwn4mG4KyQ8J+VtOGNmDxsYD3wkbKsJPCQlrlgcyokwqrqoYB7PHBJChpCGC578JGHsz8qe+GKs+C2g/CRjo5Q4aKONAoIQgZVOmZCwrMKHEIacVc68JCWg+CtiHsqbWBfdVLgp6Ai7660XFwKRUJDcPCeuYIq8JCklHTvv73wnZGnefCeuLtsOy7wlr+x4KqDXOqfk+Cuvj/gro8m8JGBlUhGOtiI77+9J/CRtpclJT/Iugo4QjbgrrjgrachNzLvv5PwkY+XwqUu0ag/JOCuo33wkYyvwqV4d2R74a6WJ+KtvPCQgIs6RPCRtLoKEUIPIlzwnZSU8Ja/pGXwkYqLCjBCLioi4KCVS/CRj48iVSXwn6GSLT/qoLF7w6/gqqw6TfCSkbN7Xz8uJ+K3sUJcJ2QKF0IVPfCehY7IuvCeuZtXwrkq4LGg4bORCjFCLyTgt5jhi4DqobIiRO+/ikgh8JGJgVDwkZi8Myc6JCLvv70r8J6fozxeMlo/IngiCiBCHjxxe8i6PzVceeCpkVzRqF/wn5W08Ja+mMO5PeCstwo5Qjc9w7Q68JCBmSUn6pWva8i6bPCRmaYuOj954LKHXGAq8J6lmE/Iukx08JCwiPCeuYIk4ay9NyElChFCD3vIuvCRiI7gqoN0aTwnPAoDQgFcCixCKi97Ol46ROGkpu+/vfCflbR0PX3it5EmJGDwn6KBe9elKS7hsbgs8J65hwonCgRuYW1lEh9CHWou6qmgMOOEiNGowqXwnrmHMiRc8J+CoSrwkaO/Cg8KCW5hbWVzcGFjZRICCAQK2we6AdcHCvAGCgdjb2x1bW5zEuQGsgHgBgovQi06WuCgsGVFwqXwkZK6JTx0Y+CvivCeuLvwkJage0fgtI5JXOCtl/CRnLfigboKF0IVIuGnkybRqCo9P/CdlJLitqLwn6K2ChRCEnPwn5W0TCRG8J66uzrgrIFcYwpBQj9cw6tHyLrgoo3wn5uwcPCcvpovw6rgr5An4aOr44eDIkjwkKCI4LGj8J+VtHvhvaHwkKCdJcOfN+C7gUdgaDwKPUI7wqVU8J6Ei/CeuIEvZCUv8J66ovCYpZI6JPCcvLU5MUdc4LGHYC8u8J+rpuqgt++/vfCSkbJyw6XhoaoKL0It772I77+977+9IkjDsfCWrZjwlqyKwqXwkaSBTy8q8JCjr/CcsJVY8J+VtCQqCiBCHm1ud/CQpKDwkJaOa9e08JCetsKla/CWraF3OCo6JAoTQhHwkJehYyXvv717fi0o8JGKkgoEQgJIXAotQis68J+VtDrgtr1c8JC0giJlKsi6JeCypHvgqLJg8JCiqnY8ePCflbQ/77+9CjpCOPCSkr1MWjxkPCRp8J+qifCQhpo/4YuI76yA8JGIvfCur7DwkbKzwrI8PSXhi6fwkZa/Pybvv70jCiNCIfCQoLxaLsKl8JCnqG3wkpC+4Kao8JCWu/Crn4Xvv73CqgosQirgpqrvv73RqGEv8JCdoUtIXGM8ePCQp4zwnZS0LkRMQPCRi7kuY++zkUcKPUI74K6aTvCfobdGUGp4ZXHwkayAbPCdi5Ar4KqH8J+gnSZc8J+VtF41dzrgs57wkKSK8JGxvi5rYyomPC8KD0INOifCqy7wkI+UPTUhJwopQidwSkvwnp+14aWAY+GKoDUsKvCfmLE5P+Cxh/CQgrk/JmDwn6i2LiQKHkIc8J+JoWYnOkvvv73gt5pZ8J+VtPCflbTwkZKvVwoWQhQ/0ajqn5B5QfCRsoPvv73wnYuqewoqQijCpjx276qt0agqcSTvuKbguoLwnYSjMGAqOsKlyLrhs5M/8JC6osKlCj5CPHLwnZON8JGogCrCtNiv8J+VtOCxiOC1o+CptVV68J60rfCQlZ9cbDbgv4o88J6yg++/vSolYENH77+9NQoMQgp78JG8gPCRkqEvCg5CDPCQjZFd8JChgS86JAoTQhFjbD3vv70uJW174Ke64LGVJgonCgRuYW1lEh9CHS48R0vwnYij77+o8K2CnEjDkPCRm5JQLiXwnZmsCjkKCW5hbWVzcGFjZRIsQiprOuOHs+G9mS7DuyZg4b+C8JG+sEp7wqXwn5W0Zi478J6ApPCWv7DhspQKmAu6AZQLCvQKCgdjb2x1bW5zEugKsgHkCgodQhtBb/Cfh6jgp6LCpMi60ahQIiYsYPCehYPDkU0KDkIM77+9JuCviG0m4YqyCjNCMfCRpqEx8J+VtGDwkJaU8J+VtPCcvIvgsZVgRfCeoIbgu50sQiZoRvCQqJJWcfCeuYIKAkIACitCKfCQrbrguoJzVjku8J+VtCPCpeCuqiYlPeGcjiJB8J+VtCTwlqG06p+8CjdCNSddP+CvhyQl4KaMc3vwm7GwKuC3j/CRtpDwkKiXL/CQjZ0iLj/NvyxNMu+/lfCRpIk/LtGoCiNCIX0qakNgV1FxX1AnJ+C6hEMuYO+/veC0hzomIiTwlq2lPAo1QjNaaeCmh2l1wrU/4YuOLvCrva1gSTRN4KqCTCTwkbSkWntN8JC1mVNkU2DgsJJ4WvCRjIwKCEIG8JC1slIvCjxCOtGoOTzho5FSYO+3j27gt6zguoHwkY2nLvCfqaw74rSn8J2LsiR0PV/qo5Uie15XRfCRtKI6L/CeuIIKQUI/MSVt4LqEJeCyuS/gr5fwkKmAYWXwn4mF8J+ghPCdlYZg8J+CtPCRm5dZOndaIuGNrHw18JGPhXlv1o3wn5W0Cj9CPSx88J2QoWAqJD1cJvCdkqIrZ+GgiiJs4KiP8J2SpT8kZfCRjaLhvIvgqLk8yLrhvZDwkJOa4aCUIvCegKQKCUIHPC/wkICCJQopQifwkY2xLi7gsqhgZvCdiI8iXCPgrLLgp5dgfPCeuqJsJyF78J+VtC8KPkI8L8i68JC8lTTCsfCQqLDCpeC3gPCQrIgieygvID1cIvCRhaXgtr05JuCpiCp+8JORi9Go8JGxkvCRh5MvCh9CHcKlPsOqJTxWUiUv8JGBmT1f4KmC4LGBMsi64reRCiRCIu2fly43yLoq8JGyqsOvYCVaLi886qmw8JCkv3zwlrWKJXwKCEIGPELwn5W0CgtCCVrCtzouPz97IApCQkDwrK6STT8o77+94bCG8J+VtCThjad7N/CfgKjvvZLqnJDwlrqY8J+ImeKtu/CRjZBTRdGoUO+sg1kkVcKlw7BcChlCFyLCpeCuglQk762DceGJneCzlfCflbQiCjtCOSdF8J+JkC01aPCek5nhnJHhiZvgq5Am0ajwq5yVaj9cL8KlP/CuhLXvv73RqOC3qScmJSU84Y6IJQoeQhw8LyYu8J+qhvCYp4EnOlzwnrinw7h7WXvhvb1gCjFCL0cqVzok77+9MeGJks+A4Kqb8J64tfCQuavwnrmd8JGMj1zgsYTiv7o8JjsiJycgCjpCOCIsyLrRqOqpjdGo4KmHXTB7JyLgqrLgpoom4ZyFcvCQioDUrSbRqDrgtrUlYDrwkaa76qyG4Y+hCiZCJPCWq7Vz8JGFn+CxlS86J9aPIlRgKci6PWDwkJWYJOCukyXIugoRQg9B8J+hlXDwn6CIw5Hgt5YKCkII8JCWuETgvKwKCUIHSjpHw6w9IgpBQj/wn6Kw8KybhSXIuuCrgPCWrogqwrVY8JCMofCfr6ZLdtGo4LuYXCY2Xuqdg/CeipbwkIGE4LuM4rajXPCQtJsKAkIACjJCMDLOjFzwkb2QLibwm7Ke8J2RkCdAezjwlr6U8J65u+qphMK7P/CRmq49yLoiP++/vQo7QjnRqDrwkYuO4aqE8J+VtC4q8JGMtfCQlrnwn6+k8J+VtHDIusOddvCQoLxo77+98JGOi/CfoqzgqrIKMUIvUMObJe+/vT888J65m2AlP0BM0ag1avCxkpoqLvCRl4rwkISALvCdqqtfLvCflbQKCEIGP8i6Ljd7CiZCJDHRqOCrufCRsqvwkIu7NfCRl4/wkIC8yLoqyLrwkK2t8JG8mgoKCgRuYW1lEgJCAAoPCgluYW1lc3BhY2USAggECuEaugHdGgqMGgoHY29sdW1ucxKAGrIB/BkKG0IZ8J+VtCbgqovwnrmkJyLwnoGS77+98J2SswoMQgpaXCJsJWk+e1IqCidCJfCegKlg8J+rsD9cajDgqZ49O8i6JMi6Je+/vfCRjIzvv706JSYKBkIE8JGKmAokQiLqo5h74YaA8J2Vj3vIukc/XynCpXgl8J+EnVEqIjoqKl8mCjRCMmnWl/CQnrrwnZWLdvCbsbLvrpvwkbCH4K6C8J6ApPCdlL57w517JOK3lvCeuKnwn5W0CjFCLyI9dT1CIyRdXCTvv7wqP3vRqPCdi4Fi8J6ToPCQqLoqJsKlXCRCLjHwkbyPKnJHCi1CK2A7XjwkXPCRioxEJeCvjFwz8J+VtC978Jiqn3vwlq2cJ0rhvJjvtrTgrogKQkJAI0zCqX7vv70u8JGOqk/wkaeeP/CQopg84ainP/CQto7wkbS6JfCfoJFOY/CRpInwn5+mJSTvv71k4b2b4rasIgoCQgAKC0IJJn5jLTXwnZSgChxCGuqSrVwlJiXwkLaOVFV0Ijbri6QnSHtgOtehChVCE0zwkZiZ8JCjtUsiyLrgqLguyLoKMEIuNHth8JGRj/CpkK3wkK27VuCpiGVD4LCkVci6XOCxlXvito4uPDpdW1/qpbc5awo1QjNQJz3vuaU94K+X77+9Jl0oPyrwkJa84Y2Oe9GoJCbwkLWvMeGBrDpc8JGpvOGcjyTCpSYKGUIXP+C6hO+/hfCetIrRqCYl8Jarm/CQoJIKIEIe8J+dotGo8JGMtcKnZi/wkKCEKsK7JPCeuZc34K6fChpCGCY68J6AgPCegZs5ZPCfiYLRqHHwnZKvJQocQhp7PfCQqYAw8J2EiO+/vfCTkYDcivCflbTDvwoFQgM5OjgKOEI2dC3ConlQ8JGKgiTvv73vrYRcIvCRtoNI4Lan8JCgiH0kJeC+ivCRgKJU4LKgKC4mwrjwnqOICj5CPGTfijrCqMi6J8Oy8J6fqzc8SuCruiTgqrnwnpe/cydn4LeqVz06wqV78JCqk/CrhKkn4LOjV8Ku8JCekQoJQgfwnqWfIkl4CklCRy/wkYqd8JGWqvCdlYbwkZy08J+ip/CeuYfwn5W0SFhp4KmeKC/wn6mh8J2SpVzwn5W08J6llu+/vTfgrYcw0agkRic5QyUvChpCGPCRipY98J2Ukdew8J+prPCeuKRs8JC0sQosQipE6qyCJMO6Kkc+8J28qSpV8Ja/oDMu8J65ndGoXPCfk7xp4b2QJV57e2gKQkJAKibgq6LgoaAu8JCzpSXqr7XgsYfgqLYv0ahv8JSUo++/vSTwmr+58J+JpOG9ofCflbTgsL8n4recyLrira0maQoLQgnhpqPvv73vv5cKREJCPPCdkrBcKl4hOsKle/CQq57wnqWV8J+Ei8i68Ja+nuKBlCJaMGByYPCfo4Bg8J2VhidJ4LOz77+9wqUqyLrwn5W0CgdCBfCRhqhmChRCEvCWrbA9UjjVoj0qfeCth+C1hwoXQhU8THxfOS974bCtWfCQj5I6Y/CRtbAKKUInJCcm8JCAvDrvv70u4KG54Y6EbPCeuYtx8J65lCMnUcOGWfCfopt4ChtCGfCflbTgqrM84LueJHwqe0lg8JCSpFRYwqUKAkIAChlCFzIiUfCRtIht8J6fuHvgs6fwkJaV4Z2mCiRCIj7wnLyje2bwkKmIJeqog3vwnYKxPzw88J2ctCFcJvCRjqIKOkI4auChnvCRpIHwkJ6B8JCGoOC6gTzgrbTwkaSE8JCihC4qJiRw8J2XgCTwkYqTN/CQlrbgrLI8TyIKNUIzekLgt6lu8JGRnUU9Ki/RqCVg8J6Xv2Pijp/XmylLLipj8JCpiFXwn4OcJlHIumRgVz0nChRCEsKkM+qgtyVfLzrRqHLgs6BgLwoPQg1K8JCzoGDwkJ6AKnskCgJCAAo9Qjvgu4Zn6qCHKiJA4Ki8ez7wkY6LeibDnCNAI8i6dz/CpeqskvCeuKRc6pm88JG0uvCRp5028JG1ui8mRQoWQhQv8J6Eo13RqDDqrKhM4raiQsK/Pwo6QjhRU3slWCLwnqWQJOGLgPCRlqxrL3Y84K2MOmDwn4i48J2qnvCbgJRxaXXqppw64aiGKndK8JGIiwokQiJXYEfgt5/DgVk/8JGMuFx44K+X8Juxqj3vv7xK8JCohtGoCiNCIfCehY/wkJel4a6kYFAu0agoVeCzh8i68JCuqe+/vVxHPAoXQhVg8K+hrNGoKfCRnKJ94KaQeydmdmUKIkIg8JCoqyPhnIPqrIzwnri7PfCRpZPwnoCBPSTgtI8uKToKGUIXZPCQtLDwnri5P/CRi7nwrrS84K6TOmgKDkIM4ayNOsONP/CWvKJ7CkVCQy8x8JG1qPCRsIXgsL3vubTwkKCIJGDirbzwkKCxwqXCu/CflbRd8J2cvPCfiLHwkISB8JC/sz9OIvCRtaLwm7KTPHsKFUIT4rah8J+VtFhOJiQ94K2VRjXCpQo4QjYl8J+VtCTgr5An8JGklSrgs4Ik44a0NfCknJdDPe2flyU/JnvDtcmeUD3gr41gwqVtYGBKOiYKK0Ip8J+VtOK4oj1aIiThnZJ76qmWfEs28JyzkCTDrTHhg4clPOC3iuC7mFwKDUILJfCQu4Lvv709JncKDEIK0ajvrYDRqFZmJAonQiVK76yEIn4v8JGPlyoiL+CgtuK0rVzgq4nhv7fwnoWGXE1obGU9CjNCMfCfiaHvv7064LOW8JGNsuGsguCtovCQto9sQyRw86CEjCIwPOChlU9MJzvDsfCQi60KMUIv8JGKgOKAuD9j8JCgiEDvv7wlw7g88JaqgMi6fGBgL2AgLsK78J+HpvCQtbt+QS4KNEIy8JGNg8K48JCWqW9c44iBNvCQo7XDs0zhvZ1g8JGNkOCovMOx8J65j3R7LcKlL30uWj0KLEIqWvCRjq7vv4dKXHkkLmjOnTpMJCQ90ajgt5Ym4KmaIuGon/CbsIHwnrmSCgJCAAofQh08YD9GYuCxjMKlI3slXPCegKjCt8Kl4LOVLz0uIgpAQj7wkYORIvCQpL/goaHwnZa9XVTRqCxgYPCRoJjwlrqM4Z2z8J2Su0M9PfCdnqo60ajwkY66OiLwn6qgyLo5JwoKQgjIuism8JatmwocQholJOG9myYkcFzCpfCQoLx1Z/CeuZLhv6M6Xwo5Qjd78JGkrnwk8Ja9sXF1PWJE0ajCpWpa8J6llz7wn5W0d++5uSJJ77+9PMi6Jzs0JvCRsLl6JdGoChFCD+K2iyk9OfCRsZAy4LetZgo6Qjh84rek77+98JCnmSQyL0wvafCRjZAvdSrwkYuw8JCypirDgT9nw61k8J65pMi6LnA9XMKl4b+jRAodQhs5XE/gsIUl4Y+88JC1hfCQhpPqrro9XH5iIGMKEEIOSfCeuLTwkY6OYCLIulcKMEIu8JCnp/CflbTwrK+S8J2nr/Ctj53goIjwn6qAPO+ujn55ez9c8Ja/oS5qJOK2ogotQitv8JCnhjrwnoqQ4K2x4KmZwqXhp5jgqYzIujHwm7KcXC7wm4Ks8LG6p2A8CgxCCkDgoZ7IunwuVnkKLkIsXMKmYiY8JuCtnMKlLkbCpSbvuYrit4DwkIu4SeCovFFPTyFMYCLgqYjhg4cKF0IVeyrwkZGXQO+/veChnjg6JPCehLU/CgtCCeCtiCc+8JOqvgolQiNaJ+Gqu/CRp5fgvovgqoLCpWfwkK2g8J+JgDzwnrmf8JGPmAonQiXCtS7gsrk2YDwkJGDgq7Hwn6mi8J+VtM6M8JGHsMOE6qe9JiVcCgVCAyPRqAodQhts4aWN4bKr8J+VtGsvJGh78JG0veKGpfCUiaoKMEIu8JGwgeCumXvIungiPD/fkMKl17Mu8JGBpcOT4K2cP+OHmCcn4K+XYkJeaeGdiwoOQgw1JScmIy3hpYDCpTcKNEIyyLpSVuG/i0g54LWo6qe0JT/wn5ueYGDigIjwn6uZ8JG2kfCQjprwkYOXwq4u8J6iiT0KKEImQERxOvCRspRQ4aCFYMKldT/CpT/gr6fgv5Ekwr4v77Cc8JCgiEIKFkIUw4Xhv47wkI+IJ/Cdkrs68JCkv1wKN0I1J/Cbsocg4KeM4LaD4LOmyLo68J+VtGbgo4Z78JCumSLjh7jIuvCSkK7wnrmSPT0xSS/vv70KAkIACkJCQMOBJeGqvEJ18JGmoPCflbTCpT3hiZzwnrSIQi7CrOCyjCIkUDXwkKi4e++/vWDwkZmp4q6GyLrXs3s8ei7vv70KDkIMLOK4lUPwn4mBe8KlCh1CG3U077+9Q2A8cDrwr6a/RD0n6qWi8J+etVo6LgokQiIiJT/wnLyzJyLitpRrJ/Cfo4Em44Oj4Kae7Z69L33wm7G2CgdCBVJrQz0kCh5CHPCRhp8v8JCHszjvv73wn5W0PScnOPCQi6Thqb8KOwoEbmFtZRIzQjEn8J+fkmzwnrmJ4Laje/CegKg0beC3tD3wrq+98JGBs18/UlB7w5Ml0ajCpcK11oc/Cg8KCW5hbWVzcGFjZRICCAQKvRG6AbkRCsIQCgdjb2x1bW5zErYQsgGyEAoNQgvwnZSHPz3wnoqoJAoZQhdg8J2UltGoPO+/vXs84K+Q77+94b62Jwo8QjpByLrgq5BNXvCav71p4Ka4czBM34LRqPCRv4lg8JCTsfCQnaEvOsKl4oGxeDzwkaCJ8J6KoSpBKcKlCgNCAV8KHEIaKuC6t/CQlKfwkKiGZfCQgJ/wm7Kc6pmmLyYKMkIwPPCflbRG8JG0iVPwnp+8byZUOHXwkaOqJCfwnZKr8J65gs6ZJn0n6qC1PXfwkYejCgJCAAoYQhZIYSooImPwkbap8J+etuG8nUxK4r6hCi1CK/CeupbwnZSlP8Kle+GgkiXvv73wlr+x4aaQ8J2SoijRqPCbspw7RPCQqJYKFEISJ/CflbQ68J67sO+/vfCbi6pPChlCF2DascKl8JKShlzRqCLgro9mWj/gtJB7Ci5CLOCnl+CxldGow5fwn5W0PPCeuYngs51oUCXRqMi68JGklfCbhZXvt7bqoKhgCi9CLTzwnqCzdypn6qOONTpgbE4u4aeEL3s5ITXwkLCNSvCfoK8m4Kaw6qyJUS7CpQo6QjjwkbKl0ahgJ1wobS8h77+98J+rmOCxmD978J2EnPCQoJwn8JGKitGoQ++tpHzwkYyC8J64nD9KXAoGQgTwkbanCgJCAAo3QjXgrYcl8JarnSk/OjJ1wqp58JCAvOGqplfwnZSQ8JCGl1lgS2jwnoSrw51DI21GMPCeuZkmXwo3QjXwkKeuN+Chs++tg/CQrprwnrqa8JC/q/CQp54n8JCohSgl8JGZl+G+vfCfnIPgr5Dwlq2keQonQiXvv71f8JKRrT3wnZKrUuCmkFzDtyp74pGF77+CasKldybRqDovCj1CO/CQlongt5Z9JH0nJuCpmfCflbQuw6JG8JGFhdGoIjzvv73DiC978Ja9hfCRmZBg8J65kTrgsKdg4b+PCg5CDCXivojhprnwsKCHKgocQhon6q2Y77+9JOqirD3grL/hoJLguoFSLuC9gwoOQgzwnrmqKj3wkLW9eyQKB0IFJPCRgaIKAkIACiJCIGBcfCQlKifiro9c77+9Jj8x8J65lDB8YCY/8JCkuS4nCi9CLSpt4KWNyLolwqXgqrJD4L2p8JC2j/CRl4E94bKq4Y+4J8ORKMKlLuGPu++/hAoGQgR7P2QqCjVCM+CnjWXCpcOJfOCsv+Czi8Kl4b2I4LysL0tTJW8qRVUiwqdvwrfqpaHhoIfwnoWOdcKldwoIQgbRqPCRjbIKEEIOJ/Cei78mVUlX8J+VtDwKF0IVPCXwnbyn8K+ilCfwk6ec8JC+s9SvCjhCNn7wkYa4wrwnQTwuJfCQmLXhsL/wlqmB8JCaoD3Iui4h77+9PPCRjbFSKuCwgsO98J+gq19mPAoPQg06L/CWrZPXpXPqoLk9Ch1CG/CRi7Uwc+K2qVEmPM6H4ZGMKXLwkYOkJjpjSQoEQgIqZwpFQkPwkL6Aw5HwnoGEXMO8PSXho4TzoISiMS/wkKS/KvCWtbl78JGNnfCei7/it4kiJyfwnp+rJ/CeuanwkKiMKCAl76yvCg5CDC848J66h+KAnOCogQoVQhNgLuCqiCVr8J6FidGo8J+hklwsCh9CHfCRjZ4q6qC3J/CRjLk/wqso4YmMLu+/gibwnrmnCg1CCy8q8JORiDpg4b+oCidCJeqhsiXwn5+wLlriupVQIjR7IjouclxC8JCAivCfpYTwnZWGwr8KEUIPfO+/veGfsuCznvCQkbU2CilCJyI48JCQvj0v6qS68J2Sn+ismcKldCLvv70mYC/wlqmZyLon8JuFlQoHQgXwnrqpIgoCQgAKPkI8IfCQqKFeO2A9QOGPvDJg77m00ajwnZaRffCQvZZWJ+GfoPCRg7As8J+VtOCziD17U/CWrIxLJvCRrII5ChhCFjp87Z+lTlwnUfCQlrBN16gpL/CbspwKEUIPwq7wkYyFWyIlVPCflbQ/Cg9CDcKl4K6F8J64oiTgsZkKDEIK8J6BhF864K6QZQozQjHtnrhge/CbsbV5RO+/vSss8J+VtPCfq5bqrpTCq+K1sOCovE7wkaS44KaF8JGctNGoCihCJu+/vU3itrlOYOCxjeCnoSpp8JC1riQm8J+rk8OpW/Cfrqrwnp+qChlCF3MnLiLgrZ0v4reN8J+bojoq8JCphEQqCjFCL8i6Oi/wnrin8J65i+Kujeqmjk8j6qmRZy7grqnwnrqGK1zwm4SyITrgqpfDpXtKCgNCASUKIEIe4L29yLoq4py24Kavw5UlcO+/veCuiCXgtoPwnYSjCg5CDO+/vSUkJ8KlYOGkswoQQg5BJfCWv6RNWirgsrhYUwoJQgfhj71q76y+ChhCFibwkY2X8JCohj3qoLE/KPCflbTgrZUKO0I5Ji4kZ1nDlPCeuYk58JuFp+GzguqshOCrkO+/vS5Yw7rhpbI8Kjc98JuFlT8mblzigqVGJ8i644aeCilCJ/CflbQh8JGMtsO8NcKl6qaK8JGqvi7wn5W0UXtrRyLgqZEu8JG0ugo2QjRvOfCRjagu6qmHJeGemOqulcOgXz3hpocu4LOj4KCs4q+g8J6FjiFgPOCrjS7wm4WVYD97CiJCIOCxvWAm8JCFheGdssOuLeGhrndiI/CQjJU6VOGFtzpKCjdCNdGoQCLhoIJS4YuVJDxgw4bIui/wkLqs8J+VtPCQoIh6SPCRmaVNPfCSlJ15J0bwkIC/L8i6ChxCGvCflbTwkaS38JKToidN3og54aeawqXwkJ6LCgxCCj3itKc/4Z2TPCcKCkIIU/CRr5PgqpEKNQoEbmFtZRItQiskL3tf8J+JgCfwn6qxJTTwm4SOPS/hvZt7J8Op8J2EksK+YO+/vVYs4aWACjsKCW5hbWVzcGFjZRIuQizvv73wkKiX77+9WSXwn5W08J+VtOCzrzxj4b+6KisiPyXvv4JZfPCQv6zRqAqlE7oBoRMKyBIKB2NvbHVtbnMSvBKyAbgSCjdCNTxOXDnwkY+R6pWX4b2dd+CngUw/8JCNoGDgrIPIulzgtI89Y+Chmy5SKivwkKC8X+K2pC9UCg5CDFzqrJbguoR38J2VgwoZQhdDPSXCpfCRsKjIulPNvuCssuCngF3CpQpHQkXitKLwm7GSw6lce/CRpInwkKCCwqVuLmvwmr+9JX0vJvCeuLngtYbRqPCQvYDgtr3vv73wkbKP8JGMrT3wn6GUQeGEnGAKGkIY4LWO8JG9jPCWqaQqyLrwkaqFPPCav71JCghCBjx78K6xpgoRQg9rWCbvv4vwn5+GYEMhaCQKAkIACixCKvCespjgrL4kNGVp8JCHskbigLbwlr+kIiXIuvCeuLnwkKiV4K62Klw/XgowQi4mJvCegKlcJuCpiFxL8J66p8OB8JGPifCfoLTgqIN54KmH0agvwrk7yLrwn4iVCghCBvCRpInCugopQidCJsi68J65gvCbipTwkKiGwqXwnLyidVs58JatoPCflbR6JDzhgqUKH0IdQOCwjsKlIuGMrXQtLeGJnD/RqD/CpS5+YuG/gnsKE0IRImA/8JG0iU43PSw9Ll1Awq4KHUIbVHAuKuK3g3vqn5hNe2wjINGoa189wqVr77mwChxCGlw26qyrPfCQl6F9IuCtsFw68J6kgWYkwqUmCiFCH/CRtZQwJPCRrIbjhIgqLjooPSZNQe+5qyYlXF3gp60KHUIb776oLvCflbRmI/CRsoDwnqONeyLCpeC6pXRcChpCGCTRqPCRpLgvb1/wnrm3YEnvv7xD4K+IJAoxQi8477+9PX5qOnnhp5fvv71gJsOq8J2Ds/CRorfgr4fwn4K48JCWm/CQnaXwlr2hJApCQkBc8JGIqSbgtK9XyLphJvCQlqXqkpxC8J2SntGoPyBW8J+VtMi68J+VtPCRiogiUj3hspnwn6Kiw6VX4Lql4LWpCgxCCsi68J65reqimCoKIkIgZ/CeuLsk8JGqjvCflbTwmKWiKiZXXEM8VnvDgcOkwqUKMUIvYHove8i68JGFhCppJisnbXpv4Yisw77hvZlBJvCel7kkTvCflbRC1o17IS43Oi4KGkIYdibhiZjRqE/gtJfwkKSSJifgtoPwm4WRChlCF/CRpLwlOMO58J6AhSXwnou5P1fwkJKDChVCE0d8dvCek6/vv70/yLoiJybgroYKFEISLj8q8JG1hGDwn4ivXHvwkL+LCi1CKynIuntNPPCQs5Dwlr+w4LuIJmAi8JColy7wkZGewqXhp4Xwnou/WkdTNHcKHUIbOUvDs/CflbQ6JGDDheCmjyojw6gnTjRAwqI/ChJCEC8k8J6AnOCpnuqfmfCflbQKGEIW8JuynvCRpqcnyLow4LaI8JCdlS8tPQoGQgTwkIChChBCDm7grolg4aqU8JGOhGt7CjBCLlZYKPCRiKLIuu+vo+CgsD9TcC4/YuGKmD3wn4mQdOK6iz0iIvCRtLokbvCQo7UKOUI38JGOi3vgt5bwlJWEfcKk77+9cVdbbCjhj7jvuahQ8J+VtOCmk+GLjj0iffCRg58/8JGkluGJlQpDQkFU8J+VtFvgsZrwnZKl8JCWs/CflbR3yLrwkICQKOCogk8q8JC+s3skSFfhnargtoJJ0ajisrwn8JC8ieCooeKCkgoTQhEuKPCfq5zwkY2Q4KyB8J6jkwo4Qjbvt7Zs4KKC8JG8ne+tgSfgtr3ivIrwkaiIefCflbTwkJWwTfCflbRPXG3RqMO98JCAt09xRmYKK0Ip4LqlP8i68JCWiuqlols6PDhUPuqphic2XfCWraDhjatTPOG9jCPIulwKEkIQ4LWXP/CbiLbwkKO1yLrRqAorQil344GcIj49PzMwL/CflbRg8JCArjI68J28mnAg4KGhwrZRyLpX4aKZLgoWQhTfm1Z2Im7igr80Sy7gqL84SyI6XAo+Qjzwn5W04b+l4aCIJWl78JGGpcOn8JC6sD8lZyfwn6GjeyI6JDc/e8KlNOChjvCfgqnCpXPgqq1d8J28pXgKBEICSSUKA0IBUwpEQkIm77+90agi8J2VuvCRipkiR/CeuZ8lQPCQjLzCpeC2rS8l77+98Jq/suC1iy5E0ajwnrmJ8JGjhSpg8JCFt3Hvv70KAkIACiJCIDxIUOGhn3vIujwpOPCeuKfwkYqMRSTvuakiyLrgrKc6Ch9CHVvvv73wm4Gr4LOGOi4k77+9NX7wkYqk77+c4KybCihCJj7goarqrZLIusKlclpgeOCumX7IuvCfhqxv4K6fWjrCpVbwnri5CkBCPvCWva3wkamsMu+/vW1i8JKRp9GoPUTwn5+wcD/goZs+YvCdkqIuYPCQvrIqe/CmqpBD6qWALz8g4Kyy4aqiCjhCNsK6Uu+sukM/ekp+JS7gq6Phq4Tqn7Jc8JCovyoiw6/RqClg4YyTKvCeo45g8JC7guGinMOBRwosQirwkKOjZFbwn66ZXPCQjoou4LaPI+G9mz1k8JGGqiZl77+9LybIun5MOWwKCEIG4K214KSqCi9CLU7hibrRqOqfli7CpeCzjWHwkai28J6Ls33wn4Ga8JCNqOGJmMi677iDPyTRqAohQh/wkZCaP03wnrmCQdiNVPCQu4LvrYNg8JatktGo4LGdCgZCBOGdriIKB0IFaPCQrq0KP0I977+9S+GXlTzwsaSdJT5QLzMie/CdqIg8IvCdjaR7MjHhioEi4aa64K+G8JCehCovKsKlRvCRioQ38JG7ogo/Qj3gqq0v8JGPhfCfiIJc8J+HpjrgtY9wY3gmIiPgp4xg4KaQSSJT4aqIUfCRhrlc4Y+kJHDwnZKr8JGMqy4iCilCJ0QnLyom8JG2kOC6pSV7bzs8wrbwn5W0JiZ7deCniDQkwqXwkKubIAoIQgbgrZ3qrI0KJkIk8JasjeKApEDwkaS44bK9YD0i8JGwgzoi8JG2pS8qRnRcb2kiCgVCA2wxPwozQjHwkaS8POK2lsi68JCojMK74LWG8J+VtCNMLuCpjEwxel7wkY2Q77+94Yqy77+9762ACiJCIOKAtCNeKlzCpVzqp7TDoyRg8JaEmNGoJPCen6skY1wuCi1CK8OSLTzvv71TP0LCpfCRgbM8Oi/hqq3wnoCEP0TgoYbwnpOX8J65kvCQp48KLAoEbmFtZRIkQiLCpfCflbTwkIGYPVzRqCpn0ahQKi8s8JCKtC7wkZivJS91CiYKCW5hbWVzcGFjZRIZQhcm0ajwn5W0eOGsscKq44CcbfCeuYI6PArtCboB6QkKqQkKB2NvbHVtbnMSnQmyAZkJCkBCPlrwnYyePTNpT9GoYuCqgSoiyLolaz/wnZqXPSYl8JCiqcKl8JCpgPCflbR78JGjsfCRrIc6QfCdvIHwkY2oCglCB8i68JGNqCIKHUIbYC7wkY+YJuC6mPCdi5HIumzwkI6t8JCovy88Ci1CK0nIusKl4KqDJl3wlryqVD8x4LGr8JGOqz3qlKdK4b69wqXhqb/gqrnvv70KIkIg8JG0uvCRtokxIjzwkJaAUip7wqXqn5HigKPIuirgt5IKI0Ih6qyTKlciJO+/vS488J6Xvypk8J+VtCd6YPCTrKbgsZgvCixCKj0lJuCunifwn4K8yLo1P+GovXdc8JGNl1suae+3uj8mXT1h8J65slwmPQo9QjvwkJu/K/CeuKfgr5DwnoCq8J6Tr8Kl8JCWmfCRlqNGViXwnZSJ8JaElPCflqjwn6ymJXfgsrAvImDCpQo5Qjfwn4mRLPCQhqDgtrUvfOGih0XRqEs98JGKjOCmsjo/duK0gu+/vT/wkauF8JG8gV3qn5Hvv70iCjRCMvCRnKngropgTPCRjovgqrU/77+9Ly7CpWDwnaKn8JG/n1xLR+C5gO+/veCpgXo84Yq6ChlCF+CtgTLwn5W0IPCWraFy8JGRnihlady2CjRCMi544oCIJCYoSm/wnZKi4LeU8JGIt3tc8J+CpSA/YHNTJFzwlr+kfSo84KmN4aqy4b6cChVCE2nwkKC3e/CRhLYuJ/CflbTDmGAKIkIg8JGMi2Lwr6OE77+9aHdYw43hipvRqMKxYuGmg2DIum8KBEICNWsKKUInJSY5Pci6c+K2s3p7JFzwlq2WKvCeub7vv6jwkbynazsqUilQ4KGeCgpCCO+9uOCtiCcqChZCFGvwn5W0J+KyiSct8JCWr/CQgJVMClFCT8i68J2RtzYv4raF8J2SpsKlOvCRtL3ho4TwnqGdJOGWkDzwn6Wh4K2d8J6Fj23hla/CpVHwkbSJVi/CpUPqp7vIuvCRvL7wkLWl8JCnh3YKDUIL8J2ouDzIuvCQlpQKDUIL8J+Fm++/vTzvv70KA0IBJgowQi4iY++5sci677+9PPCRrIDqpaDDizZ6Jns88J+fqyfvrJY/JPCRjJTcmOGKiyx0CjVCM+Ggiyfgqo06UmJ7KvCflbRgKmY8M2BxZcO58JGyqVfwkKC8wqVI77+9T3DwkYqF8J2SogoLQgnqp7zwkY+FWT0KN0I1OPCflbTwn6mA4b2SL/CRiL13PVVA8JCWvOKCuSLhirh24beKwqXhqrlcOlzwkbKnwqTgsaAKM0Ix8JGOjiZQ0ajwn5W08J60nUvwkY2ow4d+8JGXiC/wkJOzJjnwkLWx0agldSLwn5W0RQoWQhTwnZKpLvCflbRizb9c77+98JCklAoIQgY18Jq/vjkKAkIACixCKvCRtYHqrK7qqKg98JattPCQpIPgtaDwnYWw8JGZlPCRjYjgupJ38JCOoAo/Qj3wkZmhKj/RqPCQq7DwkbKXQe+/vXk64L6NV/CQu4LgrZ0r8J+VtO+/vfCdkZsv8J+VtC/wkYGYKiZr4KiFCgVCA+qimgoMCgRuYW1lEgRCAi53Ci0KCW5hbWVzcGFjZRIgQh5RMS5VXjwn8J66pSRiXFtpKD9u4KuNw67gsYjqqYYKuBG6AbQRCu4QCgdjb2x1bW5zEuIQsgHeEAo7Qjnwn66IXW8/J/CQoLzwnYSC8JGkuMKl1YtgZ3tHbjTwlq2qLMKyLiLwkIGNP+++kfCQqog/IuqntC8KMEIu4K648J+VtC4kw4d38J6LvzMv4KyI77+qwqV7KvCehLfwkJasL3vgsa0u8J2LkwpAQj5D8JGcsfCQoIjwn5um6qORVnzCuWdU8JGKnUjhnIPwkIGW8J6fq2bqrKB7PTbwkJSR8JGxtuC6ieG9vScuUQocQhrwnZKp4Ka+4LWLPGt16KG78J6AjT/wnZS9JQoWQhQ877+9PDhcOmZiaTw8PfCRtqXDjgovQi3vv73wkYuY4LW8UiZs0ajvt70/4b6y4KuNyLpXP+qhgeCngXtywqUkLPCQv6sKHEIa168vNCIna/Cdk6TwnrmPL/CRpLcv8J+VtFYKAkIACgpCCOGckD8rJi8kCilCJyVEwqXvv73wkJmV8J+VtMO8JOC/iuCtl3AuLGA6IvCflbQuVuC1jQoiQiDwnZ2QPOGJmHvwkpOPJlw8d8KlPOCjn3lgUnzwnoWJLwoQQg5777+977+a4b+YMuK1rwowQi7wsZ+4d8KzMijIuuCtnTZ977mz4KmMw54lJ2/CpSVgT+C6pfCRtaDwkKypyLovCjFCL9GoLzJHUeGmuzlMw6hoP2wi8JCHsPCRspZLJFUkOi3gsrjgp5xcJDrgs57gqLY6CjhCNvCesqDgu4zwkaWGZlvhiqA90ag/L8Klayrhi4AsJCJO8JCnmmB3XTrwkKGoLD8md0ImP+GihgoEQgJgPQo+Qjxh8JCLu1Xqp5HiuoDwn4Kp8JCov/CQnrcq8JuwnCjvv73wm7KEKvCRjKvqpopQ4LqKXMi6wqTgto4iezoKFEISIT8qTvCdqqM/PWpYPHhVOiV5CiRCIu+/veCst9GoYOCqgXzirorvv7zwkZimyLrgrKvgq4w8yLoKB0IFIkHhjokKE0IRKlPDnvCRpJzwkY6rdvCQgo0KJUIjOvCRtLrDjk1RLj1c1o7wnaqdPeCridGoJlBg0ag9Zycmbj0KEkIQ8JGxsUk/XDkiQ2vwlquEewo0QjJZXPCeiqQ6LyXwnoWO4rSn0ahKLlcx8JCPlMi6yLokJfCbhaR7fPCflbRMUPCeuYc9aQotQivgu4sv4aSJbUTwn5W0PTpS8JC5rUbwkJeQw6JgPT0ve/Cbgph0PMKgKk15ChdCFfCQp4dXez1WwqXgq7Dgv4ctQeChoAoSQhDvt7zwn6mpY+GOi/CdlYJGCjBCLvCvprYi4rWwST17Iz0oJz5vLyfwkaug7Z+aPCXwkIeT6pKZJvCRtYfwlr6byLoKAkIAChlCFyd7YyTgsqxeJdeC8J+VtPCegKQlZl4vCiRCIuCzrvCdlInwkaa+0agi8JG0iFwq8JG1qHfgu4h7wqXCpSYKAkIACkFCP9aO8J+VtMOg4K+QJPCWrbPWhybgvoTwnp+oJvCQoIU/TOCsuPCflbTwnoCQM/CflbR08JCkqPCQpL92JeGnqgoDQgE9Cj9CPSU/PTTwkYim8JCgiDrvv73IusOfRycl4LOCSvCdnY5RYC/vv73grZ3qoLPwkYyeJm/wnZOA4ramJjrhpZkKMkIwPS4lPXtqWeCmsC7igqBgw6sqPO+9iTYk4LaD77+98J+itScqOuqsjeKAl/CRjbQoCh5CHPCRjZ06YuqSme+/k1leP/CdlLDwnqWf8JGPmE8KPEI6PNaB8J+ItSUt35EmPy4u4aKK4Z2weOCyuNGo8JCTrCbgt6zwkYyfP++/vXJge/CTkYTIuj0jJCo4PwoNQgtOOy3hg43wkaanJAoUQhJ6LsKl4L626qas8J6CjyDhiZgKPUI7yLrduDwhfvCdg7Bk8KyNvuqlriPRqPCQkqXwnYi0TuGimvCRtIA94Ki84KuQ4LuI77+V8JC2hXNTw6wKGkIYNC4kwqXwkKKtKjPvv708S++/veKFsjo8ChdCFfCfoqFzJ3tUJ+qnkizwkb2A8JC1uwoNQgvwkI614KyF44OTUAorQinwkKO18JG2mGLwkK2s4aawJS/guoHwkY2QYuC0v0pt8J+VtCUu8JGsiAo3QjXDpOCwv3tye++/vVxML3nwn4Gq8J2Rhz4q8J+iokvgsangqZ7hm6vvv71yPfCQrpw6LyZESwoOQgxc8JCWlGBjMMOwYDoKAkIACi5CLCdAe3duQybRqCTgs5VBZS7wkY2BUcKlez3wm4WRVjA/LvCeuYnwnZS80agnCjZCNDrwnrqJw5tdPdGoJ/CRjJfgro7CpTzwn62mPfCRtaEk8JGviSVAVfCfqoR48Ja9lD4qPH0KFEIS4aaV0ahw8JuynXBt8J6llSQ/CgNCASYKLUIr8JGNkOG/syLRqCQqLjt54LWGRMKlX+qtgj/qoLRP8Juwmyrwn5W0wqXCpQo/Qj3wkYGeeMKvbuCxnXcn4rax8JGHmC9DQ07qoadP8J65nXlT4oe5O/CQjbLwkYqj4aqsYPCfm6nhi5U/4LGLCgJCAAojQiFQ8Jy9lWhhKuqlrkfqrKXgrZ83P/CQhJvwkY+R264kP0IKJ0IlX/CflbQi4reVNHtoyLrwnoGAffCflbTwkLqs8JCjtVIq8JGKiAotQivwkJ658JGWuyo88JGPhfCQgZI/OnrDgSkqe/CRjKrwr6ebOvCeuZ/graJPCjVCM/CSlL9HPCTDi+qspTzCpfCRpIJucz3vv708JU7gtr3wnri58JGMpy4+4K2jJ/Cdi6whXAoDQgFsCkFCP3on4KSzKi7Iuj/wkKeB8J6lkzXwnoCLKVwnwrdb8JCBlVdo8JGMie+/vU/vv73Dju+/vfCRjZAkwrvgrorCpQoiCgRuYW1lEhpCGDzwn6qvMmNjU+CytXrwnpeq8JGNh106NQodCgluYW1lc3BhY2USEEIOaT9gw6nwkYet5ZWIL3AKuwK6AbcCCuIBCgdjb2x1bW5zEtYBsgHSAQoTQhHwsIy74YqyJyXfkyc0acKlaQotQivhnIfvuI1TOlQuYvCRg5Lgq5DqpbjwkLyA6qypKk/wnrihUD3hvrxMPci6CgZCBPCrno8KDUILdEYt4ruI8J2IsmQKFUIT4rqcYCTgtr0m8JGwtibwkKiWIgoqQihrRfCQp4BLJSY9NPCRiqHvt48mOvCRjo5+IuGnk8Oze/CWq7RWwqEqCjJCMOClk3bit5rwkZuI4pGGw5HwkLSI8JCAvOG+omF6L8Kl0ahc4raqOjMnbuqfkSrCpQo8CgRuYW1lEjRCMj3wnrux4Ky58Jaskycm8JivhypHPfCWrLwn8Juxssi6YXjwlryp8JGkk/Ceu7DguIM6ChIKCW5hbWVzcGFjZRIFQgPvv70Kqwq6AacKCr8JCgdjb2x1bW5zErMJsgGvCQoQQg7IumA78J+VtCTwkJKwPwowQi4qP9+CMDXhqa1g8J2lpci68J64t/Ceu7Fc4KiseiZV8J+DovCQnY4xL/CrtqwqCipCKOCtoOCpqzrwkISCavCWq7J48JGMszxZP/CQtLhndCLwnrqzw6Q4eD0KQEI+VGzwnY20YX7itKfvrbx58JuwuWM/PPCflbRo8JG1pCnRqPCbhLLRqOCnjfCfgJZK8JuDgTxHVF/RqPCRpqQKJUIj8J66rOqVvvCQnpTqorlq77+9SMKrP/Cfn7BUP+GqsvCfnKQKDEIKYGDgrpp2PcO4UAoOQgw8JfCdkqLwnYWKJFAKAkIACg5CDPCdkIBdSeqjgmAiJAonQiV28J6fqC5AbCphXFwl8JCBg/CflbThvaXwkJS8VSdgJPCflbQqChFCDzzIuiXhoJEgw4Iu8JGNlwoGQgQnJHkqCh5CHHvwkYeieygx4KeO8JCwvPCflbRle/CQhpngs50KPUI78JC8mTfqn5EuecKqbuCwkC/gt4ThnYpg4aiE8JGDgTzgrrgm8JargT4qLlfCpe+tjvCRjYdcQ+G8m3UKPEI6Lzo9e+Gqv17wnZK/Lio6L2rhoI/vv73wnoCqcVc9PvCRtZTgqLjwn5W06qyO8JGxqCXDq30v8JGllwoIQgbwkZq4PToKJ0IlJGB7Ly88b8i60ajgt5ZZ4Z+k8JCAseCpniY48J65i1DqqoDDnwoWQhTvv73wkbWn4KS4Klxq8JCBhVMqLQo9QjvwkKedJljitInDt1wr8J6jgXt2MzDwnp+r0ajvv70mJ86c8JKShXvvv5LgrqPXtOCpnvCdlY8l8J+vtAoiQiDwnZWE4Z+1KfCRmaTwn5W0IsKl8J66u/CRv6bwkIWSMAoIQgZc77+9PyUKMkIwKuG9nXDhpJvwkYO577WkJTbgr43wn6u28JG1mCQ/OvCQgZQme2vwkI6C8Jy9mj9tCgdCBSzwkKCFCglCByfqn5U6b1AKHkIc4Kq2LzwqPfCRgaR7OjYq4raxVC7wn5W0R9aePAosQioqNHvwnoum8JCOqSXwkbC68J+VtO+/vWVqJlVoPeC7kfCQnqc/NfCvorkKDUIL8JCWneCig8i6TiYKK0IpSfCQlrThvZkqNDxX4KiI4aeQ8JGFlVw98JGoqHY68JGKiOCqtWBcJToKC0IJ4oG8JW7wnoSxCihCJvCflbQr4La4VsKlduGkuD9b8J+qruK+mkxAP+CrgzzwnrmNPDx7CgJCAAoPQg1nb2Bc8J66h/CRjoskCiFCH3vwkbWiVPCeuqLwn6GU8JauhPCehKlZ8Juwkj1QOyIKI0Ih8J+VtG3wkbS1KPCQsp7wn5W0JUsnJzjhpKdf8J65sDwjCgxCCiV9Vk054q22fDwKG0IZ77+9TcOQPy1g4Zq/JTrwkJaY0ajvv705IQoxQi/wkLyk8JGnouCngfCflbROyLpT8JGmpjjwn5W0J/CflbQn4KeXJSYv4K2iLu+5nAotQivCpe+/vS7grqjgqKvvuaMlJVw777+94K+QJDrwn5qgJDjCpUprXSfhvbZ7ChZCFFzwkKiiwronPfCek64nIvCflbQ6ClIKBG5hbWUSSkJIRGnwn4mQPOC2vVLgqLYofeChhz1W6pyFw4I64KiPTvCeorLDiPCRkJgm4L6z8JColvCQrZXvv73vv71kJOCukvCeubvRqNGoCg8KCW5hbWVzcGFjZRICCAQKnRS6AZkUCuMTCgdjb2x1bW5zEtcTsgHTEwobQhknYuCuhyImJXnwn5W04rSDJDw9Ik7wkYOZCgNCAT8KFUITJ/CQlozwkJ2jRj/wkaCrQO+/vQoLQgnwkI+D8Jatlz0KSkJI77+9JiXwk52tUfCdmJfRqPCRv6DwkKKn0ajwnqWQc/CRjYHgqYzDlUjvv70r8LC+nVclXD3wkJOl8JGmozBQ4K63JPCQsLU9CgpCCOCuqcK0KiA2CjZCNMKl8JCHu2Hgu4Yn8J2SoiLwn6qBXD0n8J+DrPCsv7w9JvCdkZg/8J+VtOGJmFwn0ajgoLUKCUIHZ8Kl8JGIqQpFQkM34YON8J6ivnNR8JCgiMKlOvCQraXqr7bwkYyTUOqlvO+5sj7Dqsi6b/Cfgq5k8J6fovCRkZ/hqqUx4Yyh8Kq4jyZNCgZCBOKDrlEKJUIj4oCwYC5LUPCRjaM64KqBXPCflbRIYGfgp4h54Lio0ajgvZ0KRkJEyLo/M/Cfgq3CpS3wnYaVJeCgvjUl8JGkpPCQuaDis783U/Cfm7PgorXwkY+he2Ix4oWHPOCoh/CdmYHwlq2tKmDDhSQKHUIb6qqA8J+rqPCWrZlFey57P3s+e2Dwn5W0w59MChtCGSRLbiJcTD7vv73qoLAqLz3CpSfiroYqUy4KBkIE4ZyQNwoDQgFmCgVCAyJMMAo7QjkgXOCyt+C0hMKlJdGo8JGcilxHL++/vfCRmZnvv73wkICEwrMnVMi6Re+/vcKwIvCeuKTDmuCxhCMKO0I5MD1cPS4l8JGHjiQq8JGkt3InyLokekbhpJrgqpDwnrmX77eP8J+Huy7hi4Thi4Iv8JC6ncKl4q6OChtCGe+sgOKiltGozok+76yVLU4n77+9Pyp2XVwKPkI8fPCRhLfigpniupgkOi9QKkjgrqg/PTrgs7Nx8JCsqiZS4aKU8J+ggUbwkKCIKl9a4YqSP/CbhabwnZKrChlCF1Vie+GJmCTDhuGDjSYu8J+VtPCWv6E8CkhCRnbwn5W0Sz9cPVTgupRQwqDwm4Wle0Twn5W0acOY8JC0tvCflbQn8KqCuC9c8JG1oT/guqXwkICc0ajwkY+h8JCWlPCdjJ8KOUI3U/Cbspxg44Gn4raJJHzCpfCbsbvguoHwkbWWIjw/6qyKwqTgrI8n4Yy9e8KhJlVCJCcgOuGghgpBQj/wnoCd4KeIJuCynGhc8J64u+K1m9GoOmMi4LWP8J+VtC/hvZRpKj/hm4Yi8JGwufCflbQm4LK34Kuj8Jy8sSYKMkIw4K6z8JCXik0k8J2UnOqvtuGKu1zwnYux0ajvv70vMD1Z8J+rqEBrQsKle8i64rG4ClBCTjzit44myLo977+h8J64ueK9gEXwnriJ8JGDtcKl8JG/ni5JLi7hv4xi8JCgv/CQnabwn5+rcPCfh7ngtJDgp4dSJXrwkY6A4aii8JaErQokQiLgp4fgsaBR8JGPhS5aP/CflbQqJjjDij/wkLmlI1rwkL+2ChxCGtGowqXvv700K1hxIvCflbThi5LRqD124KuHCgpCCEFALidP4ra+CgtCCfCRjYdOP8OONgoaQhhnYzo977yv0ajCuOGLgPCfoLc6JtGoNFoKIUIfPOGKsvCRv6wsOfCen6vhpYA/akvwkYS9V3A6bmdcPQoWQhThjJXwn4Gpw7LgqK5eIiXgrI9UJgodQhtmJibvv6vwnqOLUntpJeCogmElLk3wkJa8w58KM0IxXGw/4b+086CFlCo/TjPgra3gq6HwkKiTMuCugntcwr868J+VtDxc8JCosz7hiorCvgoCQgAKKkIoLmLDlF88eyRBw5Mkw40kQ1LwkKC4J/CQoIUo8JGBoSbqrIo/4Ku5ewoSQhAmJiLwlr6FTH7gr5fwkayCChJCEPCRmK978J65ofCQgYgqMSIKRUJDaXLwkbaRyLrqmqXCpfCQvbLIuiZsYDrgraE88J+VtF/wnrmHJeCxryQr44SdS9Go8JCyrzzwkIC9P+GamsKlUuC4oAokQiLwn5W0yLrhp5hkwq5kP+GDh07iu4bCpSHRqPCRjJU+XHIiCjlCNz3wm7KR6qCq4Ked8J+JkfCWqaU94rau77+94oCKU21PL1bwkaSWJG3wnLO4JCTDiGDwnLGweSIKGkIY776KJiTwkb6wJ/CRsJfwkLqx4LKY0ahRCgtCCS7gsYjhvafWmAoPQg3CpVN7YO2etS/vubQ3Cj1CO/CQjos8N+K3tCXWseGKq2Bibi4idW3DimDRqD3gsK144KWrd/CRnJ3gsaDwn4mD762D8JCOrDrhi4AmChpCGCoi77+9JEHwnrmbJTPwn4mlJCQi8J+HqwomQiTCpSZdRHsn17Dhv6xa8J+VtGDwn5W04KmIR++/vXPwrrazW20KOEI2Rjwn8J6Ts/CRj5E/Kkrgt7Ttn6JlWci6Y3BO76iP8Juygj9c8JCWtS7wkbG28Jy+nPCQlaEnCkhCRvCei78v8JG/gtGoJs6KJmYsOu+/lCTgqZHgvL8s8JapnuGJmHfvtac18Jq/sDpo6qumPe+/veC0gOChgMOj4Ki1QPCeub4KDEIKLvCeuLnwnpe/LgoPQg0mIu+/vXJC44iF77+CCjVCM8K78J+VtPCRjos94Ky14Ym04reCL/CQpJPIujXwkKmRQ/CRhJzvrJc9Jyc9JytxKmslJgoTQhHgsYjwkaqZPPCRpL068J6AqAoxQi/wn6OBJUtGJ+Cuo+Cumsi6IComN/CbhZVcXPCeub7wkbGoJ0884LOx8J2Ivi/CpQoGQgTwkKuzCkhCRvCRtInwkYeSe/CQlrvwnrmZV++/oi/wnqS+Isi64Ki8PMOCMu+/vfCQqLhsey9M8JGNkOG9m/CflbTwkYyPJS8i8JanuDEKIkIg0ajwnp+gPiXvv73hv4kmRsi6WiritJ4kQSUqJOCuji4KCUIH8JG0uuC1iAoPQg3gr5dcS2DvsoImP1AlCiZCJDo077+94b2bXPCcvK1q8J+VtOCviCIn8J+VtPCQpJl74b+ZJQoHQgVG4YqMTQo6Qjgk4Ka48JG0hOK2ri3gqobwkY+C762D4YuA8J64udGoIifqrJIl4LiQInbCuEJcfTw78J+Br+CpkQoKQgjwkI+LP+CsgQosQiom86CGqC5bVtGo4r2qITxcwqXqp7bwkaa4wqnwkbSB4Kuo4YuL8Jq/tSkKKUIn4KaP36rgr4fhna7gqJBSK9GGw4kl4b2bOvCbsp3wkKuuKl/wkKy8ChtCGfCWraHwkIGMJipj8JCAr/ClqJV7I2/qoIYKOkI4dCzwqq2k4KyFOu+6qj3Nvy7ikYosPy89ZCLwnL6x4b63YvCdhJPRqPCeuYtzKio8PSon8JGbgC4KFQoEbmFtZRINQgslN8i6Jyd4w6AvJAoaCgluYW1lc3BhY2USDUILOjzwkK6r8J6FjmAKpAK6AaACCq4BCgdjb2x1bW5zEqIBsgGeAQoyQjAi8JGMvGB2JjbwkaOuJeCrgTYlOuOAutGo0ag/QeGkp33wn5W08JCgt++/vfCWqZMKO0I58J+jgOCvkOqshO+okiYnOnrDmeqsvEs4e1zhjJPDjeCqt8i68JGFh+C2ljp7KvCRoJ5zwrDgt4olCitCKcKjJGAuJPCflbQqPC/grZXCpWt98J6AoeCyvfCRmKHwlr2EJT9gdTxjCicKBG5hbWUSH0IdPOGypVDwnrmh4K6jP3Aq4LaB77ihe/CRgoVlMy0KRAoJbmFtZXNwYWNlEjdCNe+/vfCdl6Hwlq2c0ajgsYF04LOLSsi6cC4i77+9ZUYnXPCeuoQmYEPwlrWUR++/veG8nCZVCqYFugGiBQq9BAoHY29sdW1ucxKxBLIBrQQKBEICT2AKGkIY4YOH8J28ji/gp5cibCJrKl7DvHvqr7E+CjNCMUwtwrHwkJ2lXCJUJuGbkci6JuC4mNW5PXIu4KmqOzDwkZix8JGOi++/vXXwkYe0wrsKHEIa8J64r/CWq7HwkLCmJXLgrLJ7LyLgqLzgprkKQ0JBKjk8P+C3p3swIvCRiqd08JCVveCxqjHwn6+5Jz/wkICB8J6Fh/Cfg6nwn5W0POqgtVxjM+G+vC7wkaOLPeCpsF4KGEIW0ajYkiJQ8JGIo/CQiqzwkbWHfCNPRAoKQgjwnYyR77+9JgoRQg/CpeC6gSl2JfCegKp7Vy8KKEImJ2DCqDR28JaqgGh+4Yu+8JapoPCQoq834KCr4ral8JGHpDDjgqgKHUIb4Z2q8JGyquGPvPCQgILwkYqF4Y2mM3ngp7QqCgZCBFgkIFwKAkIACjdCNfCQjJckeyIi6qC1JfCQvrArJ0LgsZ3wkYirJvCWvp7hvZtI4rqJPPCRjIUkJipU4Kay4oG1CjpCOOGlgFFoJ9Go8JGOufCQgL06bS7CvXLwkKCmJMOfT/CRioJW4aeZ4aqJJu+8oeqpiHvqqKzhups/CkpCSMOe8JCkv+C1vj/wkbyTZ2cq4ayZw5Mi8JG1qCkiOvCfn6ovL/CQoLzwkYyy4YmdJtGoOj3wlr6B4KOK8JGNiC7wkbaT8J+AiQoSQhBx4YuF44OM8JC+sjzgqpcnCg5CDDzCpfCflbROJjDCvApBCgRuYW1lEjlCNz3grr/wn5W0a+2etOCmtkpNP+GlsWgiPSJCJTXwnrmH8J2Nq2nguqXCuyTwkKSVLETwkaSJez0KHQoJbmFtZXNwYWNlEhBCDvCWqaVB8J66hvCRjpFgCpwSugGYEgqjEQoHY29sdW1ucxKXEbIBkxEKFkIUw7Thsr7wkJmYL2ol8Ja9rvCQqYcKGUIXJfCegKfvrLo8OmAwPOGkpC9K4KuHYFwKJUIj8J2LsTldYFglXvCRpqVsKvCwqbvqraTDjSTqraZKPD88XCYKOkI4w4nCpS3hi4jwnrmfJzo08J+VtPCQlrsu4Kui4KmIJci6ePCRoJ9N8JGipzHwm4SM8JCesvCQgZQKC0IJRCbCvifhv5FbCiJCIOCps2gv4aOYM+G8nWDgs65gJiZVPXAuX/CehJ/qrKBVChNCEeCmjycxIkwuyLrDsfCeo4c6CiVCI/CflbTwnrmiYPCfhIfgq68mXeGkqvCflbRiNyJ8Y1xbSTwlCj5CPD0kyLpgPCdPwqXwlquhc/CflbTwkJqqwqUqYci64pGC8JGMg+Cstj0vYTsn4Kyv8K+njFDwkJSH8J6kigoZQhfCuzbhvYsvUnvhn6nvv73gsKUv6qqdYAoNQgtJQPCQqJc8eSLDoAoyQjBN77+9yLpYIvCRr7dje9ex8JCWmF3CpXcq8JC6q/CeuqfCufCfqoJMZU4uOiwnLiIKK0IpU2/hhKglMiRIPFvRqPCQublVInIiSvCeuZvwnZWPVSJSJUfwkIC8XlwKQUI/Lz0uKygk8JORj/CRsIYow44k8J+trHskyLpyOvCQlZw84LK/8J66o/CflbTCvz3RqPCflbTwkZadYFLwkLWNCkJCQOC/kuCtpvCfn4RQLuGsruC+nPCQqolP4LCcIDIkyLrgrLAnLvCeuZnCsuC3ru+/vcKlM/CRpI3wqqGJRWDhqKYKA0IBIgoxQi/wn5qdVe+/h86ZJjDgqLgvZ0DwkJSf6qKre/CQhIrwmK64RUDRqC4mJCRzIu+/vQoSQhBvez9c4KuJL8KlJz0pJkNkCi1CKy9Qw4DCvMi6XOCqke+/vcKl4LeWPT/wkpGPIPCQoJZSw73wnZK7PPCbhZAKE0IRTcK68JGOiypy4LqE4Ki1IjwKCUIHOOqnluC+oQo4Qjbvv73YkeCzsTXwlr+x6qOS8JGOh/CdjaPCpSThn6fDtSZwwqXqprdOYPCWrKHgqoJM8JuFpmYKPkI8dVzqqZHRqFkn4KyB8JGMu+Cvs03itrbwpqK28JuylC7CpSbwn5W0PSrwkLqrTeGLgCXvv71cYfCbhLJ1CjZCNOCqtzwvJ0jCpTvhirXDn3ngp47hqII6JOqjlidK0agq4Ky5w6R0PeCtnHY86q6p8J+VtEYKIkIg77+9U8i64LqUXPCfpojhqpI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IeCfgr5dxSPCeuYIKOkI4wqU3yLpDK++stOC6pS9DVSo9ai8w77+LJ9Go4reF8JCXiDwkZTnhsqo64K2E0agk8J+VtOCyvXsKL0It8Kudoio6XNGoPlwnJ/CRtYTwmr+w8JCdo++/vTp6PSI8JkIqLvCRm4FcQDx7CjRCMnjgsJdc0ajRqHs7b+Cvi+CpmvCQqYE9e8i6YGbhpYA0XvCegJvwn4ex8J6Lv1vgvKQiCj1COyLIuuK2jS9hMOqomvCQgIVsaPCflbTwn4i78JGMikld77aS8J+VtGAiJiRY8JaEoj/RqMKl8JCSoXgkCjhCNkkkKsKy8J65gvCfpYXCqjzRqPCQrarqkr5KW+Cuh/CfqoXwn5W0L/Cdk4LgrpnitrzIuuK0lQoxQi9D8JyxqD1MIu+/vfCeubU9XCXgvozvuIQ/RDxc77a4J0rhsp7wnrmU8JCEmuC/igohQh/ilYnvv5vgraHqrIpc8J+VtPCRhYDgrqo63bTwkYyFCjRCMvCQgYPcoHbhsq9gJWU/8J64ouqhnWLikpY74LuX0ahm8K2mizJ7XGzhqafgsYvwnruwCi1CK0rhna/qoavhsa7IulfjlZXqo5ImcC7wkLSw4KayPTouI/CRpLcue+GvqCYKP0I9yLo4U+Cqs1Rv6qKB8JGKnSJA4YmdVTw6e2DDkUUp8J65rVxJNOCsq/CflbTwnZSSw5hc0ajwnZO04K2gPAo7QjnwkbaR8J6AoeGotOGwje+/vV/RqMKlLidf0ajgrZdvXOG9nSQkPz3qoY0vZlThnYt4LsKhL/CfgrEKHEIaYCTDvvCQqLhgITA1yLonefCRvY5PJ/CflbQKB0IFX3zgt50KKUIncSRG8K+jvC7Coy5EKvCfoa4v8JCylULwnpe/UDtGwq93w7nhpphTChdCFSB9Jci6PSHCseCpsvCdqp1oKj4qPwosQirwkb+x4Ki8LjzRqEDitZstP/CQqYhZOj/huaTwkY+CJCs/8J+fp+Gqqz8KB0IF8JGLhyYKI0IhZVwn8JG1p+GJvfCflbTwn5+w4KqryLorKvCdhqfwkL+1CiFCH/CThr1X4raxOj9H8JGkkiDwkbK0Xu+5sTjwkI+QXDsKPEI64bGj8JGmp+Ctl/CQja/hn6U1V3Qo77iVPyI18JarsfCSgaLwkb2R4raET+qpgkQ6KfCetKvwkICyewoNQgvgrKtg4K6oJ+Cogwo4QjbwkYyB4K6f6qCE8JC9mUfwkJmEM/CRiojwnp+t4LGdYFl3wqXhqpMhIlFhwrQ84bOF8J+bu0MKBkIEMuCpnAoIQgZqMF3gqYEKEkIQPPCeiqNRJvCWvpU68JGNjQo2QjQkLvCRrIngu45bXF7wnYuvLvCTlYfwkKSnVz88TvCRlpZl4aWAPT8i8J+JkSY98JCUtFFHChlCF3ttVkd74KecP++/vS7wkYGfyLrwn5W0ChBCDl/wnrqhJyYkNCLCpS06Cj1CO1UnUUngt4NW8JG/jls24Yq94KqQV/CRp4U877ix8JuygfCRo5fwn5W0Lm468J65neGaty5g8J+Gkj85CgNCAT8KP0I9PSbhqrYmbT/IutGoLPCQhpBgJPCegJ548JCPke+/vTzgp43CofCRjYg88J65keGqoip7ceK2vV/hsZ5wLgpBQj8/YirwkIycfvCYoY7CpWPwkISBe/CfhLzCpfCRlqJLPzphY2N74LSzfTzwkKSuQMO8JPCQrYLguLFn8Ja8h3wKJ0Il8JKEtPOghoXwkZyeJ+K2uO+/vSRl4Kiv8J65mVfDsjbwlr2mOgpAQj5mY+KAiTDjgZUi44aT4Ki4RCImJjwv8J+VtPCfmoFc8J2qnsKl77+98JartCXwkbC5SvCRsrJpVeCuuCp7TwogQh7wkLWsWjDIuuC9lHbgtYcuaMi6Py7guofwnYKoVSQKB0IF8JCgvCQKAkIAChdCFfCeuLvwn5W0evCeuqHgtr1c8JGctQoGQgTvrL5yCsQYugHAGAo3CgNrZXkSMEIuJkg7bvCQi49r4KiFNci64LCOPWM6evCWvbl08J+sty8p0ag6L1x1JlxW4oKtXAqEGAoDdmFsEvwXugH4Fwr1FwoGU3FsU2V0EuoXsgHmFwoFQgNcwqUKLUIr4KmAJjMk8JuFkNqUXvCeuYfwkbS6cyrwkKmwWfCRo67CpWDhsbQ/8J65iwoaQhjgq5A68JuDhfCRsp8iyLpUMfCRjIAmXHsKFkIUcTMkIlLCpfCQgI3qrIUnJiVRPzAKDkIMVC/IujpSSu+/vVkuCgZCBCrCpScKP0I98J6AgUbwkKiGLj936qW5aV7igKbigJLwm4CY77eP8JCymD8ua/CqrJYv4L+XdOqZjuCjjvCQu4JDKOK+uAosQirwkKucLCQ8Pci6PnlcblfwkbWleuKClD9tJVzwkICAVfCRko4/4aGIUToKCkIIKWBALuK3kGEKO0I58Juxt8KlJvCflbR0Km5y8JCohu+/vWA0ROOFsifwkK6qMj3wm4WnVeCziiHIumhx8JasgvCfiYYmCgtCCSLwn5W08J65jgolQiNObvCfrr3wkJK68JC5oSpIwqXCpVMy8JC5tCbwn5W0JDo6Ogo2QjQ9T1dK8J+VtMONZVBlaT/CpVzgv4xgYCo44KytI3vwn5W0YHTqm6Lvv71dwqXwkY+Fw7dKChlCF/Cfm51v4K+Ie9Go0ajCpfCRjILgqZEkCipCKMi6MPCforkley7vv707yLrvv73gqZ5m4KyK8JuxtvCRj5hcKiTDmyIKMEIu2JvitK3Dh+G9mSLgqZ7wkYyJ6LS6LvCRip8mwqV34Y+dZHtUIPCav7HCpXsvfAo2QjQ94YqKKfCdkrvDiCdo8J6BnzDhvqvgqrI88JCKoi3gu4ZE8JGWjm/wnYurw4khU8i66q2CCh1CGz/hooV74L+aU+CpkfCQlLA64K+XJ+CzouC/hgoHQgUnPMKlJQowQi7wn4O0cMi64LCs8JGPmPCUkr7grLLgp5w6e2A9P++/vSVVRG4hbfCRm4LitZEhCh9CHXk9Ru+3t+Coie+4mTrwkaSW8J65je+0qnvwnaqdCi1CK/Cdkq/wkYqNLjdY4KqYPfCRjIMuWD/CpTtw6p608J6AvO+wvjXwnZWGPGoKR0JF4K6o4Kay4oOme/CRj4kj4L6sRvCflbTwnZWL8JC1nvCdqp3CpT/gqY3wkJOO8JCWkHYu8J+psOC3lvCav7HguZAuwqU6Cg1CC8OSPdGo77+9NC5sCjFCLzpnYWAzVS5mK8KlOC4m8JCgiF/wkIGL8J6frfCen6094L+TSWlH8JG0gnbwk5GGCjxCOkLgu4bwn6214LeW4Kay76yi0ajIuiXvv706JvCRvITwkKurYPCQrqlJ8JCVgSp6MlMoYCYvL0HigqMKPUI7KmBd2oZ5yLpVOuCsieCqrOGDh+CivvCRvrDhvZZNYOqXpOGJmPCQo75L4reMPdGo8J+cnuCoiMOvVjgKC0IJVGzhqojwqJihChBCDi5meyTCpeGft/CflbQmCglCB+C0t++sk1AKMUIvTOCvgj8tLuCpmXMn77+9L/CQkbbwkYek8J+sn++4qjotyLpcYfCRq51YVsi6cW4KOEI20ajCpcO8IvCwq6Uj4b+MfDpyJO+/vTzvv714bybgur0v8J6XqfCbsp9wJ+CpniTgsY3wn5W0CjlCN/CeuZdGOvCRjoThnYXwkIu74Kus8J+VtD3wn5W0eyDwn4SrTXRr8K63rvCfgrUmPvCQtY3vv70KNEIy4KGe4LGV4LSL8JG0hsi6XT/igJIhbD/gqqhB0ajwkZuHSXk6dmdgPeCzpjg6YPCegIQKB0IF4ZyBwqUKJEIiW/CQpr7qmIjwkIGALtGoVuOBkj7CpT0kMC3wkK6qJcOoewolQiPgqZ4vNPCflbQmfiIv4b2ZyLo/byo9SVZE8JGTmTrCpT/CpQpCQkDwkLWY4KaK8JKNgDLgr4zUuOqSnj8lQsOcQzE/8J+uvXPvv71D0ajwn6KmJvCcua7wkZ2GKmAk8JCgpe+6unshCjFCLy85Ojjwkb+fPGzCpWzgrqPwnrmd4KiQYD08Lm1cffCShrZje++shuCnnCrwkZmUChBCDiDwn5W0LyUkw6fwnY2hCg9CDfCdlYbjhYHwkZOUaT0KFEISIi5HL+CupGDigptgLDUy4aWpCipCKFbwnoCA8JGOjuGkmdGoICTgsIjql6gve/CTkYbCpWzhs7lu77+9eScKA0IBPQoNQgtH8JGGtPCQhIBgJAo4QjZF4aWAVvCeupIjXGRW8JGKlntx8JGRneCsjyTvv71jLvCdnIfvv717ceCmkPCurqlEOiDhvroKF0IV4aCJOcKlRsi68J2NtvCWq5nDrmBiCgJCAAoVQhMl4r+x8Jq/vTnvv708RHsq4b2wChhCFkPwkJS3PfCbhaQ6STpY8JGxp/Cdkr8KMkIwL/CfhaHwlr6SU9Go4byl4K+Qe+KDn0xeXPCfoIYq8JGMiOCkt+GmsyXwnoCQOlovCjZCNPCWq4RxNj1ce2DDqfCQqIYk6q+x8J65h0zwn6OB8J65vj0nJeKmoyTwnL6qIiLCpdaNeFwKGEIWZTLRqCvgtJBW8JGKij3wnoWF8J6LvwofQh3CoPCflbTwkY2q8JCAg3V48J6jlTrqr7bDgsOqLwoqQihSw4rvv70uKiLVi3Q/8JGnoljhsYQvPUvhiYzitqzwkZeAPXcvMEwlCjdCNSbwlq2h8JGmpfCel5jwnp+tafCQrocm4LGnPTxEYnU8evCdiJ4mc9Go8J6Xvz4uJTzwn6KYCkdCRUHwkY+VJuGKi+K2rHs/0ajwkYK+wqDvv70g44SH8Jivmj3wnoCp4byZLicl8J+VtFnguYbCpXs88JuEsl/wkoG78JCahQoJQgfIumLwlr+xCgxCCnnwnrmU8J+Cp2kKM0Ix8JGxgOK7hVIsJfCQlpo8V+C6vPCeuKwu4K6c76y+4KiJ77+9TSLRqDVwc/CetIt3PwpHQkXwnrmZ8JCQieCniz8iQGPwkKC8yLrwkKi5UH0w77+957G98JCAoU4qwqXvuahcSfCWraF6wqXtn5XwkYqMwqI84KaZIj0KC0IJ6qylSFzvv71nCiBCHu+5sfCRj5hS8J2QgvCRspJ6RuC/jO+svvCfq7bDpgouQizojoLguJzCpS/wn6q5WfCbhLLgtocvLjrRqC7vrLvgp7HikYrihYxc4LCOLwoGQgR74Ki5CghCBvCeuZ09PwozQjFu4Y2d8J+VtC3wkKGyJfCdi68nKSLwn5W077+9J+qgsmlt8J65uT17dS5zKteje2BAChZCFCQ0QdOYeyQvQVAvTlZzPPCQk5woCh1CG1zwnqWfNvCSkLLgrr5uJy/wka+Y4Kaq8JGdgAoGQgRo4K2HCilCJ3zwnp+jyLrwn5W08Jy8mfCQjbcu8JSYvsKye/CflbTwlqu1LuGIpgorQilpLyXgt7MuLu+/vVbwkbS677+tdjrgu5DwnaqqYC1fJSfwnYurwrcqPwoUQhLwkY2CInvwkaOf8J+VtGBmIiIKDkIM8JC8invvuZ/vv710CgNCASgKGEIWT3Lqkr4iYCrRqMKle2NbLmDwnrmCUQovQi3ig5nwkYqSXOK2uT85JCQ9w7zwlq2mTfCfoZVK6qmZJSckLz/wlq2mwqVgOSIKPUI7RvCegIbwn5W0SuGMsuGLgDzwkYOayLoufmJj8JCNnnx7w7DwkIC/8J2UqlzwnZKpMNaqJ8OxJfCRpIwKD0INfPCQhIAy77+977+aJwo7Qjkq4r+6yLo9PeCthy0gKsOHLvCflbTIuvCRhoLwkYyz8J60jVI8NPCfpZ7WjfCRjoslKiAmdPCQnaQKJkIkd1Piuqzgr4c6JuCyq9Go8J64pGHvv717JuK2sy8ryLrwkY+CCkNCQeqSrfCeuLR044aE77+94LK4Ont74Yq64oCYe++/vT3wkK6qJFngrLc6XO+/veCthMKlP1tC8J+VtPCflbTqpbx7CgpCCOOslnnvv70hChpCGDbgu4JS8J2FouCnnFx+8J6Cj8K58JGojAoHQgVc8J+vhQoZQhci8JGQl0Xgp6Lwnrqo4KyX6qucJC97XAogQh7CpeG9m/CRr4XwkY2IKzrgqYLwkb6w4aWAw7LIunIKmRe6AZUXChsKA2tleRIUQhLCpeGMkzDCpfCRjYvDoHLgoZ4K9RYKA3ZhbBLtFroB6RYK5hYKBlNxbFNldBLbFrIB1xYKG0IZ0ajwn5u78JCBlfCego/grp4vWOGPrT19TAo7QjlJ4Z2yP/CehYYvPUnhqrI+4aagTS0qR/CRjZDIuiQnP3s88JGEh0Vg8JGxvdGoJjo98J66gCbgsZUKMkIwJVrwkKmQ8J65klwkPztxVXs64YmiJPCRkqciw6IiOiUu8JCKp1wq76uQOlpJ6qyuCjBCLmfhjoPwkKuyXj/it5Q96qyOPeC1jSbgr4tFZkNd8J2SpTlTIiYi4q6ILjHvv70KOEI2OWDwkY2d8J6jiy7hvZbgqLNpJ/Cfr6rhoJThi4UuPSLgs5bvv73DksKneuCxh/CdlYTDrz0uCjdCNfCQp4Pgrp7wkIyaJeCwh3bCpT9c8JCWoT938JGOi07gooI/WO+/vfCYqacg8JGysyRJ4Ki2CjJCMCwvSVA54q22P24scvCQoLzgvrp9IvCQl6I9ez3hg5/wnp+ubFUuJXdG8JCuq+GMkwo1QjMm4ZGf4K+XbiY6Lzoq8JGcgyrRqPCSkbPwnrqy8JCVoeOAosOoNvCWv6Pwm4SyJ/CegIMKLkIs4LOtUzzwmKWt0agi0ajDgjfwkI+OcSbvv73wkL2Ow7nCpfCdlYFcwqjgvbEKCUIHNlc98J+VtAoiQiDRqCdd4oWS8JG8hS5KKlxRPy/Cpk3wkZKTzb0qXHYncAooQibgqZHgtrcke1R3ezxgJvCen6o/wqVWJ8OCOicn8J2Sonjwkai6ewoCQgAKD0INXOKCmPCei7/DuSdgPwomQiRD8J28qMi68Juyh/CRjL840agnPMKp4rSn8Jy1nSY88JC8iCoKMkIwJ2rwkZGFTy/IuvCRkZojL+qtjfCflbTgrZbOhy888JCWguGNtzM9YOCrvfCThrAuCgpCCHxPJHvgrpRcCh9CHTFk4ryPRci6PyowPyQqUko4PyJZPEriu59P0ag6ChZCFC7wkKSbXEPwm7KAKuCrjM+Bd2EuCglCB+CukPCRi4QKLUIrRVwt0ahgJ++uhT970aguL+Gqpj4/4Kiq4bya8Ja/sCVgZTzOheG9kjxQYAohQh8/8J+DqvCehLvwkZy04L+Z4La7Om0gMmzIuiXwkJuuChpCGCU4L3nwn4K2dDwmwqUkNOCmrXc/8J+VtAoVQhPwm4Sy8JasouGKl+KBtG0277+9Cj5CPPCou6Fe8J65qjw8VTrgp5fRqGZFe/CQtbDwn5GPPCfgp45L8JGFlsi68JG1oiRc8J+VtDgt8JC5tHNLJwoNQgvitK3wkKGIJuGhnwobQhnIusOwXPCflbTwlqmQOfCokJY4XEIkyLolCgxCCvCQoLfgt7JcJUUKB0IFOi5YQlwKB0IF8JCOs30KB0IF77+9Zz0KCkIIwrbhvZsvOk0KIkIg8J+esCLDpOCysTo677+N8J+usiTwn5W0LuCtnDx2dy4KE0IRJPCQhKjhp4Imw559MCLfrWAKBEICWlkKMkIwciTCpeqrtirwkaS4S++omnguaSJcLC7wkaS44YOHYWB7Ku+tv+C3lvCRjaLWjS19CiZCJOGksSJL4Z6+4KiKzorwmLSD8JCpgipdJfCSv5wqw4TwnoCQPwoQQg7vv5w+KvCen67DuyVgPAojQiHwr6SM8JColypg0agq77+9762A4Z2w8J+fjyRgJfCRloAKB0IFwqUqKjUKEkIQIiTRqMOkZGnwkKKs8JCptwoNQgvgqZngsp3CvFNxLgoTQhEqJkNu8J6AiDgv8J6AiyxsZwoTQhFTIjzwkaWV8JatlfCflbRxJgpKQkjgqLzwn5W0a0h576y777+98JG8h+qfkPCQnrNcevCRjaDwkY2L4Z2u8JCArPCSkbNRYyIvUvCQrbkifPCfm7tc8Jq/sj3grY0KHkIce20nMfCfnobvt48iJUM1bTbit5jCpeGKjGHRqAonQiXvuag6KWUmLuC+t+CmkCXIuiVz8JCkku+/vSU/8J+gij3gqLw6CiFCH+yRrdGo6qa08J+VtFHwkJ2MffCfo4DIulXvqIDqrIYKJUIjPWDCpVzgtqlg77+976y+4b6gJsO1LmDCqy7wn6+LIjkv0agKN0I1LuK3meqrtT8lXPCQnoR90ag1Jz1I8JGBlzttMyTwkKuAKnnwkaiwM0PwkKe0VeCzrCbgs54KDkIM4LeE8JuxvGTwn6CFChBCDsOLLuGJmPCQoIXwkIqACjJCMMOM8JisqC9YeyfwnrmfwqXDrDAt8JCGlfCQqI036qis8J+ghzXCquK6rik+Jjp5YAoTQhEkXGfwkYyPYFomZ/CRtpPVrApFQkMpJvCflbRRP8i68J2Su/CdkrDwkpCl8J+VtOCtjcKl4Z2Cw59QJEZg8JGMtvCRh5s9JPCflbR7efCeuZlLMeqnhNGoChRCElHIuuC6hPCeuqLguqV7L+GftgoHQgVZL9GoJQoPQg1lJ/CRkJPCuOCrkNGoCh5CHOCznUDwnrmLP/CQqI3vv73wkK2cXDTCpPCRtIgKIkIgPzDCpWwmJiU9Niov8J+VtO+/veCqj0Thm6bwnrmCPy4KLUIrw7d9J/CeuafDoX3wkY+CXD/hioI88Ja/sfCRiqc876qFRScvOfCRj44nKgooQiYk4bC+YCZ777+9avCRiJA0e+G/vH7vv73hv4xgyLrwkYekRXojXAoSQhDgrIInwqEuJvCbsbzwnrm6CjJCMC/wlquwS++sufCegZHwkJSS8J6ij2vDp0RF4b6A4YqNyLp8Oj/wkbWZ8J+VtCJLcAoIQgbwkIab0agKLUIr8J+CuPCfgYfwkbKS4K2H8JGMty7hnbI8S0xoe+CspdGo4K+BLycg76yXLgo+Qjw94KuD4K6yJPCfiaRG8JCdpFo/wqXvvIlg4oOl8JGxpO+/vfCQlrw/0ajIuvCWrb7wn5W0yLrwkI2GUSoKKUIn8JGkt/CvpK4/e/CeuakxJzw/44CuaD3jgZrhnbDwkIe5Q2dh4KCyChJCEE89feGJmCw644OC8JGotj8KHkIc77+98JCWj37RqPCehI7groJCQ+qjjlt3KnpYXAorQik/4oOg8J+ogWAv0ahG8JGPlzDgqJXgqJDita/grZVrJ1k8yLo9POCqgQpGQkQz8JyzufCdkIlH8J+VtHM/wqVnw4wvL2AyOWAvJNGo8JG1kfCdlIjwn4ix8J6iqWTvv6XwkYyCJPCfiYhg4rOu8JGIiwodQhvhpIlg8JCwgz/CpXcv8JG7uF0k77+98J65h1IKAkIACkFCP/CQi6vhsr7wnoWCLz/hi4AvT/CQqbjjgZk44LuKYuOCpWA8PHRc4Ki8JDTwn6mpQvCflbTwn5W08J6Xk+KCpwotQitJW8i6cvCQhbbIui4qKm978JKSv/CRsIPgq5B7KmHqo5PqqYLRqFbwn6qpCjZCNO+/veK2qSdce3bDq3ti8JGOniLqo5clZSLgqIHvv70mOirIuiTIuvCflbThvZlTKm/CpTsKHUIbOirwn5W0WCQ/8JGFnPCRsJ4m4KyP77+90ahcCgJCAAoqQijwrJuaPFw8J+CsteGvnNW3YOCuv3jIumVU6qyM8J+hmfCQlrvgu5lKCkVCQ/CQnoLwkbSGffCRqr/wnZSXIi5N6p+T6q+1cW9q8J+IrUPgr5A98Jauj/CflbRy8JC0gkU/ZD9ec/CdkoVs4K6kyLoKMEIuJyrvv70lYOCyiHvgr4rwm4WVw5vwm7KF8Ja1jlsqTjzhsZlgw41Z8J65qci6XwoXQhXCpfCRsIZi8JCLgvCWpZnvv71cwqYKO0I5PMi6KTDhn4V2OmklSXxn0agvSjLCpfCRjo7CpfCWvpF6w6nwnoWI8J+VtDwuYeG9meCzllxi4Y+MCghCBsKv8JCWlwqBAboBfgowCgNrZXkSKUInePCRmIF7JyQ3N/CRtbzvv71b8JCFg2Dwm4GL6q6tST3DrkbgrYhgCkoKA3ZhbBJDugFACj4KBEZsYXQSNkI0JCVqdPCav74gyLpdw4HwnrmkaW1GXSRJJuGdsj1TbDzgsZ3wkpG0aPCeipTvv73wkZmgOgqhG7oBnRsKGAoDa2V5EhFCD01qVy5OLteD8J6LnCzRqAqAGwoDdmFsEvgaugH0GgrxGgoGU3FsU2V0EuYasgHiGgokQiInYSYpJCIvYPCeuaQm762teeCohfCQto4l8JGwgVtLPz1iCiVCI2dB8J65p2HigIE/Re+/vdGo8J6llVwlVV5TPeK0sD3grZ89CglCB++tgEhuLjAKEUIPQdGoKuy3ijPwkY2pLsKlCgxCCmBXUSbwkLqsOjwKHEIaJPCQu74v4aCAJ3gkO0hj8Jq/uOG/jeuxvDwKM0Ix4Ky9P2kl8JCes03vv71w4aWALu+/vVwl8JGrleG9iG1ca+GdsPCRtLEnLvCegIXRqAoxQi/wm4WRIGjwkJaX4aKRXDY9e/CQgIvhj7rwn5W0Tlrvv709KvCQipU0MzzRqCZYKgo1QjPRqPCRhYLwn4eqP+C7jOC/gO+unyLwnrm+Oi7IuvCehYk8J+CsgSoiLyrgsqXwnYSFLyQKDEIKLfCRh63wnp+tWgo7Qjk94oGx8JGPkDQn4K2I4YmW4LKG8Jatnidx4a+LKsKr8J28qCc/JSTgrYhcYCbwnZWGyLrgrqPqn78KNEIyQDzwnZOZ8J+VtHs/77+9Ki89wqXwn5W0PGXbpvCRr5hc4reSJsi6Q0NIP+K2vuKDnDwKNEIyw77guIYnw5ZgXOClv8K14LeKJ/CRmLkn8JCEnifqoLnOhFhRZfCWqbQmJlQm8JCVvVsKCkII8J65iXnCpVgKNEIyM8KlQ8K58JCksPCSvq/hpKLjgYkiKPCWrZMu4KKC4L+OdSbhj5nwkZmT8J2RnF8nQicKFkIUJlZL4rSPdeKAge+/vcOd8J+bq2sKHkIcJvCRv47hj7XIuikiPPCeuYLwkLqG8J2Nosi6KwoGQgTwkYefCiNCIVws8J+VtGVs77+9JNGo8J+VtCo92InwnYCu4Ka5JuK3nQpEQkLgpo/tnrbCuydZKuK/tjxmwqJgcHp78J6TuPCRsILhg4d84YKtPPCQo7Twnrmb76y7Ou2ftNGobmDwkaSG4bCA0agKCUIH8J2UvcKlTAo3QjVc4LWM0ajwkZmpZjzgp5xdKuCvs8Oee2fwm4a2wqVi8JGxgeCqsvCQlqkm8JCUsiVP77mwOgoFQgPit4EKJUIj8JG2pD/wkIec8JGkifCeoLxKMu+/vfCQq4M6JvCfg6rgq4wKREJCJy5Z76yV4b2bTCjgrL0n776QLz3wkbWzKsi68JCjtPCWroI/XPCeubnCpfCego/wnYitwqV98J66oy/Do1fqoYQ8CkRCQvCQtZUg8JGHonvwkLy/dPCQlrvwkICU8JGko+GlgPCen6hcRXIiw6Eu8JGMs+qsi+C3lCLCpTzvrJUlX+OEvuCojwojQiHwnZK78J+VtDrwkbSA15EvKvCflbQ9TPCflbRS0aheL1wKCkII4belWfCeuZQKNUIzInHwkL62cO+/vVXwkLqeM+S4hvCQo6omw7PwnrqhaCrwkKiF8JKGoEtc8JCNkScl77+9CiNCITrwnrqtVtGo8JGcmPCSlJDwkIGJPeCuqM28MeGIsS49JwozQjHwnri78JCXhfCek7k6OPCQjJQ88JORlG8iL/CRiKdcWGlAwrhe8J+puOCqsiHwnLyXChlCFyVD8J65kk0v8Jatkz3wmJKTWvCen6IoCiNCIfCWvp/it4vgqKjwn5W0XC8n8JCEguK0rWDwnrmk8J2MvgouQiwk8J6AnDrgpqc9JzrwkKKqSG/itKDhiZNgPSUkJCfwkaWSKvCRj6HwkbWnawomQiRZcvCflbTwm4ei8J66gu+/vUd78JG2oEhe4b2Ie+Cvhu+/vVwKCkII0ahu4aeaL04KFEIS8J2VhnM+K3s9KuK5gSLwmr+9ChdCFfCeuLXhpoPhpZEqSeqsk+Czne+5qAoDQgEuCilCJ8i6XFZC3pYvYC0hIzXgq5DwkK6b8JCoufCQkrzwkYuyWEwmMuqfkwoEQgJgXAoIQgbIumxCOm0KHEIa8Ji0hPCflbRh8JGPifCRj5MkRPCdlLs6WyIKO0I5e9GoOkLwkaq14LOd8Ja/sWAvXOqpmHsuyrc98J65l/CWrK3wm4O94K6GJci6aMOk4LG/MT/wkLCiCi1CK2DvrYMrJDDRqFw/b1nwkICl8JCWgSk34KuQJPCQoLzwkaS3Uu+/vci6TCgKQEI+KvCQjbPwl6Sze+GKiuG/hiPgvofDq++/vc6E8JG0ksi6LsOOKDrDoUN4yLrhrphYJCVpe/CdlLHwnZKww5oKEEIOUyUyRidg4aWyYPCRi7UKNUIzwqUi8JG0gnXCpfCeuYLwn6OA8JGnmlzgsJDIumrIunfgsb7wnqCu4KyiU3vgrKM94LOMCipCKFzwn5W08JCAvPCRjIHDhj5Pai7wn5W0IuG/s0zwnrmNwqXgqpBgSWQKCUIH8JarqeCthAohQh/hoJnwn5W08JG2pj8xUVxULl3hqpXwnrmCLvCWubI/ChNCESYg8JCel+K3jXI8PVHhj44uCgxCCvCQu4LqqYc3PSIKEUIP8JCplWLgoZ7gqpAkYCIoChdCFe+7i8Kl8J6Fj+G/svCRkZ8i4LqIPAo3QjUvblw2L3XwkJa7wqXvv73wlq2e4LKo8Jq/tu+slScqOvCflbQn8JG0vFAqPfCQgLzRqDTRqAonQiVjLvCdlJfRqDvwkY2LIvCeuKLvv706bMK6e+GLheGglPCdlJQxChBCDiArU/CflbTIuj4uyLokChhCFj116pmA8J6ApCTvv6NiOvCen6TgroIKAkIACgZCBPCRmYEKF0IVSsi6aCpgcMKl4LqB8JGplzzIuj17CgZCBPCRjYcKQ0JB8J60udGo77+98J64p9GoJiVY8J+EkPCYtIc58JGDpVLIuvCdvKVjW9Goe/CRrIDitK1AQD3qk7kq8J2YkPCflbQKRkJEPEV7cfCQpp86LfCfg6lO4YuC4KqJLuCysvCQhJ464LOgPvCQlrvwnou/Lj3wnYmCLuC2vWok8JC5vvCen7Dgr5DDhlMKNEIyw4ZL8J2Tt/CbhZUkJ/Cdp5xu8JCoqCpbfPCRjI/Di3Rcb2LwlryLJCcmJda7ey3gqrgKBEICTSYKOkI48JCeuTw6LyLhpLfgr4hgIkgu8JGkgO+/vWDigrciKeqSp3sxKuCgtzhYMeC3k8i6YOKCruCuny4KKUIn8JGkqiQr4L2P8JGmpCrwkYanX8O1JuGDjSVuPTp3Pyfwnbym4K6ICjhCNuCiqu+ss/Cdkqow8J+VtPCcvIon8JGMsnYiOD8/UEXwn4GpQD7wkIGAwqV6cO+/vfCRiog/UAoaQhjIutGo8JGplkdOZOCulOCguT4vPeGvvi8KIEIebyLgrYx7d/CRjY3wq52/e++/nHvqrpw9wqXwn6OBChxCGiXvv70lQ/CdlJIzXXQ88JCDnSbRqPCfq6R7ChZCFHdS8J65vu+/vdGoJDom4Ki5eTU8Cg9CDe+/lCIsOlHwkKeK0agKHUIb8J65hz8mMXvwkKGVJfCRg7BeICI84LaC4KaHCitCKXfwkZyVNfCdlZAq8JC0s1EnWD3wn4K38J2NpypcZOGBpyV78JCugCcqCh5CHCIrPCQvfOK0rVTwkaSVdFvwkZy617A/LvCctqsKK0IpRPCfnbJfOvCRtJvgqJDvubDRqMKlXPCflbRD77+9VPCRj5Xwn5W0V2sKK0IpPyLgqrnDm9Go8J2UvfCfo4EgwqUnPXtS8JGZkV86ZjLqorlX8J64uVUKFUIT8JCok/CehY9h8JCPkCo/4La0JwoxQi/wkIaXez3hvZvRqPCflbTIusi6ayvwkZywwqPwnZKm76qbPF3CuUoyPGfwnrSYOQpKQkhOX/CWq4jwkbaT4K+18JG8kPCfnozwmLSCcErhiox28J65h+Czou+/vSrwkJ6gXCfCpTrwkaawYD/wkLqxwrXivoxVZPCfg44KKkIoNUHCpVXwkKC8SeGyo8O4J/CbhLJIJz/hqpAwdic/4Kq3JuCnnHo6bQowQi7wlqmZfHvwkYyyau+slCJ76qyFODthP+CxiPCRg7Hgs7Hwn4ORdz0s8JatqCYmCh9CHSQuei958JCbjGDwkICf8J+VtMOUJ0Q/Jl7wkY2QCgxCClxU8JG1uuqshU4KL0ItN1w8XF3wm7KIN+C6hOCpgHdh8J2qo24nYzTwkbC84aWzMO+/veCnoyIn4oWRCkJCQOCpnmgu8J2UveK1iFLOhOKBh2zwkYeFUdGo77mi6qKFWNGoMXvDkU3grILgr4rhnYzwkJa84YmLIvCQlpXRqDoKO0I5QSU6Ly97OvCRoKHwkbSA4rO/ZzzXsuG8m/CeuYk68J65u+GfpOCykCTCpVzgsajIunsvJvCimKZNCglCB1M6YCVgOV0KKEImcPCQoY0i8J+bui/wkKC8aPCQhbgnOz3gq4l7Yy5b8J+DgvCRqJQKKUIn77+XYMKlJShFTPCQuaDwnYuNYOCsg/CQq7PhqJLvv71ZwrPwnZKsCglCB1l7PkFtIz8KQEI+Uci6Y/CQlprqrqtcLvCflbRgcMKl4LKOJSog4K+G8JCov3vvv70u76eyXPCRirQm0ajwkbGf8JarqfCRtqQKA0IBLwqaD7oBlg8KDQoDa2V5EgZCBO+/vScKhA8KA3ZhbBL8DroB+A4K9Q4KBlNxbFNldBLqDrIB5g4KBUIDKC8kChpCGPCQh7ok8JuFlUzvrKNs0ag/6pmp8JGkmwoLQglQ8JC1oW3vv70KG0IZ8J2Su1w+wqV4TfCTkYIl4KyzOuCguS3RqAobQhl1NOGlsuGti2srV/CQto49PyXIuirwn4irCgVCAy4qQAo1QjMz8JO3nWBdWPCQipTRqEM6P+K4ivCflbRyP+C1hj0uwrkzWCEv1LwvdHJWLsKl8JGOi2wKA0IBWQoQQg4lUjHgtIou8J6fruKRhAoMQgoiVsOobPCRiKgzCiBCHlzwnp+oSuGttirqpJR5eyZi76emN8i677+XPSBldwoOQgwm8J64p8O1JVQkyLoKCUIHLuOEjG0lPwoxQi/wkbaBIk7wn5W0aO+3j2An8JCnjT/gqZEvJkvwkKCIVCck8J+VtOCzpuCrieCunwo+QjzOjPCWro3gtr3goKEz8JyztuqgslFY8JCVtuGKjUM8Sz/qn5M/L/CQnY3jhLjwn5W04raSKC/gp50mUDwKJ0Il8JGkuz9J8JCrhj3wnLyw4Ku9wqXwkbKfPeC3j0ckLzvIujxBPAoHQgUmwqtsMAocQhonIvCdi4PgrK3wnrih4LOd4aeYfuG9m0BCdAo+QjzvpYV68J+DiuOErs288J+VtGDitK3wkaekPT82PHtc8JCMm9Go4ra7KidgyLoiwqzgqogv8JGpq+2ftz8KM0Ix4K6a4aON4K+GwqVvXDx08JKQsc6M8JariXxP0ah7wqU8WCQk4LC50ajvuYk877+9IgoCQgAKC0IJ8JGkgvCfn7AmCj9CPVzvqpDwlr6UPzsiwqXwn5W0JCTwkY2HZPCflbTwlr2GXvCdkqkgI+C2sOGMlfCehYbqmYI6PFxlLm89YCcKHUIbyLouPU0uyLo+8JORk3cm4K2Ecy9HInLwnZWGCiBCHvCQs4ZK4LeW4LOx8JGHheC2lfCetJ3CpeCwuSRcJwo/Qj1T8JCWjuCgqiVc8J28psi68JC5szZgw4RcZeOAhfCWvZ3gqq3igLEq8JCDlUot6qeZJPCbsLV70ahc4oKQCjxCOis/Li/wnrS68JCpmOCqv/CegKHitr7wnbya8J+VtO+/vWptWeG9gfCflbTwkpCu6quqw7wmIPCdjJgKOUI38JGNkCIz8JGNovCeuKRPPC7grofwn5W08J2EsMOxXG88PHov8JCghfCQjoV7yLrwn6G2Zk0vTAooQiZAbjIq77+9OFRi4Lijdzp0w5p68JGMtfCfqarwlr+jTvCfrp8uZgo0QjJ2J/CRjoB78JarnCTDovCRrIfitq3wkIGG8J+VtMOw34Ml6pKf77+C4YmbJy7wnZKrWgocQhomPVXgqKE4L04n8J+JkT0k0ahgaMi64aS6KgoTQhHwn5W0OfCRtaXwkbSI6pW+JwozQjHwkKO0XO+/vWwi4LqJPMKl8Jall3M6w5gnOWlB77mx4bGQe2Uge+qglHElLyIv77mrCgtCCXNeb2FwKuCnlwoIQgY7TTxQOi0KIEIeYD/wkK6ZIuCoj/CRu6bDk/CQoLfwkYiN8J2SvmAqCkxCSllwZ+OHu+G8r/CflbQjWfCcvaBh4K6a4KuQYHfwn6mhceCukO+/vci6JuGylntg8JG/ky/wnpeQ8J6fpfCQnrbwn5+w8JGkt0xqCiJCICLhva3iuZrIunk/XybigIAlyLpiP+OImicn8JeMgGcmCgVCA92wPwo8QjolUPCegKNc4Ki14ZyPIcK/PDzhuIkr8JG2oMKlXPCWraDwkI+QVOGgl9Go4q6U77+9LvCQirfvuasiCjNCMWngqLJ74aejXD8jJ/CRnKkuXPCeua7wkISe8JuFp/CrnoDwnp+u8J65mSAuLuOIgyQKJUIj8J65oiLCqsOw4KGhWkhqw7zwlq2Z4b+HP8KzKnLwsZugJU0KF0IVyLpcID1LL+Chl9GoXCckcHjwkIezCjFCL0vgt5nwkYOAPWk74Kmb8J2UkSrgprjhpYA58JG/muCjgci6byYl4ra6KksqXXtiCgxCCuChjHxZJ/CQg4sKJkIkKmA6LlrXsiYk4oKuPNGo8J+VtO+uuPCfopdWLkdleyfIuid3CkBCPiLwkZy6IiXwn5W0OnYiJ+C9pci64K+Bw5oq8Jy1nPCflbTCpfCflbR7Ii7vqpDwkIexdz868Jatn2tU4KehCitCKSrwkY2MZfCRjZAiIPCeuIHwm7KcXPCfr6bwnoWP8JGMsE0u8J6Fjsi6CgpCCOGJmF3qqYU8CgJCAAojQiFc0agi8JGDguKAneGkoUsv4b2deuGkoci6P2cl8J+csnEKEEIOajzCpVlp4Ku9VDo70agKP0I94ZyPRyrwkYyjJVzwlr2ROOK2qeCoqsi6JzPwnbyGLyLwn5W08JCBiHrwn66MYCfwn5W0IOqsrWw/J+GjkAoiQiBc8J66jvCQj5LwnrmCJCLgu4Zm8JCpiF888JGKi+CxigoGQgTwnoCkChxCGmDvv70l4oSgRPCehLwx8J+VtPCeuqHwnrmCCnS6AXEKFwoDa2V5EhBCDu+/veCzhiNG8JuBhCVGClYKA3ZhbBJPugFMCkoKBEZsYXQSQkJAOlzwnoKPYPCehYXqkpDgta0mXPCdhKTwnYi1XPCdoIPvv71u8J2Lsj/wnrmddCPhvLB7wrxa4KqPZfCepKllPwqRBLoBjQQKGQoDa2V5EhJCEOGLqVtg77+9Lzoi4rmDwqUK7wMKA3ZhbBLnA7oB4wMK4AMKBlNxbFNldBLVA7IB0QMKEkIQOiQq8J2qpCzCpXrwkY2QPQoTQhE6bPCRtInwkYuxJ0rwkJ6XXwpFQkMk1ZLwkIC18J+gi/CRsITgrLPwkJao8JCLoy7vuY7DqfCdjaDIuj/hn7fgs6fvv73RqOC3s3lw8J+VtHRZ4YmN6qmCCh1CG+CwvlFo1b0q8JCoj+CtoGjwn4GUwqU8bOCpnAoSQhDgoKBiIvCdvKXwkYuaOkEuCgZCBGPhjooKAkIAChVCE0cz4KaJeTzwkKC3PyXgs7IyMCgKOUI38JGOlVxM1o5j8Kehl/CflbThoo3jhJ3wnbyYPDrwkKCUJWE977+9L1zgqp0kKvCflbTgppzCpQoiQiB8Lj8lPeCqljrqqLQt6qeWNVMmL2098JCAvT864K6ZIwonQiXwn5W08J2qnWfwkKGQ8J2ipFxE8JGopfCbspVPUj/gu5hGPXkyChhCFs6f8J6AgjlLQuCmhfCWq4PgoafqrpQKNEIyZjwvUy/wkr6swqUkJFzwkYKn4Ka2QvCesoRSNj8kYHtR8J6AuCo/0ag68J2SqXDhnaoKA0IBMQosQip8TWfwpYG46q+54Kyw4LCTYPCfoZnRqCIuL9a6e1U88JGNsfCWuofgrp8KrgK6AaoCCj8KA2tleRI4QjY68J+VtPCbspjqp7Qu4LuJJfCRjpvwnYCC1o7wkJ2nIuCqh1zwkICC8JGBlj0i8J6TsHlc0agK5gEKA3ZhbBLeAboB2gEK1wEKBlNxbFNldBLMAbIByAEKHUIbJ3Twnp+oPW7IuvCutolT4LeW8JGNtDrvv71qCg1CC+GLhVzigITwn5W0ChhCFmgmJeCqgi8l8JapqPCbhp7wkbyQIioKGUIXXfCWqZPvv706JSrwn6GS4reIVvCQlowKHEIaQvCek6sz77+98JCDofCRr4PIuvCQhZE50agKOEI2Ki41XlDqqYIlPMKl77+9w6k/QfCRtaEiwr9r8JuxsPCcuLTgr5fgqp3gsb48OvCRtJxiPcKlCgtCCeK2tuCmpeCpngqLAboBhwEKMgoDa2V5EitCKe+4qiU88JCViVw88JGDgSo/wqUiJ/CflbRaTCV4YPCQjrdJP9GoIid2ClEKA3ZhbBJKugFHCkUKBEZsYXQSPUI7fD3wkY6LyLpg4reF8JC7v3vDoG3RqG/gqag84aqx0ajhi5UmLlrwn6CFw7so8J+IuuG/tEjIuvCsgpEKhQG6AYEBCh0KA2tleRIWQhRqTuGWtfCegahUOmDwkKi0b+2fpgpgCgN2YWwSWboBVgpUCgRGbGF0EkxCSvCeu7Hgrqo24LWwSWwqenvOjPCQtLRKQfCQo6Zy4LmF77+O8JGNi+G9nci68JColvCQpo7wn5W0PSbwkZamKTkx8JGwv8i64K+XCk+6AUwKEQoDa2V5EgpCCGHgrZ3wnLy+CjcKA3ZhbBIwugEtCisKBEZsYXQSI0Ih4aKhPeCqmGDgrK88YPCfooDCpT/vv70q8JCAs+qso3cnCrsLugG3CwoOCgNrZXkSB0IFIi/gt4oKpAsKA3ZhbBKcC7oBmAsKlQsKBlNxbFNldBKKC7IBhgsKC0IJ8JChoSU677+9CidCJfCQnaDgsaE84LerwqV7ITjqqJgk6p+R77+9cu2flXpe4KiBbl0KBEICwqUKHUIbL/CflbQgJ3vIuvCdkJjwnrmb4K+G8JC+uyVfCgRCAjgiCihCJi7wkJa1yLp84K6q4LqC77+9SlDwlquxUvCRvIJ7fidg8JOigiZgCjxCOvCQgK7wkZuRP+GljeCtlyUvw6vqroPvv71yPzvhp4MiyLrwlq2pJirDkzrwkKO18J+VtHp76qyNYHcKA0IBJQoFQgPIuigKMEIu8JC8v2E10ahbwrHRqCfhnaZuWTlgcOqsjOqpiG7wkKS/Mj3wkJ2QJyXwkaihMAohQh94YC4q8J65lHQk4YuzP2PwkJ604ae3wqQv8JCdjsK1CiZCJO+6gyvgvKk6e/CeuLXwnrm+8J+VtOCqs/CdgKM44KuMOOqhjQpEQkJc8Ja/oVknLitk4a6+4KyhRWDwn5W0ODzwm4qA8JK+qCI/YuGdruCovuG/kcOJ8J6ii+Gggj/grpNcb+GLnPCQo6UKH0Id8JGOmT7itbAz8Jyxssi68JCetjh7OiPwn5W0LzwKAkIAChBCDjwi8J+VtOCquOCxi1x6CixCKvCfiKhM8JCKjm06Oj3hnoZK8J+VtCTwkK6bLvCYtITwkZKg8JuEsuC4jQosQirwkLykL0Jge/CWupPCpSV88JC8l2fgqZFc8JCTkjrvv7xHO+qfme+/vSoKNkI0QHUo4K+XwqUq8JGZmVN7eyoiNu+6mvCflbTgpo/vtp3gqrd+OFXhirQk8JCklVdH8JC6sQobQhkhdyXwkICVNGN78JCWkdGodPCRo7HwsYCFCjJCMOK4lO+/vfCflbQk77mwWeCovHtc77+96qOZYu+/vUXigrFjQTxJPfCRkqXDuFBFJAopQicl8J+Fuk3CqfCQtb7DsS7IuuCvgOOBjy4l8JGvtO+3jzwve/CWvbwKN0I1ZWDwnaqd4KiJIk/qrLbhoI1EaydcMfCQhKHgrpzgprbwnri54ZykcGvDu/CRiqLgpqM7Jm4KDUIL0ajwnrqpPCYtOi8KRUJDPfCfn47wnrmh8JGKn34k8JGvjvCRk5cvYPCegI5x8J+VtPCWq4XwkLWOYDzwnZK78JCAv3fgqILqqYQiwqXit5AkPQotQivwnrm+77+PISTIuvCQlrxW0ag777+9YDwkYMOZe/CfpYPjiI0kXPCRpJUuChNCEVwpZjPgq7otXCLwnZS8JS94CjNCMfCRiKwy44CdJih8IizgprJC8JuAmOKgnldjJm4/JeCzo21877mcQOCrgMi6e/CRtpgKJ0IlWDNFzozwnLqY4aSA8Jy5n2EvwqXwkaauyLom4oKXLj5S8J+VtAoQQg5K8JCumjTwn4+r8KqljQoqQig60ajhj7lJcFw+77KRSSs8UFxUwqXwnqWeVifwnZSKXGwqcuGlgFIuCidCJXTwlrmZPNGo8JiIlSYue/CQoq3FkfCflbQ84KqH8J6fqCJiJC4KLEIq4YmW4oGT77+9KiUxKipl8JCjtG468JGllyBILvCflbRz4bG2KlTwlqqmChxCGjY9ZeGbmScvUDxMd2gvJ+CuuCclLz9cL8KoCkBCPvCtp5Iv8JGMtiU/PeK3lFzgsZXwnri7PPCfgrrwn4mgLOCoj/CQrpDwn5W0SHUpJOCuj/CQlrzCpSfwkY+FChtCGeCunmB78JC7hCJDLnHwkK6t8JGdheGLgCUKNEIy8JC1oeqmtjTikYHwnoWOLmwtLS7CpfCQlqolJ++ssVw9LUDwkKGTUeCqjPCRjJDgt5YKLEIq4LWn8JCMgCvwn6qsYNGoQyk/Snwi4KqCezxoPW7IuvCen7UkbsKlJy4vCji6ATUKGwoDa2V5EhRCEjbhvroq8JGwn3bCoy8y8J28hQoWCgN2YWwSD7oBDAoKCgRGbGF0EgJCAApDugFACg8KA2tleRIIQgZHQGAqd3YKLQoDdmFsEia6ASMKIQoERmxhdBIZQhd74LW3KkPgsYjCuT8n6q+1JiM96qyVTQpougFlCkAKA2tleRI5QjdBTuCyj/CehYXvv71YIOGDjVhrXO2fmSrwnZWG4LiFNiLwqaOVXC5s8Ja+h0s68Jq/uzrRqCpNCiEKA3ZhbBIaugEXChUKBEZsYXQSDUIL4rS+XuCtlfCQrbkKXboBWgo6CgNrZXkSM0IxOFdCJci68Jy8n+Cyp8O18JGboEbqroQqY/CWhIAuKEww4rq6wqVr4KumXCThiYxVJgocCgN2YWwSFboBEgoQCgRGbGF0EghCBkHgp4dVLgpNugFKChgKA2tleRIRQg/gsaFc8JOzluCln3tBPicKLgoDdmFsEie6ASQKIgoERmxhdBIaQhjvuanwkYyzJCThvYjdsfCRg7l5w5JWJEsKeboBdgpBCgNrZXkSOkI4wq5B8J+giOCpjfCeuYtEJU8nLtGoUSg4PXnwn4at8J2IpXVc4LOG8JComi/grILwkJaU77+j0agKMQoDdmFsEiq6AScKJQoERmxhdBIdQhvhia/wkr69Olwu77i2wr8uL1zvv71GPMKlJSUKX7oBXAogCgNrZXkSGUIXJ/CRsL7wkKS/Xifgp4fwnp+t4KaoPSkKOAoDdmFsEjG6AS4KLAoERmxhdBIkQiLXgsKl77+9LvCeuqvwkoq/77+Ue/CdkqtANXtfXC/wn5W0CtoWugHWFgoyCgNrZXkSK0Ipe+OEq+C3libwkbCm8J+VtF4uItaA8J+VtDkkey8ld3vwkLSPLtGoXiIKnxYKA3ZhbBKXFroBkxYKkBYKBlNxbFNldBKFFrIBgRYKB0IF77+9KD0KJ0IlYCfwkaalPWBgRi8q8JGbhfCShonhv5fgoaHqorduY/CSkJkkLwoEQgJ5QQpEQkJk8J6AnT808JG2kSTigbEyJOqplCQlYPCRu7TCoEcvLsOzI/CQurHgtZ/hnajguoTwkKmYKnjwkYeu4b6OQGDqqYsKD0INPSRzOvCdjJPvv73IugoCQgAKL0It4aix4Ky58JGwuMi6dyclY+GypOCoiPCQrYx74LGhJOCork0q4KiPOmA+KiJMCitCKcOY4Ki58J65iytgRi8lYFRqwqXguodgSMWqYMOoJci64Lud8JCWqTc/CgtCCfCQrKTwnoCqKgoQQg7wnqKgXPCbhLLgvac8TwojQiE6MG9EwqXwkbaj8J+VtGVc8JGlgCjwnY2j270u8J6Ao2AKSkJI4K6HLfCSkbAu8J+JkGA1UfCRjo4iZcOy8JCEhyLwkIyLJ+G9kfCQlpQm8J65h0Xitogs8JGHqO+3tC/hi4DDt2jwkKeU4YuDCi9CLeG9jC/wkbKtJEpr4aSa0ajgrIM94LeWJSbCpSfgsKFr4YOhLzXgtYbgsIjeiQoMQgpcJ3dDKiI/PWA6ChtCGeCumcOwyLpO8J65lD97wqVkUGlh0ajgr4oKF0IVP/CflbQ/77S08JC0s+CupMi6wrB7CitCKUTwkJqZ8J+hkyThm6JJ4b+vJeC3r/CQtLUk8JORg1Ru4LGK8J+podGoCi9CLVfgrqhTTi8/wqVc8KuTgu+3tzp7Ly9Q4KmLeH4qYPCflbTDvvCQoLg64aSZYgojQiHgq7t78JatnO+/vSVn6pKSIuCniCddLvCQjI3CpT86J0gKCEIGwrDwn6K2Ch9CHSXwkKi6L3tBIuGytDzwnoCBPW7CpXHvv709Yi4lCjJCMPCQoIXwkYqm8J65teCvgPCdnI1EL2NDJ1zgr4vwnp+t4Kmp8J2SpfCdkLRG8K+hmAoQQg7DnOCuny9gw5xRPWDIugoaQhgl8JCukeCtiEh7cuCnryXwnrikOvCflbQKE0IRPeKCheC1iuqsjC8jWuGmhCEKHkIc8JCwtD1DJsOFZuOGnNGo8JGNoGDbtCRg8JCgiAocQhrvrYHsuLfwlrmee8Og0agkXuCyuSXRqOCosgorQinwn4WA77+94LCAwqXwnaqd8J6BheGol1vwkZqJPFA4wq4/4KGoSiVKYwpKQkjhiYwkX/CdhrPwnrmd4KeH4oCGIvCRioHlqJ894riT8JiqiVzwnrSB4L2F4p2H8J65lPCQkYZ7XCXwkbWk0ahtIlTCpfCfh6oKC0IJKuCntci64LCTCglCBz3ikYYsPTYKLkIs6pKS8Jq/sHvwlqmDWHd3XMOcRlwk8JGkveCumdGoJi9fYHtTTUQtVmZ7TlwKHUIbw4fgvrPgpovvv6EmSj/wkJ648J+VtDzwnLy7ChRCEu+5huCtnPCflbRb0agvLsKlZwowQi7wkb+UwqVl8J66gCbhmrYy8JGsgkY8JfCfgo/Pi2B7IuCmsnbsuK/CqO+/vcKlCi9CLSrvrYHwnpe/8J64udGo8JGTkV9JOuGJtPCQsKpFcfCcsZ7gqZ7RqEvwkYiPeAocQhotJVwnRCbwnYuFJPCRnKAt4K6ZKvCfopVpOgoRQg8nJe+6nfCQp4vwnrmiwqUKDkIMJ1zRqPCRjZfIujM8ChNCESdx4KKKJ2A6LvCRjYdZYHthCglCBybwkY2QIj0KREJCw5o5cCRLPPCepLrwkY6GXeGKuuG/mT1D76y+4b2d77+9ZE7wn5uf8J+VtPCdi4B34KiB4oCJVlw/4KegwqXqo5hKCjVCM+G/suK2rS7CpcOn8J+qjyY/UCVC8J+VtOqfkz9aeiZgJ0XhoYXjgL4u8JCFqvCQiqw9IgoKQgjhoIgkKu+/vQoFQgPhvZMKFUIT8JKRsvCei5/gp47wnrS4XCIodgo9Qjsn4KyL8JGislY98JCumfCeuIInd+GuqMOSPyTRqCEkNk5YNMOE4Kqy8JGHq2Am8JarhXUi4Luf8J+ipgo4QjY8a9GoJUfwn6GZXPCQu4RmJ+CqgnB7ZCvwrJO777qEP08zXCTDndGoJeCuhvCQlrtMJS7vv4QKEEIO8JashmBgJ86F8J6fqGAKHkIc8JKRs/Cvp6Thm6giVSh7yLoq8JCWhfCWvpB4JwoIQgbhjYEiazoKFUITbMKlPycnbzvgqrLgq6Pwn5W0IwofQh3hqqQvZmDwkY6cKloqOmA/6qqeYPCfgJx+8JCohgoKQgg0OirwnZSZbAoFQgN7JmMKQ0JBJe+/vfCRioF7ZkV78J6EjeCtg/CRm4AvM/Cen6E94b6m4reO4KmZ8J+VtMKlwqXwkJ6CLzhjw4VFNeCpkWDgqokKFUITZVBz8J2SnvCQoLxke+K0lyQ5dQolQiMqIeG/l3hC4rqQdGxc8JGBq27Iuj9QYPCfn7B8Oe+/vS8kJQo+QjxgKSbIujzvv70/b/Cdkqvgt5bwkaei4KmN4Lez8Ja/sHsuwqzwn6uy8JCrs2/qk7Y9IiZdPDMk8JCAuWAKGUIXJVk/4ayYI+GjkiXwnY2MYPCuuYVp0agKREJCTiR7PGAk4Lq08J+VtPCflbTCoPCRtasl8J+VtOC6gfCflbTwkKqd4Y6E8J6Kl+qvtT/qrKPwkKGhL8OiOtGo77+9CgdCBT9H4K2jChBCDkMlL1PwkZmQ8J6AqCpwCgdCBVIv4oCFCiZCJC/wkY6APS7CtsOMJuGJnXtgJiJU8JGIi/CflbTDl8OUdMi6IgocQhrCpS9uNPCel7NUdjrwkLqteOC1g1zwkJqoNgo7Qjle0ajwnYuFSMKl8JCWpi4qKuC2tGAi4aSp8JCYqeGglvCWvpvDvtGoP2BX8J+VtMOe8J64niTvv70KC0IJ4b2RJCc94KiFCilCJ2fgvo5c77+9K2A5UuCsiibCpWBCLiZIwqUq4LSHffCdkqUjyLoxJwosQirwkbWW4KCz8J+Al+Coh+Cxnc6h4L+F4K6a8J2UvkpJXDYl8J65i2c2YD8KF0IVwrLgspVEPPCfq5AnOuC0hXt3wrtcCkBCPtGo77+9Mz3gsqPwnZu/e1fwnY2tN8i64K6KYCIi8JCwgS4mSvCfiZHwn6ykPC/wkLqx4Y2rKmzRqPCRmZgtCipCKCLwnoCoaTrwkI64JO+suzzgoZ7gtrdUJOCpmUHwnaqrYFHhhbHvv70KP0I944ecPXNA8J2TgSTgqLMl8JCWvMK+w5zhp5Vi6qurcfCQlrvgrYgh4LCqSFM78J+VtOG9iVg4USXRqMO+OgopQifwnZKrIVx8XPCflbTgu4ZjYF8neyPwkKGV4KqV4b218JuFpfCeuYIKNEIy8JCplD8n8KukveC2keGDh2BOKsKpLk7wkYSi8J+VtPCQkZrwkY2N77+9Nlzwn5W0PyQKJEIiJ2nRqCXhnoYq8J+qkCkvPCbigYXIuuCxrihJLcKld+CzlgohQh9ML8K1P8i6JvCfg4Twn5W077+bUzs/Q+GKtfCut4AnCgtCCfCQl65i8JCUsAodQhtgfiwvPEol6qyLNsi68JCTnF7DrcOOJfCegIoKEUIP4LOgSPCQjLAvYDzguok8CgtCCeCqtWAv4LagNAocQhrgr43hi4/gvrTvubMq8JCWrnMmXPCbholjPwoFQgMvajoKFUITJPCflbTwnrmZUvCflbTgqq10eQoCQgAKFkIUIk5c8J+nnEHwnoCX8JGKkSVpazYK9hS6AfIUChUKA2tleRIOQgzgp7vwkI+T8JGMsz8K2BQKA3ZhbBLQFLoBzBQKyRQKBlNxbFNldBK+FLIBuhQKCUIHZfCQjIQ0PwogQh7hj7VSeldg8JCVt/CRtaDwnrq44KmIwr/IuvCRiooKPkI8J/CQrY7hmL4k8JGcmHt6LiDCpcKlPcOc6qmKdlxP8JGOsXfvv71M0ajgtYfvv73ilLvwlrmYPfCeua8mCg9CDVXDpfCflbTvv70id3gKOEI24L2F8J66svCflbRhJPCeuLnwkKO0Om7wnYaSOi42Pyc6S/CdlYHwlr+kJVx+JvCen67vv71cCh5CHGkuYDh0IuGBqOCpjGs/4LWVZyrCpXAwe+OChyQKJ0IlJuCznvCQtJLwkaunPCLgsZ0kyLpT8J2VhC3wn5W0yLrhqZU6bQoSQhBcIS9iYeC3ijziipp2RCVcCjZCNCTgtrcnKfCflbQnwqUuU+CmtuC3ivCqtpJW8J+VtPCShYA/4ZW88J+jgENcLDI/deqpkSQKQkJAJOGprmvwnoqs4Kmew4bwnoC0QeGLgPCSvqzwkI6S0ajwn62L8J64h+OEkTnwkKeAwqIge/CegJzgsJDwkY+FSwo8QjpnPO+/vfCfq6Ng8J+VtPCQlLjwkLqL6qeW8JuCkTrwnoCJ64Sx8JCWueCroTUn8JGklTrwkI+QPMi6CgJCAAoXQhUxV9Go8J+CrMODJOC7gtKQIi8lwqUKC0IJ8J+VtC/wn6CcCgxCCvCQqIbvuavgqrYKCkII8J64py4nPCoKIEIe4pO18JG9iC/qrabhnIklwqVf8JKUmiQ/zodN44WZCgRCAk4nCjxCOtaNJcKlKmHhsYkq8J65ouCzgEHCpOCnnPCflbTwnrmxS/CbsoYiTOCssvCQlrXvuKjvq5lQ8JG2l1YKBEICyLoKG0IZ4LazwqXwn5W08JaugSUq4KCz4LaG8JCgvApCQkDwkY668JCAhzBk4aqEajl7YFolfSbwn5W0XPCflbTwnpe4LOGds1TgqqtcKuCovy9cOvCel5HgsYTgrLPwnZScCh5CHDoi8J6Xv++/vWniv4nwkKmY4KiB4aOzLuqeiyIKKUInZe+/vSfqqZcwV/Cfr5/wn5W04aqJ8J6Lo2BZ4ZytJj3jhKHhmqFdCidCJV7DptGoyLoqStGoTicl8JGDsS4w4Kqd0ah44K65P8Kl8JGKiHsKMEIuPyw6Lz95JFxk8J+rtGxm4LOy8JG1pVZkOi/CpfCcvYYlXFBQOio8JSUk8J+VtAoYQhbiuopYPUbwlqyW8J+pqeGKovCbsbZeCjlCNyTwkKiPw5xwIDxP4Ki8KvCSv5fwk4aK8J64p/CflbRo4KmM8JaEsSQ8ICo9J+CmjyokLlwjXSgKK0IpOuGqtfCeuYJz8JCUnTUs8JGHpD/DtnVm4Ymb8JCzqOK6giovWT0vyLoKIUIfOml5XtGo8JG0jSZiLCTgqrLgqYd+JnvCpWB48JGMiQoNQgtGw6TRqNGo8JCSowoTQhEuOuK2veCwi/Cdqp3CpeGltAoQQg5t4Ki+8J6kjCXwlq2ZKgoQQg7wm7CtfeGgkOKRgiTIugoMQgomImNcwqVWPz1gCgtCCe+4pXou8J+rogo1QjN5KlknTNGobO2fgPCeuZFgLsOGwrXwnrmXIiQu8JG+sHNHLmDhqKZ7e2gv77+k8JuyhCQKDEIKdeCvkOGds8i6MwodQht84Zy1J9GowqXDuu+sl+C3lD/wn5W0NO+/vTkKGEIWRPCflbQue1LRqEheTOC6uCY98J+psQo2QjTwkIqQ4aKBP3HwkL2zZeC1jzTwnqGeJSLwkbywa2syJPCRtLw6Ol7wlquCzb7vv73wn6GpCjNCMeqlp/CbhZHgppDCpeqahsKle/CQj4g7NeCws++5qTw677+9b3tV8J2Sv+GxtT/goLIKLEIqOvCflbTguokn8JCgiGDwkKaUOvCQiozwn4GGOuC8tuK2uUM5azzwn4mBCgpCCPCWq7RdRlxPChBCDi4/6pmF8JGRnvCflbRgCgtCCfCdlYM/8JGbiAobQhnCpUki4KaTa1vgtb1WOiPCuHsow7nwnZS9CjVCMyXDtXLhn7cmJeCyuTrgpoU6Iu+/pPCQmJ7wkaSgPXvwn6CJ8J+VtHd58J6AqT034Yq+JgoCQgAKC0IJ8Ja8lEzwkYOVChFCD1zgobtFXW7wkYWU8J+VtAoGQgTwn5W0Ci9CLfCRjosi4qGk1o0iJ/CeuJfCpSM/6qSryLrhg4dce8KxIsKlPSk5J8i68JCTrQo+QjxzbD/wn5W077+9RfCxvIg/wrDwn6u00aggXOK3lOC3s/CegKPwnYy88J64j8i6JETvv43wn4mgL/CbhZUKJUIj0ajwnrmpajQ68JCog+G+rXVcRfCflbRc8J6EsO+/vfCbhr0KL0ItXNGoJ25CJPCQupjgqLzwn4ylJj9RRPCQhpsmJk0i76yG76yfey4q4bCdSmkmCj1CO++/nCPwkK2IdPCfoIoia1t14Zyj77+9QdGoJ8i68J2UlGDwn5W0P+CosyRKLjDwlqq40aha8J6EuiUvCgVCA3IuOgolQiPwkLmqYEzgppPwkaWZw6DvrIXgp5fgqY1pVGAy4K+G8JGqvgoPQg1cKCdj4KaQJi3wkYKpCghCBuCooXQ/JgocQhpGZELwkJ2kJ2x74Ki88J64p/CfnqbwnZKrXApCQkB78JC2jiHgqLnwka+WwqXigoDwkbaXPfCen6Tvv7064LqlTvCflbTRqCRi8JG9l/CRjpxkIXzhv740evCQurEvCitCKS9SU+GwjyTwkaSM8JCbluC6pWAoJVwnJvCflbQ90ajRqCQkJS3vv70vCh5CHFF0JkE/b/CRhbRK8J67seK0jS4o8JCOkD3hi4AKC0IJ8JyxvGTvv71cCiJCICUrYe+/vTPwm4WVblxhP/CRgYEiyLrwkYy14KqlPzwvCidCJTlcwqFy8J+Bui8lfPCQpIgm8JuFlXfhro1E6qOT3aPwnp+uw4sKDkIMwqXwkKmA4K+QJSVCCiZCJGBte0Dvv73wnrmSwqXgqZpMe3vIuuK2siJKXEd+77+9yLo1Ogo7Qjld8JGOkHxVJfCfmpU3Ly3wlr6Pcj3wloSZyLrwkbSJ8J+VtDJG8JGlkeKChTs7XC7wkYGiXEbgsZgKLEIq4aqTw7YkWPCRj4Lwn5W0MHtx8J2Su++/miRRZci6dzBZIiMu4Ku7yLp7CgJCAAopQic64Yq00ahB762E77m06qGo8JGOu9GoLy/wkKyN4Y6jLy7vv71qJ0UKDkIMwqV2ZeChnnvwkIqKChdCFcqCKtGo8JCpgjE9Lmvwnrmy8JGkgAoNQgvhnafwnrmiKuGJpApAQj7wn52K8JG1pfCUmKzwnZWA8J+mmS7vrIY/PNiNQci6L+Ggly7RqMKvPMi6KSVc4LGLXO+5suCpkfCRj4UnPApAQj7RqPCWra1SNT3gu4bwnqOVJu+/veK1sHsmXPCQlZckPSbwn5W0yLokMmA68JG0p/CQo6HitK00PfCRtLzCsApougFlCj8KA2tleRI4QjZJLvCdqqgvPdyy4Zum8J2Su1xg4Z2CPOCxmljWuXs6SCTwkYy1UyXgqKHCpUxFw7TgqIHigIoKIgoDdmFsEhu6ARgKFgoERmxhdBIOQgxc8JCdoi7gq4nvu4sKtQO6AbEDCh4KA2tleRIXQhXigKHDncWQT0km77SlJCIsw5RTez8KjgMKA3ZhbBKGA7oBggMK/wIKBlNxbFNldBL0ArIB8AIKOUI3JD9X8JGMtzJg4q6ARlEn8J65qc6IPPCRv4bvv73hoJTgsLjwn5W08JCmlFzgsZ0i8J+ghsi6MAowQi488J+uuO+/pnt18JGnl/CRm5nvv4Xvv71q4b67KWsn8J+VtCYlKvCRmIXRqHY6CiZCJHNV8JCHkEfwkKu04ra+JVLwkYyPTPCRlos94Ky1bSrwlr+hKgoCQgAKFUITTCRhwrh7OlMq8JGklmAmKlpLegojQiHwn5W08JG2hvCQqYdoYCbgq5A6XDVbWidv8J2SuCQ+SGAKNUIzPPCflbTwn5W0e23wkY6OV23wn5W0QWDvv7zwkL2B4aCUXFwp4LOL6qCQ4YquPSbit4QuCkRCQiQuXPCen6Hwm4WSYGfvv73Cpci6ae+/vfCQhpbwn5W076mqIsKl4aqI4aWaJCZSIuCxhvCfgrfhpYDCpS/gqId6JAoMQgrDhCYn4byaOiVvCg5CDEhRIsi6OsOc4YmKewr0DLoB8AwKQAoDa2V5EjlCN/CeuYvwkZqwUzwmJfCfraQoOiTwnoCnPOCxmfCen7Pwnrqi8J+VtD/gqIPhpZXhv5bjgJTvv70KqwwKA3ZhbBKjDLoBnwwKnAwKBlNxbFNldBKRDLIBjQwKN0I18J6xuvCRspg74L+O8J2Sqlg38JG1ky5cIidu8Ja/sOC7g3F38JCWlS4vSPCQjofhp7rvq40KJEIiP14iOPCfm7dP8JCegOK3tsi64Ky56qenKvCflbRd8J2qrAo3QjVr8J6fsmw9d+CotnDwm4Cr8JGlmFPhhZ4/JFbho4jCv/CbhLIvIsOi8JG/hDwk44SS8J6BowopQifDhT1rdTk8cfCQtbYnenPwkY+X8JCOt+K1mD/gt7IiUk7Dk++/vWAKEEIObeGloFNW8JCgvOC9jC4KFEISXPCQj4glUCZcbsKle1nwnrmkCiFCHzDvv703yLoi4KuMIiLwkbyqPDwuPzHhioxg8J64gUEKM0IxdkY8Py55L/CRjK06ID0i0ahg4LOC8JGvtirDpS9c8J2UnPCQj4LgqYLIuuC3s+qnkAo/Qj3wkbalOiNc0ag98J64uSpeJ17hp5rwm4Wn8JGPjSLvv718PSXCpci6e/CRjLU8KTzwn5W04LWg8JCohtGoCjRCMnrwn4W2yLrwkI2T4YmbJvCRq4bCpSov4aa5P0vwm4WQLuG/sz3wkJSFKuGKtTrhnKV7CiBCHl1gR+K0leC2vfCRjI/wkYqp8J6EmXQq4LuG8JGkgwoXQhU/0ahn8JCugUM/wqXqoLAiIvCRjKwKK0Ip4Ku/YHvwnrm34Ki877+9SvCRipbgr5DwnZKrXWQvKeCuiTI96p2xwqUKNkI04LuOPNGoKuG9q8i6wqV5K/CUjbLvv73wkbS6KvCflbTWji8gP+Gpr/CdjZZG8JSUgOqdigoWQhTwnrmpPz3wnrmJIntcyLovXF89JgokQiLwnrmXMSJIa/CRsLR78JGNl24jTWDCtjrwkIKkIzpp4KiDCjBCLirwkLaOJS9xPyYr0ajwkY+Cw7zvv73grp9sRMKl8J6frfCRtafwnrmk6qWrM1IKI0Ih8J6fofCQtZx5ez068JORkCUl8JKQoC9Ib/CQupN24LOMChNCEe+/vXg98JarhU8/w4zwkKiGCiFCH/CRtIYq0ag64KmH4oKRPSJRJu+2nmrwn5W00ajhv4QKJ0IlUE7WjvCRtb8qwqbwkYSaOPCRsKfDoy4ucPCQuqHqnrRt4aOaPwoyQjBgJvCQv4fqpanwnoKPPGA8Rz8+Tjp28JGKiOKChWfwka+Q8JGZqPCdlYF7KnLgrJUKIUIfK2Tgp4zIumbgsI5q77y0JT57bUnIus2+SsK+8J+DggoCQgAKSkJI8J66sV7wkYyzIiLgtZThi43hp5rvv73qoqjgt55g8JCLkMi68J6Bo++7juGMgThgyLpO8JC0s/CRkLwv0ajIuvCfqbzwnZWGChdCFT9H8JGOjuGcreC6hlzwka+dLCU9bgoRQg/wnZ2pItGoeuGJm03qnr4KMUIvwr5ISmvwnriw0ag64K2VL/Cav7o/6pOy8J2LpeGJnPCbiLjvv71ce+Giu+CxnS8KEUIP4Y+86qylw6VzKCcmcsOfCjFCL9GoJi54e/CepZckJz8i4KeIJMKiby5LPcOtMmRc6qCwPPCeuaFcfPCdiJEsLnQlCjFCL+CvgHgmXTbRqGDDu+qsjVI8Kk97e++tpVzhp4Dwn6Kk6qyMIj/wn5W0N/Cego8vCjZCNGUq0ajgqorikYJc6qmXPC4v4Ymy4LSPLvCfm7M/XPCWqaIkP/CRj4LwnruxLm/gt7LgqK8KAkIACg9CDWDgqIrgsZrgsJDvuKcKFUITUyLfoVl08JC6sD0q0ajqqYo/PQo8QjpwLu+/vTzwnZKx8JuEsnR04KqzNio68JC6sCYlTybwkayA4LK3cnPCpSfwnrmU4oKxw5DOhjbwkbSJCgxCCu+2reG/nlzDszoKREJCJFzCpe+/ve+tgHvwkbak8JG8tmxLyLrwq5qiJi/RqO+/vfCWqo/igJrhm6onItWDL/CflbTwlq2XIuCoiVHgtbo2CkZCRCR6PyfwkLOXL/CeuYLwkIagJfCWv6BcPPCRtpDwkZOW8JuFlULhn7PhsIfvv70mVvCRsbjwkbaXOyfwkJacXHbvv706Cla6AVMKKwoDa2V5EiRCIu+/vSTwkbC9N14lYFN7emDCpfCQs44i4b2QPMK8Oi7gro4KJAoDdmFsEh26ARoKGAoERmxhdBIQQg5mRjvgoJ8lXfCWoJYiOgrhCroB3QoKHQoDa2V5EhZCFF/IuvCdqq48YDPwkIqc8JG/kE55CrsKCgN2YWwSswq6Aa8KCqwKCgZTcWxTZXQSoQqyAZ0KCjFCL0vwmr++4KiBJyrwkbWRdMO5P3vCpeKFkVzgp4xG8J2FrFc+UTxoJPCQh6PwnLyXChVCE/CQqI7CpUrDgsi6YPCehLU8PSIKF0IVOznwkKS477+9Klw677+9KSonJz89CjtCOeG9kCbwnrqTbD/wnqSyVicu4LKE4KuQ4KGodyRx8JCks/CQtYzgv5LwkKCCJ8Kl762AJSfwn5W0XAo8Qjrwn4OIP0RgMNGo4LucQOCohTsi4YmLeHbqp4tT8JargCdJcFHjhoN+8J+ilWAm1YHwkKCEbuGgjGgmCgJCAAorQikvPOCpnPCrnpk88JuAuSrwkK6O8J+VtDF3wqVrefCfm6V+4oOT8J2UiAoaQhjwn6uUSvCQqIYte+Cyg8i6RuGgkT9KPFEKE0IR8JC+tk3CuEvwnrqjwqJdwqUKD0IN6qmE77+kw5Dvv71IYQoYQhYtYFEybPCQhIBPSlklUHrIujzwn5W0CiFCH1fwkbakJ/CQuazwkYy44rSnw5jIuuGktmjCuTxt0agKPEI6ZCLgrp/gprLCpfCflbQvLsKlwqfIuizwkI6l8J6fqci68J64ou+/vcK98JCpiPCeuZLwkK2sJ3DCpQoWQhTCpSXwn5u18J65ifCeuaLDoi4lJwoQQg4n8JCnjeGfmvCSkonCpQoDQgE5CiNCIVgkeOCtiGHwnZSIPSvisazRqHsqwqV7ezpNLm488JCohQpHQkXCsPCdpprwkY+JTuqoovCRv57wnou/w4020ahC4bKX4KW6emY8TO2fkuCupC5mJvCeooYiPOqcpznwlq2h8Jars++/vSUKJEIiXUDIuvCRjZd9IvCQlbonLjwke+qspiV1IuCovHnhvL57LwoWQhQl0ahqyLrgr4suZzzwkZaZfHXIugoDQgFIChhCFj3RqPCRpqHqq6jgtJAqIidv4LKHYSIKEUIP77+94YmdPOKMpCzwnbypChFCDznwnrux0ajwnoCnJlI6cAoLQgk68JCZr1zhqboKA0IBbAoyQjA58J+VtPCel7/wnrmp4KercvCQkJ7wnriiJifgrKJ94LOjU+CwpCg68JyyivCRjK8KDkIM8JuynEAt0ajwnoSmCgJCAAowQi48Ku+/veG9jfCeuqVkJ/CWqqHCq++5q8KlJibhjbfvrL7hvZsg8J2UicKlyLo/CgtCCTxnUu+/vX4mOgoDQgEmCjVCM2Dvv71ULvCflbTwm7KRRilfPy1RwqXwnaqrRWzhs7o5KvCRqbLIusOj8JCdo8OcLilMXAoLQgngs40mJSrgrp8KMUIvWiTwkIC04KmMI24nXiXgtrUqKvCtvaFfQy9w0ajRqGIuJPCeuZknUWcm4LWKNWgKGUIXwqUidjrht5JhKsKr4ZyMPybgpr3itroKHEIaOj8zL1zhnYvwn5qE8J2Bj2rwr6ClJF/gp4gKE0IRLykkfsKl4KizL++/vfCdkqIKF0IV7Z+xw4Eq4YqN8Ja9pCdQKuCtlz0vCi9CLeG/hyfvvJds8JCtnCY2Ry/wkaCdyLrchyLwkLaP4a6g4oSryLp70ajvuZBcJgokQiJLMDrvv71h4rS3IsOByLo6Il8uIvCRnIYiWeqrp8KrJHUmCkNCQfCRkYYk4LaJcvCQrq0uffCespTwn5W04Z+18JChiGAw4KizL/CdlIc/8J6EnTw/du+/vfCcvYI4KiR9KnTwnYuNChdCFTxd4LGdLzwq4pGKa+qsrCfwnrm+QwpZugFWCjUKA2tleRIuQizgtJXwkbaUJHxGKn7wkLm7Jyd74ZyIS2nCqCbgtr0l8Jy+r/CRtL3wlr6WZwodCgN2YWwSFroBEwoRCgRGbGF0EglCB1vRqPCQrYkKkwG6AY8BCkUKA2tleRI+QjxO4LWPw4018JCkrDrwkpOOOirwn5W0YPCfoZZVIlfwn6GX8J2UlyXvv70/8JuFpuGghmJs4K6aVPCflbQKRgoDdmFsEj+6ATwKOgoERmxhdBIyQjDvv71vPy7gq6nwn5W0Jeqnki5QQFzwnp+t77+94aqxN/Cfg44veT3wkKCA4aCAw6UKhwG6AYMBCjgKA2tleRIxQi938J66syUkLtGo8Jy2sSnwm7KdNy7hvKIneu+/hcKlOfCYtIYvJ+qsimzgpqtcSQpHCgN2YWwSQLoBPQo7CgRGbGF0EjNCMS7goYrgp5xzJT3gqYLgroM7e2ZmJEU34pGD4YuA4LeQ0ag9XPCRvrDwkIKCLC1oZWMKoAy6AZwMCkIKA2tleRI7Qjkx8J65m+qfkfCQhYnOh/CdlI7CpSPwnou/76yCKio6b9GoPuCpiPCegYMlPCZa4Yy8e+GlgOGmuFAK1QsKA3ZhbBLNC7oByQsKxgsKBlNxbFNldBK7C7IBtwsKIEIe77Ga8JC7vCTwkJKi8JGZq+K4k0Avalk68JGXk9GoCjFCL/CRpZVkwqXhi43DjHpp4LuN4raRwqVcLMOx4K6yJlZH8J+VtPCdi4LIunjigok6CghCBnHCueGJmAoGQgTwnoCqCktCST898JGvkT/qrJE18JCAlfCRtajwkKm/8J2UiSY98Jaqg/CbspnhvZki4Z2R4LquNCFY8J2WgOG/lvCbspkv8JGWnfCQqZUib0MKL0It8Ja6lPCRkrVa4LOje3vRqPCXlJnwn6CWLyTCpTzwn5W00ajwkbWAXUxReicnCh9CHTNcOnM6XNGoPSQ6MyV7Kmh9ey7wm4G9JTrCpVA9CjZCNHJ6YSQs4reW4Kq1di4m4LeKLi/CpT7wkaWAUl1gPFzwkKS0wrHwkKSg8JGxsvCRprR9IjYKJUIjVuChkid9YCouLO+/vfCdi4LwnaqhIiVg6q+T8JK+uOCnrS8KM0IxLzwmP/CQqYJh8J64gSfCpWDDvdGoTiJpPMi6ZzpkVuGxkvCQoLhC4LSaOkDqpbnUswowQi58MDphJCciOj0h4KanWDzvqrrCrj3VkT7wnbyoPzDwnqOT4b6pP/CQh6vwkaSJCgdCBfCQpoMuChhCFiHwnp+48J+VtEbgrppg8Juyny8mSDoKMkIw4Y+9JPCflbRyzozCpSbwkJK/4LOWPjxeyLo3NdGo8J+VtCXDpfCflbQmT+CxlVxxCi1CK1bRquC4tvCctK90SvCRtafwkLqxPEo38JuFkfCQlqY8yLo6L/CfrbHRqDcKJ0Il76uXTPCflbTikYPwlr2QJi3wlr284KiCVT/gsrXgqJjDqUckJgoOQgw/XNGo8J2Sv106eyIKCkIIJCIi8J2VgCUKOEI20ahQYHsl8JG2o/CbhZDCpdGoJ/CRk4M8YT/wnrmC77+mXD82eyZp8Jarq249JzPDiPCRjIEiCjVCM2Pvv7118J2UilRR0agr4LacP+CyjjI04LOiw6Z78Jy1qD/wkZmlQy/IuvCRgawl8JORgwolQiPwnriaKuK2psi6L2V2SPCRj4IkQeC9qfCeuKQ0wqQx8JGDggo8QjrhjJPwkZmUUu+/vTAqJGpg77+98J2TpeGzgTxI8J+HqdGo4K6CP8Kle+GclHImYPCeuYLhnIbwlrW1CjlCN1zwnZKc4bGC0ahWyLpaw6skYkvgurTwnrmU4LemIuOGlCjgqrjitrriv7tcKuGmtz3vv41nY1AKFkIU77+9Q/CdkLbgp4xn8JCRhPCRiIcKLUIrVSLCsGLCpVzwn5W08JGSnfCRtadUJy/hpJzwn6ujQHvwn5W0JSLCtiI/PAoGQgTwkaWYCh9CHTxp8JuEsuCmicKlL1vwnrmkPMK08JGbkvCdkrVUCilCJ/CRtIjgobBmJFxE6qSvWsKg8J60gT/RqPCQlonDvOCmsEZY8J+VtAoUQhLwkYyGQENK4KyPLuCtsuCutC4KBUIDQlwkChNCET/wn5W077+T4raMw73wkYqSCh9CHcKiRi998JChhSt14LK1P+C1huKEoUYsyLpq4LOyCi5CLOqihPCfq7TgrZbwkIyGOvCflbQlfF838J+VtPCeubZv4b2b6qyjOvCRmrkmCjxCOuGdsOCsuOGJjci6Xy9+8J+VtDpv77+9XPCRi7h9J/CQi7fCsy/wn5W0JOCri3te8JGytPCQmYAqJVUKJkIkeeOAiyUi8J6khkHwn5W04aWyPCUt8J2SouC/iGwuP8i6UC8vCgdCBVjwkY2XCj1CO/CQnrPgr5dYwqVv8JCSoDzwkYqi77+9ZPCego/Iuj1RNyp70ajgprDDiCXvv70uPygmPfCRtajgsaA7CkFCP8KlLyPwkZGg4KOYMzw88JarseC6l3lgfFXhjpHgrIPIuvCeuaTwkKCm8JC/suqfke+/vfCRg6Uy8JGKiO+/vQrbD7oB1w8KDgoDa2V5EgdCBeK3gD0tCsQPCgN2YWwSvA+6AbgPCrUPCgZTcWxTZXQSqg+yAaYPCidCJSdtOjwq4KmZXCfvv73wloSr4Y+4JvCeuqHwkY+XTTtT8JCrlmEKHkIc8JGMhj9c4aKQ8JGDnEfitrNs4K6efjzwkYewXwobQhkuJ+CnoFxdyLpAJ/Cei7bgq6LitK3wn4KzCjZCNC7gt4Nn8J65iWDDvvCdqpsmIWBcKvCen6Tvv71NXSclXOquj+Cpp306Y2Am8JCWjzM6P20KKkIo4Kq88J+VtFzwn5yy4K2h0ahY67aHZmfwmLSHND9R77+98JCDpe+/vQo+QjwkanvwkY+Czb8tezLwkbWnPeGClOK3oD1+8J+ptCIm8J65mzJ4wqXwnYuH8JCAvTwk8J+qkFVgV3su0agKLkIs4LuLPGBLwqfRqGXwr6SM8JG8kEUwP/CWrKTCpcK8WDzqrKFtLy5yw6s8PTwKSUJHUvCflbQo4Lac6p+T8JC6reGksvCeuqnDr01t4Kut8JGLkDzhiYvRqGXwkICCJXDgs6/CpXs04LS38JCouDo98JGkieGMkyoKE0IRwqUuwqXwnLy5J/CWqoDgt54KB0IF8J+dgT8KJUIj4K+LJOColfCWraXhprDgorgvJCVW8JCikuGDh8OrLns/Tk8KHkIccCYndT0nee+/vSbDoeGpucOzMCQgL3fwkJSGJAoyQjA60ajbnj/wkaSlOSd54K+4Vjvwn5W06pajOi/Dv048ReqnkmZb8JCegzAm8J+VtDMKEUIPyLouckNK4LKGwr9O77+9CkJCQG3wkK6uLtGoZfCflbQv8J+ulzPwkJ2g8J6jki46Lz3Domom8JCnsibwn62wYeKRh1TwkK2A8JCAvMi6JDHgrYMKOEI28JCrs0YqPSbwkamS8Jauge+/ve+/vXtVP+GLgO+5suCrh+qYh8Oe8JGPhSRUVOC0muCpqnc7Ci9CLfCfqa3wkKS/8J+rlF3gqZ514L2a8J+Dq0EkPWbWvuCrsPCcuqvvv6A94LuGeQodQhs88J2qq8OR76yuyLpRIvCQtoNd0ajCufCfgo8KIEIe8J+roioiOkw/8JCmtirIuifwkICZ4Leyw70lJy49CjFCL+Cvti7wkY+Y4Kyaw4DIuvCfgIxR4KmAKvCRvLbwkpG08JG9kNGodiJgaG0n0ahQCihCJkwv76y4POC1j8Ob4Ky1IikqTz3DoVs8euCpiCRcbyV78J+VtCUkCjNCMeG/jPCRnJ/gp5dj8JK/nUjwkYiY8JarteC3mdWGPTo68JCkqOK6hVg/eVpnUPCflbQKEEIOPTom4rasPXshyLrtn4UKEkIQV2ki8J+VtCAj8JiDg1lTcgohQh8nYci66qyh8J6EtTPwnrqlUXvwkY6LJPCSkJnwkbaQCgJCAAoWQhRkJD9DZzPwkZaF0ajwnoGtcS1dQQovQi148JuxuPCfm7AuaPCRpIYmKvCQo7Ul8JC5pybwnYGbyLoiYGjWjifwnLyLIiAKN0I1W9Go4bOAej1z4LGBKuGltPCQgIrgr4AuInvwn6OBTNGo8JGIgit78JGOji8v8JG8ifCRgqAKJkIkckLwsJKOUOCxmuK2o++sheCwhfCQqYLhpLEy4Kq48J2Ujlx8CiBCHicnyLrwnp+tPzwiJSol4LmDe0XwrrGqJjpLP2fIugpAQj4uVeqsqHE/yLrRqPCQoLxgLtGo8J+VtD188J+VtO+/vT00aeCzsuCzh8O2yLrwkKeEwqV+R3t86pOC8JC/iwoVQhPRqPCRiKTwkLWNJ9GoJ1Twnrm3ChVCE/CdkqUv4K2iVHvwkbaQe/CRsKoKMkIwKCc88JSWr+CzlTxT77myN3vCs+GcgsOSVfCdiLXitK1pwqXgto/wnZKm8JGKoiYlCjZCNCbwnrqpP+Cos8O+K+GcrSLhqoktP/CQhILgqYhVJichPVwqJHhBP+GhqvCbsp7gsqrgq4wKK0IpJPCen7siJCLhi4Au4KiHdi7DnHHCpSUxw4Pwn5W0LHsu4Y2eeF7DvH0KE0IRTeqimGMidGAkwqJOLzxvKjcKHkIcPfCRnIvhpIQ6Olfgu44i8J2VhtGo6pKeSdGoIgpLQknCuvCQnqnOh+GnqeCor/CRj4XsiY7gqazDolzCpXJUyLoqPXDwkZGdffCav74+7Z+GwqXwnLis8J2SrHBG4amG4b2b4K6CNNGoCgJCAAoHQgXwnYuTXAo6QjjDluGqifCek509az0uIfCRjIdg8JCUiHvigJ5g4L+Je0bCpfCWvYjwn5GR8JCWhU0677+98JGkpwowQi4uYHo6RTrwn5W0PDrvuajwkY+KcCZgLjzwn6GXIvCQoIglyLrgp5/hi4t7JCYmCilCJzzwn5W0w78m76SX8J+VtMOFw7Ik8JGPgj/CpS3CpfCRjLLwnrm+PwpBQj/gqYHwn5W077+9IuK1sFIi8J65pE574oCjJ+KCgOGdr1zqmYjvrYDwnriOZ/CQiprRqDp277+976y68J+HumsKI0Ih4LGMPOGwj3JYKvCflbQqWSIvXntt8J2bticzavCdiYA/Ch5CHCXhvZs8TfCQirjqqqngpp/wkKO8YOK6gfCflbQKH0Id4KeX0ajwnZSa8JCBiy7grqTCpmTwkICIfuCroFEKULoBTQorCgNrZXkSJEIiLjgoYUtcQ+OIkSYkeyXCpeCuqiVnbyrwkLqRQe+spi4newoeCgN2YWwSF7oBFAoSCgRGbGF0EgpCCO+5qU3gsZgvCt4XugHaFwo0CgNrZXkSLUIrXPCdqqfIunngraIkPDp78JCdiGHwkaSJ4LuBSCfwkJKo8Ja+guCziCUvewqhFwoDdmFsEpkXugGVFwqSFwoGU3FsU2V0EocXsgGDFwofQh3vv717eVwq4LOyPntmZUpRIvCRp5xGRSbRqOK2qQonQiXCpfCfg63wnZSIXeCmsmAgMmXgqIN78J6ln/CRvKjIuvCflbQiCjRCMipaayfhirglSNGoJislyLrwkYKY8JGDgDfqrJMzaOKugjoiIvCeuJTwkKGSYO+skyZBCgdCBSLwkYupCjpCOOCxhvCQqIU8LzrRqE1pQiLwnouwXOC6pWtrJSLwnYa38JCMv/CegIY1MdGoQuCtluG/r03wn5W0CiFCH1w1dPCQi7XgrpPCpSpryLrgsoxKfic8KuCulSVPU30KKkIoJCThoZ7wkbWoIuOEpvCdgJLqn7I8LnMncj3wnoCk8J+fqeCrvCMkXAoRQg/VleC1oj/guoY+4Ky5eycKDUIL8J+jgPCflbThqZgKB0IFfvCRlogKD0INw482yLrwkIaafGNRYAo5QjdsXOGckvCRtIlTLz/CrELgqZzwkaSEPOCzgci6Rzzgs7LwkbS68JCEgvCQjpBT8J2BpuCrvy8qCiJCIFrgsInhiZxe0ajwkKiGw7p7yLok4a+TyLrwm7Cn4pGBCh5CHD3gsq57PNGoJCXRqOCigu+/vfCQjoRsOuGhmmAKI0IhLzl7eeCwgPCRnLHqp6pBXPCRj4kq77m6Sz/Iuj9PyLoiCihCJlQg8J64pOOCqS/hs5R7ZXUvJHvCrMKlUSJHPT3wkYeFJuC1mGB0ChlCF+CrqfCdk4I6I/CcvKh74by1WiDwnbyKCglCB/CRtpEowqUKAkIACjlCNzxgN2ku8LC5nzclJPCfn7Dwn5W08JuFkfCQo7R74LOd8J2SuyYw8JC2heOBniDwkJOH6qKx0agKA0IBOgocQhoj4LenKuKEp00hW+CorPCRioTwnoCbTyF7cgoZQhfCpTs9yLoiJfCflbTwnrmH8JuFpduqVQo+Qjw/4LWrJi7CpXg88JG1py/DmWPjhKXDpu+4tGAmJuC6pfCeuqc98J67sPCeupskYGfwkYqk1IVB8JGkjC4KMUIvSyU/Jj078JCOlCcqwqU3XD8m8J+VtPCek5VZ8J66oyLgt7PhvZPwn5W0UPCctaQKF0IV8J2UiVjhvZnCpWDgt5bXniUuPcOdCiNCISbgqqskJ8OyOdGo4KGeLFwvJHvwkbWoRPCRvJZ8LOKAhgo5QjfwnrmfP/CQpJvqqZLwmr+9fPCdkqLwkaahJG/wn4KQ4KKa0ajwkaSq4aSjw512di/wkaSCJlY9Cj1CO/CWv7Fw4oKj8J6frSXwn4e88J2VizxcJ0IvcNKh4YmdXC898JGYhGDRqPCbsJbwkaea8JGHtDZcPm9gChRCElTwkYWWYMOp8J2dmlw+UOG9nQoiQiDwkKGx8JGDpfCrvr/wnrqISG1cKy4m8JKTkOC3reqnkAoeQhzCpci6XDrwnaSWWEls8J+VtOCqijTgspPgr4xlCipCKOGzt1tG4b+lUcKl4peaw7wu44S/4K+I4YuF8J6fu3s88JOMl3A/0agKN0I1L8KyIvCfkbnihZhg8Ja5micq4Z2uIuCmvCV7PDnwn6K7YD8vKiZc8JGvnOqdhvCeub4iJT0KIUIfYTzgsLjwmKWkwqXwkLqxLuG9mWTgroLwkY+CSuCtlwoLQgkkV1giOjpg0agKK0Ip8J+DnH7wn5W08J+VtPCeuqNg8JGNkPCcvZrgv5fiioEiIi/hpZvguogKOUI34Zuo8JCBiPCegKDgvp4vJicpIklM6qmAJEThiqjIui/CpfCfo4EgXeGAqjbhpbHgtqxH4LOIfQodQhvgrJNE77+9bWfvrLgqNvCflbTwkKmhdibhnbIKO0I5YPCRpJNg4KmeP3Hgr4fij7bgqZxW8J2SrEtGLvCQnYngqLJ2WDbwkbaXJTrRqCckOvCQoYsiO8OICkJCQNGo8JGgh+CviEnwkbKv4K+vw4bguqF5TfCQqLjwnZSqw47wn6WB4K2N4K6jw7FE8JCUpOG1oCbwkK2k8J+Ag1YKGkIYcPCfrrxYdGXwkJatw5Rg8JGwru+7uM6EChFCDzDwnLm88KiWtsO/QVYnOQonQiUmeCck8KueouqsoivwnZKmLkVi4rqDLTzwkICN8JGrsybwrZSsCihCJj/wnYui4LuGw50oJ1h7PeClp8OEJ8OzXO+/vfCQtarCpTrwkbKTChZCFCLRqPCTgI7wrICsJjY9JTrCuiZSCkhCRnTwkKms4Z2z8JG9hGIi8JGPiOGksWt08JCHtOCykF/DlfCeuK8i8JCVr+G/vO+/vfCfl5Ey44KRezx0369I8JGPhWLvv70KD0INcvCfooQk4KaydeCmsgomQiQ90ajgr4xN8JCVsz9gezVOdeCvlyUiIiVCRPCRj5fhjYl7P2AKMEIuPTnRqOGnvmDwn5W0Ki7wnLOeJuCsgfCRiqPgrpk80ahdO+GnkGY84rSt8JG1lgoRQg/wkYyCbmLwn5W04YmMwqUKDEIKyLrwkKyY8J+VtAo/Qj098JG0iPCbhLI6zozwkaSJRkI9cSrIuiPwkLmi6qGcPD3DteG/o1I/8J+VtPCRsYNxLuKBsPCdhatncNGoChtCGeK2pOGJizTgurt7Kci6J/Cbhrpn8JCOvXAKPEI68J2HizNpV1ou8J+VtOC6hCx2OvCRhrRh8J+VtC488J+btzvgrIMneCLgs4zRqOK3mjrwnZGl4K+HKgovQi3vv70uaibqoKUqQu+/lvCdlIngtYrwkIWZwqFzeMi6dfCQja4hyLrCpVhiQk0KNEIyey8zKGBqJHtTJvCdkrVg8J67se+/rPCfoZEqNO+/ikFEK/CflbQnV3tf6qaWJvCQh7QKOkI477CXNWB7WCdUOvCQoIg/Qynvv70q8J6Brci6PyUkKy4lTdGo4b2dfeG9meGliz4u8J65j/CWvpkKJUIje8Kl8J2elMKl8JCrh+GqrDLgp4094aeZ8J+gh2Hwn5W0ZCoKJkIkRPCego8qKvCQrppjUCris7w5PO+ltO+sl+Garz3wnrmLKk09CidCJS5oJu+/vSrwmLSGYCfvv71D8JGpnSTgu4wkclw/8Jq/sTxWVz0KFEISJeGMlTBNyLpZLeC3n0hX4Y+9ChBCDi7nlJR8JSYuwqXwn6ufCgZCBO+/vXsKLUIr8J+qqPCfoIcq4Y6LJzzwnouk8J2Uu2A/4Zu38JuEsi9JyLpgIkzwkIGYXAo/Qj3RqC4/KvCRmLYv4KuJ8JuyhmbwkL+DJz3RqO+/vTrwn6Gj8J65h9GoyLovMSw/OjwlPOCpqPCQo6rqrYw4CgNCASIKMEIu8JG0gmDihYhQJHN1XO+thPCcs5F3YuChu+2fpPCRpJhiMWs98J6Lv3DwnoqtXQoeQhx74LOh4LW+LyLRqMO1eyjOjGrvsa/hn6At77yRChpCGMi68JGWrfCdkrtc4KWSNnxMXCfDo8i6LAovQi3wkZOXX+CovD3wkKSPP3vIuu+/vfCQqoZ48J+VtCc9Q/CflbTwkICrJPCesoMKC0IJ8JGbgXbgqZFgCgRCAmkmChVCEyTwlrqB76y4d++/vXvDoPCQs5gKQ0JB8JGGvSZn8JC0kOCuqu+/lFPcifCQu4MmL33hqrXvubNONilRU23wnZOC4K2L8JGPokQwMXnvrptqVfCflbTitIkKEUIPLzxyM+CxqW/qrJVA4K2dCiVCI/CfooEqfvCWpZ/DszDRqPCRkZ3wkZymYWo9YfCfiaTwn5W0CjhCNmzgro7CpSLwm4WVbWTgt48wyLrhqLtjeyR7QeCvjSJR4oCjRETwnYupPjp7SuCopDzwnoCkLgoGQgQmPSJaCkhCRvCQqI5cJPCQhpQh4q6O0ajwnrux8J+VtD024KCzXuCsoeGtrDdaPWHwnriD8J+VtPCRj4Lvv70hyLrwkJa8PC4mTuGxpCUKsgO6Aa4DCjsKA2tleRI0QjJYVuGKo0pYIuC/iMO0Smkm77W+4KqPeyzvrahg8Jaipy57UExuIings7Pwnrql8JGpsQruAgoDdmFsEuYCugHiAgrfAgoGU3FsU2V0EtQCsgHQAgokQiJ8XPCflbR4JSJXR3HgtYZkZ++/vT/wnri7wqU9T3vwkJSFCidCJeChoi4kRDw877+9QHTgta/wkbSCKSrwnZSNw5tge+C1q+CrjCoKQkJAQXhpTiUmYEE8JvCQraguJeC/kXvgr6lc8Juyn/CQupgk77+94ami8JGTk/CehYHwkIq34LCv8JKRkmPhv7w9XgonQiVI4K2dyLpU8JGbhVNBXOGPvCXDivCQg5Q94K6fYS7vv70vTyZfCiFCH1kuVcOF0ajgrojjhpU6yLoubOKDrcKlYPCQnJLbpXcKBUIDW2gnCh1CG3PCpV7wnZOfXNGoL8i60ag6O1su8JGCg+KylQoiQiAr17RJOMKww7I/Lz9g8Jq/vuC3syQ8Iifjhq3wkKmTYAoeQhw8OvCQjYTwkaS3PDXwkbSIRiUvKjrgq4c6LkkuCgVCA8KlXAqUB7oBkAcKGwoDa2V5EhRCEiIv8J+rkzZc8J6AgjzwkLuCJwrwBgoDdmFsEugGugHkBgrhBgoGU3FsU2V0EtYGsgHSBgo2QjROPVck4LGD4Ki2UyUv8JisleC6kvCRqbrwkZeR8JC6gHAx8Ja+lSQhOuKCiS8o8J+DqyouCjJCMPCeiqzIunlnZvCRr5dVJ3vwkZeI4oKE766XPTDwkZi+VjrRqPCRh6IhMjR08JGwuAoIQgbqpqDivpsKB0IF77+VJmsKO0I5w7UlIiVc8JCApvCSkbA8IkTIuiZ5JvCRjY3CpfCQvJAiV+qhkG97J1pZ77+94Kqz4KquOvCQgJ4nCkJCQMKlw4jwn5W0SfCRtIki4KqC8J6fpjpcOyQl8JC0oEbwkYyKJCJdKTDwkbKOZHEhO3vwn5W08J+VtOCun/CWra0KNkI0Oifgtrg/PeqfkeCpmeCukyIvPF/hj5ckZ3vgobBQwqVSUeC1jPCRgIk9Vk3gtrVZN9GoSwoLQgnwnaqiJPCegKEKMkIwJnDwnrmZXzxPe3J78J+soOChslw9Uy98wqXwn5W08J+covCfqYnIujzqqYtWJDpLCjdCNfCRg5M54bKU8JChk/Cego/hvrbgs5404LCGL/CRsLbgrpwwezPitrImITw8w7vwnoCGcVtgChtCGSrvv70/YTh5PiXvv73wkYiEKXbwn5W0OiYKDEIKPfCflbQz4reOJwoLQgk7PNGoJ/Cbsp4KKkIoKtGoPOGIhvCflbQ8fcO8PCrwk5GRYkbwlqyXIk/hjJJ8VM2+LznRqAoQQg7qrJLIuvCeuZI/8J+VtAoqQiggKSVJ8Jizv2Un8J65guCuuHUqe/CQnqs/e/CRmpTgqILvv73wkY2IClJCUOGNgyfwkLWN8J+VtCTwroKC8KGuoe+snvCflbTwkYqKJVQ98Juxuykq8J2Xj1FO8J64p/CfooHqkqo/77mq6qqhyLrwn4ODJ+OChSHvv4oiCgZCBDrvrL4KCkIIWuC2huGKpWAKI0IhIPCei7Uy4Keidz/wkaSW8J2UmcOWbSrwn5W08JaimXQ1CjFCLztGLvCRtYUuQ/CSvrXwnLy+wrvwn4iQYE5h8J+VtOqglmVg4KuQwqVuOHnwm7G6ChZCFC488J+VtCzwn4OI4Kmsbi8xfj09CiRCIvCdlJzgoLB0cirDjuCpkU7wkr+PPNuiKmPwkY2pSfCQqLgKgQG6AX4KPgoDa2V5EjdCNfCepZDwkY2xMGXwkLqs4K6pcfCRqrAtJOKMpmBpX/CdlI3hnbMlaC/vv7174KqHJSfwnZKeCjwKA3ZhbBI1ugEyCjAKBEZsYXQSKEImKsOp4Kik4b+qRvCeuZdpOiYm8J2GiOGLhSYqPfCeoKwkJci6WScKvgO6AboDCjMKA2tleRIsQirwkI+PP/CflbQufiVXyLo68Ja8tO+/hPCWv7A8I/CQoJ4/8J+VtFzvuasKggMKA3ZhbBL6AroB9gIK8wIKBlNxbFNldBLoArIB5AIKDkIM0ajRqPCQnrV6762ECiFCHyLhoJDgsK984YyV6qKy8J2Gpy99e3VtOvCfgJ9GJj0KFkIU8J65m/CQjK3CpdGo8J+VtOCtlyIKOUI34aOy8JGylDo9JGkkQyJZ8J+DiDYi4KKmevCflbTwkYi144G58J+AuyUkJTwqcyjwm4SyLtGoJgoqQijvrIXCrlfhi4vwka+H8J+VtGTjhY8/0ahnP1g9OC8lYmnjgaYuNz0nChZCFNGodSc/wrbDmuC+mj9YyLrwkK6OChdCFSLgqojvv73gt53wkK2y8JCRnCIkTgoEQgJPNwpBQj/wnri58JGmtz/wnrm+LsKl8J+Biko9XUTgrYfwkYy4YPCQoLx08J6ktzrDgsi6cCbwn5W08JCGoE/hvrYmLXoKNkI0L1w6MDkq8JORiFMqw4gmeHt78JGOvOGkojfgrq48aSLCouC1iCUlPSonwqVm8J+No++sgArjELoB3xAKGwoDa2V5EhRCEvCRkZfCpfCRkZ/wlr+j77+HIQq/EAoDdmFsErcQugGzEAqwEAoGU3FsU2V0EqUQsgGhEAoOQgzwkr6wU2E/fWtoXHsKN0I14KuN77ePOFHitItgYHvwn5W0Y1fwnrmi8Kqdgjwn77+9KiJcwrHDivCRm4cmPSTvv71EPSUKG0IZYCRmLtGo8J65mS7vv71c4KiuXeC3luCrpwpAQj7wkLaPL3tx4Y+5b03igok9Mj1UPTw/8J2UlzY/4KWeKvCQpIXwk5GS8JGOjvCforHwnYaQOeK3hT8/4KiPIgpCQkDgrYzwkaSl8JCerNGo8JCyr/CRiItcSFRg77+9MMOuJyUwPeCwnNiZL+GLkeG9gzzwnrm58JC+uT9B8J2GuEROChxCGiJzLmDwkLSx8J+VtFzwn5W0XF4qYCUlPC8mCjdCNSpt4bK9PC3hs5TwkLqxcnsvJCZU6qOXLi57b3TRqPCRiqJR8JCGoHgm8J+VtNGoMSc84LC0ChhCFsKl4rWwRlwv0agvLns7YfCQlKQqYyAKIEIeMDLwlquH8J64ofCfgKnwnZK74KCTXOChlvCforomCiNCIeChjfCRj5Lwnpe/8JGlhWhVTzrwkJec77+94rS0wrg/LwogQh7WmOG/qjjqrY8n4KenS2BgOfCegZsi4LWYXHvgq4kKHEIaXOCslj3Dldew8JCEk1wi8JG1pOC7ksKlLiYKI0Ih4KeOS0xJPT/wkLu/LybhnatL4LW6XC/wn5ylKy9cblI1CjFCL/CvoI7grZ8qw5E6duGPvPCav7Ii4LG4dGB04LKPccKl6q6aXMi6NfCbhLLwkJqlCiJCIPCeuLTgtZxcSXvCpfCRpqQnwqV6YD/Cpeqoly/CpUNgCjlCNznwkaqKdeCpiCpvfi7CpSfwn5W0L9ux8JGwvsKu8Jy+jfCdhpThn6YxYCo6e+OEpDzhpJXgragKKUInfFnwkZy24LGVyLrwkbC+YiUqIuGMle+sviQ/4reQJVTgsZXgratGCjNCMSrwnrmhLjrigJDqrKDvv71o4Ymc8J+qvOCouSrvv6jIuvCRiL7wkKiX6qeP8J+IpGEKB0IFOPCRsLoKL0ItPypXPfCRj4rvrL7vv6F78JuLgT3wn5G58JGktyzvubI88JGDkvCbhqYs77+9ChVCEy8vS/CQqK8+eW4iyLrCo/CUlYMKMkIweO+4pX1L8J28qSQiYz8iSfCfn7DDk1Rp77+94La94KG4cj3gsZXgrqhCSjzvv6k+CkNCQSZg8J6AkSXwkISx4LGMJfCdm716e+CjpeC5gy7wn5W0fsKl8JGXnPCRpIlZ4KGeR3Mg8JCGkWoqQ3FsPfCQro06ChxCGsi68LC1uW9a4LWs4reUMSJi4KqzwqXIusi6CilCJy7wnrm+T+GciVFpLvCen6N777+9a+CwgfCQvKdp8J+VtO+/jO+/vQpJQkc68JGZke+/vfCQqJDgqpHhgaLwnrmkI3s6yLrwnpe/VOGPlPCdlJLgo4LwkZy94LaC6aO0KivguoEwXWQo8J+VtDQu8JCplQpAQj4lcy88NC4pafCdor5swqXwlqWj0ajwnZm6WHE64r6yPCLthot18J+VtPCQjaLgprIk8J6LrPCRhL9U8JargAoSQhAn6pK04K2f4aeV8JC/isi6CjNCMci6bvCQq4s/4KyP4b6a8JGIh+GNgSfwn5W077mwJuC8tTzwkYeP8JGbhWYr4Yma0agKKEImPSLitoAnYm4n8JuCni8/4KyT8JGZrPCWrZQxQ3giKkTwnp+0NyYKEkIQfTw8JPCflbQm77+98JGRngobQhnwn6Gz8JargfCRj44vP/CRv69KJy/CpWk6CgdCBfCRtIgqCkNCQTLhvaHwnYau6qeTSSZ86p+QUWbwnrqR4KyDZShidPCQurDhvJ3hqofwkYqIXF9p4aCSPe+suD/DueCquSnwkpGwCi1CKybCpe+sgeCzjOGJkMKlL8Kh8J6Xv8i6fSRv6p+RJOCmhj088J+VtDrhvZkKAkIACgpCCPCQoLwiIj1GChFCD8Klb++0iml726bqoZTIugoNQgvjgpJgLvCdhqwvJgpAQj7wkKusXEnwn4K68JGkheCrvT8u8JaEt/CRqK/wnZKiPVxH4b+3dvCdobEiZEB0O/CRhInCpfCeubVc4oCEOwoNQgvwn5W08J+VtEs6XQohQh8qTCTit5JcwqlgfHjqoIV5XGo8PWMqIlzwlq2s4K+LCiNCIVLwnouU8JCVkuC3ljnwn4mR4Yqp77+9aDUxP1fwnbynSQpBQj8k4KG98Jq/vSVwJuCugigvOC7it40ie3sqPeqnlFrwkJyl8JCouMi68JGIi+Gdr+Cgt+qSr+GPvMi6w5drPCwKKUInU++2p1g6S07wkJacYOCvink8zLXnvY9i8JGaidGoVtGo4amp4K6cCkRCQvCflbThn4EmYC5vOsO78JC9hO+/vVtI8JCTgCbhv4I/Z+C/lDo6blzwkIGD8JGNiOGBjWAq8J65kvCRhZgqfeG/mAoOQgw1w4bgt4p6LiTDoVUKMEIu4b+Ac/CegKMv54KOL++/vSYiSHvwkbSB8JGyrUlfLnsubsKlKvCWrZTwn5ugKgoQQg5Z8JGmoUInIvCdlJnCpQoPQg084KqwL+C1n3HDlnUuCjxCOuGqqV4vbiQ68JCjqPCRir/hg403Xlzwn4KqLi5LKuCpi+CznSrwnouw4K2d4YmSJWDwkbaiez1+wqUKQUI/SMi6P+GHpeGJmvCav7bgq4sn8JC0sSIgeDw68JCAgsKlJCTwnrmLTO+/veC6miQn8J+VtDx78JGMp+KAnMKlCkq6AUcKFQoDa2V5Eg5CDCUv8JGPkGUmTuK0pwouCgN2YWwSJ7oBJAoiCgRGbGF0EhpCGPCQjIJJJC0nOkwq4LCQdD9idy7grJ97UArUGroB0BoKNQoDa2V5Ei5CLMKlKnzgp7nvv73wkr+GwqUl8J+lp++/veCpjVPguopcJNGodMOfKOCysU9aCpYaCgN2YWwSjhq6AYoaCocaCgZTcWxTZXQS/BmyAfgZChxCGmQ8SF3DuTLgtoHwkJaNSjgv8J+VtPCgi6UlChdCFfCRgr9Dcio6J8i68JCAgljwnrmSLgo2QjTwkoOnPOqfs0Mq8J+VtOCzlW3vt489JEg3c/CwjIvDn3vIumk84Kmb4aiA8J+ppfCRj4o8CjFCL8Klw4cv4K+BOiRVKvCQobPRqG8l8J65my8vPOqnlPCQlqvwnZKbNDrwlq2ue2onCiRCIu+/vUg8OF8uPfCfppsv4aa9Ikk54Ka4L3slJ1w/YSRcMS8KIkIg8Juykz8lS8OkJjslLiTCpT5bw7/vv73ihKkk8JC+ty4KLUIrVifwnrihJPCRqorwkIuvwqVz8J+hoyQv8Jq/svCWq7QvOvCRtpB78J6lnwoHQgXhjJQvaQoKQgg8ecKl4L6jYgoGQgQmPSomCjdCNfCQq7AlJybwm4mZa+GJrC556qCBPXAm4KWs4b6x0ah7IjpY4Km2PkrwkLaO8J66hvCRpJY/ChRCEjJ7Omfwn5W0JNGo8JCXpEEuPwo1QjM/8JCWgGw9QOGihiYv8J28pVrwnqWf8JCVsTBn4KGpQ/CQoLzwkLqr8J+VtHLgtJAlNCcKFEISOuC7gz0nJ+G/rXsk8JGQtC0uCipCKPCehJ894Kum4ra94b+0LvCegIYvXe+/vSPCpXkn0aguP+CpkeCvulAKAkIACixCKj89PXtg4YON7Z+r8J+VtD4o8J6Tn/CRjYQ68J+VtCfVgOG+iTpgdcKlXAo2QjTgqZo9w7o3J1vwn5W08JCrs0fwn56e8JCAheKBsXo844OdYPCRgZpkLuG/suqnkvCQkqN7CidCJeCuo3vCtDRdd2DhvZ17U+K0rSTIulzwnoCE762syLp1J/CRiIkKGEIWccOyOPCen6g56qCz8JCqkcOTXOCnnApIQkY477+9IsOi77+cXO+/vSfwkKKGJvCQo7Tih7DRqF5A8JCUn/Cforov77+9zozwnZK76qGGJybitZPgsYzwkJazPci60ahsCgtCCfCRiJHwnY2hegoSQhAk4LqBw7nwkbGm4KaJIkwwCj1CO/CWv7EmSOCnl+qnoXFXJOG9nSY98JCGkD/gsZXwkLW5Zkc18J64pPCesqpg8JC7g1fwsaGeJlngtYhYCjhCNld34YqRbT3gsbjgsrU/8JGysOGctVox4oKY4KqQ8JGTkD/wnZOQJfCQlrjwkaqA8J+lpeGdrgoHQgUx8JCnlgo7Qjnwn6OBP++/vSVgXPCRjaLwkIC88JGOlU4qaOqop0TgsZ08N/CWvbclKvCRkoRcJeGLvnFceEXIulEKDEIKKi498JG1h++6ogotQis6e8i68JGHsMi68JG8p/Cav7AkLz/vv70g8J+qlUB7KMKl8J2MouCriSUiCgNCASYKEkIQ4YuD8JaEiOqrrDok8JC6sApBQj9o8K2IneCoh8Kp8JGKpSYnYCMu8Ja8gjMnUcKlKi498JC8nmfwkIe34K2fTeGKiuqnp8K3XCLgp53jhoDhi4IKD0INb++/vVA7SGFbMuCpjQo9QjvwkbKBwqXwkbWo4KmzLi7RqPCRvIUu4LeZe8Kq8JCWgD0vezY68J+VtCp7JCZ9JPCRvZQ/XMOC4bGBLwoIQgZDefCQs7EKN0I1YCdo8JG8oPCWqZDwlr+g6p688J65m+qdt/CdkYzwkICEP0jgsZ0s4LGG8JCWu/CRtaTvv70KB0IFL9GoPSAKR0JF8J+VtCXdlO+/vfCQpL9naD/RqOK3hkjwkISx8JGWu+C6pXAu8JyzgSpyOvCfg4HgtbPwrYWlRvCfm5wme/CflbTgspAxChhCFuCqme+/vfCdnprDjD1gL/CQubfjhZMKAkIACh1CG/Cxn4Lit5lmwqVU6qe38Ja/pHvwkLqwRO+/vQoHQgXwnaSgewpFQkMqP37wn5W04LSDcvCQlLk/8J+VtMi6YPCfqaw9y7/wlq2c4KuqK+CxmVrIuntl8JGos+K3iHjgp4hN4LGYwqjwkaSMCidCJTxbwqXwkYys44Oce+2fhCU2PGAi8J+IgvClsKgvPPCflbTgpr4KEUIPWWItPfCQlrskQvCRpqQkCilCJy9kZzwkLiZZe1U98JGOi/CQlqDIuuCtgCJ38KuGlPCdja3wkY2EYwoqQigmwqXvv5vwkZOXL2Am8J66hvCRjLN1J+KJkSXwnZCY6qiU4La1LmBaCgdCBWklPyYlCjhCNsKiXPCQoIDIusKibvCRtZg8OsKlUnvDqfCWrIE60ajwkbS1w77wnpemRSvCpTov4oKc8J+VtAomQiQk8J+hmETRqCIx8JCRteCvi/CQh7k6LyYuwqXihZTwmK2byLoKPUI74LqCyLrgqKw8JDI/JfCeuLfgqrZgVjrhl5/wkKiR8J+VtPCfnK49Ou+/vX7wn5W08J+ghiThsJXvrLoKFUITwrbtnrrjgYTwnZWML/CeuqgkKApFQkNF8J65gi/wkK2+4aad8JGoiuCoj+CpiPCfoZhU8J2VgTomdDIvOnXgqYDwn5W0POG9m/Cdkrth4bCq4K+NIeCos0heCjlCN+qSnDrvv70u8JGCieqnouGcqn0iLuGzhWc98J2Ir+GhgFxHby/wkZmZ8J2SpfCRi7gl8JGYqCUKFUIT4Yu48JG1l2w84oCAO0Twn4OSKQoYQhYt8J6Xv2Ru8J+ulvCRipEifVzgu5E8ChRCEuu7sjowPCbwn5W0TCpX4KqmJgo1QjPwkKy8e/CvpqtBJOC9njQncPCQraXIui7wkbaQP0TwnrK0yLo9XDTgu4Y9OvCQvrs6cyoKFUITXPCQtZvvv70077+u8JGlgifDjApCQkBP8JCegj/wkai3J/CbhZXwnri54b+0J/CflbTwn5W0b+OBscKlLyVL4b+0NfCflbTRqPCbhafDtuGKtfCQoIgvCj1CO++/veGwivCws7hLL9GoIXPgrpnvrbNgJiQmTC7qrIzhg4c+4bmSLu+/veCvh2Pwn5W0XOGKutGozoxTCidCJfCfqbA877iE4pGHJvCbg5rwnrmLwqUvbPCRjovwn5W0fCo8L3UKJ0IldCI8biIiPFMk8JGysUnwkpCKPeGfqT1ndiRm8JGMmSpV8K+ojAoTQhHCuD0l3oQ/8KOakNGo8JGZlwobQhnCpe+/vfCegKE6O/CWvIIg0ajgtYdY4L+XCiBCHuCuqeG/vC7wkZmZ77+9QUJH8J+VtOGdjXtS8JGCvwo6QjjwkYOT8J+fplfIuifRqD3wnruwKsKldzDIuk7wnL6Jb/CdkqY6L8OqOSQgWXNB8JKSoDXCpVkgJgoQQg7RqOGqgcKlP23wka+YMwoHQgVd8JCNkQoFQgNDw4cKNkI0Sk48M2o8wqTwkY2LPS8/8JuLiS8j8J+fsPCQlrvwkLuCJmY6Nu+/vci68LG3ryPwkJSSewoPQg09dExGRCLwn5W00ag8CjBCLmBp6qyL8JC0hcOgV1Bz4LGg4YqzcV1b77+K8J6AheG/u/CRtYHwkY6OLjdxcEkKCUIHVifwkKC3bAowQi7gs6FOJ9GowqXgrKzCpSfwkKe0Z+K2gvCRj4Ii8JCcmtm68J+GhdGow5XwkY2HCjJCMEx70ag2XjxgP/Ceubvwn5W0KvCYs7/CpSo3wqUiwqXDmci60ahg8JC/p+C3qOKyogoCQgAKAkIAChNCETnhibnwkbaFXS/iroxgISE6Ci1CKyYyIvCen6Au4YuE8JCkuHjtn4In77+gJmhD4Ki8YE9gRuK2ovCQs48mPXQKBUID4KCWCkVCQy9f4LGnPOCulPCWrb3gtbI84Y6sL/Cfg51gdci6YPCRiqhOKvCflbRP4K2r4Kai4YmW8JG9mWsqZz898JGDkvCbhaQKJUIje3dK8JCohSjwkZmU4LeP8JGasvCQtaLgs6Mu0ahcJPCeuaQKJUIje+C+kTY94aWA8J6fpTTwkKm0ei86ITrwkICN8JCRp+C3gHsKKEIm8JCgvMi6P++/vW88PXfwkY20UfCbg5bvv706P+KFpGhFLtaPQ3IKM0IxPD3igbVcOPCdvKo64aeI4KqMJcKyLzrtn57CpdGoJHTgrLzwnrmX8J+dkibwkIuLRwoDQgE6CitCKUfwn5W0N/CQlpRUJ1dEciRz8J6fqjM88J+jgCXwkbS14KGeLfCbhZFBChpCGCXhiZIiJvCTqp86w5UqdSLIulzwnrSjIwoRQg8mKlzwlJi3P9GoNO+3jz8KJ0IlXCrgqovCpfCfrbvqn5vwkLaOwqV7JfCQrb7gqZxoYDzwkLW6WwonQiXCpfCel7/Ds3YuVDzgt4p98JC8gS85JVxX4YqM8JGKnci6YFcvCjxCOtGoPCYl4aSnISfwkaSVaPCepKThrKrwnZSK8J6FhCfCpTLjiJhC4raq8J6KqfCRjLkq8J2VgMOpPToKmxS6AZcUCkEKA2tleRI6Qjjvrr12O/Cdqqbit4M/8JGMtifwqrOm16dgLkLCunvwn4e80ahs4aiE6q2DIkAg4am14L6X8J+VtArREwoDdmFsEskTugHFEwrCEwoGU3FsU2V0ErcTsgGzEwoRQg8kImXwlJGIw5vwkY6FLy0KBEICWyQKE0IR7Z+N4YmK6qujPS5qXPCflq0KK0IpIvCbgZ9aMC/wkaSOJSjwkY2wL1HwkY+KJ1s9W/Cego90IuCujkIiIWkKO0I5P++/vSLgpLslX+CojyLgt518L/CRioAmMWfDtO2fgXzIuvCdlIfvv4926qWpKiQ8JeqjhfCRtblmCiNCIT0t77+94KqR8JCRhmAnyLpwYEtD4KmeXOGLhMKoVOKAogoCQgAKIEIecS7qpbXwr6OW8J+fsDHgr5cl6qy4OmXwkYyTJSYiCixCKntc8J2HiO+/vfCRgr7wkaOSYzN8L/CdkrJa8JCumnHIusi6YPCflbRaagoeQhx4w57IuvCdkqLwkL+I8J+VtOG9kPCdlIrvv71cCgxCCuKtu/CQu4I9MzQKKEIm6qqeJyrigbDwkI6m8JGWnOC3s9ygKvCWroBiVnbqnrVgI/CRr7MKDkIM8JGKgfCRiovwn5W0ChVCE8K4IuCqo0w/4rStP+C6ufCRjY0KGUIX4LCMO1bwn6m6JuCgqC48JOGkuOCgkkwKPEI6ZlzwkaWF8JatsvCQtIfRqHvvrYTqma8lLyrwkIag8JGOi/CflbQlOPCfq5dzJUfDtS9MPCfwnLyBSgoHQgU18J6knAoCQgAKKUInPOC6hD9cWfCdkrbvv717PVfCpS8iMyoxPSHwnrSjbcKlLiXwmr+yCgtCCVJcWihGTuqpkQoHQgU/8JCgvAoNQgsu0ajRqHQ84LGZdQosQirDmfCRoKjCpe+/vfCWhKvDmzRgJ/CRjLPDhD7hvpd9JPCflbTCpSQvw6oKE0IRPFzDulPqq58iLk3wkYevw6kKI0IhP8i677+977+9blx1TW3wkJa8KUTwnrmhI/CQvbrYp8K5Cg1CC/Cel7/it5RcIjQnCh9CHS/gprYiZsKzJUckbuG/ue+5s/CQqLjtn4bvrKdaCjdCNXjgqY18aOGkjPCdk4Nx6quiY3te4YyS4aaWwqVLKuCsgykkLvCRsq8/8JGvsPCdno3wnpebCkBCPi89W0Lhiojwn66WQc6aI8KlJTwqYPCfiJ868J+VtCrhnbNg8JuwkSrgtITCpUlv4b6w4ra+8J+VtOC0gUNQCiNCIfCQnZTwn5W0ZjrhoZY/4KaPbM6MW/CRsqV1PW1U0ajCpQopQidQ8J2Ehu+/nHcnPWvwkbGhPDI48J65i+qSluG9ivCQlrDvv70mIi8KM0Ixae+5mj0m8Ja5nHvwn5W0XcKlwrfqrLnwnrinw6VX8JGvgjk88JC6sD3vv71d44C+fAoTQhFVKvCflbTgs53wnrmbKiXIugo4QjZ+4K6jJiVb4b2X4LOg4KmyYDwi8J6Cj/CQrq3RqPCeuoAq4K6IzqDhj7g64rubPTx2w5Hgp50KTEJK8JCslOGyv1DRqGDCqvCeua3hi4VYKkLwn5W0yLrwnri78J+EmfCQrqw4LVwzT/CRiIrwkZyj8J65ojxW77+98J65oiQi8J65gjkKDkIMbvCQpL/grLNccj8iCgZCBPCWqrkKGEIW4Ku9blVSajbwn5+G77+94b6HaSo8RwoDQgE9CgJCAAomQiQt8JCtsvCfgYPgsI7vv5vwkKmUTjrwkL204KuQOvCRk5nDkSkKK0IpSfCdk61gJe+/vS7qqYsvPCdmIns844OT4oOiJ0tc4KuJVSvwkJarPWIKGUIXPWvwkJSMLyJc8J+VtCUv8JGKluG9lCIKEEIOWC4v8JGktzJcYO+/vVcKLEIqbCTwkJOw4K2s8J+rlidDNeCrqjYlJXHhvrMm8JCAivCRjI/wn6+OwqU8CgJCAAoxQi9kOsOpVOqspvCRmo98YOGLheGauGDgu4YkRibhobbqn5MrfT068J2UvT86VMOZdAoeQhzXsD7wnYWtwqXvv73grI97Lnvhi4Pvv73wkKG3ChRCEsi677+96qyLRO+/vci68JGvsgokQiLgrL498J+rodGoYCLhnLYq4LuGJHvhjJVg77+98JCOlCp7CidCJeK1r/CfgqrwkKur8JGOizpBSci64ayF8J6ApyVB8J+HqPCRqIsKH0Id8Jy5vmDwkaSc8J+gg0LwkI+JL3NpJMi68J+VtEYKNkI0PD1aPyslOuCwiyTwn6um8J+VtD1sKuCyidev8J6Bj/CQl6g8di/wkbS88J65pEkqJsOaJwoJQgcmJPCetI5nCjdCNdaqQ2AnSXVh8J6Lv/CRsKMmdvCSi4pp8Jamg/CRkpkvPz3gqLZiIvCWqaEq8JGkt/CRjZdcCiNCIcKy8J6kvyXIul4/8J+Hpjok8KyznkDwkam18J65meCmkAo0QjLwkbCC8J2UiSrwkJao4K6p8J+Hp+K2rC8vw6p78JGNsi8t4byxJVEswqZVPFDIukFqPwoQQg4z8K6mtSbwnrmdyLolUAopQic176yWbz08XOCmrPCfgrhC4YmN4Kqv8J+gpuG9usO1RvCeubTikYYKDkIM6pyZYsOGUlwlay8uChNCEXJy8J2UviZoyLrqmZBq4YqoCgpCCMi68JChikZjCjpCOOOHl+Gwu8OnLuqgsCc8yLoqe/CRj4jgpqol8JCBkPCRtIgq8JC/rSVoPC/wkYqBS+GIgz0lw4AnCi9CLSTwn6m48Ja/o+Ktu0NdJi7wnoWC8J+VtFPwkLqP77+DJPCfq6MmJ3vvv70nWwpBQj9X77+98JC1rjo6LvCQqYfwkbS98J6ln/CbsbckJFovSC5177+9SPCRiovwrbGh4beVJ1rgr4BEejx8fPCflbQKREJCe+GDh/CdkrlgfuG9ly/goovwn5W076y5I0ThqrLwkICdfu+9huCnhyo88JGboFfRqGpXfeK3m/CQgL3wkKCE77+9ChRCEjxYMPCflbQn8J6llF0vOOCsiAoeQhw/aM2/PT7wnoGp8JCyiCrguJ97P2Dwkb6wyLonCjlCN/Cyg6LrmqTDmzx+4LaD6qyV0ajqraI9PS7tnrcv8J64uyrwlr+w8JGPlyXwn5W0QUPwkL2AyLoKL0ItIeC/lfCQq7LwkJazSXPwlJSj8JK+oToiduG+kTo3YMKlJcOKbfCWtbfvv70kCidCJT9GXCbwkY2iS++5myfwkYWnbT0meyrwkZyoaPCRpqTIusO6e3sKO0I5Ij7wnaqc4bGRJeKCifCQraLwkZue8JGggeC9jDrqqZjgvIN677+977+N4La9fCXIuuKtvfCcupcuCg1CCy/qqZVHLnvwn4iSChFCD2Al4LKfKi7wkbCnLnbRqAoJQgfCrDnwnZemCtoKugHWCgouCgNrZXkSJ0IlJFzRqFnwkY6+e1Lgp6PhoKDwkYKQI+++jdGowq4k4Kyy4bCkaAqjCgoDdmFsEpsKugGXCgqUCgoGU3FsU2V0EokKsgGFCgoGQgRj4r+SCklCRyUiyLp78JCMj30lJ++/vVwl8JasofCQrKcnZvCRgoLwm7G38J+JkXLhv7Pwn6ub77C276q18JuyhsKlXzV94oG38JCXsMOaCgJCAAonQiXihoHwk4qbKvCWrZkoJ8i6M3lg4LCO8J+qu27vv6guPDo6JHZJChFCDy8y4auJZuqYkVxgVe+/kgo1QjM/8Ja/pPCRiorwkYqDPC8w4YqzyLovP/CQhIwvyLrwkaSFLvCbhZDwkbuu8J+VtPCRlqMKMUIv762AKnLvv73DgVDgrZzgrJDwnLy3SvCtt40uffCQlJTCo+GKpfCeuZvwn4mHPDkKOUI377+9KXtXMMO9PPCepZQkeyRD0ajDssi6Q+GJiuGwoPCeuI468JORh+Coh/CRtLrgpoXwkKC3awpCQkAqQ+qguOG8mOC2vW4hJvCSkpAj4LufIsOp8J2ZhdS3QfCcvL9g8Jq/tlHPpVwi4aGv4oKr8J65lGAnwrdS4rWfCihCJvCdlIknJlk2cypjJUDbuD3hvYVLKsKk8J+VtGk48JCAvDrgrIEqChtCGWMmeyXwlq22W++/vVopJTzguqLgr4vOhTMKHEIaLmA1TiRgKvCQtZPahe+/vcOvwqE48JCOpDUKD0IN8JORjn3vv71OJV0qPAosQip+JMO3YChk8JaslOC2veqfm23wnrikflrwkbaX0ah78J6AnvCflbTgpr0KRkJEaS/wm7KQ8J+VtGBcSEDwkKms4K6v7Z+x77+94KmNJmQiflXwnZKi8J+HreqikeC3m/CRkZ8vLu+2l2wk8JGnl2Am0agKE0IRaWgi8J2SvfCeuYLvv70ueD0KQkJA8JGIgDTwkbaga+Cur/CRjIVD8J2agFzhpIjvuIQ/P+CxoCjgoIY644CI8J28mS44SW3wkY+KOj8myLom8JGZpQo2QjThjbxFw5Is77mWLlDhv4Yh8JGXijzwkbGo4b2dKeCtldOc4KugJlzCrEpPRDLgrIHwnri5ChBCDiDDh+OAhVVo4La9KmknCi1CK+CtiNGo6p+TL/CQhIjDgXgm0ah7M/CQrLo6deCzh1Bfbzok8JCooPCdkqIKEkIQ8J2Som3wkL2McuCoh3ZHSwoSQhDwkZCne3TIuvCRpInDkGIuCjxCOuCohfCQoaDhibgm4r2V4Z6bJlHwm4WS4oKHJuODrWDvv70lL9GowqXwnriCUldF5qaJ8JCNqGDvv70KKUIn8JGMt2QkcSLwnrqHzp7vv702JCI98JGXmPCdlJokJe+/vdGo4LOACiRCIuC/mvCdk6HwnoSWOS8yR+K3k1t54YONYDvguog24KiIQ2gKMkIwLlo9OuG9m2Ql8JGHr/Cfk5lseiXwn6uyXNGofvCflbThsYXvual3YGXhiZAnwqUhChdCFfCflbTgq41gYG7DkT9MNu2fp+qsiQo4QjbwnrmL8J+ggGA86qyiJ+K2imDRqFtPL1bguoTwnZKy8J+rmljqpo4q0ajDiC4k8JuynfCegKMKHUIbbO+ssm/wm4uN8JGFg2Dvv73IuvCRm4VGaS8kCiBCHvCRv448ZO+qoci68J64i/CRlp7wkbagJDnCpVV2PwpKQkhU8JGAhO+tgPCQpJnwkYqI4KiK6qytyLpg4Z2MIlBj8J6An/Cfppp64KiQJmlc4aWA8JGOjvCflbRcRO+/muCqrsi6w5DDgiYKbLoBaQooCgNrZXkSIUIfJCXRqOGPmtGoKdGoYE9rKlzgqLwl77+98JCOtOqnowo9CgN2YWwSNroBMwoxCgRGbGF0EilCJ+GfpfCQgIThvZIi4YeEOvCegIUz4LazW8KlNTXigbDRqD8qYGEvKgqcD7oBmA8KLwoDa2V5EihCJuG2sVImcmkkyLrivJfIui/it4x3T3jhiZN08JCGkTbguofwnoKPCuQOCgN2YWwS3A66AdgOCtUOCgZTcWxTZXQSyg6yAcYOCgZCBPCfr4YKNkI0P+CunPCQvoFY8J65vvCWraU/SMOO8JChhzzwn5W0JfCeuo3CpeqnqMKjJ3k6Rz8676y+TAo4QjZgwqVgyLoxwqklWfCQrK8kOWZt77ePIk1N4aWwc8Ov8JGDt2DwkKaj0ag377+9wqVyJPCRtawKJUIj77+9L2LgsLHwn5W08J65keqSuXvwm7GxP/CQlrRaP/CcspsKKUIn8K60oEfwkKSf8J2Su8O3fu+/nC7hiprhjJVXeyfDoeK6vm1tbVUkCixCKjrikYfgq4dcKi7wkY20wqVndOCzh+CglXs18J+cpS8iXGEi4YuO4KyvcwoWQhQzJjrwkJW+wqXwkLqP8JGKpsKlago/Qj3wn4OBLydS8J65oiLCpDIidyY64LSO8JCXn8K68J65oi8i8J+jgT/wnZG8TXvvv73wnYaowqUmKfCRjLkkCgJCAAo/Qj3wn4KG8JGriEjwkKC38J+jgFYvPeK0p+qpmSp+Jy5EJ/CRvJIx8JCPifCbsIZ4XPCSk5pBeyVefGrwkJ6ACjxCOiXNviU9MPCRkJtEOvCWrZkn8JCAi3Isw4wvJOK0ufCflbQ88J+qgvCfnK9TRngnKj/ikYTvt488QC8KG0IZ77+V4LKBdGtg77+94LKl8Jy+p3nwnZSINgoiQiAncOGgkOC1iOqfkOqggGfgs53wkYqY77e4cnrwnZKeNgoZQhc0YeCnnOK2hXHwkYy88JCBhiJ6RkcvLgoTQhE877+98J+rou+slPCehI1YNgoRQg9QJiThvY0q8J6FgfCeo44KHkIc4LqEdCrwkY+hOnjwka+2V3siT35y8JG2gCouNAoiQiAvVCXwkZuCLyVGTeG9nfCfg60/ZDoke2Y8Py/gqrIqJAoQQg5E4YmMKvCRnIE9eyRZegoLQgnqrJFW8JaogF8KGkIY4oG2JOCvkC/vrYPCp2doQuK2ovCWtYs9ChpCGPCQtJlg4KiCIvCRo6kqOuG4tfCQhalcewoNQgtW4aCLdtGoK8KlNQo3QjVgPycvw7d7JyZZ17QlJ/CQgLjwnoCe8JGarzojKuGdqiTCpfCQopfvv73hqrLqqZdX8J2ShgooQiZ3QuqsilThjKwiOvCWv6DqqYB8PfCQio43PDwk4Y+OJeCotns/ZApKQkjgs6rwkYCDw4nwnrm04KqCRk3wlr+je3U34Yq94KC7bOGdssOs8JGFqNGo8JCdpy9c8LGLlFM58J65md2bTuK3peGKsu2fqy8KRUJD8JGNjPCfiZHwkI+IPT/hiZjwkJOLePCbh6BkLy7asyDwn5W0yLrgrr/wn5W0Tk3hv5IuPT9yJOCwssi68JG+sC7CpwonQiXCpeC2pj/qrKYvwqU4OVHDrfCflbQ/44GY77+MP0RN8J2TmlwmCj5CPFNL8J6yqjw8aUp7L0XwkZua8JGTmeGen+C1uuCqsGDwkY+F4K2hOPCeuLkk8J+VtOC6p9GoVSJOUOK2tQpBQj9p8J6Fj0s86p+RamhNw6UqKn3CpSPwkJW/8J+VtCV80ajwkKmQUeK2rPCflbQlyLrhsYc9KuCmruC1vPCflbQKN0I125zwkb2Zbnx9JPCQoLjIuuCzs2B3Pypw4K+M8JGNiPCQlrvIuvCWhLloL9Go4KON8JCsuXMKREJCPyUiR/Ceu7A9eT094LWj4Ky48JC1v+Cqi+G1jToi4LSPIyfqooHwkJi0JfCQsoA88J65l1DCpfCeuqjwkYyP4oOsCgdCBfCflbQ0CihCJkYqQEnhkYcmbDnwkbWn772FzoTgsYs9LyBJVfCbsp0o8J2Eq1w9CiBCHuCotcOT4KqsSOGfsvCbspxgPV4veFBGYfCroIQ/MQo9Qjs6J8Kl8JG/kWBd77+98JG1kSTDpC4m8JCiriYv8JGPmOCokOCqguCwpSVy8LKAtvCRjLYm4rircOCokAoJQgd7YjrwkJunCjNCMSV+U2HCuPCfl7jqnqBKJOKFklwq8J6LliYq8Jy8vjrwkbaj8J+tqeCtqvCfiKFULycKGkIYLz3it4lE4aSK4Yqz8J+VtMi6RVbwn5W0ChpCGCbwkY2jyLpie9GoOOGkqlFP8KCggci6Pwo4QjbwkY2X8J2Hh/CbhZDCqvCdkp528JapqCbvv73wn5W0ctGo4ZygJ/CdvKXvqow8VybwnYyXZ2QKFEIS8J+VtCAhPeCwmfCRm4Twnri7CjpCOO+/ve+/vTxVPHU177+98JChkz9P8JCjoy7zoIWb4b2ZQPCego88JcKlcvCQoIh88JCAvPCel79yChdCFfCQupPgu5Q88JChi+CqgsOEUuCsuQoyQjBcJ8OlMiAlJiolLmMv8JCUmDjDuSdq8J+JgD/ita8/8JipmMKlYCc88J6KnPCQlbUKOkI4OtaDXfCbg4bwn5+wKj/wkY+U4Kmb8JG7ouC7geGnkeCunO+/vci68JCdoFjwn5W0byVX8JCFhiQKnBO6AZgTCjQKA2tleRItQivwkaef8J6XkeGkl8K1IvCQqZTgp5cqfiXwnYaX8J+VtHXwkbaQJCXwnriiCt8SCgN2YWwS1xK6AdMSCtASCgZTcWxTZXQSxRKyAcESCjBCLlws8Ja/ovCeuYtZ8JCdozwlcOCii/CRpKHihY5e77+98J65myrwnrmd8J64tycKLkIs8JGGv8O4yLrwkaSP4KGCJdGo8JCetyXCoibhqpTRqOCvi+CriWwiX+CtnSoKK0Ip8J6Fj8i6dSd2NuG8uvCRhLnvv71VyLoq6qyrbeC3ilwi8JCTtibvv70KGkIYPDBcZuqgsz1pLvCdlLvqn5E6wqnRqHhACgJCAAomQiRFPcOKeD0v4YmK8Jq/uT8/4KC54b+4ZzFyez89XHDwn5W0JyoKA0IBUwoSQhA/LC43PSVA8JC2jiQ6OMKlCgtCCT9q77+98JCgiAoiQiDCpeCshsORL/CdqqUr4YuIWTom8JGCv0Pwn5W08J+ipwoMQgoiUeGlgNGo4aWaCgtCCX0iK+qsliR5VwoIQgbIunxg0agKKEIm4KaP8JGvsy/wlqal8J6KmCfqn5M9aEBiJC/grpx3ezw88Ja6hlwKOUI34KeDZznguoHwkYy/YGTwkKKp8J65iT93Zi5bKlkkw4kiJ+CroXvwn5mrKlnwnqWeezw9PjzCqwoOQgxDIlnwn5+w8J+VtHsKNkI0TOGDjeC/lCTigrfiu4hvJfCQnZIrL1x98J+poOCvgu+/vfCQgrZgL/CRh4jwm4WV4Ki5UAo+Qjw9YPCen6YlLkQtwqU676yWJ/CflbQl7K2yT33wkoey77+9PcOv4LW9P1LwkaeVXCTigrl8JOCnjvCWv7EKEUIPeeCxhO+/vdGoPCd7X8KmChBCDm0vXPCRu61+XmDhprskCitCKfCav7AnIvCfopzqpbHvvo8lXPCYtIJrXPCWrZdpS/CTsbBhdu+/vTwhCkBCPkDwnrm7a1w/8J6Ejzwl8J2NpfCbhLLRqM2/8J65qiUl4LOMwqUk8JGDoFpgey7wkIGWPTxbJvCeub42wqUvCjVCM3Is8JiojSpD8J6ftO+/vUHCpXdQ8J+VtCXCpS55w73grLZcO23wn5W0wqXtn6xJ4YqfUgo4QjZGXCovPSfDi/CRnIHCpSbqk4R44LWNK1vwkbKW77+o8JGPhS5oPFE/XS9KP+qgsHZoe/CRo44KG0IZdSLhv5si8JuFleC7ju+utvCeuZQk8JCohgoyQjDIunLwkbyTNfCRtYLCteKAneGJmvCRtLLitKciaPCQl7J7wqXhqLVgcUPhnavqqJMKJEIiImBFOicwRuCpmeCoviJyIi4mLmPwnrmJPU9gYDwvd+K1sAoKQggqPyTwnZSUYAozQjHgs6Jsw77gqIHwkY+HYMOCK/OghrnwkYO54aqDbj/gvrnYpNGoJ/CSkZ89wqU7JD8iCglCBzo6LvCflbQKQUI/8JGNnmA6aV7DmeK1sPCRjoDwkaSZw68yL8OFP/CQirbwn5W0Ou+/vSrhnKYq8J+hliLRqFXgqYh0Kirwn5W0ChBCDvCQjJw9JuGJmCfwkIW0CghCBipgIicmewo2QjQi8J64rDzwkIurKuqqtlJp8JGLiijwn4mQfS5cIvCcsb7gp502yLrwn6CEWfCrmZBcJ1M9ChVCE/CRiJbgt6/wnZSJV1Vg8JGImyYKFEIS8JGklVBeVSXwkaiIJS97M3lZCglCBzpg4reePzoKLUIrVW1mJuKugmZDwqLCrlTCqybwn5W08J+VtPCWtYUq8JGNpj3wkKiFw6soJgoCQgAKLEIqez/hpYw64oCHRMKlL/CflbRg8J64rnPwkY+HPF7wn6eU4Z2vOuCzlmxMCkdCRT1yUOqnkm7vrL7qqobgrL4l8JGMtvCRjJYvQyTgsrnwkKC8YvCQoIA/NOGlgPCdqp/wkYyQ8J6AnPCXsonCtvCfn7A2JAofQh3CpUTwnrqVZiTwkY+N8JC/pULwm7KQ8JuEsjVgLQohQh8mdmDCtPCfm55U4Ym9OuCqvvCSvrRv4auC8J66tEU9CgpCCNSPdWTwmLSDCgxCCsKlJiLhoKBVwqUKNEIy8JCsufCQnrU78J65rychwqXwnYSCXEFcVC9IJSPhpJnvv73wnp+heT8/YGHwn4KgXjoKJUIjcz4iL27wkKCW77+9USrCtjpMw4PwkY+C8J6Fj+qfnCQ/a2AKIEIeP1U88Jy+qvCQgobgqLXwmLSG8J65iXs/Iibgr41gCgxCCkDwkY+H8JCgiFwKE0IR77iK4oGD8JCSsC7hirAlJToKKUIn4Laf4Kme8JCsk3pc8J6lnn3gs4dcJ/CYprzRqGbwkYS2PUjgqIdWChRCEvCel78vLuGutPCRjqDvv73CpQoVQhPIuvCQqIXDi8i68JGKoz82wqUnCkBCPvCRsok/J0ov8JGkpPCRipkgPPCRtqXwkIWu8J6gpNeybfCQvLfgqLNQ8JG/kSQkJyc/8K+ktioiYPCQoIhtCj9CPXMr4KqR36shLtGobuGfpfCQgK3DlX3DuC/wkY+h4aKBJy55M/Cdhq7gqLPgsrU64KqyYPCRv4sueu+slToKGUIXWfCdloBgPCUq4K6f4q+jw7g6cOC5jWgKBUIDP8KlCipCKOqsqVE/Ku+/vfCQla/hvZYv8J2RuOKCluClnTohXMO98JCtuPCQhqAKQEI+4K2VwqUlWnTCpSIie3XwnYKd4b+YPPCdkp7wnZKw8J+VtOqgj/Ceo5Xwn6GSeeGMlSE8RsOCOsO0ZCzDnjwKCEIG8J65nT0qCh1CG/CdkqxY2JTgp47wm4e/Jz3wkIe50ajwkY+Iewo5Qjdi76yV4LGpQls/L/CRsqPhvZ3ivaJNwqXDhT0/YuCos/CRhYfCpTzvv6DwnoCo8K+llWLwqIWcCgpCCPCRto0k4L+BCgtCCeCovDgq8JC8oQojQiF7TfCfop524KGD8J2AgfCShYbgrZcvcXw98J66ofCflbQKLEIqJzXgu4bwnrqsYOG/hCbwlqy88JGgnOGJkuC/iT3gp4jwlJimOiMm7Z61CjFCL0I88Jq/vTpt8JaqofCdlqDIunYuRSbgrYzwn5W08JGFmFzqrKUvYGbRqETwkaSJCiBCHsKl6qyi3qUi0ag/aicywqok8J2egy8lP0jwnoCdLwopQicl4LeCNzzRqOCwhWjvv73wkYqAw6Ak0ahXJ8Kp8J64oj3wkJOfw6gKEEIOPeCnolwiKvCQtZThirMKiRe6AYUXCjQKA2tleRItQitabi5GJuG+sXvwkbS68JG0ieC3iuCvl8i676y5fuCnryQiXuCtjPCflbRCCswWCgN2YWwSxBa6AcAWCr0WCgZTcWxTZXQSshayAa4WChRCElxDJdGo8JGZolzDi2lASCIlNwoFQgM0J0oKRUJDLyfvpZrgoKXwnrmHYH3wnoSz4KmNL+GKuPCQqJN3NOGJksKl8JGiqfCel786JvCQqIXvv70k8JCkn9GoXcKlP+qnkAo3QjUq8J65i/CdvKkuPOCqj+CnnFZLPGNt4KC6KuGlq+GDh/CRvrAqwqwv77+9YHngvqRjwqVFKgo3QjUn77+9P2g9TfCRpLdc8JCEjOCssyU98J2qrCU8yLpg8K2/iPCQtLnRqHvhqqt3ceGJjMOsPwo5QjfwkbanL2jwn5W0MvCflbRPYvCQlqgrJVTvv71JJFXwkKKtLic9PErwkLyaUGFc77+98J2dmWtACgtCCeGmi3rwkYicOgoeQhzwkYyy4ramPOKwguK2pXbhnKvwnZKuXGJQYCJxCiNCIci6PuCyvuGPhMKs2pbwnqWfMXvgprbgsoTCpfCek6E9LwoMQgo68JCEneGzo3g9Ch1CGy5ESci6YuCntcKlJF0kLi3wn6+X8J+pvC7WlQoXQhUgO2cuUyZgNSc48JGklSpSK/CQqIUKFkIUyLrwmr++YvCWv6RRP1TgqZ7hio0KIUIfY/CWrZ7YjzrwkKiQdCcsXuGdsPCWppPIuuGvjcOZYAoLQgki8JGMjPCWv7AKGUIXL1zwn5W08Kq6nu+/vfCdm6PwkbKzJSUKO0I5P/CQvbFgezrwkY6JIu+/vWA8TNGo4Kay8J65iyp+IfCRjrjwnZKKPe+/veqnpPCRiK0o4aWyyLonClFCTyTwkIqy4oKX8J+CofCWrZ54wqXwn6uSXuGJmO+/vfCRjbLgt4rwn62zXPCQrofwn6ul77+9fe+5pMO18JCWncO1XOqtpHt78JCunOG8pSkKG0IZJ/CflbTwkYyG8JCQquqsqtGoIOCzp2nIugoZQhcl8JG/rSPwn5W0PuG+uuCyviYu4bCgegoaQhgl8J+VtCrDqSLwnrql0ahc4K2HPT/hj4MKAkIACgpCCDonYFw/wqUzCgpCCHtQIybwmr+2CgRCAsi6CgVCAydgRgolQiNAL05W4ZOvJDx3JsKl4Z2wRuC6iCYv8JCkti7CtCYm4KShYwoVQhPDgSVsPeOCnkvgrpzigrngqYcnCkBCPjzwn5W0PvCflbRqVfCflbTgtrHivawqfNGowqUq8J+VtFbvv73wkY+Y8JCjtfCSkpQ/PS8vP3xlXvCRg7EqCjZCNC5pJHk6PXHgqLMv8J+Dhu+/vSZi4b+YXO+/jfCQvY3goZDgsZY4Uzcx8JGkqEngrKrHlCoKJUIjIfCQnqnDpT95W24g4Le08Ja/ovCQi6/RqDoq8J+VtPCQvbUKB0IFJi7hsYEKI0Ih8JCkvyR7P3zgrZcmJyZ8XNGo4LaC8JCOkz09I/CQjYlFCgRCAmBRChpCGC4mJfCeipZtyLok4K2c8J2LpfCvo5RHPAofQh1Y4Yyj4Ymr0ajwkYa4O23wn5W0WPCflbQ98JGNiApKQkjwn5638JGDgvCRgKNPOvCdkK3wlqux4LSP4KqzJOCugy86Wy7wn6q38JGNnz3wkZuQ8JCgiCQi0ajgtYrCojzwkpKk4Yq4emwKHUIbJtGocPCRsqA8w4Y/4LmNOuGijz0p8JCUiiRICg5CDPCQoLzwkZaN77+9bQoqQijwkISC8JGKkTHRqOCvrHwsVPCeu7Azez3wkY+IJ/CRmobvv73wkY2XCjRCMkI/R+GNkMi6Tjgl4LGL8JGyqm/vv5tEPGDhpI3wlJeY8J66gWDvv73wnYydLz8z4YmYCilCJyfwkYyIXUjhirgwQvCegIbwkYqI4LKPM2ozaScueyQv4KunMic6RgpAQj7dsXkz4KmZ8J64ouGPuPCRj5fwkY2XRSbwkKCILkHwn5W0J3bwkYyBKvCQoKUvZyXvt49pL3vguJd30ah5JQoFQgNg0agKF0IV4LuGP+GOg+CxlvCQlbwqM/CQlbk/CiZCJErwsJSy0ah18JattTbhqbY/J2jgu5nvv73Dn0laVlxE8J6BnwonQiVBc0BG8JCqinY9YCQi77+9YPCQgITOjHgm4Len4KCyPzrwkbaUCihCJkLqqJ7wn4K+IvCRjIDwlr2iOuGKviwv4bWq0ajwn5W08J2Lj0p7CjBCLuCzoNGoXOqtgk9L8JGMquGJmiLwnZOBwqXgq6nwm7Gz8JCLo/Cfgqvvv706w50KGkIYXHspLjrwkIGAMOCuo+Gwu/CflbTwn4KhCidCJSB1P2nCp8OGdSchwqXwn5W04KyDKPCQh5cw4a2CLjddWvCRtqQKKkIoL+C6hNGoe2cq4Z2gdjHwn6GRXEjwn5+wUCXvqo5B8JGNqu+tgXAqbwo8Qjrwn5W08J2Lo1AyKFXwlqmnLfCWrZLwnZWG8J6AhuqgqyRf8JCnoyYi77q/YDbwnqSaw7JrXSrit4RWCgdCBT8g4KyQCj1CO2RMOlwkXC46MvCRtpfwnZKleyw877+98JCAsXt78J+toH3DsPCflbQ/8J65je+tg1VcKfCQrq3wkLm7ChBCDnZMJvCRjojDu8KoedGoCj9CPS9j8JOEgEEnVvCQlprhs7U/8JC9se+/vTzwnoS38J+VtEDvv70m8JCnsvCRjKhgLOC/ktGoRVh3Oidv0agKKUInP+Cuvj028JC8jPCRmoBl4oKm77+98JCGjmBrOeCvh/CQgpngsoo/CiBCHvCRioJbw5Ql77+9KHtEw5FcJtyXzozwkKSzKuCyvAooQibhvZbwnoS16p6O8JGkjysv8JCdovCRqKjvv700J3JcezXwkJeMJgoVQhPqn5MgUOGKjTpge1Lvv73gt5h0Ci5CLOGDjcKh4LuC4bG9ckdZwqVm0ajwm4Sy8Juyhi7hqqFgYFI6VTzhi7Vg4LOiCiVCI3vCpeGJrm498JGDlHQqSfCRu7bRqNGo4reJ8JG+sCbwr6OPCjBCLvCepZFD0ajDmyTCpS/DhOGMkmTvrIM84b2U0ahc4LWM8JeauTTCpfCWupbCtGQKUUJP8JC6muG/kj/Ct+C7klnwnrqo8J2jv/CeuZQl8JCWh/CQhqDIuiR34YuPI1zwkJSd4LmF77+O4KGeMDrwnp+98JGItSY9L/CRh6TwkKiGYAoIQgbgoKRQw7gKHUIbcPCRjbQlwqVgQzXwkKyHJC9V4LGh8JKRijkxCjVCMz9x4Lql8JCWqDoqWXVgbvCeuLkv4KehYDzwn6qFOi9XdT9k8JC5sFE8w4lgLyJS8J+VtAowQi4v8JGshSRtXOqsgyLgq4MuwrjIutWb0agyb9Go8Jy8oiLwkLqrwqUs8J2UkF1hCgJCAAo+QjxUJibwkLuE8J+VtNaO8JaqmfCQqohwYHvIujzwkYyCPifRqPCRiqQiJSbwsZWkRzxZLy/wlq2l8JCeuVQKM0Ix4Yq7UfCQuowo4LeRN/CfmbrgqqckS+CmssK2wqUkwqVzJj9VJfCQralKXSdHw6JebgozQjEn4KC2IsKyKvCWrofDsHvwnZS+PeCpgeCnjPCQgLbwkY+FffCcvLfCpfCQgIEv4rusCj1CO/CRq5p74LeP8JGNsS7wkKiPe/CQtLHasyXwkYiL4aW0deCvhtGoOuCqs2Dit5zgro/hr45c8JGLsVw2CiVCI+CpmvCQlrHDmCdlKvCRh5nwkIagLyLwk5GDJGdcbPCdjKIuCh5CHDo6ZOqntPCflbQu4ra60ajCpWbwn6mTOj/CoD8KMkIwyLrgvZ3DlU3wn4KnYPCut4E176yW8J64tWDwlryM4LOeYPCQhoY6L+qphO2fnGBJCucVugHjFQo6CgNrZXkSM0IxOsi6UEbguojigLhb8JGHgeC3iizqpo1e4b2NJO+/vcOU4LeRMXvwk6Cae3ki6KG3PwqkFQoDdmFsEpwVugGYFQqVFQoGU3FsU2V0EooVsgGGFQouQixxOiXivadg4K2CKlrwkIaTbEJ44b2d8JCetngyauCpkeqfm30r8JassjpgRQoUQhLCpHjwnLKmLi8u8JColmLvv70KF0IVWuGDh+KClirwnoSw8J6KqHvgu4IuCh1CGyUv8JGwqmXgp73wm7Kd6qyJ4ragMC4vIiQiWAogQh4nw7skcj1I76SORSZd8JGbgyTCsfCWq7UnKnVcKjoKOkI4JeKrqfCflbTCs/CbhabDvuCos8KlZ23wnoCb8JGkifCxjpDvv5XwnZWLb/CQlo7CpT8iLy/ir7EKDUIL6p+QwqUkfeCxnVUKOUI376y+JOqjjyIq8J+VtOK0iWDvv73wn6OBJ9iJ4KiP4K6P6qOVQPCeuYvgsaBEde+/vdGo8JC+uQobQhnhpqfDuXsn76y+6qyBUe+/vSbwlqmkPS46CkVCQ/CRrIjWjsKr4K6P8J+VtHszYHtHJ/Cus5nwnrmR8JGNsSjwkZGg4KiQ4Kmzw5Mi8JCunCLwkZuA8J+VtPCRi7TIuiQKLUIr8J60hMOuLuqfk3Zj8J+VtPCfoIHCq0Am0ag8QlYs8J+VtNGoyLrwkJesJwoxQi8uPCbwlqKr4K+Q4YOgL3jwkZKd4aCYIvCeuYclWi7wkKC4KuCunsKl8JGqm+GFjAo3QjV9PT1gImDgt7TCpT/gp4ThpLDDqOOEhtGo6qq9JeCxonoq8JGkt+KCkPCav7JBfdiM4aeBPQovQi17LTzigIIrMz97JiTRqCJgPcOJKvCRioTwnbyXOuKAljwuJMO28JGNiMi6PSoKE0IR8JGNgDxOb/CYtIDgrqPgqYgKNUIzY/CQhKB58JC/rMOG6qe0ZMOOXCrhiZht8JCCnDokK+C0gSc677ePKMi6JOCqsyjhvZsvCjlCNyJ7PS/IulRr8JKLtCIl8J64ouCst/CRj4LRqOGLgCHvv73Zr/CRmZjwnqWf8JCFllYgX+GxvC8KOEI24Ku+Libgr4Y61phNwqUuWj3guqXgtaJbW/CTmrPOidGo8JatpfCQnKwxIuK2hOCwneCwtTp1Ck5CTM6h6qWzJSrwkaSJ4aKN8JG/l++/qyXwnouGfO+svvCQoYnIuuyUuSIi8J2SpvCQu4Nw4Le08J+VtPCfqofgqJDiiLvwkaS48JG1p3sKH0Id4KiIYPCeuIsnbVDgsZXRqGDRqDlgPeCoslclJD0KLUIrI+KCtPCQnYnRqFzgsYbRqCJk4Kayw4om4LWhdj/CpcOCyLo/8JGKgO+/lwodQht18JCWu3fCpSYiYOK2oci6flzwkLKW8JGSgyIKE0IRZVZMU+CnoeC7iSc9UmbCpT0KMEIu8Ja+kfCRj6HwlqyxIvCeuLsvPOCwjsK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3nAotCgdncmFudG9yEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgoyI3USeAeYJ1N8CpYBugGSAQo3CghhY2xfbW9kZRIrugEoCiYKCGJpdGZsYWdzEhrCARcKCgGZiDN3WIQUc3wQ////////////AQooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBFUA3g2Y2eTJWfAotCgdncmFudG9yEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgoBNGhYFZZXJJNcCnG6AW4KLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBVXRgQIIFRSICTAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBdBc2UEaHkSaZbAoTCgdncmFudG9yEghCBlB1YmxpYwqcAboBmAEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBVphjYlBQFlSQfAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgqCk4YVmZZwQIgsCjgKB2dyYW50b3ISLboBKgooCgpQcmVkZWZpbmVkEhrCARcKCgiXRmGHSWJ4R0wQ////////////AQqIAboBhAEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBN0eGEEYAM0l2XAopCgdncmFudGVlEh66ARsKGQoGU3lzdGVtEg/CAQwKCgIRN1NpKQdBYmwKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLARdwmQlUkngnGRwKkgG6AY4BCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKYFAIVUYoMGNjPAopCgdncmFudGVlEh66ARsKGQoGU3lzdGVtEg/CAQwKCoNpFZKYZyIiZ0wKMwoHZ3JhbnRvchIougElCiMKBlN5c3RlbRIZwgEWCglQgZEZcGhjUxwQ/v//////////AQqRAboBjQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBOCJWmRKSBiMinAoxCgdncmFudGVlEia6ASMKIQoEVXNlchIZwgEWCgkSlBckKVlglZwQ/v//////////AQopCgdncmFudG9yEh66ARsKGQoGU3lzdGVtEg/CAQw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gJPCfrqU8CjVCMyfCqtGoLjnwkYyHyLrwkaeUPVxe4oKU8J6Es+G/tijgsqPIumhowqXigbHwn5W04YyVQAofQh3wkbCE4aWyyLrwn6+Y4LGZRCUoLsi6b2DIujVgVgoRQg88R3JTPWPCt+GNojpCTycKAkIACjRCMl518J+VtC4lOvCQsqsme+CsiOCvkGtcJTbwkKyNyLo/8JaEmvCesoEgLjdLbydo4K6jChlCF0DhirVcR8Kl4aiuyLo6P+K3k3nhsaBcCkNCQc6Meybwm4WV8J6XqvCRj6FgwrB7JijgpqvIujfCpTzwm4Sy8J+VtCrwn6aJ4LK94rWv8J+VtEvwqrad4oG1yLp4CjlCN3vDmeCph1zgt4Hwnp+lJCJIYO+/vci6QvCdhYktSOCvsPCQpr1+8J65i+GzlfCfqqEzPcOZfmQKCEIGcPCYqKRNCjpCOFxEw7/vrL5QXOG9mfCel7/wkYyI8JGPiMi6e+CgiT/wkI2GJuCovPCflbThj704YPCQqIXhs4VpCjFCL/CRp4TIukY68JGFtSfwn5W0IvCRtpM58JGRnzDhv757YCR28JGLtOCprO+/hcKlCjxCOuGdpfCRip/vv40+X++/vUldJuCwvysv4rqUXOK2uNGoVvCRj5huKvCepZ/Iuj/wnrmk8JGIjCROImcKKUInKvCRspjqr7PvrJXhioHguo8iJ2M8LvCQhqAwYOmsiHp7YEZ34Y6VCkJCQPCRhL3hnbLwlqmINXvhoJMtJPCRtIRs8J2BlW3wr6KdJPCWq7Pgrpl5XeC3jzLCpUDwsLe88JuxufCRp5zgrK0KLkIs0ajhvaDYrOGknuqfkXvgsr8uw6fgt4YkUOC6sCbjhK3gprzwnZWBL/CRiqkKIUIfYPCflbRp3a11c+GJncKl8J6AuVMnYMi6JCpi4KakaQoDQgFtCkdCRT0m8J2qn/CdhY5DcfCflbRjOi8y8JC7hPCQtaBS4L2RIDwv8J+Al/CRjZDwkbaR8J+ro8i64KuHOirgtr3grLI88Juxuwo2QjRu4K2BKiI14KqzPzrwnZKf8J+VtCJT8JC6oHtLOuK0n2Dwn5W035RS4Kus8JCNpOCxizw5CjRCMuCtiDbDgSRRL+CmnPCQq65kcPCQgIgw8JCmgsi6eTngrYfRqOGcry7DpPCRjZDDhdGoCjNCMfCQs73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oIgonQiUiafCWqakv8JC7vPCSk4/wmr++LlbwkY2EOmt08J64t+GMklwrCiRCIs6MW2c64b+RYCRZ8Jq/ti/wkIO1wqUq4aaD8JG+sPCWvp4KE0IR8Ja/sfCRo588JmBVYPCRj4IKK0Ip8JCRhOqpje+/vWBs1qc/0agmJD88dvCeuKTwnLyw8JC2j0ngsZg9RiQKNEIy8J65ifCWvptnMvCQlq/wn5W04bKrw6AuPyTwnrqjKj9c8JCFmijzoIeWOSc0Z+GKingKHUIb8JG1lGY9VUAhL+CpiyYl8JG0uu+2qDwowqtgCiRCIiomKidA4b+b8JGNq3twJS9l4ZyO8JGMivCSkKU2RPCQhqAKQEI+8JGNl/CRnLdLIvCtu78/4KGeLi7wkKKP8JGKiOK6kfCRr48paks+SOCzjeG/ligiJUAv8JCOjfCflbTivJMKEEIO8J+VtErwkKuAXSbhv64KH0IdSzA6Qe+wui8i8JuFkfCflbQn8JCggO+/m/CRiIQKJkIkKuCprUoqVl4l8JCdp+CvjHzvv73vur8i77+9yLpme3xf4rapCg1CCy08IlzwnpOre9GoCiNCIdGoP++5s8KlJ2/CpW864LuAVCtZ8JGyqyXgqrI/Jci6ewpEQkJt8JG0unl78J2ToVXwkYyP3rAi4aS24LOgOvCeuIJ6JvCdjaE/8J6fuz9cL+Cto++/vfCegI5O4b+rKlHwkr+bYE4KOUI377mz8J65r/OghJE9bcKlYMOvYPCcspZSJyJC8JCMivCbgr1JXCok4LyDXT/RqPCTkYIqTeKCowotQis6LfCRjZfvrYAmL1x26qmVyLpceGYmKmc8J1B7P0RfLydVJvCQhpDhirAvCiZCJOGih/CdlLjwnrmdTzXwnrik4KS6YOGDh1zwkJ2j0agi8J65jQo4QjbwmKuZIuqplPCfoa7guqXwkLS14KmvL2/wkJKg8JGPlTclYCTgrLM8Isi68JGKjPCfiJThoaUKNUIz8J+pojYndHvhqJbwkKiW4aCLwqXwnrmJSOGjjT9c77+977mh8Ja9s/CegI/wnrmHdcKnCiRCIvCSkocmwrLhsr1c4aCYcVZm8JGlli8l8JCip8OzLvCRjoQKHkIcMHs90ajgsqvgrIUu4LK58JCBnCLgqLkuP0N7PApFQkM/77+94YKB7Z+O8J6TrmTgu4H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GhjBzEhMcCh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwFoBRZUCViZaGc8Cii6ASUKIwoGU3lzdGVtEhnCARYKCVEIYyREOBBlbBD///////////8BCim6ASYKJAoGU3lzdGVtEhrCARcKCgaVMWlnJUU3KXwQ////////////AQoJQgdFeHBsYWluCh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEFhzKIlWY3mXk8Chy6ARkKFwoEVXNlchIPwgEMCgpEh0CXUjgUOFY8Cjq6ATcKNQoYSW50cm9zcGVjdGlvblNvdXJjZUluZGV4EhnCARYKCZAACDhGeEdUXBD///////////8BCh66ARsKGQoGU3lzdGVtEg/CAQwKCmNyA3VChjYyYUwKHroBGwoZCgZTeXN0ZW0SD8IBDAoKCYiJBEWSYwhjXAosugEpCicKCVRyYW5zaWVudBIawgEXCgoHmAY2hGk5KAR8EP///////////wEKH7oBHAoaCgZTeXN0ZW0SEMIBDQoLASEZk3WHJ5Y2GVwKCUIHRXhwbGFpbgoJQgdFeHBsYWluCjG6AS4KLAoYSW50cm9zcGVjdGlvblNvdXJjZUluZGV4EhDCAQ0KCwERgwFkcjNSMSecCie6ASQKIgoEVXNlchIawgEXCgoEAIlyNQMgA5NcEP///////////wEKIroBHwodCglUcmFuc2llbnQSEMIBDQoLASIpAXKIVIQhQzwKH7oBHAoaCgZTeXN0ZW0SEMIBDQoLASkxJzkJIYNmJlwKCUIHRXhwbGFpbgocugEZChcKBFVzZXISD8IBDAoKOBRnMJQBcRSHfAowugEtCisKGEludHJvc3BlY3Rpb25Tb3VyY2VJbmRleBIPwgEMCgoFI5RQU5FQMXFcCiy6ASkKJwoJVHJhbnNpZW50EhrCARcKChZpMod5mWU1lIwQ////////////AQoeugEbChkKBlN5c3RlbRIPwgEMCgqQcwEjF0l2mZacCjC6AS0KKwoYSW50cm9zcGVjdGlvblNvdXJjZUluZGV4Eg/CAQwKCnkJNWBHR1BERxwKJroBIwohCgRVc2VyEhnCARYKCZaJaShAhyZ1LBD+//////////8BCh66ARsKGQoGU3lzdGVtEg/CAQwKCkAwhkE3F0EQczwKIboBHgocCglUcmFuc2llbnQSD8IBDAo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iJPCWqoU/4aW0NT9KCjdCNTpyIvCQlKPOjPCflbTvv73wnYS+PyQi8J+VtPCegJ09IeC6iSwv8J+VtPCWq7NaOirwnou/Cj1COy9hdvCdqqHgoLTgqq57SuK2s+K0rfCRsrElJiI3ZC/wkKiufibCpe+suyLIuvCSvqEvKfCdlInwkY2wCjxCOvCRg4LRqOCwjuKAkU5RJeCsrzkre++/veK3kfCQqLrRqCbwlqWP4reUWS8iNzdlVj/gooXwkKyzTV0KFkIU6qqe8J+VtGvYiUhayLp78J2BlyYKCkIIP+qctvCRmLUKQkJA8J2VhOqcqiXhp5Yq4K6kQkDCpW0iOu+/vfCRgpfwkIO48JCBmvCQlb/wnYSc8JasiOKBmfCfq5bwnrmiL+CmvwosQirgr4bgtZ3wkKCIJCrDgzwi8J65pCRN8J6ijiLwkpGeXiXipbjwkbSJYCUKEUIPJz06Lu+vgCbhiYos77+9CjhCNvCdk4PgsaE84rWw8J2SqvCRm5RcPy/wlqmgJ+KClz/wkZCDYPCeuqgkPy/igb5ZOkFcO+KCmgoiQiA8zowv8J+qifCRhJ9QOuG/vDNy44ezImA94LGI8JG7pQo6QjhF4oSzw67wkKKt4aWAdlt50agiOuGpo23gu4Yx4Zmu8JGWi/CRiojwn5W0w4rgoZ4nOns6NmBgSQoLQgkq76y+4bC/yLoKLUIr4ZGv8JGklW/CpzYn0ag88J2Uu196wr3goZRkYOqsquGOguCmj17wkaS3VQo9QjtnYibgprI/8JC6sfCflbQ88J6FhvCel78mTy0u4YmU8K6SovCRjYcvL3Pvv73wnriO4byY8JuEssKlXgo1QjPwn6CH8JCKhPCen6nhm5AiYEHRqD3gu4bwk5GOWMKle/CRsqkv8J28qPCeuqfwkYqaVjwKDEIK4am/W07gt5rDpAoYQhbwn5+j8JCMovCflbQi8J2Sv/CWrokkCkdCReCni/CdlJYqL0RL8JCXh9Go5aa4LtGo8JGLuPCRjJzwn5W0ez3wkIydKu+/veCtnDzwkYyL4KGe0agn0ahJYPCWhLQiLAogQh7vv71UZeK1h+C3ll3RqPCQlbVgYHV16pKX8J+hqGAKG0IZ8J+DjPCQoq0s8JGNgljwlrqY4K+MOuCggAoLQglU8JCHtCPqqI4KREJC8J+urWBc8JCouiA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nKgorQinqq6DhsYLwn5W0T/CRv6/wkIev8JCVuuGmjXske2Dgu4ThoLrdkOGniQo5Qjdg8JG2i/CRjo7wn4OZ5YeiLvCQja578Jy8jGjgroPwn5W0J2Dvv71eae+/vfCfrqUuw6N7bl8nChZCFCcvLXttJfCdh6PwkYyv8J+unycvChtCGeCzlTY9IvCQhpk9PWDwn5W04Y2QN1Xvv70KOkI44LuT8J67sPCdi6VT4KatO3sm8JCdo8KlPeGLhDbsu7vit5TCpcKl4b2ZJ+CupEguKjxEbeKvlFwKCEIG4bKSaVxNCgdCBVvwkZuSCjZCND0u77+P8J+VtCrwkbS68J6KruCogy3qq6kj0agl8JCUlOChlPCQppvwnrm2MeCsuS8veDoKNEIyKvCel6nhnpU8OiU68JCFk3tcPy8v76y7JfCehL184KmL8J66qfCeuKRO8JGOi++/vXEKPUI7ey8k8JGnnyFgOvCQoqjgtI/hrYnqqYpIJCV8KkHwnLyP4Lip8J6Lv+qhiu+su/CQh58qKng8PMKlYEYKLUIr8JGKiHxcOtGoNFw5J3LIuvCRnIVDcXLqrbpSyLomT2B7Jl0qUD3gs4rDuwoVQhPvv73gvabgsY15ROOBqsKwL1wvCjZCNDwnWXpg8J+VtD/wn5upJ/CdhbEiMCrCpfCRj5U+w5xLZtGoXPCav7498J2VhCtSPCJ4JioKP0I98Jats/CflbR7fvCRgLvwm4OrVPCfoanbsyTwkaC14KGa8JGAkz/wlrWx8JuEsi49cVF6IuG9m+GJq317ewoYQhY/8JGKi0wkJ++/vTJv1ZHwkYyVPWR6CjpCOCx74bSKe9mJIfCflbQxKXbhv5hl8J65myIkVkXwn5W04LOh44W+1q3wkJ624LmV4oK2IPCRtIhkCkJCQGzqo5PigpzRqPCQm49k4Kuo4aShRmAvYfCfpJxc8JCWj/CfrIwuJT1m8J2Uk2l78J6ln+CovGDhiapc4YmhwroKJ0IlzpnirpDwkKO0XOqsljw+8J+FriVNTiDwn6K6UOGjoPCQppMmOwohQh/RqMOA77+9MMKl4LOGJMOAJcKlJzp78Ji0hGhv4Ki8CkFCP+GHsSU4P3nwnYGt8JGKnPCRpKXwkYOCImfCpMO68JGjv+qjt/CRgYxSJOCqgfCQurHgoZ7CpSp1SMi68J65iQo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bhZVAMuqfmGDRqPCflbTwnYil8JGcvMKl4ralyLrigq03OsKldyc/OsORO8KlyLoKSUJHYy5J4b6h8JG1l/CRiJHwmLSH8JCTtWsle/CQqI3hjpHIunow8Jq/vvCQoIor8JCNseCxoOCmtzLwkI6ZKu+svvCeuYLCpSQKA0IBewonQiUv0agjdCLwn5W0w6/wn4mi0ahcXC7ihIVnKjMkJUjgqrDwkKC8CkhCRvCdlJrwkbWl8JGIpiLqqKXwkYyB0ag9ODpg8J2disi676yELvCRpIzRqD8y4LWI8J+urO+/vWBte8ODJ+GPh3M/8JCHsWAKFkIUUu+/vfCdlJrgsYtv4aaGYvCRjoQKPkI8Peqsq+CssuC1r2DjhZvgtYc9yLpD8JGvm03wkJa7LzrwkJ2E8J65ouqqiUlQPfCirYfwkJKP4a2A4ampChJCEC7gupo2TzEpQig/17TCo0QKG0IZ8JChoE0944Kb8JGKqeKAlyTguoHitqgiJgooQibwkoeEw4XwkaS3Pz1mcPCQpLTwkYOiLirhsL1sOsKlOsKq8JartAoxQi8sYm/RqOqsjmDwnZS5Juqrm/CQoY1gTjxJJeKLtGLvrII8MVwg4K6SJW0uJSQlNAo6QjhXfeCmsvCvppp64KmeYPCQk7IqPOqfl0tWNSbwnri0azxke+OAkvCQubpWXCbqlJvRqC9C4La9YAo8Qjrwlr+xwqVq8J65hzzCpjrwnruw8JCBgyt68J+fsNGo8JGBkuC9tUEq8JGngiZgajo38J2qq/CQpKw8CgxCCmDwkIGH4KagOHgKOUI3Y3EqP/CWtbjqqo3wnriiRu+/vci6IGZ7bfCRjINBMSMlU++/vS488J66qeG+iUrwk5GDyLp7XAo7Qjnwm7Gw8Ja+kXvhrZhgPy9cdkIo8JC8uOGyvykiJmEiIuGnmSpN77+9cFYs8J2IkDzgoLLRqOCnjGUKBkIE8Ji0iAozQjE8w5DwkJagPOK6kiRYavCRhIXwn5W0yLpSRPCQqaPqo7Zg8JC+s/CeuaTigrNK4Ki5CkFCP86M8J2AtlTDj8i6JWA0Inngrr8zIDnwnrmd8J+JoVFZ6qyhKCQu8JGMtick77+98Jy8v/CcsaZz8J+VtOCgswoqQigkc/CdjaHwkamkdOGJmH5f8JGWiXLwn5W04aaZJ+GKuPCWvpQ/IiQ/CkBCPiok8J+VtCrwqoaZWFpIe/Cfm6MvMvCTkYrgs7PwrrGhNvCbhLJL8Ku4hz3hn6TIuu+/vSvgq4stZeCznXsjChlCF/Cego/RqCTgqY1byLoy8J2NtvCeuZIuCjNCMWA4wqXIujIk8J+VtPCRr7hxyLrgqI/wnYCR8KqSqPCflbTgs41mKndcIvCRkZ3hnakKO0I5cXTgup7gq5DqrIklJOqskS/gt5PwrriuIvCQgKbwnbyZyLo98JGNiD8m4KC5PShvNfCav74lKidFCgRCAj5QChxCGiLwn5W0Ju+/veGzgiDgu5jwnYiNJCok4K6oCiJCICrhj7jhvZnCtsKlXGvgsabguoHwn4Kj4K2WSCHwnrm2CjUKBG5hbWUSLUIr4KivXjTwnZCwIlg/0ajwnLyofC4wezwk8JCtgyIzW/CRg7ngrIExKyolVQpPCgluYW1lc3BhY2USQkJA4b2Z77+9UCtoe/CWrZ060ag8LDfjhJzhv5vwnrSG4Ym7IifitrXDv/CUjbsh8J2Hoe+2oSPvv73vv73wkJ6nIAqdDroBmQ4KoA0KB2NvbHVtbnMSlA2yAZANCiBCHlE08J6LpPCen75WZmUue+K9vMKlPD0+w6bgvZYwIgpKQkgvPNiN8J6Kp/CRvrDIuuC1lCIgbih68JuAh/Cen7jgt4BcwqUm8JGNiC888JCWt/Cen7XwkLKMKtGo8J+VtOCwj/CdvKZp0agKKEImw5gvJyd34LiYPSUk8JCVgPCRjadN8JC9k3taXvCdlI0k8J2Ev1kKLUIr8Kufj+GhnfCRj6EqNCZjXD97P0rCpSLwsb2D4LKBOiThvaxlw5hbaFo8PAo6QjhQLeCtrPCRjaFh8J66lmzgr6xF8JuEsj/gtoJ9LvCei6Ml8JGMkPCQuqvwkIC88J64ufCegKPRqAoHQgU70ahgNgoTQhHwlJSLKWDguqg24Kq3JeqZjQoJQgfbueC6pT13Ch9CHSfhsY/wn5W0PfCRjbTRqCJCZVZg4KCDWPCfgro/Cj5CPMOM0ajqkqXwkY2s8J66uyfNu07wkLCy8J64tCTDnvCRrIDDuHIq1oV5WDo98J6Cj1w6w6rIuuCrkOCoswozQjHwm7KGKz12IC894KiPUj1V8JuwpO+/vSbqrIQ84Km08J+VtPCQj5Eu8J64pPCbspxHCkBCPvCRjLXCpSrwkKGP8JGxvvCRtpbwkIC/8JCWiSJvJuC2t0g3JzzgppzigbDgrIritoNgSu+/vSfgtI/wpYKfCgJCAAo4QjbhqoLgsI7wn5W0ZfCQvbc6LiThv4vwlq2WUeCtne+/vci6OuGnkfCQoIVcKuGdsGHIuvCflbQKDUILIuGfo0Qj8J+hk3QKGEIW8JaiuOGKi1zhi4Btw5jhv4dMPO+/vQoxQi/CpSJM4YqZ8J6AleqVs8i6yLrRqOCzjfCflbTgq4lb4Y6XeuG9lNGoXCQ98J65kgokQiLwnZSIJEjjgJPqrKQu8JCEnPCRtoPwn5W08J+Oh1F6S3Y7CiRCIic/8J64uXjwq7SYLz/wn4KubfCetJ/wnrmZT2Dwnp+mwqYKHEIayLo8cTrwkZyO4KCyZiJhUD/wnL2pY/CRspYKEUIPXPCQoqrRqEnDqy83762DCgxCCiUkzox8dci6VSUKPkI8asKl8Jy+vj1cyLrwnqCiL+C4m+CjlOqjlCcvyLrwm4qa8J+jgfCQoIjwkKiQXyZHe/CRjIB7Z9W/MMKlCjBCLvCWv7BzOkleVCciYfCen67wmLSIyLp1LyQlyLo9XDrwnoWCfsO7wqU/Z/CQgZgKO0I5Ri7wn6ms4KqW4Kq/wqXwkIyUYD1rJu+/vSbDmvCcvrzwmr+w4Kay77+9JPCWqq4uK/CdkqLvv70kCiVCI/CQjJ5FyLrhv58l4aaSL1/wnY2Rw7LRqMOdPvCeuqHis75xCjJCMCLwlq2X4LuGYWBtcPCQoII6JjzwnqWeLsO4wqVgZ+C3tCzgsL3iuLvigbzvrad2XAo1QjNkyLomPGFj8J2Mgi7Iunsi8JGKnyR1L8i6TnRHJj5c4Y2XZz/wkZeXYG/wkaeh8JChkGoKC0IJQXjwn5W0YMKsCjdCNV7CpfCdmLPwsayk8JGSnFwmIuCnhOCpquCqn/CRiqfwnrmhL8i6ZuCskOCwuC/wkaSJaXxgChxCGvCWqabwkLO8PfCQhpfwnriiIiRM4KmCPW8nCkNCQT1K4L66P+qfnDt98J65pPCRgLEt8J+VtPCQvLgs4KqowqXvvYwn8J64uVw6PeKDn/CflbTwkKCCSiZ5P+CqgTplCidCJfCeuZkv4LSLe0PRqFzgsZglJlwiSMi64q+B77+94Le0L3s9OjoKK0IpcD1h8JK/mW9h4aqrKO+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wmr+78J2crvCeubk88JGNsfCehY8v0agxbXx3bj3qrIFHyLrCveCmsiolLgo8QjrwkY2xw6Vge2JJIvCYq5HqpLNwIWAue2hgKi8mJWFAX3zwkISA8JuxvGbwkbGZ8JarmWDvv4fwkK6qCjNCMeGcgHbwrpunQSou8K60gD0lIlvwn6ui8JuxuuCqtUxuXirwlqmpPfCQq60nYvCQlrsKE0IR4Kqg8J2AqXtd44SVyLpVS3sKQEI+4LaCJ0nwnZKi8J+VtOG5t+CzsVl6yLrCpfCWqaAleCd78JGAmzo/8JKRs3BA4ou80aheMvCWvIFBZvCflbQKOkI4e/CctKDwnYuy8JCEgu+/gtGoMEdN8J+VtD07PGDwlrycY1toZ+GwmE9zYCFlZNGo77+9Ve+/vTwKL0It8J6Apkgi8JGDkCbwkKCBw74mPGB38JG7s3EiUfCTpqBg0ahqJEZL77ac77+9CiNCISTgsajvuao677+98J6jksKl8JaqjiU/8J+VtFN9wqVwOgoDQgElCi5CLMOJbzjgq7/Csz8m8JGIl/CeuYLCpfCRsL9le33wmr+9Iko/XiUv4rSteDpiCjxCOirwn5W08JapkjpMKvCei5rwkbWH8J+VtD1AP+Czpi7hja3wm4WSQE/wnYOtLiI68JaEnuGjiWDhi74KI0Ih8J6ym8OneELwkK2x8JGMj31777+m4K+H4aGk8J64uSklCh5CHNGowqXDsnfwnriT8JGcssKl77ixKixtRfCRhaYKMEIuOiV9PXMkOuChgz/wnrmRL/CRr5MiwqV+OuKRgCrwkYqAeTvRqC4lX8Kl77+9KgoOQgzwrrWnK++/vWzgsI8KKkIo8J65iyTwnrqiPfCbsbXwnrSBYPCRtphC4b+CLuGgkvCWv6Jl8JGcngoKQgjwkY+h8JC+sAokQiJLezxGdnsuPzHwkKOpPnzitIkk8J+VtDrgoJzwn5W04oyRCkxCSvCQrITvrqw9K/Ccua3CuvCQs4kn4KG28JCoo2vhiorwrY2RLcKqez0z8Jaug1fhvZPvv713wqzgtpbwnZ2qSC508JCMnUDwkYyLCilCJyphXsOL4Z2u4K2W772SOibvv708IvCegKrgtr1c8JCsvHtte1zRqAoSQhDirpTwn6GswqXwk42d4LK2ChpCGD/vvIQ/dvCeuqgvIvCRtKgmIi5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 diff --git a/src/catalog/src/durable/upgrade/v92_to_v93.rs b/src/catalog/src/durable/upgrade/v92_to_v93.rs new file mode 100644 index 0000000000000..2c396414a3c94 --- /dev/null +++ b/src/catalog/src/durable/upgrade/v92_to_v93.rs @@ -0,0 +1,19 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +use crate::durable::upgrade::MigrationAction; +use crate::durable::upgrade::objects_v92 as v92; +use crate::durable::upgrade::objects_v93 as v93; + +/// Adds two collections without changing any existing records. +pub fn upgrade( + _snapshot: Vec, +) -> Vec> { + Vec::new() +} diff --git a/src/catalog/src/memory/objects.rs b/src/catalog/src/memory/objects.rs index e6fd520c3a8a2..5afb4a2688185 100644 --- a/src/catalog/src/memory/objects.rs +++ b/src/catalog/src/memory/objects.rs @@ -4323,6 +4323,8 @@ pub enum StateUpdateKind { Comment(durable::objects::Comment), AuditLog(durable::objects::AuditLog), // Storage updates. + CollectionCompactionBound(durable::objects::CollectionCompactionBound), + MaintainedReadRequirement(durable::objects::MaintainedReadRequirement), StorageCollectionMetadata(durable::objects::StorageCollectionMetadata), UnfinalizedShard(durable::objects::UnfinalizedShard), } diff --git a/src/catalog/tests/read-write.rs b/src/catalog/tests/read-write.rs index 387133251a3db..07c36497bcf45 100644 --- a/src/catalog/tests/read-write.rs +++ b/src/catalog/tests/read-write.rs @@ -1162,3 +1162,248 @@ async fn test_persist_sync_snapshot_stays_bounded_under_churn() { Box::new(writer).expire().await; Box::new(reader).expire().await; } + +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn test_persist_read_protection() { + use mz_catalog::durable::objects::{CollectionCompactionBound, MaintainedReadRequirement}; + + let builder = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation(); + let mut state = builder + .clone() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + let input = GlobalId::User(1000); + let output = GlobalId::User(1001); + let ungoverned = GlobalId::User(1002); + let mut txn = state.transaction().await.unwrap(); + txn.insert_collection_metadata( + [input, output, ungoverned] + .into_iter() + .map(|id| (id, ShardId::new())) + .collect(), + ) + .unwrap(); + txn.set_collection_compaction_bound(input, Some(10.into())) + .unwrap(); + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), Some(10.into())) + .unwrap(); + txn.validate_read_protection().unwrap(); + let expected = txn.current_snapshot(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + Box::new(state).expire().await; + + let mut state = builder + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + let snapshot = state.snapshot().await.unwrap(); + assert_eq!( + snapshot.collection_compaction_bounds, + expected.collection_compaction_bounds + ); + assert_eq!( + snapshot.maintained_read_requirements, + expected.maintained_read_requirements + ); + let bounds: Vec = snapshot + .collection_compaction_bounds + .into_iter() + .map(RustType::from_proto) + .map_ok(|(k, v)| DurableType::from_key_value(k, v)) + .collect::>() + .unwrap(); + assert_eq!( + bounds, + vec![CollectionCompactionBound { + id: input, + frontier: Some(10.into()) + }] + ); + let requirements: Vec = snapshot + .maintained_read_requirements + .into_iter() + .map(RustType::from_proto) + .map_ok(|(k, v)| DurableType::from_key_value(k, v)) + .collect::>() + .unwrap(); + assert_eq!( + requirements, + vec![MaintainedReadRequirement { + id: output, + inputs: BTreeSet::from([input]), + frontier: Some(10.into()), + }] + ); + + // Each rejected commit must leave the persisted protection state unchanged. + for scenario in 0..13 { + let mut txn = state.transaction().await.unwrap(); + match scenario { + 0 => txn + .set_maintained_read_requirement( + ungoverned, + BTreeSet::from([input]), + Some(9.into()), + ) + .unwrap(), + 1 => txn + .set_collection_compaction_bound(input, Some(11.into())) + .unwrap(), + 2 => txn + .set_collection_compaction_bound(input, Some(9.into())) + .unwrap(), + 3 | 4 => { + txn.delete_collection_metadata(BTreeSet::from([input])); + txn.insert_collection_metadata(BTreeMap::from([(input, ShardId::new())])) + .unwrap(); + if scenario == 3 { + txn.set_collection_compaction_bound(input, Some(9.into())) + .unwrap(); + } + } + 5 => { + txn.delete_collection_metadata(BTreeSet::from([input])); + } + 6 => txn.set_collection_compaction_bound(input, None).unwrap(), + 7 => txn + .set_maintained_read_requirement( + output, + BTreeSet::from([ungoverned]), + Some(10.into()), + ) + .unwrap(), + 8 => txn + .set_collection_compaction_bound(GlobalId::User(9999), Some(0.into())) + .unwrap(), + 9 => txn + .set_maintained_read_requirement(GlobalId::User(9999), BTreeSet::new(), None) + .unwrap(), + 10 => txn + .set_collection_compaction_bound(ungoverned, Some(10.into())) + .unwrap(), + 11 => txn + .set_collection_compaction_bound(ungoverned, None) + .unwrap(), + 12 => { + txn.delete_collection_metadata(BTreeSet::from([ungoverned])); + txn.insert_collection_metadata(BTreeMap::from([(ungoverned, ShardId::new())])) + .unwrap(); + txn.set_collection_compaction_bound(ungoverned, Some(10.into())) + .unwrap(); + } + _ => unreachable!(), + } + assert!( + matches!( + txn.validate_read_protection(), + Err(CatalogError::Durable( + DurableCatalogError::InvalidReadProtection(_) + )) + ), + "scenario {scenario}" + ); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + assert!( + matches!( + txn.commit(ts).await, + Err(CatalogError::Durable( + DurableCatalogError::InvalidReadProtection(_) + )) + ), + "scenario {scenario}" + ); + let snapshot = state.snapshot().await.unwrap(); + assert_eq!( + snapshot.storage_collection_metadata, + expected.storage_collection_metadata + ); + assert_eq!( + snapshot.collection_compaction_bounds, + expected.collection_compaction_bounds + ); + assert_eq!( + snapshot.maintained_read_requirements, + expected.maintained_read_requirements + ); + } + + for bound_first in [true, false] { + let mut txn = state.transaction().await.unwrap(); + let frontier = Some(if bound_first { 20.into() } else { 30.into() }); + if bound_first { + txn.set_collection_compaction_bound(input, frontier) + .unwrap(); + } + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), frontier) + .unwrap(); + if !bound_first { + txn.set_collection_compaction_bound(input, frontier) + .unwrap(); + } + txn.validate_read_protection().unwrap(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + } + + let mut txn = state.transaction().await.unwrap(); + txn.delete_collection_metadata(BTreeSet::from([input, output])); + txn.validate_read_protection().unwrap(); + assert!( + txn.current_snapshot() + .collection_compaction_bounds + .is_empty() + ); + assert!( + txn.current_snapshot() + .maintained_read_requirements + .is_empty() + ); + drop(txn); + + let mut txn = state.transaction().await.unwrap(); + txn.set_maintained_read_requirement(output, BTreeSet::from([input, ungoverned]), None) + .unwrap(); + txn.set_collection_compaction_bound(input, None).unwrap(); + txn.validate_read_protection().unwrap(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + + let mut txn = state.transaction().await.unwrap(); + txn.set_collection_compaction_bound(input, Some(40.into())) + .unwrap(); + assert!(matches!( + txn.validate_read_protection(), + Err(CatalogError::Durable( + DurableCatalogError::InvalidReadProtection(_) + )) + )); + drop(txn); + + let mut txn = state.transaction().await.unwrap(); + txn.delete_collection_metadata(BTreeSet::from([input])); + txn.validate_read_protection().unwrap(); + assert_eq!(txn.current_snapshot().maintained_read_requirements.len(), 1); + txn.delete_collection_metadata(BTreeSet::from([output])); + txn.validate_read_protection().unwrap(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + let snapshot = state.snapshot().await.unwrap(); + assert!(snapshot.collection_compaction_bounds.is_empty()); + assert!(snapshot.maintained_read_requirements.is_empty()); + Box::new(state).expire().await; +} From 995b9e36034deb9d0a47dc1a2841be5bb3896a8d Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 22:28:13 +0000 Subject: [PATCH 22/36] doc: Choose durable client-scoped read protection Keep valid client protection independent of the components enforcing compaction, while query execution remains ephemeral. Record the volatile alternative and its failure tradeoff without prescribing a component, schema, or lease mechanism. Add independent-client recovery evidence to milestone 3 and retain the protected-MV production path as the immediate integration target. --- .../design/20260903_decoupled_coordination.md | 71 +++++++++++++++---- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index b7b41e0979677..1b1cd46f69402 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -65,7 +65,7 @@ independently of the creating adapter's lifetime. This places maintained read requirements and permission to discard history under the same durable authority. Cluster-side components have an explicit limit to -enforce and recover, without treating a live owner's local accounting as the +enforce and recover, without treating a process's local accounting as the authority to advance beyond it. The cost is ongoing catalog traffic proportional to the number of changing bounds @@ -108,20 +108,38 @@ another's protection. Keep compaction bounds distinct from a dataflow's installation `as_of` and an MV's initial storage visibility boundary unless the implementation establishes -how they fit. The representation and accounting of individual read requirements -remain open. +how they fit. The representation and accounting mechanisms for individual read +requirements remain implementation choices. Propagation to persist critical since handles must respect all valid read requirements. Those handles are the durable backstop, not a substitute for multi-client accounting. Stale owners must not advance compaction or destroy -data based on incomplete local knowledge. Abandoned client holds must be -reclaimable without allowing that client to resume using invalid protection. +data based on incomplete local knowledge. Recovery must establish actual readability and restore valid read requirements before further advancement is authorized. Reconstructed plans must use inputs readable at the protected timestamps, rather than assume equivalent access paths have equivalent history. +### Client read protection + +Client protection is scoped to a client incarnation and the collections and +frontiers it requires. A client's read protection remains valid across restarts +or replacement of the components enforcing compaction. Durable client requirements +participate in compaction accounting alongside maintained requirements. + +Clients may aggregate query and transaction needs locally under established +protection. Ordinary query execution must not require a durable write for each +hold change. Establishing protection and reclaiming it must be coordinated with +compaction so that neither a new requirement nor a stale client can rely on +discarded history. + +Abandoned client protection must be reclaimable without releasing other clients' +requirements. Maintained requirements belong to maintained objects, not their +creating clients. Query execution and transient dataflows remain ephemeral and +require their own readiness and execution protection. This does not introduce +transparent session or query failover. + ### Admission and conversion Admission of maintained read requirements respects committed compaction permission @@ -164,23 +182,33 @@ logical-input protection rather than making optimization decisions authoritative ### Delegated compaction advancement -The catalog could define maintained requirements and retention policies while a -fenced lifecycle owner accounts for client and maintained reads and advances +The catalog could define maintained requirements and retention policies while +fenced components account for client and maintained reads and advance compaction directly. Persist critical handles would provide the durable storage backstop, without publishing advancing bounds to the catalog. This avoids ongoing frontier-publication traffic and its dependence on catalog write availability. Delegation still requires coordination when durable read requirements are -introduced or strengthened. Their admission must be tied to owner-held protection. -Reclaiming abandoned precommit protection must exclude a late commit that relies -on it. Observing an object's absence in a catalog snapshot is not sufficient. -Recovery of valid holds and enforcement against stale owners remain necessary -under either approach. +introduced or strengthened. Their admission must be tied to protection held by +those components. Reclaiming abandoned precommit protection must exclude a late +commit that relies on it. Observing an object's absence in a catalog snapshot is +not sufficient. Recovery of valid holds and enforcement against stale owners +remain necessary under either approach. We choose explicit bounds for the catalog-local permission boundary. Delegation reduces ongoing catalog traffic but shifts coordination into maintained-DDL admission and cleanup. We accept the publication and retention costs of explicit -bounds rather than this owner-backed admission and reclamation protocol. +bounds rather than this delegated admission and reclamation protocol. + +### Volatile client protection + +The components enforcing compaction could account for client requirements only in +memory. This avoids durable client-metadata traffic, but losing that accounting +requires either invalidating clients' protection or reconstructing all valid +requirements before compaction advances. An aggregate frontier alone does not say +which clients still need it. We choose durable client protection to avoid tying +otherwise-live clients' protection to those components' lifetimes, accepting the +metadata and reclamation costs. ## Implementation and verification @@ -238,7 +266,9 @@ Responses, cancellation, query-local dataflows, and disconnect cleanup are isolated between clients. Demonstrate independent clients without requiring concurrent catalog writers or -a multi-adapter deployment. Together, these milestones complete the +a multi-adapter deployment. Cover one client advancing or losing its protection +while another retains an older timestamp, recovery of the components enforcing +compaction, and an expired client returning. Together, these milestones complete the fresh-environment decoupling outcome, subject to the correctness and performance acceptance criteria above. @@ -509,3 +539,16 @@ wire initial bounds and MV logical-input requirements into creation, accounting registration timestamps and actual readability when reusing shards. Recovery must consume the requirements, and durable output progress must advance them and permit bound publication before this becomes the complete protected-MV path. + +### 2026-09-08: Durable client protection agreed with Aljoscha + +Choose client-incarnation-scoped durable protection that survives replacement of +the components enforcing compaction, without durable query execution or session +failover. Schema, grouping, and reclamation mechanics remain implementation choices. +No separate component called a lifecycle owner is prescribed. + +Milestone 1 remains active. Next: complete the protected-MV production path. +Integration cases identified in review include table commit before initialization, +read-only bootstrap racing a drop, and intermediate bound updates removed by a +same-transaction drop. Independent-client protection is part of milestone 3, not +a prerequisite for the first maintained-recovery example. From b47e0bbb462c621cd832eee65323488d7d8d54cc Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 22:37:12 +0000 Subject: [PATCH 23/36] doc: Separate compaction authorization from application Allow any component to apply committed monotonic permission without exclusive ownership. Keep coordination at authorization, read admission, and reclamation boundaries, with collection identity and all valid read protection preserved. Record concurrent and delayed application as milestone evidence without removing fencing from controllers that still rely on private accounting. --- .../design/20260903_decoupled_coordination.md | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 1b1cd46f69402..9a027877f0e82 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -79,6 +79,19 @@ Coalescing or rate-limiting bound advancement may retain extra history, but must not delay protection until after it is needed. Choose cadence and batching from measured catalog load, DDL latency, and retention cost. +### Applying committed permission + +Any component may apply committed compaction permission for the identified +collection lifetime. Applying permission does not require exclusive ownership or +leadership. Concurrent or delayed application must not regress compaction or +bypass other valid read protection. Permission cannot be reused for a different +collection lifetime or an unrelated use of a shared shard. + +Coordination belongs at the boundary that authorizes compaction, admits read +requirements, and reclaims client protection, not at the act of applying +already-committed permission. Proposing bounds need not require a leader either, +provided catalog transactions validate proposals against authoritative requirements. + ### Logical recovery dependencies Maintained recovery protection covers all logical collection inputs, including @@ -113,8 +126,7 @@ requirements remain implementation choices. Propagation to persist critical since handles must respect all valid read requirements. Those handles are the durable backstop, not a substitute for -multi-client accounting. Stale owners must not advance compaction or destroy -data based on incomplete local knowledge. +multi-client accounting. Recovery must establish actual readability and restore valid read requirements before further advancement is authorized. Reconstructed plans must use inputs @@ -183,7 +195,7 @@ logical-input protection rather than making optimization decisions authoritative ### Delegated compaction advancement The catalog could define maintained requirements and retention policies while -fenced components account for client and maintained reads and advance +components account for client and maintained reads and advance compaction directly. Persist critical handles would provide the durable storage backstop, without publishing advancing bounds to the catalog. This avoids ongoing frontier-publication traffic and its dependence on catalog write availability. @@ -192,8 +204,8 @@ Delegation still requires coordination when durable read requirements are introduced or strengthened. Their admission must be tied to protection held by those components. Reclaiming abandoned precommit protection must exclude a late commit that relies on it. Observing an object's absence in a catalog snapshot is -not sufficient. Recovery of valid holds and enforcement against stale owners -remain necessary under either approach. +not sufficient. Both approaches must recover valid holds and prevent compaction +based on incomplete or superseded read requirements. We choose explicit bounds for the catalog-local permission boundary. Delegation reduces ongoing catalog traffic but shifts coordination into maintained-DDL @@ -256,7 +268,8 @@ Creation, changes, deletion, and compaction work across recovery. Losing the adapter does not interrupt maintained work. Demonstrate a production subscriber applying committed changes without -creator-local plans, including same-batch dependencies. +creator-local plans, including same-batch dependencies. Include concurrent and +delayed application of committed compaction permission. #### 3. Independent query execution @@ -552,3 +565,10 @@ Integration cases identified in review include table commit before initializatio read-only bootstrap racing a drop, and intermediate bound updates removed by a same-transaction drop. Independent-client protection is part of milestone 3, not a prerequisite for the first maintained-recovery example. + +### 2026-09-08: Non-exclusive compaction application agreed with Aljoscha + +Applying committed permission does not require a fenced or exclusive executor. +Separate this responsibility from authorization and read-protection accounting. +Milestone 1 remains active. Existing controller fencing cannot simply be removed +while advancement still depends on private hold accounting. From e45c54cc12f494b3f3a1cf6f81f2b4980db02722 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Tue, 8 Sep 2026 23:19:04 +0000 Subject: [PATCH 24/36] adapter: establish catalog-backed MV recovery protection Commit logical MV requirements and collection compaction permission before installation, including fresh builtin collections. Advance requirements from durable output progress and publish compatible bounds in batches, while retaining valid execution and creation holds. Recover using actual protected readability, co-register shared-shard aliases, and preserve leased readability during read-only handover. Retry finite persist since delivery through rate limits and primary changes. Separate DDL conflict tracking from planning-cache freshness so background protection and cadence controls preserve existing SQL behavior. Enable the path at fresh-environment initialization, off by default in production and on in tests. Existing-environment conversion and builtin schema migration remain outside milestone 1. Add a production-path demonstration of optimized-away input protection, actual compaction, historical admission, uncached pending-refresh recovery, and progress-driven history release. Keep catalog consistency comparisons complete by quiescing publication while acquiring their snapshots. Validation: formatting and targeted compilation checks pass. The milestone demonstration passes in draft-PR CI. See the PR for broad check outcomes. --- .../design/20260903_decoupled_coordination.md | 40 + misc/python/materialize/mzcompose/__init__.py | 5 + .../materialize/parallel_workload/action.py | 2 + src/adapter/src/catalog.rs | 28 +- src/adapter/src/catalog/apply.rs | 10 +- src/adapter/src/catalog/open.rs | 67 +- src/adapter/src/catalog/state.rs | 91 ++ src/adapter/src/catalog/transact.rs | 669 +++++++++++++- src/adapter/src/client.rs | 11 +- src/adapter/src/coord.rs | 107 ++- src/adapter/src/coord/catalog_implications.rs | 11 +- src/adapter/src/coord/command_handler.rs | 17 +- src/adapter/src/coord/ddl.rs | 7 +- src/adapter/src/coord/read_protection.rs | 409 +++++++++ src/adapter/src/coord/sequencer/inner.rs | 5 +- .../inner/create_materialized_view.rs | 54 +- src/adapter/src/coord/storage_bootstrap.rs | 214 +++++ src/adapter/src/peek_client.rs | 2 +- src/adapter/src/session.rs | 8 +- src/catalog/tests/debug.rs | 4 + src/catalog/tests/open.rs | 4 + .../tests/snapshots/debug__opened_trace.snap | 6 + .../snapshots/open__initial_snapshot.snap | 2 + src/compute-client/src/as_of_selection.rs | 4 + src/environmentd/src/test_util.rs | 11 +- src/environmentd/tests/server.rs | 6 + src/sql/src/session/vars.rs | 5 + src/sql/src/session/vars/definitions.rs | 18 + src/sqllogictest/src/runner.rs | 4 + src/storage-client/src/storage_collections.rs | 816 +++++++++++++++--- src/testdrive/src/action/consistency.rs | 123 ++- test/restart/mzcompose.py | 508 ++++++++++- .../audit-log.td | 9 +- test/testdrive/audit-log.td | 9 +- 34 files changed, 3059 insertions(+), 227 deletions(-) create mode 100644 src/adapter/src/coord/read_protection.rs create mode 100644 src/adapter/src/coord/storage_bootstrap.rs diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 9a027877f0e82..88acd136342ea 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -260,6 +260,10 @@ Evidence includes actual compaction and an input eliminated by optimization. Measure publication and retained-history costs as the real path becomes available. This milestone can use the current single-writer arrangement. +Include fresh builtin initialization and same-version recovery, including MVs +reading system-catalog collections. Builtin schema migration belongs to milestone +2. Milestone 1 does not establish version-upgrade support for protected environments. + #### 2. Catalog-driven maintained lifecycle Cluster-side lifecycle components establish and follow maintained state from the @@ -572,3 +576,39 @@ Applying committed permission does not require a fenced or exclusive executor. Separate this responsibility from authorization and read-protection accounting. Milestone 1 remains active. Existing controller fencing cannot simply be removed while advancement still depends on private hold accounting. + +### 2026-09-08: Production integration scope question + +Table initialization must respect committed birth permission independently of +later transaction-WAL registration. Secondary MV visibility must not advance the +shared shard's physical since. Source exports and sinks also inherit storage +dependencies, so zero is not a generally valid initial bound. + +The protected-MV path reaches builtin initialization and the catalog's own shard. +Pending question for Aljoscha: include builtin schema evolution in milestone 1, +or leave it with milestone 2's lifecycle changes? The implementer's recommendation +is to include system-catalog inputs rather than silently use legacy protection. +Record production is in progress. Progress publication and the production +compaction/restart demonstration remain unwired. + +### 2026-09-08: Builtin milestone scope agreed with Aljoscha + +Include fresh builtin initialization and same-version recovery in milestone 1. +Defer builtin schema migration and protected-environment version-upgrade support +to milestone 2, separately from conversion of unprotected environments. Builtin +shard replacement reuses GlobalIds while discarding contents, so its interaction +with protected dependent history needs an explicit lifetime/recovery treatment. + +### 2026-09-09: Catalog-backed MV recovery path + +Milestone 1 is complete for fresh environments and same-version recovery. +Creation commits logical requirements with collection permission, including builtin +inputs. Publication follows durable output progress and respects early creation +holds. Finite persist downgrades retain pending delivery through rate limits and +primary handoff. Read-only bootstrap relies on leased readability, not stale +snapshot permission. + +Publication cadence is configurable. Planning-cache freshness and DDL conflict +tracking have distinct revisions, so cadence changes do not abort open DDL. +Next: milestone 2's catalog-driven maintained lifecycle and builtin schema +migration. MV compute installation still uses the sequencer closure. diff --git a/misc/python/materialize/mzcompose/__init__.py b/misc/python/materialize/mzcompose/__init__.py index a2e156a9e627b..1d105d04eec18 100644 --- a/misc/python/materialize/mzcompose/__init__.py +++ b/misc/python/materialize/mzcompose/__init__.py @@ -181,6 +181,9 @@ def get_minimal_system_parameters( if version < MzVersion.parse_mz("v26.41.0-dev"): config["enable_zero_downtime_cluster_reconfiguration"] = "true" + if version >= MzVersion.parse_mz("v26.41.0-dev"): + config["enable_catalog_read_protection"] = "true" + return config @@ -683,6 +686,8 @@ def get_default_system_parameters( "memory_limiter_usage_bias", "memory_limiter_burst_factor", "catalog_info_metrics_reconcile_interval", + # Zero pauses publication, so vary this only in targeted consistency checks. + "catalog_read_protection_publish_interval", "compute_server_maintenance_interval", "compute_dataflow_max_inflight_bytes_cc", "compute_flat_map_fuel", diff --git a/misc/python/materialize/parallel_workload/action.py b/misc/python/materialize/parallel_workload/action.py index c5678150d34c6..f0dcd3a1ed8d2 100644 --- a/misc/python/materialize/parallel_workload/action.py +++ b/misc/python/materialize/parallel_workload/action.py @@ -3233,6 +3233,8 @@ def __init__( # behavior, you should add it. Feature flags which turn on/off # externally visible features should not be flipped. self.uninteresting_flags: list[str] = [ + # Latched at fresh catalog initialization, not changed by ALTER SYSTEM. + "enable_catalog_read_protection", # Read once at environmentd startup, so an ALTER SYSTEM SET only # takes effect after a restart. Flipping it here would be a no-op # for the running process. diff --git a/src/adapter/src/catalog.rs b/src/adapter/src/catalog.rs index a53d8db4f58e7..04fb807a70d80 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -145,17 +145,18 @@ pub struct Catalog { expr_cache_handle: Option, storage: Arc>>, transient_revision: u64, + ddl_revision: u64, /// The latest `transient_revision`, shared by all clones of this catalog. /// While `transient_revision` is this clone's own revision, frozen when /// the snapshot was taken, this field always tracks the latest revision /// across all clones. Comparing the two lets a snapshot holder detect - /// from off-thread whether its snapshot is still current, via + /// from off-thread whether its planning-visible state is still current, via /// [`Catalog::transient_revision_is_current`], without a Coordinator /// round-trip (see `PeekClient::catalog_snapshot`). /// /// The store happens in `transact`, before the transaction's effects can /// be observed anywhere (responses, notices, builtin table writes), so a - /// session that has observed any evidence of a catalog change is + /// session that has observed any evidence of a planning-visible change is /// guaranteed to see the corresponding bump. shared_transient_revision: Arc, } @@ -185,6 +186,7 @@ impl Clone for Catalog { expr_cache_handle: self.expr_cache_handle.clone(), storage: Arc::clone(&self.storage), transient_revision: self.transient_revision, + ddl_revision: self.ddl_revision, shared_transient_revision: Arc::clone(&self.shared_transient_revision), } } @@ -356,15 +358,24 @@ pub struct DebugAwsContext { impl Catalog { /// Returns the catalog's transient revision, which starts at 1 and is - /// incremented on every change. This is not persisted to disk, and will - /// restart on every load. + /// incremented on every planning-visible change, including system configuration. + /// Audit logs, read protection, and shard finalization bookkeeping do not affect it. + /// It is not persisted to disk and restarts on every load. pub fn transient_revision(&self) -> u64 { self.transient_revision } + /// Returns the revision used to detect conflicts with open DDL transactions. + /// It starts at 1 on every load and advances on the same changes as + /// [`Self::transient_revision`], except changes to + /// `catalog_read_protection_publish_interval`. + pub fn ddl_revision(&self) -> u64 { + self.ddl_revision + } + /// Reports whether this catalog's transient revision is still the latest, - /// i.e., whether no catalog transaction has committed since this snapshot - /// was taken. Can be called on a snapshot from off-thread, without a + /// i.e., whether its planning-visible state is equivalent to the current + /// catalog's. Can be called on a snapshot from off-thread, without a /// Coordinator round-trip. See the field documentation on /// `shared_transient_revision`. pub fn transient_revision_is_current(&self) -> bool { @@ -2642,6 +2653,7 @@ mod tests { .await .expect("unable to open debug catalog"); assert_eq!(catalog.transient_revision(), 1); + assert_eq!(catalog.ddl_revision(), 1); assert!(catalog.transient_revision_is_current()); let snapshot = catalog.clone(); let commit_ts = catalog.current_upper().await; @@ -2658,10 +2670,12 @@ mod tests { .await .expect("failed to transact"); assert_eq!(catalog.transient_revision(), 2); + assert_eq!(catalog.ddl_revision(), 2); assert!(catalog.transient_revision_is_current()); // The pre-transaction snapshot detects its own staleness through // the shared latest revision. assert!(!snapshot.transient_revision_is_current()); + assert_eq!(snapshot.ddl_revision(), 1); catalog.expire().await; } { @@ -2669,8 +2683,8 @@ mod tests { Catalog::open_debug_catalog(persist_client, organization_id, &bootstrap_args) .await .expect("unable to open debug catalog"); - // Re-opening the same catalog resets the transient_revision to 1. assert_eq!(catalog.transient_revision(), 1); + assert_eq!(catalog.ddl_revision(), 1); catalog.expire().await; } } diff --git a/src/adapter/src/catalog/apply.rs b/src/adapter/src/catalog/apply.rs index d919404588159..2262a1dca9777 100644 --- a/src/adapter/src/catalog/apply.rs +++ b/src/adapter/src/catalog/apply.rs @@ -392,7 +392,15 @@ impl CatalogState { diff, ); } - StateUpdateKind::MaintainedReadRequirement(_) => {} + StateUpdateKind::MaintainedReadRequirement(requirement) => { + let id = requirement.id; + apply_inverted_lookup( + Arc::make_mut(&mut self.maintained_read_requirements), + &id, + requirement, + diff, + ); + } StateUpdateKind::UnfinalizedShard(unfinalized_shard) => { self.apply_unfinalized_shard_update(unfinalized_shard, diff, retractions); } diff --git a/src/adapter/src/catalog/open.rs b/src/adapter/src/catalog/open.rs index 41204f71779ce..33bc0a3e34c9e 100644 --- a/src/adapter/src/catalog/open.rs +++ b/src/adapter/src/catalog/open.rs @@ -43,10 +43,11 @@ use mz_catalog::expr_cache::{ }; use mz_catalog::memory::error::{Error, ErrorKind}; use mz_catalog::memory::objects::{ - CommentsMap, DefaultPrivileges, RoleAuth, StateUpdate, StateUpdateKind, + CatalogItem, CommentsMap, DefaultPrivileges, RoleAuth, StateUpdate, StateUpdateKind, }; use mz_controller::clusters::ReplicaLogging; use mz_controller_types::ClusterId; +use mz_expr::CollectionPlan; use mz_ore::cast::usize_to_u64; use mz_ore::collections::HashSet; use mz_ore::now::{SYSTEM_TIME, to_datetime}; @@ -171,6 +172,8 @@ impl Catalog { comments: Arc::new(CommentsMap::default()), source_references: imbl::OrdMap::new(), storage_metadata: Arc::new(StorageMetadata::default()), + maintained_read_requirements: Default::default(), + catalog_read_protection_enabled: false, temporary_namespaces: Default::default(), mock_authentication_nonce: Default::default(), config: mz_sql::catalog::CatalogConfig { @@ -452,6 +455,19 @@ impl Catalog { let last_seen_version = get_migration_version(&txn); + // Latch the mode at environment birth. Changing startup defaults must not + // adopt existing collections whose required history may already be gone. + let protection_key = "catalog_read_protection_enabled".to_string(); + state.catalog_read_protection_enabled = match txn.get_config(protection_key.clone()) { + Some(value) => value != 0, + None => { + let enabled = last_seen_version.is_none() + && state.system_config().enable_catalog_read_protection(); + txn.set_config(protection_key, Some(u64::from(enabled)))?; + enabled + } + }; + let mz_authentication_mock_nonce = txn.get_authentication_mock_nonce().ok_or_else(|| { Error::new(ErrorKind::SettingError("authentication nonce".to_string())) @@ -604,6 +620,7 @@ impl Catalog { state, expr_cache_handle, transient_revision: 1, + ddl_revision: 1, shared_transient_revision: Arc::new(AtomicU64::new(1)), storage: Arc::new(tokio::sync::Mutex::new(storage)), }; @@ -680,6 +697,7 @@ impl Catalog { let mut storage = self.storage().await; let shard_id = storage.shard_id(); let mut txn = storage.transaction().await?; + let existing_collections = txn.get_collection_metadata(); // Ensure the storage controller knows about the catalog shard and associates it with the // `MZ_CATALOG_RAW` builtin source. @@ -698,18 +716,55 @@ impl Catalog { .await .map_err(mz_catalog::durable::DurableCatalogError::from)?; + if state.catalog_read_protection_enabled() { + // The catalog's controller critical reader is established by durable + // catalog open and holds its since until storage registration. Thus the + // catalog shard, as well as freshly allocated shards, is readable here. + let birth = txn.upper(); + let born: BTreeSet<_> = txn + .get_collection_metadata() + .into_keys() + .filter(|id| !existing_collections.contains_key(id)) + .collect(); + for id in &born { + txn.set_collection_compaction_bound(*id, Some(birth))?; + } + for (_, entry) in state.get_entries() { + if let CatalogItem::MaterializedView(mv) = entry.item() + && born.contains(&mv.global_id_writes()) + { + let inputs = state.logical_collection_inputs( + mv.resolved_ids + .collections() + .copied() + .chain(mv.raw_expr.depends_on()) + .filter(|id| state.get_entry_by_global_id(id).is_relation()), + ); + txn.set_maintained_read_requirement( + mv.global_id_writes(), + inputs, + Some(birth), + )?; + } + } + } + let updates = txn.get_and_commit_op_updates(); - let (builtin_updates, catalog_updates) = state + assert!(updates.iter().all(|update| matches!( + update.kind, + StateUpdateKind::StorageCollectionMetadata(_) + | StateUpdateKind::CollectionCompactionBound(_) + | StateUpdateKind::MaintainedReadRequirement(_) + | StateUpdateKind::UnfinalizedShard(_) + ))); + // Bootstrap consumes the committed bounds when registering collections. + let (builtin_updates, _catalog_updates) = state .apply_updates(updates, &mut LocalExpressionCache::Closed) .await; assert!( builtin_updates.is_empty(), "storage is not allowed to generate catalog changes that would cause changes to builtin tables" ); - assert!( - catalog_updates.is_empty(), - "storage is not allowed to generate catalog changes that would change the catalog or controller state" - ); let commit_ts = txn.upper(); txn.commit(commit_ts).await?; drop(storage); diff --git a/src/adapter/src/catalog/state.rs b/src/adapter/src/catalog/state.rs index ab96b18a97b9a..2b291db975c91 100644 --- a/src/adapter/src/catalog/state.rs +++ b/src/adapter/src/catalog/state.rs @@ -27,6 +27,7 @@ use mz_catalog::builtin::{ BUILTINS, Builtin, BuiltinCluster, BuiltinLog, BuiltinSource, BuiltinTable, BuiltinType, }; use mz_catalog::config::{AwsPrincipalContext, ClusterReplicaSizeMap}; +use mz_catalog::durable::objects::MaintainedReadRequirement; use mz_catalog::expr_cache::{LocalExpressions, latest_item_version}; use mz_catalog::memory::error::{Error, ErrorKind}; use mz_catalog::memory::objects::{ @@ -155,6 +156,9 @@ pub struct CatalogState { #[serde(serialize_with = "mz_ore::serde::map_key_to_string")] pub(super) source_references: imbl::OrdMap, pub(super) storage_metadata: Arc, + #[serde(serialize_with = "serialize_maintained_read_requirements")] + pub(super) maintained_read_requirements: Arc>, + pub(super) catalog_read_protection_enabled: bool, pub(super) mock_authentication_nonce: Option, // Mutable state not derived from the durable catalog. Populated @@ -479,6 +483,8 @@ impl CatalogState { comments: Arc::new(CommentsMap::default()), source_references: Default::default(), storage_metadata: Arc::new(StorageMetadata::default()), + maintained_read_requirements: Default::default(), + catalog_read_protection_enabled: false, license_key: ValidatedLicenseKey::for_tests(), mock_authentication_nonce: Default::default(), } @@ -2895,6 +2901,16 @@ impl CatalogState { &self.storage_metadata } + /// Returns the durable maintained read requirements, keyed by output ID. + pub fn maintained_read_requirements(&self) -> &BTreeMap { + &self.maintained_read_requirements + } + + /// Whether this environment was initialized with catalog-backed recovery protection. + pub fn catalog_read_protection_enabled(&self) -> bool { + self.catalog_read_protection_enabled + } + /// For the Sources ids in `ids`, return their compaction windows. pub fn source_compaction_windows( &self, @@ -3107,10 +3123,85 @@ impl Catalog { } } +fn serialize_maintained_read_requirements( + requirements: &BTreeMap, + serializer: S, +) -> Result { + let entries = requirements + .iter() + .map(|(id, requirement)| (id.to_string(), (&requirement.inputs, requirement.frontier))); + serializer.collect_map(entries) +} + #[cfg(test)] mod tests { use super::*; + #[mz_ore::test(tokio::test)] + async fn maintained_read_requirements_apply_snapshot_update_drop() { + use mz_catalog::memory::objects::{StateDiff, StateUpdate, StateUpdateKind}; + use mz_repr::Timestamp; + + let mut state = CatalogState::empty_test(); + let initial = MaintainedReadRequirement { + id: GlobalId::User(1), + inputs: BTreeSet::from([GlobalId::User(2), GlobalId::User(3)]), + frontier: Some(Timestamp::from(10)), + }; + let intermediate = MaintainedReadRequirement { + frontier: Some(Timestamp::from(20)), + ..initial.clone() + }; + let completed = MaintainedReadRequirement { + frontier: None, + ..initial.clone() + }; + let update = |requirement, ts, diff| StateUpdate { + kind: StateUpdateKind::MaintainedReadRequirement(requirement), + ts: Timestamp::from(ts), + diff, + }; + let _ = state + .apply_updates( + vec![update(initial.clone(), 1, StateDiff::Addition)], + &mut LocalExpressionCache::Closed, + ) + .await; + assert_eq!( + state.maintained_read_requirements(), + &BTreeMap::from([(initial.id, initial.clone())]) + ); + let snapshot = state.clone(); + + let _ = state + .apply_updates( + vec![ + update(completed.clone(), 2, StateDiff::Addition), + update(intermediate.clone(), 2, StateDiff::Retraction), + update(initial.clone(), 2, StateDiff::Retraction), + update(intermediate, 2, StateDiff::Addition), + ], + &mut LocalExpressionCache::Closed, + ) + .await; + assert_eq!( + state.maintained_read_requirements(), + &BTreeMap::from([(completed.id, completed.clone())]) + ); + assert_eq!( + snapshot.maintained_read_requirements(), + &BTreeMap::from([(initial.id, initial)]) + ); + + let _ = state + .apply_updates( + vec![update(completed, 3, StateDiff::Retraction)], + &mut LocalExpressionCache::Closed, + ) + .await; + assert!(state.maintained_read_requirements().is_empty()); + } + async fn create_collection_input_item( catalog: &mut Catalog, name: &str, diff --git a/src/adapter/src/catalog/transact.rs b/src/adapter/src/catalog/transact.rs index 2d0dc92d45c93..c574dc09c94cf 100644 --- a/src/adapter/src/catalog/transact.rs +++ b/src/adapter/src/catalog/transact.rs @@ -40,6 +40,7 @@ use mz_catalog::memory::error::{AmbiguousRename, Error, ErrorKind}; use mz_catalog::memory::objects::{ CatalogEntry, CatalogItem, ClusterConfig, ClusterVariant, DataSourceDesc, DefaultPrivileges, ReconfigurationState, ReconfigurationStatus, ReconfigurationTarget, SourceReferences, + StateUpdateKind, TableDataSource, }; use mz_cluster_controller::ctx::RefreshWindowDecision; use mz_controller::clusters::{ManagedReplicaLocation, ReplicaConfig, ReplicaLocation}; @@ -67,11 +68,12 @@ use mz_sql::names::{ use mz_sql::plan::{NetworkPolicyRule, PlanError}; use mz_sql::session::user::{MZ_SUPPORT_ROLE_ID, MZ_SYSTEM_ROLE_ID}; use mz_sql::session::vars::OwnedVarInput; -use mz_sql::session::vars::{Value as VarValue, VarInput}; +use mz_sql::session::vars::{self, Value as VarValue, VarInput}; use mz_sql::{DEFAULT_SCHEMA, rbac}; use mz_sql_parser::ast::{QualifiedReplica, Value}; use mz_storage_client::controller::StorageTxn; use mz_storage_client::storage_collections::StorageCollections; +use mz_storage_types::sources::envelope::SourceEnvelope; use serde::{Deserialize, Serialize}; use tracing::{info, trace}; use uuid::Uuid; @@ -445,6 +447,12 @@ pub struct TransactionResult { pub audit_events: Vec, } +struct TransactInnerResult { + state: CatalogState, + planning_changed: bool, + ddl_changed: bool, +} + #[derive(Debug, Clone, Copy)] enum TransactInnerMode { /// Prepare storage state and return a commit-ready transaction state. @@ -779,16 +787,24 @@ impl Catalog { // Dropping here keeps the mutable borrow on self, preventing us accidentally // mutating anything until after f is executed. drop(storage); - if let Some(new_state) = new_state { - self.transient_revision += 1; - // Publish the new revision before returning. Everything that can - // reveal this transaction's effects (responses, notices, builtin - // table writes) happens after `transact` returns, so any session - // that has observed such evidence is guaranteed to see this bump - // and refresh its cached catalog snapshot. - self.shared_transient_revision - .store(self.transient_revision, atomic::Ordering::SeqCst); - self.state = new_state; + if let Some(TransactInnerResult { + state, + planning_changed, + ddl_changed, + }) = new_state + { + if planning_changed { + self.transient_revision += 1; + // Publish before any planning-visible effects (responses, + // notices, builtin table writes) can be observed, so sessions + // that observe them must refresh their cached catalog snapshot. + self.shared_transient_revision + .store(self.transient_revision, atomic::Ordering::SeqCst); + } + if ddl_changed { + self.ddl_revision += 1; + } + self.state = state; } Ok(TransactionResult { @@ -867,8 +883,9 @@ impl Catalog { // Transaction is NOT committed — drop it. drop(storage); - // transact_inner returns Some(state) when ops produced changes. - let state = new_state.unwrap_or_else(|| base_state.clone()); + let state = new_state + .map(|result| result.state) + .unwrap_or_else(|| base_state.clone()); Ok((state, new_snapshot)) } @@ -923,6 +940,7 @@ impl Catalog { /// Performs the transaction described by `ops` and returns the new state of the catalog, if /// it has changed. If `ops` don't result in a change in the state this method returns `None`. + /// The result indicates which catalog revisions must advance on commit. /// /// `mode` controls whether storage prepare-state side effects are allowed. /// In `DryRun` mode, this method may update the in-memory state returned to @@ -941,7 +959,7 @@ impl Catalog { audit_events: &mut Vec, tx: &mut Transaction<'_>, state: &CatalogState, - ) -> Result, AdapterError> { + ) -> Result, AdapterError> { // We come up with new catalog state, builtin state updates, and parsed // catalog updates (for deriving catalog implications) in two phases: // @@ -1016,19 +1034,6 @@ impl Catalog { updates.append(&mut op_updates); } - if !updates.is_empty() { - let mut local_expr_cache = LocalExpressionCache::new(cached_exprs.clone()); - let (op_builtin_table_updates, op_catalog_updates) = state - .to_mut() - .apply_updates(updates.clone(), &mut local_expr_cache) - .await; - let op_builtin_table_updates = state - .to_mut() - .resolve_builtin_table_updates(op_builtin_table_updates); - builtin_table_updates.extend(op_builtin_table_updates); - parsed_catalog_updates.extend(op_catalog_updates); - } - match mode { TransactInnerMode::Commit => { // `storage_collections` can be `None` in tests. @@ -1064,7 +1069,34 @@ impl Catalog { // Batch extraction repeats this check for other durable callers. tx.validate_read_protection()?; - let updates = tx.get_and_commit_op_updates(); + // Storage preparation can retract permission staged by an earlier op + // when it deletes metadata. Derive implications from the consolidated + // final batch, never from such intermediate permission. + updates.extend(tx.get_and_commit_op_updates()); + // Classify raw updates, not parsed implications, which omit + // planning-visible changes. + let mut planning_changed = false; + let mut ddl_changed = false; + for update in &updates { + match &update.kind { + StateUpdateKind::CollectionCompactionBound(_) + | StateUpdateKind::MaintainedReadRequirement(_) + | StateUpdateKind::UnfinalizedShard(_) + | StateUpdateKind::AuditLog(_) => {} + StateUpdateKind::SystemConfiguration(config) + if config.name + == vars::CATALOG_READ_PROTECTION_PUBLISH_INTERVAL.name.as_str() => + { + // current_setting is folded to a literal during optimization, + // so changing publication cadence must invalidate cached plans. + planning_changed = true; + } + _ => { + planning_changed = true; + ddl_changed = true; + } + } + } if !updates.is_empty() { let mut local_expr_cache = LocalExpressionCache::new(cached_exprs.clone()); let (op_builtin_table_updates, op_catalog_updates) = state @@ -1079,7 +1111,11 @@ impl Catalog { } match state { - Cow::Owned(state) => Ok(Some(state)), + Cow::Owned(state) => Ok(Some(TransactInnerResult { + state, + planning_changed, + ddl_changed, + })), Cow::Borrowed(_) => Ok(None), } } @@ -1302,6 +1338,11 @@ impl Catalog { let shard_id = state .storage_metadata() .get_collection_shard(new_entry.latest_global_id())?; + if state.catalog_read_protection_enabled() { + let bound = + &state.storage_metadata().compaction_bounds[&new_entry.latest_global_id()]; + tx.set_collection_compaction_bound(new_global_id, bound.as_option().copied())?; + } // TODO(alter_table): Support adding columns to sources. let CatalogItem::Table(table) = &mut new_entry.item else { @@ -1351,6 +1392,17 @@ impl Catalog { )); }; + if state.catalog_read_protection_enabled() { + let old_writer = mv.global_id_writes(); + let requirement = &state.maintained_read_requirements()[&old_writer]; + // Retained collection aliases still serve readers, but this writer + // no longer needs its inputs for recovery. + tx.set_maintained_read_requirement( + old_writer, + requirement.inputs.clone(), + None, + )?; + } mv.apply_replacement(replacement_mv.clone()); tx.remove_item(replacement_id)?; @@ -1693,9 +1745,24 @@ impl Catalog { CatalogItem::Table(table) => { let gids: Vec<_> = table.global_ids().collect(); assert_eq!(gids.len(), 1); + if state.catalog_read_protection_enabled() { + let bound = match &table.data_source { + TableDataSource::TableWrites { .. } => Some(oracle_write_ts), + TableDataSource::DataSource { desc, .. } => { + source_initial_compaction_bound(state, desc)? + } + }; + tx.set_collection_compaction_bound(gids[0], bound)?; + } storage_collections_to_create.extend(gids); } CatalogItem::Source(source) => { + if state.catalog_read_protection_enabled() { + tx.set_collection_compaction_bound( + source.global_id(), + source_initial_compaction_bound(state, &source.data_source)?, + )?; + } storage_collections_to_create.insert(source.global_id()); } CatalogItem::MaterializedView(mv) => { @@ -1704,12 +1771,38 @@ impl Catalog { let target_gid = state.get_entry(&target_id).latest_global_id(); let shard_id = state.storage_metadata().get_collection_shard(target_gid)?; + if state.catalog_read_protection_enabled() { + let metadata = state.storage_metadata(); + let bound = &metadata.compaction_bounds[&target_gid]; + tx.set_collection_compaction_bound( + mv_gid, + bound.as_option().copied(), + )?; + } storage_collections_to_register.insert(mv_gid, shard_id); } else { + if state.catalog_read_protection_enabled() { + let initial_as_of = mv.initial_as_of.as_ref().ok_or_else(|| { + AdapterError::internal( + "create materialized view", + "missing initial storage frontier", + ) + })?; + tx.set_collection_compaction_bound( + mv_gid, + initial_as_of.as_option().copied(), + )?; + } storage_collections_to_create.insert(mv_gid); } } CatalogItem::Sink(sink) => { + if state.catalog_read_protection_enabled() { + tx.set_collection_compaction_bound( + sink.global_id(), + dependency_compaction_bound(state, sink.from)?, + )?; + } storage_collections_to_create.insert(sink.global_id()); } CatalogItem::Log(_) @@ -3258,6 +3351,64 @@ impl Catalog { } } +/// Returns birth permission compatible with a source's storage initialization dependency. +fn source_initial_compaction_bound( + state: &CatalogState, + desc: &DataSourceDesc, +) -> Result, AdapterError> { + let dependency = match desc { + DataSourceDesc::IngestionExport { + ingestion_id, + data_config, + .. + } => { + if matches!(data_config.envelope, SourceEnvelope::CdcV2) { + None + } else { + Some(state.get_entry(ingestion_id).progress_id().ok_or_else(|| { + AdapterError::internal( + "source birth permission", + "ingestion export must refer to an ingestion with a remap collection", + ) + })?) + } + } + DataSourceDesc::OldSyntaxIngestion { + progress_subsource, .. + } => Some(*progress_subsource), + DataSourceDesc::Ingestion { .. } + | DataSourceDesc::Introspection(_) + | DataSourceDesc::Progress + | DataSourceDesc::Webhook { .. } + | DataSourceDesc::Catalog => None, + }; + match dependency { + Some(id) => dependency_compaction_bound(state, state.get_entry(&id).latest_global_id()), + None => Ok(Some(mz_repr::Timestamp::MIN)), + } +} + +/// Returns the committed permission of a governed storage dependency. +fn dependency_compaction_bound( + state: &CatalogState, + id: GlobalId, +) -> Result, AdapterError> { + // Storage initializes a dependent at least as far as its dependency's since. + // Governance proves since <= bound without making physical readability the + // authority. The preliminary state includes permissions from preceding ops. + state + .storage_metadata() + .compaction_bounds + .get(&id) + .map(|bound| bound.as_option().copied()) + .ok_or_else(|| { + AdapterError::internal( + "storage birth permission", + format!("missing compaction permission for dependency {id}"), + ) + }) +} + /// Resolves the session UUID that durably owns a temporary item being /// created. The durable owner is the session that created the item. /// @@ -3583,6 +3734,7 @@ mod tests { use std::sync::{Arc, Mutex}; use mz_catalog::SYSTEM_CONN_ID; + use mz_catalog::memory::error::{Error, ErrorKind}; use mz_catalog::memory::objects::{CatalogItem, Table, TableDataSource}; use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem, PrivilegeMap}; use mz_repr::role_id::RoleId; @@ -3593,11 +3745,286 @@ mod tests { ItemQualifiers, QualifiedItemName, ResolvedDatabaseSpecifier, ResolvedIds, }; use mz_sql::session::user::MZ_SYSTEM_ROLE_ID; - use mz_sql::session::vars::{MAX_CONNECTIONS, OwnedVarInput, SystemVars}; + use mz_sql::session::vars::{self, MAX_CONNECTIONS, OwnedVarInput, SystemVars}; + use crate::AdapterError; use crate::catalog::{Catalog, Op}; use crate::session::DEFAULT_DATABASE_NAME; + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] + async fn test_storage_birth_inherits_dependency_permission() { + use std::collections::BTreeMap; + + use mz_proto::RustType; + use mz_storage_types::sources::SourceExportStatementDetails; + use mz_storage_types::sources::load_generator::LoadGeneratorOutput; + use prost::Message; + use timely::progress::Antichain; + + use crate::catalog::state::LocalExpressionCache; + + Catalog::with_debug(|catalog| async move { + let database = catalog + .resolve_database(DEFAULT_DATABASE_NAME) + .expect("default database exists"); + let database_spec = ResolvedDatabaseSpecifier::Id(database.id()); + let schema = catalog + .resolve_schema_in_database(&database_spec, DEFAULT_SCHEMA, &SYSTEM_CONN_ID) + .expect("default schema exists"); + let qualifiers = ItemQualifiers { + database_spec, + schema_spec: schema.id.clone(), + }; + let prefix = format!("{}.{}", database.name, schema.name.schema); + let details = hex::encode( + SourceExportStatementDetails::LoadGenerator { + output: LoadGeneratorOutput::Default, + } + .into_proto() + .encode_to_vec(), + ); + let oracle_write_ts = catalog.current_upper().await; + let permission = oracle_write_ts.step_forward(); + let mut base = catalog.state().clone(); + base.catalog_read_protection_enabled = true; + let mut state = base.clone(); + let mut ops = Vec::new(); + for (name, sql) in [ + ( + "birth_source", + format!("CREATE SOURCE {prefix}.birth_source IN CLUSTER quickstart FROM LOAD GENERATOR COUNTER"), + ), + ( + "birth_connection", + format!("CREATE CONNECTION {prefix}.birth_connection TO KAFKA (BROKER 'localhost:9092', SECURITY PROTOCOL PLAINTEXT)"), + ), + ( + "birth_table", + format!("CREATE TABLE {prefix}.birth_table FROM SOURCE {prefix}.birth_source (REFERENCE counter) WITH (DETAILS '{details}')"), + ), + ( + "birth_export", + format!("CREATE SUBSOURCE {prefix}.birth_export (counter bigint) OF SOURCE {prefix}.birth_source WITH (EXTERNAL REFERENCE counter, DETAILS '{details}')"), + ), + ( + "birth_sink", + format!("CREATE SINK {prefix}.birth_sink IN CLUSTER quickstart FROM {prefix}.birth_table INTO KAFKA CONNECTION {prefix}.birth_connection (TOPIC 'birth-permission') FORMAT JSON ENVELOPE DEBEZIUM"), + ), + ] { + let (id, global_id) = catalog + .allocate_user_id_for_test() + .await + .expect("allocate collection identity"); + let item = state + .with_enable_for_item_parsing(|state| state.parse_item( + global_id, + &sql, + &BTreeMap::new(), + None, + false, + None, + &mut LocalExpressionCache::Closed, + None, + )) + .unwrap_or_else(|err| panic!("parse {name}: {err}")); + ops.push(Op::CreateItem { + id, + name: QualifiedItemName { + qualifiers: qualifiers.clone(), + item: name.to_string(), + }, + item, + owner_id: MZ_SYSTEM_ROLE_ID, + }); + // Replay the whole batch to exercise dependencies that only exist + // in preliminary state, without any installed storage collections. + (state, _) = catalog + .transact_incremental_dry_run(&base, ops.clone(), None, None, oracle_write_ts) + .await + .unwrap_or_else(|err| panic!("create {name}: {err}")); + if name == "birth_source" { + assert_eq!( + state.storage_metadata().compaction_bounds[&global_id], + Antichain::from_elem(mz_repr::Timestamp::MIN), + ); + ops.push(Op::SetCollectionCompactionBound { + id: global_id, + frontier: Some(permission), + }); + } else if name != "birth_connection" { + assert_eq!( + state.storage_metadata().compaction_bounds[&global_id], + Antichain::from_elem(permission), + "{name} must inherit dependency permission, not MIN or oracle time", + ); + } + } + }) + .await; + } + + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] + async fn test_ddl_replay_inherits_fresh_dependency_permission() { + use std::collections::BTreeMap; + + use mz_persist_client::ShardId; + use mz_proto::RustType; + use mz_repr::{GlobalId, Timestamp}; + use mz_storage_client::controller::StorageTxn; + use mz_storage_types::sources::SourceExportStatementDetails; + use mz_storage_types::sources::load_generator::LoadGeneratorOutput; + use prost::Message; + use timely::progress::Antichain; + + use crate::catalog::state::LocalExpressionCache; + + // This catalog harness has no storage controller. Seed metadata and its + // initial permission in place of prepare_state's shard allocation. + async fn seed_collection(catalog: &mut Catalog, id: GlobalId) { + let updates = { + let mut storage = catalog.storage().await; + let mut tx = storage + .transaction() + .await + .expect("collection setup transaction should open"); + tx.insert_collection_metadata(BTreeMap::from([(id, ShardId::new())])) + .expect("collection metadata should be inserted"); + tx.set_collection_compaction_bound(id, Some(Timestamp::MIN)) + .expect("initial compaction bound should be accepted"); + let updates = tx.get_and_commit_op_updates(); + let commit_ts = tx.upper(); + tx.commit(commit_ts) + .await + .expect("collection setup should commit"); + updates + }; + let _ = catalog + .state + .apply_updates(updates, &mut LocalExpressionCache::Closed) + .await; + } + + Catalog::with_debug(|mut catalog| async move { + catalog.state.catalog_read_protection_enabled = true; + let database = catalog + .resolve_database(DEFAULT_DATABASE_NAME) + .expect("default database exists"); + let database_spec = ResolvedDatabaseSpecifier::Id(database.id()); + let schema = catalog + .resolve_schema_in_database(&database_spec, DEFAULT_SCHEMA, &SYSTEM_CONN_ID) + .expect("default schema exists"); + let qualifiers = ItemQualifiers { + database_spec, + schema_spec: schema.id.clone(), + }; + let prefix = format!("{}.{}", database.name, schema.name.schema); + let details = hex::encode( + SourceExportStatementDetails::LoadGenerator { + output: LoadGeneratorOutput::Default, + } + .into_proto() + .encode_to_vec(), + ); + let mut source_id = None; + for (name, sql) in [ + ( + "replay_source", + format!("CREATE SOURCE {prefix}.replay_source IN CLUSTER quickstart FROM LOAD GENERATOR COUNTER"), + ), + ( + "replay_table", + format!("CREATE TABLE {prefix}.replay_table FROM SOURCE {prefix}.replay_source (REFERENCE counter) WITH (DETAILS '{details}')"), + ), + ] { + let (id, global_id) = catalog + .allocate_user_id_for_test() + .await + .expect("allocate collection identity"); + let item = catalog + .state + .with_enable_for_item_parsing(|state| state.parse_item( + global_id, + &sql, + &BTreeMap::new(), + None, + false, + None, + &mut LocalExpressionCache::Closed, + None, + )) + .unwrap_or_else(|err| panic!("parse {name}: {err}")); + let op = Op::CreateItem { + id, + name: QualifiedItemName { + qualifiers: qualifiers.clone(), + item: name.to_string(), + }, + item, + owner_id: MZ_SYSTEM_ROLE_ID, + }; + if let Some(source_id) = source_id { + let revision = catalog.transient_revision(); + let oracle_write_ts = catalog.current_upper().await; + let (dry_run_state, _dry_run_snapshot) = catalog + .transact_incremental_dry_run( + catalog.state(), + vec![op.clone()], + None, + None, + oracle_write_ts, + ) + .await + .expect("table birth dry run should succeed"); + assert_eq!( + dry_run_state.storage_metadata().compaction_bounds[&global_id], + Antichain::from_elem(Timestamp::MIN) + ); + seed_collection(&mut catalog, global_id).await; + let fresh_bound = Timestamp::new(20); + let oracle_write_ts = catalog.current_upper().await; + catalog + .transact( + None, + oracle_write_ts, + None, + vec![Op::SetCollectionCompactionBound { + id: source_id, + frontier: Some(fresh_bound), + }], + ) + .await + .expect("dependency permission should advance during the DDL transaction"); + assert_eq!(catalog.transient_revision(), revision); + + // Final DDL commit replays ops, not the dry-run state or snapshot. + let oracle_write_ts = catalog.current_upper().await; + catalog + .transact(None, oracle_write_ts, None, vec![op]) + .await + .expect("DDL replay should use current birth permission"); + assert_eq!(catalog.transient_revision(), revision + 1); + assert_eq!( + catalog.state().storage_metadata().compaction_bounds[&global_id], + Antichain::from_elem(fresh_bound) + ); + assert!(catalog.state().try_get_entry(&id).is_some()); + } else { + seed_collection(&mut catalog, global_id).await; + let oracle_write_ts = catalog.current_upper().await; + catalog + .transact(None, oracle_write_ts, None, vec![op]) + .await + .expect("source birth should commit"); + source_id = Some(global_id); + } + } + catalog.expire().await; + }) + .await; + } + #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] async fn test_read_protection_admission_and_committed_updates() { @@ -3620,17 +4047,22 @@ mod tests { // does not run storage shard allocation or controller implications. let updates = { let mut storage = catalog.storage().await; - let mut tx = storage.transaction().await.unwrap(); + let mut tx = storage + .transaction() + .await + .expect("collection setup transaction should open"); tx.insert_collection_metadata(BTreeMap::from([ (input, ShardId::new()), (output, ShardId::new()), ])) - .unwrap(); + .expect("input and output collection metadata should be inserted"); tx.set_collection_compaction_bound(input, Some(ts(10))) - .unwrap(); + .expect("initial input compaction bound should be accepted"); let updates = tx.get_and_commit_op_updates(); let commit_ts = tx.upper(); - tx.commit(commit_ts).await.unwrap(); + tx.commit(commit_ts) + .await + .expect("collection setup should commit"); updates }; let _ = catalog @@ -3647,6 +4079,9 @@ mod tests { id: input, frontier, }; + let revision = catalog.transient_revision(); + let ddl_revision = catalog.ddl_revision(); + let snapshot = catalog.clone(); let before = catalog.state().storage_metadata().compaction_bounds.clone(); let oracle_write_ts = catalog.current_upper().await; let result = catalog @@ -3671,6 +4106,34 @@ mod tests { .is_err() ); + catalog + .transact(None, oracle_write_ts, None, vec![bound(Some(ts(15)))]) + .await + .expect("bound-only publication should commit"); + assert_eq!(catalog.transient_revision(), revision); + assert!(snapshot.transient_revision_is_current()); + assert_eq!( + catalog.state().storage_metadata().compaction_bounds[&input], + Antichain::from_elem(ts(15)) + ); + assert_eq!( + snapshot.state().storage_metadata().compaction_bounds[&input], + Antichain::from_elem(ts(10)), + "planning-equivalent snapshots need not have identical protection authority" + ); + + let oracle_write_ts = catalog.current_upper().await; + catalog + .transact(None, oracle_write_ts, None, vec![requirement(Some(ts(19)))]) + .await + .expect("requirement-only publication should commit"); + assert_eq!(catalog.transient_revision(), revision); + assert!(snapshot.transient_revision_is_current()); + let committed_requirement = &catalog.state().maintained_read_requirements()[&output]; + assert_eq!(committed_requirement.inputs, BTreeSet::from([input])); + assert_eq!(committed_requirement.frontier, Some(ts(19))); + + let oracle_write_ts = catalog.current_upper().await; let result = catalog .transact( None, @@ -3679,7 +4142,8 @@ mod tests { vec![bound(Some(ts(20))), requirement(Some(ts(20)))], ) .await - .unwrap(); + .expect("matching compaction bound and read requirement should commit"); + assert_eq!(catalog.transient_revision(), revision); assert_eq!( catalog.state().storage_metadata().compaction_bounds[&input], Antichain::from_elem(ts(20)) @@ -3693,6 +4157,61 @@ mod tests { ) })); + assert_eq!(catalog.ddl_revision(), ddl_revision); + let interval = vars::CATALOG_READ_PROTECTION_PUBLISH_INTERVAL.name.as_str(); + let default_interval = catalog + .system_config() + .catalog_read_protection_publish_interval(); + assert!(!default_interval.is_zero()); + let oracle_write_ts = catalog.current_upper().await; + catalog + .transact( + None, + oracle_write_ts, + None, + vec![Op::UpdateSystemConfiguration { + name: interval.into(), + value: OwnedVarInput::Flat("0s".into()), + }], + ) + .await + .expect("pausing publication should commit"); + assert_eq!(catalog.transient_revision(), revision + 1); + assert_eq!(catalog.ddl_revision(), ddl_revision); + assert!(!snapshot.transient_revision_is_current()); + let paused_snapshot = catalog.clone(); + assert!(paused_snapshot.transient_revision_is_current()); + assert_eq!(paused_snapshot.ddl_revision(), ddl_revision); + assert!( + paused_snapshot + .system_config() + .catalog_read_protection_publish_interval() + .is_zero() + ); + let oracle_write_ts = catalog.current_upper().await; + catalog + .transact( + None, + oracle_write_ts, + None, + vec![Op::ResetSystemConfiguration { + name: interval.into(), + }], + ) + .await + .expect("resuming publication should commit"); + assert_eq!(catalog.transient_revision(), revision + 2); + assert_eq!(catalog.ddl_revision(), ddl_revision); + assert!(!paused_snapshot.transient_revision_is_current()); + let snapshot = catalog.clone(); + assert!(snapshot.transient_revision_is_current()); + assert_eq!( + snapshot + .system_config() + .catalog_read_protection_publish_interval(), + default_interval + ); + let oracle_write_ts = catalog.current_upper().await; assert!( catalog @@ -3700,15 +4219,46 @@ mod tests { .await .is_err() ); + let database_id = ResolvedDatabaseSpecifier::Id( + catalog + .resolve_database(DEFAULT_DATABASE_NAME) + .expect("default database exists") + .id(), + ); catalog .transact( None, oracle_write_ts, None, - vec![bound(None), requirement(None)], + vec![ + bound(None), + requirement(None), + Op::CreateSchema { + database_id: database_id.clone(), + schema_name: "protection_revision".into(), + owner_id: MZ_SYSTEM_ROLE_ID, + }, + ], ) .await - .unwrap(); + .expect("schema and protection changes should commit together"); + assert_eq!(catalog.transient_revision(), revision + 3); + assert_eq!(catalog.ddl_revision(), ddl_revision + 1); + assert!(!snapshot.transient_revision_is_current()); + assert!(catalog.transient_revision_is_current()); + assert!( + catalog + .resolve_schema_in_database( + &database_id, + "protection_revision", + &SYSTEM_CONN_ID, + ) + .is_ok() + ); + assert_eq!( + catalog.state().maintained_read_requirements()[&output].frontier, + None + ); assert_eq!( catalog .state() @@ -4311,6 +4861,51 @@ mod tests { let base_state = catalog.state().clone(); + let mut protected_state = base_state.clone(); + protected_state.catalog_read_protection_enabled = true; + let (born_state, born_snapshot) = catalog + .transact_incremental_dry_run( + &protected_state, + vec![op_t1.clone()], + None, + None, + oracle_write_ts, + ) + .await + .expect("table birth establishes committed permission"); + assert_eq!( + born_state.storage_metadata().compaction_bounds[&global_id_t1], + timely::progress::Antichain::from_elem(oracle_write_ts) + ); + let historical_ts = oracle_write_ts.step_back().expect("nonzero catalog upper"); + let result = catalog + .transact_incremental_dry_run( + &born_state, + vec![ + op_t2.clone(), + Op::SetMaintainedReadRequirement { + id: global_id_t2, + inputs: [global_id_t1].into_iter().collect(), + frontier: Some(historical_ts), + }, + ], + None, + Some(born_snapshot), + oracle_write_ts, + ) + .await; + assert!( + matches!( + result, + Err(AdapterError::Catalog(Error { + kind: ErrorKind::Durable( + mz_catalog::durable::DurableCatalogError::InvalidReadProtection(_) + ), + })) + ), + "admission must reject history preceding birth permission" + ); + let (state_after_t1, snapshot_after_t1) = catalog .transact_incremental_dry_run( &base_state, diff --git a/src/adapter/src/client.rs b/src/adapter/src/client.rs index 29c04ba36c6f5..8b86043a8b6c7 100644 --- a/src/adapter/src/client.rs +++ b/src/adapter/src/client.rs @@ -588,9 +588,9 @@ Issue a SQL query to get started. Need help? /// Returns a snapshot of the catalog. /// - /// Does a Coordinator round-trip. Session-bound callers should - /// prefer [`SessionClient::catalog_snapshot`], which serves from the - /// session's snapshot cache. + /// Does a Coordinator round-trip. Session-bound callers that only need + /// planning-visible state should prefer [`SessionClient::catalog_snapshot`], + /// which serves from the session's snapshot cache. pub async fn catalog_snapshot_expensive(&self) -> Arc { let (tx, rx) = oneshot::channel(); self.send(Command::CatalogSnapshot { tx }); @@ -1095,7 +1095,7 @@ impl SessionClient { } /// Fetches the catalog, served from the session-side snapshot cache when - /// the catalog is unchanged since the cached snapshot was taken. See + /// planning-visible state is unchanged since the cached snapshot was taken. See /// [`PeekClient::catalog_snapshot`]. #[instrument(level = "debug")] pub async fn catalog_snapshot(&mut self, context: &str) -> Arc { @@ -1123,7 +1123,8 @@ impl SessionClient { /// No authorization is performed, so access to this function must be limited to internal /// servers or superusers. pub async fn dump_catalog(&mut self) -> Result { - let catalog = self.catalog_snapshot("dump_catalog").await; + // Dumps include protection authority, which does not invalidate the planning cache. + let catalog = self.inner().catalog_snapshot_expensive().await; catalog.dump().map_err(AdapterError::from) } diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index db67f4afda7c7..82a456443497d 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -68,6 +68,7 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, VecDeque}; +use std::fmt; use std::net::IpAddr; use std::num::NonZeroI64; use std::ops::Neg; @@ -76,7 +77,6 @@ use std::sync::LazyLock; use std::sync::{Arc, Mutex}; use std::thread; use std::time::{Duration, Instant}; -use std::{fmt, mem}; use anyhow::Context; use chrono::{DateTime, Utc}; @@ -232,6 +232,7 @@ pub(crate) mod id_bundle; pub(crate) mod in_memory_oracle; pub(crate) mod peek; pub(crate) mod read_policy; +mod read_protection; pub(crate) mod read_then_write; pub(crate) mod sequencer; pub(crate) mod statement_logging; @@ -251,6 +252,7 @@ mod message_handler; mod metric_sink; mod privatelink_status; mod sql; +mod storage_bootstrap; mod validity; /// The oldest leader version against which a replacement-migrated builtin materialized view may @@ -3341,10 +3343,8 @@ impl Coordinator { /// This method takes care of collection creation, as well as migration of existing /// collections. /// - /// Creating all storage collections in a single `create_collections` call, rather than on - /// demand, is more efficient as it reduces the number of writes to durable storage. It also - /// allows subsequent bootstrap logic to fetch metadata (such as frontiers) of arbitrary - /// storage collections, without needing to worry about dependency order. + /// Registers shared-shard aliases together, with prerequisites installed first wherever + /// possible. Subsequent bootstrap logic can fetch metadata of arbitrary storage collections. /// /// `migrated_storage_collections` is a set of builtin storage collections that have been /// migrated and should be handled specially. @@ -3563,15 +3563,19 @@ impl Coordinator { // // To avoid violating frontier invariants, we need to bump their sinces to times greater // than all of their upstream storage inputs. To know the since of a storage input, it has - // to be registered with the storage controller first. Thus we register collections in - // layers: Each iteration registers the collections whose dependencies are all already - // registered. + // to be registered with the storage controller first. let mut pending: BTreeMap<_, _> = collections.into_iter().collect(); - // Precompute storage-collection dependencies for each collection. - let transitive_dep_gids: BTreeMap<_, _> = pending - .keys() - .map(|gid| { + // Only ungoverned builtins need transitive frontiers. Registration ordering uses direct + // catalog edges, including non-storage objects, rather than expanding user reachability. + let builtin_dep_gids: BTreeMap<_, _> = pending + .iter() + .filter(|(gid, collection)| { + gid.is_system() + && collection.since.is_none() + && !storage_metadata.compaction_bounds.contains_key(*gid) + }) + .map(|(gid, _)| { let entry = self.catalog.get_entry_by_global_id(gid); let item_id = entry.id(); let deps = self.catalog.state().transitive_uses(item_id); @@ -3587,32 +3591,48 @@ impl Coordinator { }) .collect(); + let dependencies = self + .catalog + .entries() + .map(|entry| (entry.id(), entry.uses())) + .collect(); + let batches = storage_bootstrap::registration_batches( + &dependencies, + pending.keys().map(|gid| { + ( + *gid, + self.catalog.get_entry_by_global_id(gid).id(), + storage_metadata.collection_metadata[gid], + ) + }), + ); let mut created_gids = Vec::new(); - while !pending.is_empty() { - // Drain collections whose dependencies have all been registered already - // (i.e., are not in `pending`). - let ready_gids: BTreeSet<_> = pending - .keys() - .filter(|gid| { - let mut deps = transitive_dep_gids[gid].iter(); - !deps.any(|dep_gid| pending.contains_key(dep_gid)) + for batch in batches { + let mut ready: Vec<_> = batch + .into_iter() + .map(|gid| { + let collection = pending.remove(&gid).expect("registered exactly once"); + (gid, collection) }) - .copied() - .collect(); - let mut ready: Vec<_> = pending - .extract_if(.., |gid, _| ready_gids.contains(gid)) .collect(); // Bump sinces of builtin collections. for (gid, collection) in &mut ready { + // Governed collections recover their persisted readability. Pristine + // builtin MVs initialize from committed birth permission in storage. + if storage_metadata.compaction_bounds.contains_key(gid) { + continue; + } // Don't silently overwrite an explicitly specified `since`. if !gid.is_system() || collection.since.is_some() { continue; } let mut derived_since = Antichain::from_elem(Timestamp::MIN); - for dep_gid in &transitive_dep_gids[gid] { + // Builtins cannot depend on user objects or be replacement targets, so their + // prerequisites cannot be co-registered in a replacement-induced cycle. + for dep_gid in &builtin_dep_gids[gid] { let (since, _) = self .controller .storage @@ -3623,17 +3643,6 @@ impl Coordinator { collection.since = Some(derived_since); } - if ready.is_empty() { - soft_panic_or_log!( - "cycle in storage collections: {:?}", - pending.keys().collect::>(), - ); - // We get here only due to a bug. Rather than crash-looping, we try our best to - // reach a sane state by attempting to register all the remaining collections at - // once. - ready = mem::take(&mut pending).into_iter().collect(); - } - created_gids.extend(ready.iter().map(|(gid, _collection)| *gid)); self.controller @@ -4206,7 +4215,22 @@ impl Coordinator { let linearize_reads_notified = linearize_reads_notify.notified(); tokio::pin!(linearize_reads_notified); + let mut publication_delay = self + .catalog() + .system_config() + .catalog_read_protection_publish_interval(); + let publication_timer = tokio::time::sleep(publication_delay); + tokio::pin!(publication_timer); + loop { + let delay = self + .catalog() + .system_config() + .catalog_read_protection_publish_interval(); + if delay != publication_delay { + publication_delay = delay; + publication_timer.set(tokio::time::sleep(delay)); + } // Before adding a branch to this select loop, please ensure that the branch is // cancellation safe and add a comment explaining why. You can refer here for more // info: https://docs.rs/tokio/latest/tokio/macro.select.html#cancellation-safety @@ -4217,6 +4241,17 @@ impl Coordinator { // before receiving a new batch of commands. biased; + // Polling a pinned Sleep is cancellation-safe. Bootstrap restores execution holds + // before this runs. Give publication a turn even under continuous load, + // but schedule from completion so a slow commit cannot monopolize us. + _ = publication_timer.as_mut(), + if self.catalog().state().catalog_read_protection_enabled() + && !self.controller.read_only() && !publication_delay.is_zero() => { + if let Err(error) = self.publish_read_protection().await { + warn!(%error, "unable to publish catalog read protection"); + } + publication_timer.set(tokio::time::sleep(publication_delay)); + } // `recv_many()` on `UnboundedReceiver` is cancellation safe: // https://docs.rs/tokio/1.38.0/tokio/sync/mpsc/struct.UnboundedReceiver.html#cancel-safety-1 // Receive a batch of commands. diff --git a/src/adapter/src/coord/catalog_implications.rs b/src/adapter/src/coord/catalog_implications.rs index 54ce8842774b2..b139c777a68c3 100644 --- a/src/adapter/src/coord/catalog_implications.rs +++ b/src/adapter/src/coord/catalog_implications.rs @@ -1352,8 +1352,15 @@ impl Coordinator { .map(|target_id| self.catalog().get_entry(&target_id).latest_global_id()); mv.collection_descs() .map(|(gid, _version, desc)| { - let mut collection_desc = - CollectionDescription::for_other(desc, mv.initial_as_of.clone()); + // Applied replacements retain the original shard and its history. + // Their SQL visibility frontier is not an instruction to initialize + // that shard again. Compute receives it separately as initial_as_of. + let since = if mv.collections.len() > 1 { + None + } else { + mv.initial_as_of.clone() + }; + let mut collection_desc = CollectionDescription::for_other(desc, since); collection_desc.primary = primary; primary = Some(gid); (gid, collection_desc) diff --git a/src/adapter/src/coord/command_handler.rs b/src/adapter/src/coord/command_handler.rs index aa7c8249f6ada..0ac1328385067 100644 --- a/src/adapter/src/coord/command_handler.rs +++ b/src/adapter/src/coord/command_handler.rs @@ -1411,7 +1411,7 @@ impl Coordinator { | Statement::CreateTableFromSource(_) | Statement::CreateSource(_) => { let state = self.catalog().for_session(ctx.session()).state().clone(); - let revision = self.catalog().transient_revision(); + let ddl_revision = self.catalog().ddl_revision(); // Initialize our transaction with a set of empty ops, or return an error // if we can't run a DDL transaction @@ -1419,7 +1419,7 @@ impl Coordinator { if let Err(err) = txn_status.add_ops(TransactionOps::DDL { ops: vec![], state, - revision, + ddl_revision, side_effects: vec![], snapshot: None, }) { @@ -1879,9 +1879,9 @@ impl Coordinator { let mut ids = self .index_oracle(cluster) .sufficient_collections(resolved_ids.collections().copied()); - ids.extend( - &self.materialized_view_logical_inputs(resolved_ids.collections().copied())?, - ); + let logical_inputs = + self.materialized_view_logical_inputs(resolved_ids.collections().copied())?; + ids.extend(&logical_inputs); // If there is any REFRESH option, then acquire read holds. (Strictly speaking, we'd // need this only if there is a `REFRESH AT`, not for `REFRESH EVERY`, because later @@ -1931,7 +1931,12 @@ impl Coordinator { // after its creation might see input changes that happened after the CRATE MATERIALIZED // VIEW statement returned. let oracle_timestamp = timestamp; - let least_valid_read = read_holds.least_valid_read(); + let least_valid_read = + read_holds + .least_valid_read() + .join(&self.materialized_view_input_permission( + logical_inputs.storage_ids.iter().copied(), + )?); timestamp.advance_by(least_valid_read.borrow()); if oracle_timestamp != timestamp { diff --git a/src/adapter/src/coord/ddl.rs b/src/adapter/src/coord/ddl.rs index 599c9465293b4..0e946dcf18ae3 100644 --- a/src/adapter/src/coord/ddl.rs +++ b/src/adapter/src/coord/ddl.rs @@ -272,7 +272,7 @@ impl Coordinator { ops: TransactionOps::DDL { ops: txn_ops, - revision: txn_revision, + ddl_revision: txn_revision, state: txn_state, snapshot: txn_snapshot, side_effects: _, @@ -290,8 +290,7 @@ impl Coordinator { return result; }; - // Make sure our Catalog hasn't changed since openning the transaction. - if self.catalog().transient_revision() != *txn_revision { + if self.catalog().ddl_revision() != *txn_revision { self.metrics .catalog_transact_seconds .with_label_values(&["catalog_transact_with_ddl_transaction"]) @@ -380,7 +379,7 @@ impl Coordinator { ops: combined_ops, state: new_state, side_effects: vec![Box::new(side_effect)], - revision: self.catalog().transient_revision(), + ddl_revision: self.catalog().ddl_revision(), snapshot: Some(new_snapshot), }); diff --git a/src/adapter/src/coord/read_protection.rs b/src/adapter/src/coord/read_protection.rs new file mode 100644 index 0000000000000..5b5bffeb8774b --- /dev/null +++ b/src/adapter/src/coord/read_protection.rs @@ -0,0 +1,409 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +//! Publishes durable recovery requirements and compaction permission together. + +use std::collections::BTreeMap; +use std::time::Instant; + +use differential_dataflow::lattice::Lattice; +use mz_catalog::durable::objects::{CollectionCompactionBound, MaintainedReadRequirement}; +use mz_catalog::memory::objects::CatalogItem; +use mz_repr::{GlobalId, Timestamp}; +use mz_storage_client::storage_collections::CollectionFrontiers; +use timely::progress::Antichain; + +use crate::AdapterError; +use crate::catalog::Op; +use crate::coord::Coordinator; + +impl Coordinator { + /// Publishes recovery progress and compatible bounds in enabled, writable environments. + pub(super) async fn publish_read_protection(&mut self) -> Result<(), AdapterError> { + if self.controller.read_only() || !self.catalog().state().catalog_read_protection_enabled() + { + return Ok(()); + } + + let start = Instant::now(); + let frontiers = self + .controller + .storage_collections + .active_collection_frontiers(); + // Coordinator handlers serialize new creation holds with this publication. + let compaction_frontiers = self.controller.storage_collections.compaction_frontiers(); + let candidates = publication_candidates( + self.catalog().state().maintained_read_requirements(), + &self.catalog().state().storage_metadata().compaction_bounds, + &frontiers, + &compaction_frontiers, + |id| { + let Some(entry) = self.catalog().try_get_entry_by_global_id(&id) else { + return false; + }; + let CatalogItem::MaterializedView(mv) = entry.item() else { + return false; + }; + // A pending replacement observes the target's shared upper, not its own + // progress. Retired writers' requirements are completed by replacement. + mv.replacement_target.is_none() && mv.global_id_writes() == id + }, + ); + let requirement_updates = candidates.requirements.len(); + let bound_updates = candidates.bounds.len(); + if requirement_updates == 0 && bound_updates == 0 { + return Ok(()); + } + + let ops = + candidates + .requirements + .into_iter() + .map(|requirement| Op::SetMaintainedReadRequirement { + id: requirement.id, + inputs: requirement.inputs, + frontier: requirement.frontier, + }) + .chain(candidates.bounds.into_iter().map(|bound| { + Op::SetCollectionCompactionBound { + id: bound.id, + frontier: bound.frontier, + } + })) + .collect(); + self.catalog_transact(None, ops).await?; + + tracing::info!( + installed_collections = frontiers.len(), + requirement_updates, + bound_updates, + duration_seconds = start.elapsed().as_secs_f64(), + max_policy_lag_ts = candidates.max_policy_lag_ts, + unbounded_policy_lag = candidates.unbounded_policy_lag, + "published catalog read protection" + ); + Ok(()) + } +} + +#[derive(Debug, Default)] +struct PublicationCandidates { + requirements: Vec, + bounds: Vec, + // Timestamp distance from the resulting bound to finite policy permission, + // not wall-clock age. Empty policy frontiers with finite bounds count separately. + max_policy_lag_ts: u64, + unbounded_policy_lag: usize, +} + +/// Computes changed records from committed protection and installed storage frontiers. +fn publication_candidates( + requirements: &BTreeMap, + bounds: &BTreeMap>, + frontiers: &[CollectionFrontiers], + compaction_frontiers: &BTreeMap>, + is_active_mv_writer: impl Fn(GlobalId) -> bool, +) -> PublicationCandidates { + let mut candidates = PublicationCandidates::default(); + let installed: BTreeMap<_, _> = frontiers.iter().map(|f| (f.id, f)).collect(); + let mut input_limits: BTreeMap = BTreeMap::new(); + + for requirement in requirements.values() { + let mut frontier: Antichain<_> = requirement.frontier.into_iter().collect(); + if is_active_mv_writer(requirement.id) + && let Some(output) = installed.get(&requirement.id) + { + // An upper at a refresh timestamp does not complete that refresh. + // Its predecessor preserves the pending input snapshot. At MIN, + // saturating subtraction leaves the committed requirement intact. + let predecessor = output + .write_frontier + .iter() + .map(|t| t.saturating_sub(1)) + .collect(); + frontier.join_assign(&predecessor); + } + let frontier = frontier.into_option(); + if frontier != requirement.frontier { + candidates.requirements.push(MaintainedReadRequirement { + frontier, + ..requirement.clone() + }); + } + + // Every remaining requirement limits its exact committed input versions, + // including consumers that are uninstalled or ineligible to advance. + if let Some(frontier) = frontier { + for input in &requirement.inputs { + input_limits + .entry(*input) + .and_modify(|limit| *limit = (*limit).min(frontier)) + .or_insert(frontier); + } + } + } + + for collection in frontiers { + let Some(old_bound) = bounds.get(&collection.id) else { + continue; + }; + let Some(mut bound) = compaction_frontiers.get(&collection.id).cloned() else { + continue; + }; + // Keep early creation holds authoritative during optimization. The storage + // proposal excludes only its permission cap, not those read requirements. + bound.extend(input_limits.get(&collection.id).copied()); + bound.join_assign(old_bound); + if let Some(bound_ts) = bound.as_option() { + match collection.implied_capability.as_option() { + Some(policy_ts) => { + candidates.max_policy_lag_ts = candidates + .max_policy_lag_ts + .max(u64::from(policy_ts.saturating_sub(*bound_ts))); + } + None => candidates.unbounded_policy_lag += 1, + } + } + if &bound != old_bound { + candidates.bounds.push(CollectionCompactionBound { + id: collection.id, + frontier: bound.into_option(), + }); + } + } + candidates +} + +#[cfg(test)] +mod tests { + use super::*; + + fn frontier(t: u64) -> Antichain { + Antichain::from_elem(Timestamp::from(t)) + } + + fn policy_frontiers( + frontiers: &[CollectionFrontiers], + ) -> BTreeMap> { + frontiers + .iter() + .map(|f| (f.id, f.implied_capability.clone())) + .collect() + } + + fn collection(id: GlobalId, upper: Option, policy: Option) -> CollectionFrontiers { + CollectionFrontiers { + id, + write_frontier: upper.map(Timestamp::from).into_iter().collect(), + implied_capability: policy.map(Timestamp::from).into_iter().collect(), + read_capabilities: frontier(0), + } + } + + fn requirement(id: GlobalId, inputs: &[GlobalId], t: u64) -> MaintainedReadRequirement { + MaintainedReadRequirement { + id, + inputs: inputs.iter().copied().collect(), + frontier: Some(Timestamp::from(t)), + } + } + + #[mz_ore::test] + fn durable_progress_preserves_pending_refresh_and_completion() { + let input = GlobalId::User(1); + let output = GlobalId::User(2); + let requirement = requirement(output, &[input], 10); + let requirements = BTreeMap::from([(output, requirement.clone())]); + let bounds = BTreeMap::from([(input, frontier(10))]); + + for (upper, expected) in [ + (Some(0), Some(10)), + (Some(10), Some(10)), + (Some(11), Some(10)), + (Some(12), Some(11)), + (None, None), + ] { + let frontiers = [ + collection(input, None, None), + collection(output, upper, Some(0)), + ]; + let candidates = publication_candidates( + &requirements, + &bounds, + &frontiers, + &policy_frontiers(&frontiers), + |_| true, + ); + let expected = expected.map(Timestamp::from); + if expected == requirement.frontier { + assert!(candidates.requirements.is_empty()); + assert!(candidates.bounds.is_empty()); + } else { + assert_eq!( + candidates.requirements, + vec![MaintainedReadRequirement { + frontier: expected, + ..requirement.clone() + }] + ); + assert_eq!( + candidates.bounds, + vec![CollectionCompactionBound { + id: input, + frontier: expected, + }] + ); + } + } + + let completed = BTreeMap::from([( + output, + MaintainedReadRequirement { + frontier: None, + ..requirement + }, + )]); + let candidates = publication_candidates( + &completed, + &bounds, + &[collection(output, Some(20), Some(0))], + &BTreeMap::new(), + |_| true, + ); + assert!(candidates.requirements.is_empty(), "completion is final"); + } + + #[mz_ore::test] + fn all_consumers_limit_exact_input_versions() { + let input = GlobalId::User(1); + let other_version = GlobalId::User(2); + let writer = GlobalId::User(3); + let pending = GlobalId::User(4); + let uninstalled = GlobalId::User(5); + let requirements = BTreeMap::from([ + (writer, requirement(writer, &[input], 10)), + (pending, requirement(pending, &[input], 15)), + (uninstalled, requirement(uninstalled, &[input], 18)), + ]); + let bounds = BTreeMap::from([(input, frontier(10)), (other_version, frontier(10))]); + let frontiers = [ + collection(input, Some(100), Some(90)), + collection(other_version, Some(100), Some(90)), + collection(writer, Some(51), Some(0)), + collection(pending, Some(100), Some(0)), + ]; + for (requirements, limit) in [ + (requirements.clone(), 15), + ( + requirements + .into_iter() + .filter(|(id, _)| *id != pending) + .collect(), + 18, + ), + ] { + let candidates = publication_candidates( + &requirements, + &bounds, + &frontiers, + &policy_frontiers(&frontiers), + |id| id != pending, + ); + assert_eq!( + candidates.requirements, + vec![requirement(writer, &[input], 50)] + ); + assert_eq!( + candidates.bounds, + vec![ + CollectionCompactionBound { + id: input, + frontier: Some(Timestamp::from(limit)), + }, + CollectionCompactionBound { + id: other_version, + frontier: Some(Timestamp::from(90)), + }, + ] + ); + assert_eq!(candidates.max_policy_lag_ts, 90 - limit); + } + } + + #[mz_ore::test] + fn local_creation_holds_limit_permission() { + let input = GlobalId::User(1); + let frontiers = [collection(input, Some(100), Some(90))]; + let candidates = publication_candidates( + &BTreeMap::new(), + &BTreeMap::from([(input, frontier(10))]), + &frontiers, + &BTreeMap::from([(input, frontier(20))]), + |_| false, + ); + assert_eq!( + candidates.bounds, + vec![CollectionCompactionBound { + id: input, + frontier: Some(Timestamp::from(20)), + }] + ); + } + + #[mz_ore::test] + fn bounds_require_installation_and_governance_and_never_regress() { + let advancing = GlobalId::User(1); + let regressing_policy = GlobalId::User(2); + let uninstalled = GlobalId::User(3); + let ungoverned = GlobalId::User(4); + let complete = GlobalId::User(5); + let bounds = BTreeMap::from([ + (advancing, frontier(10)), + (regressing_policy, frontier(10)), + (uninstalled, frontier(10)), + (complete, Antichain::new()), + ]); + let frontiers = [ + collection(advancing, Some(100), Some(90)), + collection(regressing_policy, Some(100), Some(5)), + collection(ungoverned, Some(100), Some(90)), + collection(complete, None, Some(90)), + ]; + let candidates = publication_candidates( + &BTreeMap::new(), + &bounds, + &frontiers, + &policy_frontiers(&frontiers), + |_| false, + ); + assert_eq!( + candidates.bounds, + vec![CollectionCompactionBound { + id: advancing, + frontier: Some(Timestamp::from(90)), + }] + ); + assert_eq!(candidates.max_policy_lag_ts, 0); + + let mut bounds = bounds; + bounds.insert(advancing, frontier(90)); + let candidates = publication_candidates( + &BTreeMap::new(), + &bounds, + &frontiers, + &policy_frontiers(&frontiers), + |_| false, + ); + assert!(candidates.requirements.is_empty()); + assert!( + candidates.bounds.is_empty(), + "unchanged publication is a no-op" + ); + } +} diff --git a/src/adapter/src/coord/sequencer/inner.rs b/src/adapter/src/coord/sequencer/inner.rs index a91d4792ab98f..9b8122878b406 100644 --- a/src/adapter/src/coord/sequencer/inner.rs +++ b/src/adapter/src/coord/sequencer/inner.rs @@ -2272,11 +2272,10 @@ impl Coordinator { ops, state: _, side_effects, - revision, + ddl_revision, snapshot: _, } => { - // Make sure our catalog hasn't changed. - if *revision != self.catalog().transient_revision() { + if *ddl_revision != self.catalog().ddl_revision() { return Err(AdapterError::DDLTransactionRace); } // Commit all of our queued ops. diff --git a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs index 0287e34236886..0b2c03425b70e 100644 --- a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs +++ b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs @@ -19,7 +19,9 @@ use mz_repr::explain::{ExprHumanizerExt, TransientItem}; use mz_repr::optimize::OptimizerFeatures; use mz_repr::optimize::OverrideFrom; use mz_repr::refresh_schedule::RefreshSchedule; -use mz_repr::{CatalogItemId, Datum, GlobalId, RelationVersion, Row, VersionedRelationDesc}; +use mz_repr::{ + CatalogItemId, Datum, GlobalId, RelationVersion, Row, Timestamp, VersionedRelationDesc, +}; use mz_sql::ast::ExplainStage; use mz_sql::catalog::CatalogError; use mz_sql::names::ResolvedIds; @@ -94,6 +96,32 @@ impl Staged for CreateMaterializedViewStage { } impl Coordinator { + /// Returns the committed input permission for MV admission, not a read hold. + pub(crate) fn materialized_view_input_permission( + &self, + ids: impl IntoIterator, + ) -> Result, AdapterError> { + let mut permission = Antichain::from_elem(Timestamp::MIN); + if self.catalog().state().catalog_read_protection_enabled() { + for id in ids { + let bound = self + .catalog() + .state() + .storage_metadata() + .compaction_bounds + .get(&id) + .ok_or_else(|| { + AdapterError::internal( + "create materialized view", + format!("logical input {id} has no committed compaction bound"), + ) + })?; + permission.join_assign(bound); + } + } + Ok(permission) + } + /// Discovers storage inputs required to reconstruct an MV from its definition. pub(crate) fn materialized_view_logical_inputs( &self, @@ -671,6 +699,7 @@ impl Coordinator { refresh_schedule.as_ref(), read_holds, &additional_read_holds, + &logical_inputs, )?; tracing::info!( @@ -708,7 +737,7 @@ impl Coordinator { let local_mir_for_cache = local_mir_plan.expr(); - let ops = vec![ + let mut ops = vec![ catalog::Op::DropObjects( drop_ids .into_iter() @@ -740,6 +769,13 @@ impl Coordinator { owner_id: *ctx.session().current_role_id(), }, ]; + if self.catalog().state().catalog_read_protection_enabled() { + ops.push(catalog::Op::SetMaintainedReadRequirement { + id: global_id, + inputs: logical_inputs.storage_ids, + frontier: dataflow_as_of.as_option().copied(), + }); + } // Pre-allocate a vector of transient GlobalIds for each notice. let notice_ids = std::iter::repeat_with(|| self.allocate_transient_id()) @@ -863,6 +899,7 @@ impl Coordinator { refresh_schedule: Option<&RefreshSchedule>, read_holds: &ReadHolds, additional_read_holds: &ReadHolds, + logical_inputs: &CollectionIdBundle, ) -> Result< ( Antichain, @@ -881,9 +918,14 @@ impl Coordinator { // For non-REFRESH MVs both the `dataflow_as_of` and the `storage_as_of` should be simply // `least_valid_read`. - let least_valid_read = read_holds + let mut least_valid_read = read_holds .least_valid_read() .join(&additional_read_holds.least_valid_read()); + // Physical compaction may lag permission. Admission cannot rely on + // that extra history, even for inputs eliminated by optimization. + least_valid_read.join_assign( + &self.materialized_view_input_permission(logical_inputs.storage_ids.iter().copied())?, + ); let mut dataflow_as_of = least_valid_read.clone(); let mut storage_as_of = least_valid_read.clone(); @@ -941,6 +983,12 @@ impl Coordinator { .and_then(|r| r.try_step_forward()); let until = Antichain::from_iter(until_ts); + if self.catalog().state().catalog_read_protection_enabled() && storage_as_of.is_empty() { + return Err(AdapterError::internal( + "create materialized view", + "no readable timestamp for materialized view inputs", + )); + } Ok((dataflow_as_of, storage_as_of, until)) } diff --git a/src/adapter/src/coord/storage_bootstrap.rs b/src/adapter/src/coord/storage_bootstrap.rs new file mode 100644 index 0000000000000..bcf5c60bf669e --- /dev/null +++ b/src/adapter/src/coord/storage_bootstrap.rs @@ -0,0 +1,214 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +use std::collections::{BTreeMap, BTreeSet}; + +/// Orders storage registration batches by catalog prerequisites, co-registering versions and +/// shared-shard aliases. Only dependencies within a strongly connected component may share a batch. +/// +/// `dependencies` includes non-storage items so paths through views and functions retain their +/// ordering. `collections` supplies (collection ID, catalog item ID, committed shard ID) triples. +/// Every item and prerequisite must be present in `dependencies`. Ordering within a batch is left +/// to storage, which knows the primary ordering of table and MV versions. +pub(super) fn registration_batches( + dependencies: &BTreeMap>, + collections: impl IntoIterator, +) -> Vec> { + let items: BTreeMap<_, _> = dependencies + .keys() + .enumerate() + .map(|(index, id)| (*id, index)) + .collect(); + let mut edges = vec![Vec::new(); items.len()]; + for (id, deps) in dependencies { + edges[items[id]].extend(deps.iter().map(|dep| items[dep])); + } + + let mut shard_items = BTreeMap::new(); + let mut item_collections: Vec> = (0..items.len()).map(|_| Vec::new()).collect(); + for (gid, item, shard) in collections { + let item = items[&item]; + item_collections[item].push(gid); + let representative = *shard_items.entry(shard).or_insert(item); + if item != representative { + // Bidirectional edges make all aliases one registration unit without a clique. + // A pending replacement adds its FOR and query dependencies to this unit. Those + // dependencies can induce cycles between otherwise unrelated shards. + edges[item].push(representative); + edges[representative].push(item); + } + } + + // Iterative Kosaraju avoids a call-stack limit on catalog depth. Both passes visit each + // vertex and edge once. Edges point from an item to its prerequisites. + let mut reverse_edges = vec![Vec::new(); items.len()]; + for (item, deps) in edges.iter().enumerate() { + for dep in deps { + reverse_edges[*dep].push(item); + } + } + let mut visited = vec![false; items.len()]; + let mut finished = Vec::with_capacity(items.len()); + let mut stack = Vec::new(); + for item in 0..items.len() { + if visited[item] { + continue; + } + visited[item] = true; + stack.push((item, 0)); + while let Some((item, next)) = stack.last_mut() { + if let Some(dep) = edges[*item].get(*next).copied() { + *next += 1; + if !visited[dep] { + visited[dep] = true; + stack.push((dep, 0)); + } + } else { + finished.push(*item); + stack.pop(); + } + } + } + + let mut component_of = vec![None; items.len()]; + let mut components = Vec::new(); + let mut stack = Vec::new(); + for item in finished.into_iter().rev() { + if component_of[item].is_some() { + continue; + } + let component_id = components.len(); + let mut component = Vec::new(); + component_of[item] = Some(component_id); + stack.push(item); + while let Some(item) = stack.pop() { + component.push(item); + for dep in &reverse_edges[item] { + if component_of[*dep].is_none() { + component_of[*dep] = Some(component_id); + stack.push(*dep); + } + } + } + components.push(component); + } + + // Kosaraju emits dependents before prerequisites, so reverse iteration can assign each + // component a batch after its external storage prerequisites. Non-storage items propagate + // ordering without adding registration rounds. Downstream components remain separate even + // when a cycle prevents ordering the members of an upstream component. + // In read-only bootstrap this also lets storage recover leased input readability before + // registering outputs. No ordering is inferred from committed compaction permissions. + let mut next_layers = vec![0; components.len()]; + let mut batches: Vec> = Vec::new(); + for (component_id, component) in components.into_iter().enumerate().rev() { + let layer = component + .iter() + .flat_map(|item| &edges[*item]) + .map(|dep| component_of[*dep].expect("all items have a component")) + .filter(|dep| *dep != component_id) + .map(|dep| next_layers[dep]) + .max() + .unwrap_or(0); + let has_collections = component + .iter() + .any(|item| !item_collections[*item].is_empty()); + next_layers[component_id] = layer + usize::from(has_collections); + batches.resize_with(batches.len().max(layer + 1), Vec::new); + for item in component { + batches[layer].append(&mut item_collections[item]); + } + } + batches.retain(|batch| !batch.is_empty()); + batches +} + +#[cfg(test)] +mod tests { + use std::collections::{BTreeMap, BTreeSet}; + + use super::registration_batches; + + #[mz_ore::test] + fn prerequisites_versions_and_pending_aliases() { + let dependencies = BTreeMap::from([ + ("table", BTreeSet::new()), + ("view", BTreeSet::from(["table"])), + ("a", BTreeSet::from(["view"])), + ("b", BTreeSet::from(["table"])), + ("ar", BTreeSet::from(["a", "b"])), + ("downstream", BTreeSet::from(["ar"])), + ]); + let batches = registration_batches( + &dependencies, + [ + ("table_v1", "table", "table_shard"), + ("table_v2", "table", "table_shard"), + ("a_v1", "a", "a_shard"), + ("a_v2", "a", "a_shard"), + ("ar", "ar", "a_shard"), + ("b", "b", "b_shard"), + ("downstream", "downstream", "downstream_shard"), + ], + ); + let batches: Vec> = batches + .into_iter() + .map(|batch| batch.into_iter().collect()) + .collect(); + assert_eq!( + batches, + vec![ + BTreeSet::from(["table_v1", "table_v2"]), + BTreeSet::from(["b"]), + BTreeSet::from(["a_v1", "a_v2", "ar"]), + BTreeSet::from(["downstream"]), + ] + ); + } + + #[mz_ore::test] + fn cross_dependent_replacements_leave_downstream_separate() { + // The catalog DAG is legal. Only grouping ar with a and br with b induces a cycle. + let dependencies = BTreeMap::from([ + ("table", BTreeSet::new()), + ("a", BTreeSet::from(["table"])), + ("b", BTreeSet::from(["table"])), + ("ar", BTreeSet::from(["a", "b"])), + ("br", BTreeSet::from(["b", "a"])), + ("view", BTreeSet::from(["ar", "br"])), + ("downstream", BTreeSet::from(["view"])), + ("further_downstream", BTreeSet::from(["downstream"])), + ]); + let batches = registration_batches( + &dependencies, + [ + ("table", "table", "table_shard"), + ("a", "a", "a_shard"), + ("ar", "ar", "a_shard"), + ("b", "b", "b_shard"), + ("br", "br", "b_shard"), + ("downstream", "downstream", "downstream_shard"), + ("further_downstream", "further_downstream", "further_shard"), + ], + ); + let batches: Vec> = batches + .into_iter() + .map(|batch| batch.into_iter().collect()) + .collect(); + assert_eq!( + batches, + vec![ + BTreeSet::from(["table"]), + BTreeSet::from(["a", "ar", "b", "br"]), + BTreeSet::from(["downstream"]), + BTreeSet::from(["further_downstream"]), + ] + ); + } +} diff --git a/src/adapter/src/peek_client.rs b/src/adapter/src/peek_client.rs index b9b77940783ce..81e08e18b8bf2 100644 --- a/src/adapter/src/peek_client.rs +++ b/src/adapter/src/peek_client.rs @@ -221,7 +221,7 @@ impl PeekClient { /// Serves from the session-side cache when the catalog's transient /// revision is unchanged since the cached snapshot was taken (see /// [`Catalog::transient_revision_is_current`]). An unchanged revision - /// means the cached snapshot is identical to what a fresh fetch would + /// means the cached snapshot is planning-equivalent to what a fresh fetch would /// return. Otherwise falls back to a `Command::CatalogSnapshot` /// round-trip and re-populates the cache. /// diff --git a/src/adapter/src/session.rs b/src/adapter/src/session.rs index 905f543259f49..ceefa45b254c7 100644 --- a/src/adapter/src/session.rs +++ b/src/adapter/src/session.rs @@ -1453,14 +1453,14 @@ impl TransactionStatus { } TransactionOps::DDL { ops: og_ops, - revision: og_revision, + ddl_revision: og_revision, state: og_state, side_effects, snapshot: og_snapshot, } => match add_ops { TransactionOps::DDL { ops: new_ops, - revision: new_revision, + ddl_revision: new_revision, side_effects: mut net_new_side_effects, state: new_state, snapshot: new_snapshot, @@ -1661,8 +1661,8 @@ pub enum TransactionOps { + Sync, >, >, - /// Transient revision of the `Catalog` when this transaction started. - revision: u64, + /// [`crate::catalog::Catalog::ddl_revision`] when this transaction started. + ddl_revision: u64, /// Snapshot of the durable transaction state after the last dry run. /// Used to initialize the next dry run's transaction so it starts /// in sync with the accumulated `state`. `None` for the first diff --git a/src/catalog/tests/debug.rs b/src/catalog/tests/debug.rs index b610de12793b1..ed48525b43667 100644 --- a/src/catalog/tests/debug.rs +++ b/src/catalog/tests/debug.rs @@ -110,6 +110,8 @@ impl Debug for StableTrace<'_> { replica_system_configurations, system_privileges, storage_collection_metadata, + collection_compaction_bounds, + maintained_read_requirements, unfinalized_shards, txn_wal_shard, } = self.0; @@ -162,6 +164,8 @@ impl Debug for StableTrace<'_> { ) .field("system_privileges", system_privileges) .field("storage_collection_metadata", storage_collection_metadata) + .field("collection_compaction_bounds", collection_compaction_bounds) + .field("maintained_read_requirements", maintained_read_requirements) .field("unfinalized_shards", unfinalized_shards) .field("txn_wal_shard", txn_wal_shard) .finish() diff --git a/src/catalog/tests/open.rs b/src/catalog/tests/open.rs index a34093f60a57e..c9048c961f2ac 100644 --- a/src/catalog/tests/open.rs +++ b/src/catalog/tests/open.rs @@ -103,6 +103,8 @@ impl Debug for StableSnapshot<'_> { default_privileges, system_privileges, storage_collection_metadata, + collection_compaction_bounds, + maintained_read_requirements, unfinalized_shards, txn_wal_shard, } = self.0; @@ -140,6 +142,8 @@ impl Debug for StableSnapshot<'_> { .field("default_privileges", default_privileges) .field("system_privileges", system_privileges) .field("storage_collection_metadata", storage_collection_metadata) + .field("collection_compaction_bounds", collection_compaction_bounds) + .field("maintained_read_requirements", maintained_read_requirements) .field("unfinalized_shards", unfinalized_shards) .field("txn_wal_shard", txn_wal_shard) .finish() diff --git a/src/catalog/tests/snapshots/debug__opened_trace.snap b/src/catalog/tests/snapshots/debug__opened_trace.snap index 86cc92d68ab5b..ceff360913bb2 100644 --- a/src/catalog/tests/snapshots/debug__opened_trace.snap +++ b/src/catalog/tests/snapshots/debug__opened_trace.snap @@ -1627,6 +1627,12 @@ Trace { storage_collection_metadata: CollectionTrace { values: [], }, + collection_compaction_bounds: CollectionTrace { + values: [], + }, + maintained_read_requirements: CollectionTrace { + values: [], + }, unfinalized_shards: CollectionTrace { values: [], }, diff --git a/src/catalog/tests/snapshots/open__initial_snapshot.snap b/src/catalog/tests/snapshots/open__initial_snapshot.snap index 249de836bccb2..9823d67982000 100644 --- a/src/catalog/tests/snapshots/open__initial_snapshot.snap +++ b/src/catalog/tests/snapshots/open__initial_snapshot.snap @@ -856,6 +856,8 @@ Snapshot { }, }, storage_collection_metadata: {}, + collection_compaction_bounds: {}, + maintained_read_requirements: {}, unfinalized_shards: {}, txn_wal_shard: {}, } diff --git a/src/compute-client/src/as_of_selection.rs b/src/compute-client/src/as_of_selection.rs index d9cca3c6d5cb2..6d5cbb516f056 100644 --- a/src/compute-client/src/as_of_selection.rs +++ b/src/compute-client/src/as_of_selection.rs @@ -928,6 +928,10 @@ mod tests { unimplemented!() } + fn compaction_frontiers(&self) -> BTreeMap> { + unimplemented!() + } + fn check_exists(&self, _id: GlobalId) -> Result<(), StorageError> { unimplemented!() } diff --git a/src/environmentd/src/test_util.rs b/src/environmentd/src/test_util.rs index 24d0d17749ba6..de1641ec2858d 100644 --- a/src/environmentd/src/test_util.rs +++ b/src/environmentd/src/test_util.rs @@ -232,10 +232,13 @@ impl Default for TestHarness { deploy_generation: 0, // This and startup_log_filter below are both (?) needed to suppress clusterd messages. // If we need those in the future, we might need to change both. - system_parameter_defaults: BTreeMap::from([( - "log_filter".to_string(), - "error".to_string(), - )]), + system_parameter_defaults: BTreeMap::from([ + ("log_filter".to_string(), "error".to_string()), + ( + "enable_catalog_read_protection".to_string(), + "true".to_string(), + ), + ]), internal_console_redirect_url: None, metrics_registry: None, orchestrator_tracing_cli_args: TracingCliArgs { diff --git a/src/environmentd/tests/server.rs b/src/environmentd/tests/server.rs index fce5218fa3ec3..a3b566520cecd 100644 --- a/src/environmentd/tests/server.rs +++ b/src/environmentd/tests/server.rs @@ -2759,6 +2759,12 @@ async fn migrated_builtin_mvs_readable_at_ready_to_promote(hydrate_migrated_mvs: .unsafe_mode() .data_directory(tmpdir.path()) .with_deploy_generation(1) + // Builtin schema migrations are unsupported in protected mode. Initialize unprotected + // from the first startup because the mode is latched at environment creation. + .with_system_parameter_default( + "enable_catalog_read_protection".to_string(), + "false".to_string(), + ) // Tick often, and tolerate far less lag than production, so the test finishes quickly and // actually exercises the gate. The allowed lag has to stay *below* the stability period: a // frozen collection looks caught up for as long as the tolerance lasts, so it must fall diff --git a/src/sql/src/session/vars.rs b/src/sql/src/session/vars.rs index 2e7d3b18086f0..e0fc269a432d2 100644 --- a/src/sql/src/session/vars.rs +++ b/src/sql/src/session/vars.rs @@ -1293,6 +1293,7 @@ impl SystemVars { &SENTRY_FILTERS, &WEBHOOKS_SECRETS_CACHING_TTL_SECS, &COORD_SLOW_MESSAGE_WARN_THRESHOLD, + &CATALOG_READ_PROTECTION_PUBLISH_INTERVAL, &grpc_client::CONNECT_TIMEOUT, &grpc_client::HTTP2_KEEP_ALIVE_INTERVAL, &grpc_client::HTTP2_KEEP_ALIVE_TIMEOUT, @@ -2225,6 +2226,10 @@ impl SystemVars { *self.expect_value(&COORD_SLOW_MESSAGE_WARN_THRESHOLD) } + pub fn catalog_read_protection_publish_interval(&self) -> Duration { + *self.expect_value(&CATALOG_READ_PROTECTION_PUBLISH_INTERVAL) + } + pub fn grpc_client_http2_keep_alive_interval(&self) -> Duration { *self.expect_value(&grpc_client::HTTP2_KEEP_ALIVE_INTERVAL) } diff --git a/src/sql/src/session/vars/definitions.rs b/src/sql/src/session/vars/definitions.rs index 2e8aeb5461dc7..407fe531dc17b 100644 --- a/src/sql/src/session/vars/definitions.rs +++ b/src/sql/src/session/vars/definitions.rs @@ -956,6 +956,16 @@ pub static COORD_SLOW_MESSAGE_WARN_THRESHOLD: VarDefinition = VarDefinition::new false, ); +pub static CATALOG_READ_PROTECTION_PUBLISH_INTERVAL: VarDefinition = VarDefinition::new( + "catalog_read_protection_publish_interval", + value!(Duration; Duration::from_secs(1)), + "Sets the minimum delay after a catalog read protection publication completes before \ + starting the next publication. Zero suspends publication and retains history without \ + releasing protection. Used to measure the publication cadence and history retention \ + tradeoff and to quiesce periodic metadata for consistency checks.", + false, +); + /// Controls the connect_timeout setting when connecting to PG via `mz_postgres_util`. pub static PG_SOURCE_CONNECT_TIMEOUT: VarDefinition = VarDefinition::new( "pg_source_connect_timeout", @@ -1854,6 +1864,14 @@ feature_flags!( enable_for_item_parsing: true, }, // Actual feature flags + // Sampled only when initializing a fresh catalog. Later toggles do not + // change the persisted mode or convert existing environments. + { + name: enable_catalog_read_protection, + desc: "Enable catalog-backed recovery protection when initializing a fresh environment.", + default: false, + enable_for_item_parsing: false, + }, { name: enable_binary_date_bin, desc: "the binary version of date_bin function", diff --git a/src/sqllogictest/src/runner.rs b/src/sqllogictest/src/runner.rs index f1f7648eb8612..79943dbd51e02 100644 --- a/src/sqllogictest/src/runner.rs +++ b/src/sqllogictest/src/runner.rs @@ -1377,6 +1377,10 @@ impl<'a> RunnerInner<'a> { "log_filter".to_string(), config.tracing.startup_log_filter.to_string(), ); + params.insert( + "enable_catalog_read_protection".to_string(), + "true".to_string(), + ); params.extend(config.system_parameter_defaults.clone()); params }, diff --git a/src/storage-client/src/storage_collections.rs b/src/storage-client/src/storage_collections.rs index 2d4953787780a..25a32cfe7a322 100644 --- a/src/storage-client/src/storage_collections.rs +++ b/src/storage-client/src/storage_collections.rs @@ -130,6 +130,12 @@ pub trait StorageCollections: Debug + Sync { /// capabilities. fn active_collection_frontiers(&self) -> Vec; + /// Snapshots local read requirements, excluding each collection's own writable permission cap. + /// Dependency-propagated capabilities remain included. This reserves no protection. + /// Callers must serialize new local read requirements with publication or reconcile them + /// before committing, and must also constrain proposals by durable read requirements. + fn compaction_frontiers(&self) -> BTreeMap>; + /// Checks whether a collection exists under the given `GlobalId`. Returns /// an error if the collection does not exist. fn check_exists(&self, id: GlobalId) -> Result<(), StorageError>; @@ -239,18 +245,18 @@ pub trait StorageCollections: Debug + Sync { /// inconsistent state. It is almost always wrong to do anything but abort /// the process on `Err`. /// - /// The `register_ts` is used as the initial timestamp that tables are - /// available for reads. (We might later give non-tables the same treatment, - /// but hold off on that initially.) Callers must provide a Some if any of - /// the collections is a table. A None may be given if none of the - /// collections are a table (i.e. all materialized views, sources, etc). + /// Pristine tables initialize since at the aggregate committed shard bound, + /// or at `register_ts` if the shard is ungoverned. Non-pristine tables retain + /// their persisted since. Callers must provide `Some(register_ts)` if any + /// collection is a table. /// /// `migrated_storage_collections` is a set of migrated storage collections to be excluded /// from the txn-wal sub-system. /// - /// Committed bounds must permit both the recovered since and requested initial - /// since. All governed aliases of a shard being created must be present in - /// this batch or already registered. + /// In writable mode, committed bounds must permit both the recovered since + /// and the primary's requested initial since. Read-only readability comes + /// from leased handles, not snapshot bounds. All governed aliases of a shard + /// being created must be present in this batch or already registered. async fn create_collections_for_bootstrap( &self, storage_metadata: &StorageMetadata, @@ -307,6 +313,7 @@ pub trait StorageCollections: Debug + Sync { /// Applies committed, monotonic compaction permissions to already-governed collections. /// Validates the whole batch before applying it. Omitted IDs retain their bounds. /// The caller must commit these permissions durably before calling this method. + /// Read-only controllers do not apply permission, so this is a no-op for them. fn apply_compaction_bounds( &self, bounds: BTreeMap>, @@ -529,6 +536,7 @@ impl StorageCollectionsImpl { finalizable_shards: Arc::clone(&finalizable_shards), shard_by_id: BTreeMap::new(), since_handles: BTreeMap::new(), + pending_since_downgrades: BTreeMap::new(), txns_handle: Some(txns_write), txns_shards: Default::default(), }; @@ -1457,6 +1465,23 @@ impl StorageCollections for StorageCollectionsImpl { res } + fn compaction_frontiers(&self) -> BTreeMap> { + let collections = self.collections.lock().expect("lock poisoned"); + collections + .iter() + .filter(|(_, collection)| !collection.is_dropped()) + .map(|(id, collection)| { + let mut capabilities = collection.read_capabilities.clone(); + if !self.read_only + && let Some(bound) = &collection.compaction_bound + { + capabilities.update_iter(bound.iter().map(|time| (*time, -1))); + } + (*id, capabilities.frontier().to_owned()) + }) + .collect() + } + async fn snapshot_stats( &self, id: GlobalId, @@ -1824,6 +1849,11 @@ impl StorageCollections for StorageCollectionsImpl { } } for (id, description) in &collections { + // Secondary descriptions express visibility, not permission to compact + // the shared shard. Only the primary passes its since to open_data_handles. + if self.read_only || description.primary.is_some() { + continue; + } let shard = storage_metadata.get_collection_shard(*id)?; if let Some(bound) = shard_bounds.get(&shard) { if description @@ -1904,53 +1934,61 @@ impl StorageCollections for StorageCollectionsImpl { ) .await; - if let Some(bound) = shard_bounds.get(&metadata.data_shard) { + if !this.read_only + && let Some(bound) = shard_bounds.get(&metadata.data_shard) + { if !PartialOrder::less_equal(since_handle.since(), bound) { return Err(StorageError::ReadBeforeSince(id)); } } - // Present tables as springing into existence at the register_ts - // by advancing the since. Otherwise, we could end up in a - // situation where a table with a long compaction window appears - // to exist before the environment (and this the table) existed. - // - // We could potentially also do the same thing for other - // sources, in particular storage's internal sources and perhaps - // others, but leave them for now. - match description.data_source { + let initial_since = match description.data_source { DataSource::Introspection(_) | DataSource::IngestionExport { .. } | DataSource::Webhook | DataSource::Ingestion(_) | DataSource::Progress - | DataSource::Other => {} - DataSource::Sink { .. } => {} + | DataSource::Sink { .. } => None, + DataSource::Other => { + if description.since.is_none() + && write.upper().elements() == &[Timestamp::MIN] + { + shard_bounds.get(&metadata.data_shard).cloned() + } else { + None + } + } DataSource::Table => { let register_ts = register_ts.expect( "caller should have provided a register_ts when creating a table", ); - if since_handle.since().elements() == &[Timestamp::MIN] - && !migrated_storage_collections.contains(&id) - { - debug!("advancing {} to initial since of {:?}", id, register_ts); - let initial_since = Antichain::from_elem(register_ts); - if shard_bounds.get(&metadata.data_shard).is_some_and(|bound| { - !PartialOrder::less_equal(&initial_since, bound) - }) { - return Err(StorageError::ReadBeforeSince(id)); - } - let token = since_handle.opaque(); - // NOTE: A failed compare refreshes the handle's cached - // token. It must not be reused as the other owner. - let _ = since_handle - .compare_and_downgrade_since(&token, (&token, &initial_since)) - .await - .unwrap_or_else(|epoch| { - mz_ore::halt!("fenced by envd @ {epoch:?}"); - }); - } + Some( + shard_bounds + .get(&metadata.data_shard) + .cloned() + .unwrap_or_else(|| Antichain::from_elem(register_ts)), + ) } + }; + if let Some(initial_since) = initial_since + && description.primary.is_none() + && since_handle.since().elements() == &[Timestamp::MIN] + && !migrated_storage_collections.contains(&id) + { + // Permission cannot advance before installation. It also provides + // the birth frontier for builtin MVs without a SQL AS OF. A later + // table registration must not override it after a crash before init. + // Preserve non-pristine sinces until recovery restores read holds. + debug!("advancing {} to initial since of {:?}", id, initial_since); + let token = since_handle.opaque(); + // NOTE: A failed compare refreshes the handle's cached + // token. It must not be reused as the other owner. + let _ = since_handle + .compare_and_downgrade_since(&token, (&token, &initial_since)) + .await + .unwrap_or_else(|epoch| { + mz_ore::halt!("fenced by envd @ {epoch:?}"); + }); } Ok::<_, StorageError>((id, description, write, since_handle, metadata)) @@ -2123,12 +2161,17 @@ impl StorageCollections for StorageCollectionsImpl { metadata.clone(), ); if let Some(bound) = storage_metadata.compaction_bounds.get(&id) { - if !PartialOrder::less_equal(&collection_state.implied_capability, bound) { - return Err(StorageError::ReadBeforeSince(id)); + // A read-only savepoint can lag actual compaction. Installing its + // bound could manufacture a capability before the leased since. + // Promotion reboots with fresh writable controllers. + if !self.read_only { + if !PartialOrder::less_equal(&collection_state.implied_capability, bound) { + return Err(StorageError::ReadBeforeSince(id)); + } + collection_state + .read_capabilities + .update_iter(bound.iter().map(|t| (*t, 1))); } - collection_state - .read_capabilities - .update_iter(bound.iter().map(|t| (*t, 1))); collection_state.compaction_bound = Some(bound.clone()); } @@ -2217,7 +2260,9 @@ impl StorageCollections for StorageCollectionsImpl { "missing compaction bound for {new_collection}" ))); } - if let Some(bound) = new_bound { + if !self.read_only + && let Some(bound) = new_bound + { if !PartialOrder::less_equal( &existing.read_capabilities.frontier().to_owned(), bound, @@ -2359,9 +2404,11 @@ impl StorageCollections for StorageCollectionsImpl { collection_meta, ); if let Some(bound) = new_bound { - collection_state - .read_capabilities - .update_iter(bound.iter().map(|t| (*t, 1))); + if !self.read_only { + collection_state + .read_capabilities + .update_iter(bound.iter().map(|t| (*t, 1))); + } collection_state.compaction_bound = Some(bound.clone()); } @@ -2448,7 +2495,9 @@ impl StorageCollections for StorageCollectionsImpl { finalized_policies.push((id, ReadPolicy::ValidFrom(Antichain::new()))); let collection = self_collections.get_mut(&id).expect("checked above"); - if let Some(bound) = collection.compaction_bound.take() { + if let Some(bound) = collection.compaction_bound.take() + && !self.read_only + { let mut changes = ChangeBatch::new(); changes.extend(bound.iter().map(|t| (*t, -1))); bound_releases.insert(id, changes); @@ -2503,6 +2552,9 @@ impl StorageCollections for StorageCollectionsImpl { &self, bounds: BTreeMap>, ) -> Result<(), StorageError> { + if self.read_only { + return Ok(()); + } let mut collections = self.collections.lock().expect("lock poisoned"); for (id, bound) in &bounds { let collection = collections @@ -2786,8 +2838,8 @@ struct CollectionState { /// Accumulation of read capabilities for the collection. /// - /// This accumulation contains `self.implied_capability`, any committed - /// compaction bound, and capabilities held by readers of this collection. + /// This accumulation contains `self.implied_capability`, capabilities held by + /// readers, and, in writable mode, the committed compaction bound. pub read_capabilities: MutableAntichain, /// The implicit capability associated with collection creation. This @@ -2798,7 +2850,8 @@ struct CollectionState { /// The policy to use to downgrade `self.implied_capability`. pub read_policy: ReadPolicy, - /// Independent permission capability, released only by an authorized drop. + /// Saved permission, retained for metadata consistency checks in both modes. + /// In writable mode it also holds a capability until an authorized drop. pub compaction_bound: Option>, /// Storage identifiers on which this collection depends. @@ -2863,6 +2916,8 @@ struct BackgroundTask { // when re-enqueing futures for determining the next upper update. shard_by_id: BTreeMap, since_handles: BTreeMap, + /// Finite targets refused by persist's rate limiter, retained until delivered or superseded. + pending_since_downgrades: BTreeMap>, txns_handle: Option>, txns_shards: BTreeSet, } @@ -2934,8 +2989,17 @@ impl BackgroundTask { None => async { std::future::pending().await }.boxed(), }; + // Persist owns the write rate limit. This timer bounds the delay before + // retrying an eligible target when no new frontier changes arrive. + let mut since_retry_interval = tokio::time::interval(Duration::from_secs(1)); + since_retry_interval.set_missed_tick_behavior(MissedTickBehavior::Delay); + loop { tokio::select! { + _ = since_retry_interval.tick(), if !self.pending_since_downgrades.is_empty() => { + let downgrades = std::mem::take(&mut self.pending_since_downgrades); + self.downgrade_sinces(downgrades).await; + } (id, handle, upper) = &mut txns_upper_future => { trace!("new upper from txns shard: {:?}", upper); let mut uppers = Vec::new(); @@ -2997,6 +3061,26 @@ impl BackgroundTask { panic!("already registered a WriteHandle for collection {id}"); } + { + let collections = self.collections.lock().expect("lock poisoned"); + if let Some(collection) = collections.get(&id) + && collection.primary.is_none() + { + // A primary handoff can inherit a frontier ahead of persist + // without a frontier change to trigger delivery. Use the + // effective frontier, which includes the bootstrap implicit + // capability and compaction bound, not just the bound. + let frontier = collection.read_capabilities + .frontier().to_owned(); + let since = since_handle.since(); + if PartialOrder::less_than(since, &frontier) { + downgrades.entry(id) + .and_modify(|since| since.join_assign(&frontier)) + .or_insert(frontier); + } + } + } + let previous = self.since_handles.insert(id, since_handle); if previous.is_some() { panic!("already registered a SinceHandle for collection {id}"); @@ -3149,48 +3233,64 @@ impl BackgroundTask { async fn downgrade_sinces(&mut self, cmds: BTreeMap>) { // Process all persist calls concurrently. let mut futures = Vec::with_capacity(cmds.len()); - for (id, new_since) in cmds { - // We need to take the since handles here, to satisfy the borrow checker. - // We make sure to always put them back below. - let Some(mut since_handle) = self.since_handles.remove(&id) else { - // This can happen when someone concurrently drops a collection. - trace!("downgrade_sinces: reference to absent collection {id}"); - continue; - }; - - let fut = async move { - if id.is_user() { - trace!("downgrading since of {} to {:?}", id, new_since); + { + let collections = self.collections.lock().expect("lock poisoned"); + for (id, mut new_since) in cmds { + if let Some(pending) = self.pending_since_downgrades.remove(&id) { + new_since.join_assign(&pending); + } + // A pending target can outlive primary ownership after schema evolution. + // Empty targets arrive after collection state has been removed. + if !new_since.is_empty() + && !collections.get(&id).is_some_and(|c| c.primary.is_none()) + { + continue; } + // We need to take the since handles here, to satisfy the borrow checker. + // We make sure to always put them back below. + let Some(mut since_handle) = self.since_handles.remove(&id) else { + // This can happen when someone concurrently drops a collection. + trace!("downgrade_sinces: reference to absent collection {id}"); + continue; + }; + + let fut = async move { + if id.is_user() { + trace!("downgrading since of {} to {:?}", id, new_since); + } - let epoch = since_handle.opaque().clone(); - let result = if new_since.is_empty() { - // A shard's since reaching the empty frontier is a prereq for - // being able to finalize a shard, so the final downgrade should - // never be rate-limited. - Some( + let epoch = since_handle.opaque().clone(); + let result = if new_since.is_empty() { + // A shard's since reaching the empty frontier is a prereq for + // being able to finalize a shard, so the final downgrade should + // never be rate-limited. + Some( + since_handle + .compare_and_downgrade_since(&epoch, (&epoch, &new_since)) + .await, + ) + } else { since_handle - .compare_and_downgrade_since(&epoch, (&epoch, &new_since)) - .await, - ) - } else { - since_handle - .maybe_compare_and_downgrade_since(&epoch, (&epoch, &new_since)) - .await + .maybe_compare_and_downgrade_since(&epoch, (&epoch, &new_since)) + .await + }; + (id, since_handle, new_since, result) }; - (id, since_handle, result) - }; - futures.push(fut); + futures.push(fut); + } } - for (id, since_handle, result) in futures::future::join_all(futures).await { + for (id, since_handle, target, result) in futures::future::join_all(futures).await { let new_since = match result { Some(Ok(since)) => Some(since), Some(Err(other_epoch)) => mz_ore::halt!( "fenced by envd @ {other_epoch:?}. ours = {:?}", since_handle.opaque(), ), - None => None, + None => { + self.pending_since_downgrades.insert(id, target); + None + } }; self.since_handles.insert(id, since_handle); @@ -3510,14 +3610,20 @@ mod tests { } async fn bound_test_controller() -> (StorageCollectionsImpl, PersistClient) { + bound_test_controller_with_clock(Duration::ZERO, SYSTEM_TIME.clone()).await + } + + async fn bound_test_controller_with_clock( + critical_downgrade_interval: Duration, + now: NowFn, + ) -> (StorageCollectionsImpl, PersistClient) { let location = PersistLocation { blob_uri: SensitiveUrl::from_str("mem://").unwrap(), consensus_uri: SensitiveUrl::from_str("mem://").unwrap(), }; let registry = MetricsRegistry::new(); - let mut config = PersistConfig::new_default_configs(&DUMMY_BUILD_INFO, SYSTEM_TIME.clone()); - // Exercise actual since downgrades without waiting for persist's rate limit. - config.critical_downgrade_interval = Duration::ZERO; + let mut config = PersistConfig::new_default_configs(&DUMMY_BUILD_INFO, now.clone()); + config.critical_downgrade_interval = critical_downgrade_interval; let cache = Arc::new(PersistClientCache::new(config, ®istry, |_, _| { PubSubClientConnection::noop() })); @@ -3526,7 +3632,7 @@ mod tests { location, cache, ®istry, - SYSTEM_TIME.clone(), + now, Arc::new(TxnMetrics::new(®istry)), NonZeroI64::new(1).unwrap(), false, @@ -3537,6 +3643,24 @@ mod tests { (controller, persist) } + async fn bound_test_read_only_controller( + leader: &StorageCollectionsImpl, + ) -> StorageCollectionsImpl { + let registry = MetricsRegistry::new(); + StorageCollectionsImpl::new( + leader.persist_location.clone(), + Arc::clone(&leader.persist), + ®istry, + SYSTEM_TIME.clone(), + Arc::new(TxnMetrics::new(®istry)), + NonZeroI64::new(2).unwrap(), + true, + ConnectionContext::for_tests(Arc::new(InMemorySecretsController::new())), + &TestTxn(*leader.txns_read.txns_id()), + ) + .await + } + fn frontier(ts: u64) -> Antichain { Antichain::from_elem(ts.into()) } @@ -3610,6 +3734,7 @@ mod tests { controller .apply_compaction_bounds(BTreeMap::from([(id, frontier(10))])) .unwrap(); + assert_eq!(controller.compaction_frontiers()[&id], frontier(5)); assert_eq!( controller .collection_frontiers(id) @@ -3620,6 +3745,7 @@ mod tests { drop(holds); await_frontier(&controller, id, frontier(10)).await; await_persist_since(&persist, shard, frontier(10)).await; + assert_eq!(controller.compaction_frontiers()[&id], frontier(30)); assert_err!(controller.apply_compaction_bounds(BTreeMap::from([(id, frontier(9))]))); assert_err!(controller.apply_compaction_bounds(BTreeMap::from([ (id, frontier(15)), @@ -3640,6 +3766,128 @@ mod tests { writer.expire().await; } + #[mz_ore::test(tokio::test(start_paused = true))] + async fn test_rate_limited_compaction_bound_retried_without_frontier_changes() { + for handoff in [false, true] { + // Persist's rate limiter and the worker's retry timer must share a clock. + let start = tokio::time::Instant::now(); + let epoch = SYSTEM_TIME(); + let now = + NowFn::from(move || epoch + u64::try_from(start.elapsed().as_millis()).unwrap()); + let (controller, persist) = + bound_test_controller_with_clock(Duration::from_secs(30), now).await; + let id = GlobalId::User(1); + let shard = ShardId::new(); + persist + .register_schema::( + shard, + &RelationDesc::empty(), + &UnitSchema, + Diagnostics::for_tests(), + ) + .await + .unwrap() + .expect("initial schema"); + let mut metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(id, shard)]), + compaction_bounds: BTreeMap::from([(id, frontier(5))]), + ..Default::default() + }; + let description = if handoff { + CollectionDescription::for_table(RelationDesc::empty()) + } else { + CollectionDescription::for_other(RelationDesc::empty(), None) + }; + controller + .create_collections_for_bootstrap( + &metadata, + Some(0.into()), + vec![(id, description)], + &BTreeSet::new(), + ) + .await + .unwrap(); + let persist_since = || async { + persist + .open_critical_since::( + shard, + PersistClient::CONTROLLER_CRITICAL_SINCE, + Opaque::encode(&PersistEpoch::default()), + Diagnostics::for_tests(), + ) + .await + .unwrap() + .since() + .clone() + }; + assert_eq!(persist_since().await, frontier(5)); + controller.set_read_policies(vec![(id, ReadPolicy::ValidFrom(frontier(20)))]); + controller + .apply_compaction_bounds(BTreeMap::from([(id, frontier(10))])) + .unwrap(); + tokio::time::sleep(Duration::from_secs(5)).await; + assert_eq!(persist_since().await, frontier(5)); + + controller + .apply_compaction_bounds(BTreeMap::from([(id, frontier(15))])) + .unwrap(); + tokio::time::sleep(Duration::from_secs(5)).await; + assert_eq!(persist_since().await, frontier(5)); + assert_eq!( + controller + .collection_frontiers(id) + .unwrap() + .read_capabilities, + frontier(15) + ); + + if handoff { + let new = GlobalId::User(2); + metadata.compaction_bounds.insert(id, frontier(15)); + metadata.collection_metadata.insert(new, shard); + metadata.compaction_bounds.insert(new, frontier(20)); + controller + .alter_table_desc( + &metadata, + id, + new, + RelationDesc::empty(), + RelationVersion::root(), + ) + .await + .unwrap(); + // The inherited implicit capability and dependency hold are both + // at 15, so installing the dependency emits no frontier change. + assert_eq!( + controller + .collection_frontiers(new) + .unwrap() + .read_capabilities, + frontier(15) + ); + tokio::time::sleep(Duration::from_secs(5)).await; + assert_eq!(persist_since().await, frontier(5)); + } + + // No further frontier changes or commands drive the downgrade. + tokio::time::timeout(Duration::from_secs(40), async { + loop { + let since = persist_since().await; + assert!( + PartialOrder::less_equal(&since, &frontier(15)), + "persist compacted beyond permission" + ); + if since == frontier(15) { + break; + } + tokio::time::sleep(Duration::from_secs(1)).await; + } + }) + .await + .expect("rate-limited persist since did not converge without frontier changes"); + } + } + async fn await_persist_since( persist: &PersistClient, shard: ShardId, @@ -3670,22 +3918,293 @@ mod tests { .expect("persist since did not converge"); } + #[mz_ore::test(tokio::test)] + async fn test_read_only_stale_compaction_bound_uses_leased_readability() { + for requested_since in [None, Some(frontier(20))] { + let (leader, persist) = bound_test_controller().await; + let id = GlobalId::User(1); + let shard = ShardId::new(); + let mut metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(id, shard)]), + compaction_bounds: BTreeMap::from([(id, frontier(20))]), + ..Default::default() + }; + leader + .create_collections_for_bootstrap( + &metadata, + None, + vec![( + id, + CollectionDescription::for_other(RelationDesc::empty(), None), + )], + &BTreeSet::new(), + ) + .await + .unwrap(); + await_persist_since(&persist, shard, frontier(20)).await; + let mut writer = persist + .open_writer::( + shard, + Arc::new(RelationDesc::empty()), + Arc::new(UnitSchema), + Diagnostics::for_tests(), + ) + .await + .unwrap(); + writer + .compare_and_append( + &[((SourceData(Ok(Row::default())), ()), Timestamp::new(20), 1)], + frontier(0), + frontier(40), + ) + .await + .unwrap() + .unwrap(); + + let reader = bound_test_read_only_controller(&leader).await; + metadata.compaction_bounds.insert(id, frontier(10)); + reader + .create_collections_for_bootstrap( + &metadata, + None, + vec![( + id, + CollectionDescription::for_other(RelationDesc::empty(), requested_since), + )], + &BTreeSet::new(), + ) + .await + .unwrap(); + let holds = reader.acquire_read_holds(vec![id]).unwrap(); + assert_eq!(holds[0].since(), &frontier(20)); + assert!(matches!( + reader.snapshot_stats(id, frontier(10)).await, + Err(StorageError::ReadBeforeSince(found)) if found == id + )); + + leader.set_read_policies(vec![(id, ReadPolicy::ValidFrom(frontier(30)))]); + leader + .apply_compaction_bounds(BTreeMap::from([(id, frontier(30))])) + .unwrap(); + await_persist_since(&persist, shard, frontier(30)).await; + reader + .apply_compaction_bounds(BTreeMap::from([(id, frontier(30))])) + .unwrap(); + assert_eq!( + reader.collection_frontiers(id).unwrap().read_capabilities, + frontier(20) + ); + // The leader's critical since has advanced, but the read-only lease + // must still protect the actual persist snapshot at 20. + assert_eq!( + reader + .snapshot_stats(id, frontier(20)) + .await + .unwrap() + .num_updates, + 1 + ); + + reader.drop_collections_unvalidated(&StorageMetadata::default(), vec![id]); + assert_eq!( + reader.collection_frontiers(id).unwrap().read_capabilities, + frontier(20) + ); + drop(holds); + tokio::time::timeout(Duration::from_secs(10), async { + while reader.collection_frontiers(id).is_ok() { + tokio::task::yield_now().await; + } + }) + .await + .unwrap(); + writer.expire().await; + } + } + + #[mz_ore::test(tokio::test)] + async fn test_read_only_stale_compaction_bound_preserves_dropped_since() { + let (leader, persist) = bound_test_controller().await; + let id = GlobalId::User(1); + let shard = ShardId::new(); + let mut metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(id, shard)]), + compaction_bounds: BTreeMap::from([(id, frontier(20))]), + ..Default::default() + }; + let collections = vec![( + id, + CollectionDescription::for_other(RelationDesc::empty(), None), + )]; + leader + .create_collections_for_bootstrap( + &metadata, + None, + collections.clone(), + &BTreeSet::new(), + ) + .await + .unwrap(); + leader.drop_collections_unvalidated(&StorageMetadata::default(), vec![id]); + await_persist_since(&persist, shard, Antichain::new()).await; + + let reader = bound_test_read_only_controller(&leader).await; + metadata.compaction_bounds.insert(id, frontier(10)); + reader + .create_collections_for_bootstrap(&metadata, None, collections, &BTreeSet::new()) + .await + .unwrap(); + assert!( + reader + .collection_frontiers(id) + .unwrap() + .read_capabilities + .is_empty() + ); + assert!( + reader.acquire_read_holds(vec![id]).unwrap()[0] + .since() + .is_empty() + ); + assert!(reader.active_collection_frontiers().is_empty()); + } + + #[mz_ore::test(tokio::test)] + async fn test_read_only_compaction_bound_initializes_pristine_lease() { + for description in [ + CollectionDescription::for_table(RelationDesc::empty()), + CollectionDescription::for_other(RelationDesc::empty(), None), + ] { + let (leader, persist) = bound_test_controller().await; + let reader = bound_test_read_only_controller(&leader).await; + let id = GlobalId::User(1); + let shard = ShardId::new(); + let metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(id, shard)]), + compaction_bounds: BTreeMap::from([(id, frontier(10))]), + ..Default::default() + }; + await_persist_since(&persist, shard, frontier(0)).await; + reader + .create_collections_for_bootstrap( + &metadata, + Some(100.into()), + vec![(id, description)], + &BTreeSet::new(), + ) + .await + .unwrap(); + assert_eq!( + reader.acquire_read_holds(vec![id]).unwrap()[0].since(), + &frontier(10) + ); + await_persist_since(&persist, shard, frontier(0)).await; + reader.set_read_policies(vec![(id, ReadPolicy::ValidFrom(frontier(20)))]); + await_frontier(&reader, id, frontier(20)).await; + } + } + + #[mz_ore::test(tokio::test)] + async fn test_read_only_alter_table_desc_uses_leased_readability() { + let (leader, persist) = bound_test_controller().await; + let old = GlobalId::User(1); + let new = GlobalId::User(2); + let shard = ShardId::new(); + persist + .register_schema::( + shard, + &RelationDesc::empty(), + &UnitSchema, + Diagnostics::for_tests(), + ) + .await + .unwrap() + .expect("initial schema"); + let mut metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(old, shard)]), + compaction_bounds: BTreeMap::from([(old, frontier(20))]), + ..Default::default() + }; + let collections = vec![(old, CollectionDescription::for_table(RelationDesc::empty()))]; + leader + .create_collections_for_bootstrap( + &metadata, + Some(100.into()), + collections.clone(), + &BTreeSet::new(), + ) + .await + .unwrap(); + await_persist_since(&persist, shard, frontier(20)).await; + let reader = bound_test_read_only_controller(&leader).await; + metadata.compaction_bounds.insert(old, frontier(10)); + reader + .create_collections_for_bootstrap( + &metadata, + Some(100.into()), + collections, + &BTreeSet::new(), + ) + .await + .unwrap(); + metadata.collection_metadata.insert(new, shard); + assert!(matches!( + reader + .alter_table_desc( + &metadata, + old, + new, + RelationDesc::empty(), + RelationVersion::root(), + ) + .await, + Err(StorageError::InvalidUsage(_)) + )); + metadata.compaction_bounds.insert(new, frontier(10)); + metadata.collection_metadata.insert(new, ShardId::new()); + assert!(matches!( + reader + .alter_table_desc( + &metadata, + old, + new, + RelationDesc::empty(), + RelationVersion::root(), + ) + .await, + Err(StorageError::InvalidUsage(_)) + )); + metadata.collection_metadata.insert(new, shard); + reader + .alter_table_desc( + &metadata, + old, + new, + RelationDesc::empty(), + RelationVersion::root(), + ) + .await + .unwrap(); + for hold in reader.acquire_read_holds(vec![old, new]).unwrap() { + assert_eq!(hold.since(), &frontier(20)); + } + reader.set_read_policies(vec![ + (old, ReadPolicy::ValidFrom(frontier(30))), + (new, ReadPolicy::ValidFrom(frontier(30))), + ]); + await_frontier(&reader, new, frontier(30)).await; + await_persist_since(&persist, shard, frontier(20)).await; + } + #[mz_ore::test(tokio::test)] async fn test_committed_compaction_bound_recovery_validation() { - for (description, register_ts, recovered_since) in [ + for (description, recovered_since) in [ ( CollectionDescription::for_other(RelationDesc::empty(), Some(frontier(6))), None, - None, - ), - ( - CollectionDescription::for_table(RelationDesc::empty()), - Some(6.into()), - None, ), ( CollectionDescription::for_other(RelationDesc::empty(), None), - None, Some(frontier(6)), ), ] { @@ -3716,7 +4235,7 @@ mod tests { let result = controller .create_collections_for_bootstrap( &metadata, - register_ts, + None, vec![(id, description)], &BTreeSet::new(), ) @@ -3725,6 +4244,40 @@ mod tests { } } + #[mz_ore::test(tokio::test)] + async fn test_committed_compaction_bound_crash_before_table_init() { + for bound in [Some(frontier(5)), None] { + let id = GlobalId::User(1); + let shard = ShardId::new(); + // Catalog birth committed, but no collection or persist handle was + // installed before the crash. Recovery registers at a later timestamp. + let metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(id, shard)]), + compaction_bounds: bound.clone().map(|b| (id, b)).into_iter().collect(), + ..Default::default() + }; + let (controller, persist) = bound_test_controller().await; + controller + .create_collections_for_bootstrap( + &metadata, + Some(100.into()), + vec![(id, CollectionDescription::for_table(RelationDesc::empty()))], + &BTreeSet::new(), + ) + .await + .unwrap(); + let expected = bound.unwrap_or_else(|| frontier(100)); + assert_eq!( + controller + .collection_frontiers(id) + .unwrap() + .read_capabilities, + expected + ); + await_persist_since(&persist, shard, expected).await; + } + } + #[mz_ore::test(tokio::test)] async fn test_committed_compaction_bound_recovers_table_history() { let (controller, persist) = bound_test_controller().await; @@ -3759,6 +4312,14 @@ mod tests { ) .await .unwrap(); + assert_eq!( + controller + .collection_frontiers(id) + .unwrap() + .read_capabilities, + frontier(3) + ); + await_persist_since(&persist, shard, frontier(3)).await; controller.set_read_policies(vec![(id, ReadPolicy::ValidFrom(frontier(20)))]); await_frontier(&controller, id, frontier(5)).await; await_persist_since(&persist, shard, frontier(5)).await; @@ -3918,6 +4479,65 @@ mod tests { await_persist_since(&persist, shard, Antichain::new()).await; } + #[mz_ore::test(tokio::test)] + async fn test_committed_compaction_bound_alias_visibility() { + for alias_bound in [frontier(10), frontier(4)] { + let (controller, persist) = bound_test_controller().await; + let primary = GlobalId::User(2); + let alias = GlobalId::User(1); + let shard = ShardId::new(); + let mut metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(primary, shard)]), + compaction_bounds: BTreeMap::from([(primary, frontier(5))]), + ..Default::default() + }; + controller + .create_collections_for_bootstrap( + &metadata, + Some(5.into()), + vec![( + primary, + CollectionDescription::for_table(RelationDesc::empty()), + )], + &BTreeSet::new(), + ) + .await + .unwrap(); + metadata.collection_metadata.insert(alias, shard); + metadata + .compaction_bounds + .insert(alias, alias_bound.clone()); + let mut description = CollectionDescription::for_table(RelationDesc::empty()); + description.primary = Some(primary); + description.since = Some(frontier(10)); + let result = controller + .create_collections_for_bootstrap( + &metadata, + Some(100.into()), + vec![(alias, description)], + &BTreeSet::new(), + ) + .await; + if alias_bound == frontier(4) { + assert!(matches!(result, Err(StorageError::ReadBeforeSince(id)) if id == alias)); + continue; + } + result.unwrap(); + controller.set_read_policies(vec![ + (primary, ReadPolicy::ValidFrom(frontier(30))), + (alias, ReadPolicy::ValidFrom(frontier(30))), + ]); + assert_eq!( + controller + .collection_frontiers(primary) + .unwrap() + .read_capabilities, + frontier(5) + ); + await_persist_since(&persist, shard, frontier(5)).await; + } + } + #[mz_ore::test(tokio::test)] async fn test_committed_compaction_bound_bootstrap_aliases() { let (controller, _) = bound_test_controller().await; @@ -3954,10 +4574,11 @@ mod tests { .await; assert!(matches!(result, Err(StorageError::IdentifierMissing(id)) if id == old)); let (controller, persist) = bound_test_controller().await; + primary_desc.since = None; controller .create_collections_for_bootstrap( &metadata, - Some(0.into()), + Some(100.into()), vec![(old, old_desc), (primary, primary_desc)], &BTreeSet::new(), ) @@ -4161,6 +4782,7 @@ mod tests { collections: Arc::new(Mutex::new(BTreeMap::new())), shard_by_id: BTreeMap::new(), since_handles: BTreeMap::new(), + pending_since_downgrades: BTreeMap::new(), txns_handle: None, txns_shards: BTreeSet::new(), }; diff --git a/src/testdrive/src/action/consistency.rs b/src/testdrive/src/action/consistency.rs index c84f25a01d379..83fbf7aa0fe97 100644 --- a/src/testdrive/src/action/consistency.rs +++ b/src/testdrive/src/action/consistency.rs @@ -77,9 +77,9 @@ pub fn skip_consistency_checks( /// dumps of it, so it costs far more than a query and scales with the size of /// the catalog, not with how long a single query may take. It bounds a hang, /// so it sits far above what a healthy check costs on a loaded agent. -async fn with_deadline(state: &State, check: F) -> Result<(), anyhow::Error> +async fn with_deadline(state: &State, check: F) -> Result where - F: Future>, + F: Future>, { let deadline = state.consistency_check_timeout; match tokio::time::timeout(deadline, check).await { @@ -99,7 +99,7 @@ pub async fn run_consistency_checks(state: &State) -> Result Result<(), anyhow::Error> { } } +/// Quiesces periodic protection publication while comparing independently acquired snapshots. +async fn check_catalog_state_quiesced(state: &State) -> Result<(), anyhow::Error> { + use crate::util::postgres::postgres_client; + use mz_postgres_util::{PostgresError, batch_execute, query_opt}; + use tokio_postgres::error::SqlState; + + if state.materialize.catalog_config.is_none() { + return with_deadline(state, check_catalog_state(state)).await; + } + + let url = format!( + "postgres://mz_system:materialize@{}", + state.materialize.internal_sql_addr + ); + let (client, _handle) = with_deadline(state, async { + postgres_client(&url, state.default_timeout).await + }) + .await?; + let supported = with_deadline(state, async { + Ok(query_one( + &client, + sql!("SHOW catalog_read_protection_publish_interval"), + &[], + ) + .await) + }) + .await?; + match supported { + Err(PostgresError::Postgres(error)) + if error.code() == Some(&SqlState::UNDEFINED_OBJECT) => + { + return with_deadline(state, check_catalog_state(state)).await; + } + Err(error) => return Err(error.into()), + Ok(_) => {} + } + let read_only = with_deadline(state, async { + let dump: serde_json::Value = reqwest::get(format!( + "http://{}/api/coordinator/dump", + state.materialize.internal_http_addr + )) + .await? + .error_for_status()? + .json() + .await?; + dump.pointer("/controller/read_only") + .and_then(serde_json::Value::as_bool) + .ok_or_else(|| anyhow!("coordinator dump is missing controller read-only mode")) + }) + .await?; + if read_only { + return with_deadline(state, check_catalog_state(state)).await; + } + let original = with_deadline(state, async { + Ok(query_opt( + &client, + sql!( + "SELECT data->'value'->>'value' FROM mz_internal.mz_catalog_raw + WHERE data->>'kind' = 'SystemConfiguration' + AND data->'key'->>'name' = 'catalog_read_protection_publish_interval'" + ), + &[], + ) + .await? + .map(|row| row.get::<_, String>(0))) + }) + .await?; + + // Like the schema comparison itself, this assumes no concurrent DDL or + // consistency checker. SET completes on the coordinator after any in-flight + // publication. Existing bounds continue protecting history while paused. + let paused = with_deadline(state, async { + batch_execute( + &client, + sql!("ALTER SYSTEM SET catalog_read_protection_publish_interval = '0s'"), + ) + .await + .map_err(Into::into) + }) + .await; + let result = match paused { + Ok(()) => with_deadline(state, check_catalog_state(state)).await, + Err(error) => Err(error), + }; + // Restore outside the timed check, including on timeout or comparison failure. + let restored = with_deadline(state, async { + match original { + Some(value) => { + batch_execute( + &client, + sql!( + "ALTER SYSTEM SET catalog_read_protection_publish_interval = {}", + mz_postgres_util::Sql::literal(&value) + ), + ) + .await? + } + None => { + batch_execute( + &client, + sql!("ALTER SYSTEM RESET catalog_read_protection_publish_interval"), + ) + .await? + } + } + Ok(()) + }) + .await; + match (result, restored) { + (result, Ok(())) => result, + (Ok(()), Err(error)) => Err(error.context("restoring publication interval")), + (Err(check), Err(restore)) => Err(anyhow!( + "{check:#}\nrestoring publication interval: {restore:#}" + )), + } +} + /// Checks that the in-memory catalog matches what we have persisted on disk. async fn check_catalog_state(state: &State) -> Result<(), anyhow::Error> { #[derive(Debug, Deserialize)] diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index c2af3c4798ac1..7f0e8a3f6c4fb 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -16,6 +16,7 @@ import copy import json import time +from collections.abc import Callable from datetime import datetime from textwrap import dedent @@ -973,6 +974,496 @@ def verify() -> None: c.sql("DROP CLUSTER uncached_refresh", reuse_connection=False) +def workflow_catalog_read_protection(c: Composition) -> None: + """Exercise logical recovery protection and persist compaction without cached plans.""" + c.down(destroy_volumes=True) + + def query(sql: str) -> list[tuple]: + # Observations must not leave a transaction or a blocked peek holding history. + with c.sql_connection( + port=6877, + user="mz_system", + startup_params={"statement_timeout": "5s"}, + ) as conn: + cursor = conn.execute(sql.encode()) + return cursor.fetchall() if cursor.description is not None else [] + + def td(sql: str, timeout: float = 120) -> None: + c.testdrive( + service="testdrive_no_reset", + input=f"$ set-sql-timeout duration={timeout:.3f}s force=true\n" + "> SET statement_timeout = '5s';\n" + dedent(sql), + ) + + item_ids: dict[str, str] = {} + + def gid(name: str) -> str: + [(item_id, global_id)] = query(f""" + SELECT o.id, g.global_id FROM mz_objects o + JOIN mz_internal.mz_object_global_ids g ON g.id = o.id + WHERE o.name = '{name}' + """) + item_ids[global_id] = item_id + return global_id + + def encoded_id(global_id: str) -> dict[str, int]: + tag = {"s": "System", "u": "User"}[global_id[0]] + return {tag: int(global_id[1:])} + + def record_sql(kind: str, global_id: str) -> str: + key = json.dumps(encoded_id(global_id)) + return ( + "SELECT data->'value' FROM mz_internal.mz_catalog_raw " + f"WHERE data->>'kind' = '{kind}' AND data->'key'->'id' = '{key}'::jsonb" + ) + + def record(kind: str, global_id: str) -> dict: + [(value,)] = query(record_sql(kind, global_id)) + return value + + def inspect(global_id: str) -> dict: + return query(f"INSPECT SHARD '{item_ids[global_id]}'")[0][0] + + with c.override( + Materialized( + additional_system_parameter_defaults={ + "enable_catalog_read_protection": "true", + "enable_expression_cache": "false", + "enable_logical_compaction_window": "true", + # Small batches must exercise real blob compaction, not inline writes. + "persist_inline_writes_single_max_bytes": "0", + "persist_compaction_heuristic_min_inputs": "2", + }, + ), + Testdrive( + name="testdrive_no_reset", + no_reset=True, + materialize_url="postgres://mz_system@materialized:6877", + ), + ): + c.up("materialized", Service("testdrive_no_reset", idle=True)) + td(""" + > SELECT count(*) > 0 FROM mz_internal.mz_catalog_raw + WHERE data->>'kind' = 'CollectionCompactionBound' + AND data->'key'->'id' ? 'System'; + true + + > SELECT count(*) FROM mz_internal.mz_catalog_raw m + WHERE m.data->>'kind' = 'StorageCollectionMetadata' + AND NOT EXISTS (SELECT 1 FROM mz_internal.mz_catalog_raw b + WHERE b.data->>'kind' = 'CollectionCompactionBound' + AND b.data->'key'->'id' = m.data->'key'->'id'); + 0 + + > CREATE CLUSTER protected_refresh SIZE 'scale=1,workers=1', REPLICATION FACTOR 0; + > CREATE TABLE protected_live (a int) WITH (RETAIN HISTORY = FOR '1s'); + > CREATE TABLE protected_eliminated (a int) WITH (RETAIN HISTORY = FOR '1s'); + > CREATE TABLE protected_control (a int) WITH (RETAIN HISTORY = FOR '1s'); + > INSERT INTO protected_live VALUES (1); + > INSERT INTO protected_eliminated VALUES (1); + > INSERT INTO protected_control VALUES (1); + > CREATE TABLE protected_catalog_probe (a int); + """) + eliminated_birth = record( + "CollectionCompactionBound", gid("protected_eliminated") + )["frontier"] + td(""" + > CREATE VIEW protected_logical AS + (SELECT a FROM protected_eliminated UNION ALL SELECT a FROM protected_live) + EXCEPT ALL SELECT a FROM protected_eliminated; + > CREATE MATERIALIZED VIEW protected_once IN CLUSTER protected_refresh + WITH (REFRESH AT CREATION) AS SELECT a FROM protected_logical; + > CREATE MATERIALIZED VIEW protected_builtin IN CLUSTER protected_refresh + WITH (REFRESH AT CREATION) + AS SELECT name FROM mz_catalog.mz_tables WHERE name = 'protected_catalog_probe'; + """) + ids = { + name: gid(name) + for name in ( + "protected_live", + "protected_eliminated", + "protected_control", + "protected_once", + "protected_builtin", + "mz_tables", + "mz_catalog_raw", + ) + } + bound_kind = "CollectionCompactionBound" + requirement_kind = "MaintainedReadRequirement" + once = record(requirement_kind, ids["protected_once"]) + [(first_refresh,)] = query(""" + SELECT next_refresh::text FROM mz_internal.mz_materialized_view_refreshes r + JOIN mz_materialized_views v ON v.id = r.materialized_view_id + WHERE v.name = 'protected_once' + """) + first_refresh = int(first_refresh) + assert ( + isinstance(once["frontier"], int) and once["frontier"] <= first_refresh + ), once + td(f""" + > SELECT last_completed_refresh IS NULL, next_refresh = {first_refresh}::mz_timestamp + FROM mz_internal.mz_materialized_view_refreshes r + JOIN mz_materialized_views v ON v.id = r.materialized_view_id + WHERE v.name = 'protected_once'; + true true + """) + expected_inputs = [ + encoded_id(ids[name]) for name in ("protected_live", "protected_eliminated") + ] + assert sorted(once["inputs"], key=str) == sorted(expected_inputs, key=str), once + builtin = record(requirement_kind, ids["protected_builtin"]) + assert builtin["inputs"] == [encoded_id(ids["mz_tables"])], builtin + for global_id in ids.values(): + assert record(bound_kind, global_id)["frontier"] is not None, global_id + + def verify_plan() -> None: + plan = query("EXPLAIN OPTIMIZED PLAN FOR MATERIALIZED VIEW protected_once")[ + 0 + ][0] + assert "protected_live" in plan and "protected_eliminated" not in plan, plan + print(plan) + + control_sql = record_sql(bound_kind, ids["protected_control"]) + + def await_state(description: str, ready: Callable[[], bool]) -> None: + # INSPECT is not composable SQL. Native testdrive Retry waits for each + # publication advance, then we inspect without acquiring input read holds. + deadline = time.monotonic() + 120 + while True: + control_bound = record(bound_kind, ids["protected_control"])["frontier"] + if ready(): + return + if time.monotonic() >= deadline: + sample(f"timeout: {description}") + raise UIError(f"timed out waiting for {description}") + # Persist schedules merges in response to writes. Keep producing real + # batches while waiting, including after a requirement has completed. + for _ in range(8): + query("UPDATE protected_eliminated SET a = a + 1") + query("UPDATE protected_control SET a = a + 1") + print(f"Waiting for {description}") + td( + f""" + > SELECT (v->>'frontier')::numeric > {control_bound} + FROM ({control_sql}) AS r(v); + true + """, + timeout=max(1, deadline - time.monotonic()), + ) + + def batches(state: dict) -> list[dict]: + return [*state["batches"], *state["hollow_batches"].values()] + + def compacted_past(state: dict, timestamp: int) -> bool: + # A nonempty persisted batch covering the old input, with an advanced + # batch since, proves a merge was applied. The shard since alone doesn't. + return any( + batch["len"] > 0 + and batch["lower"][0] <= timestamp + and batch["since"] + and batch["since"][0] > timestamp + for batch in batches(state) + ) + + def updates(state: dict) -> int: + return sum(batch["len"] for batch in batches(state)) + + def sample(label: str) -> dict: + catalog_state = inspect(ids["mz_catalog_raw"]) + response = requests.get( + f"http://localhost:{c.port('materialized', 6878)}/metrics", timeout=10 + ) + response.raise_for_status() + metric_lines = [ + line + for line in response.text.splitlines() + if line.split("{", 1)[0] + in ( + "mz_persist_shard_diff_size_bytes", + "mz_persist_shard_cmd_succeeded", + "mz_persist_shard_compaction_applied", + "mz_persist_shard_batch_part_count", + "mz_persist_shard_usage_current_state_batches_bytes", + ) + ] + + def metrics_for(shard_id: str) -> dict[str, float]: + return { + line.split("{", 1)[0]: float(line.rsplit(" ", 1)[1]) + for line in metric_lines + if f'shard="{shard_id}"' in line + } + + counters = { + metric: value + for metric, value in metrics_for(catalog_state["shard_id"]).items() + if metric + in ( + "mz_persist_shard_diff_size_bytes", + "mz_persist_shard_cmd_succeeded", + ) + } + assert len(counters) == 2, counters + state = { + "label": label, + "monotonic_seconds": time.monotonic(), + "catalog_seqno": catalog_state["seqno"], + "catalog_counters": counters, + "collections": {}, + } + for name, global_id in ids.items(): + shard = inspect(global_id) + state["collections"][name] = { + "id": global_id, + "bound": record(bound_kind, global_id), + "shard_id": shard["shard_id"], + "since": shard["since"], + "upper": shard["upper"], + "environmentd_metrics": metrics_for(shard["shard_id"]), + "updates": updates(shard), + "persist_upper_minus_since_ms": ( + shard["upper"][0] - shard["since"][0] + if shard["upper"] and shard["since"] + else None + ), + "batches": [ + {key: batch[key] for key in ("lower", "upper", "since", "len")} + for batch in batches(shard) + ], + } + state["requirements"] = { + name: record(requirement_kind, ids[name]) + for name in ids + if name in ("protected_once", "protected_builtin", "protected_ongoing") + } + byte_metric = "mz_persist_shard_usage_current_state_batches_bytes" + state["extra_retained_input_bytes"] = ( + state["collections"]["protected_eliminated"]["environmentd_metrics"][ + byte_metric + ] + - state["collections"]["protected_control"]["environmentd_metrics"][ + byte_metric + ] + ) + print(json.dumps(state, sort_keys=True)) + return state + + def verify_pending() -> bool: + assert record(requirement_kind, ids["protected_once"]) == once + assert record(requirement_kind, ids["protected_builtin"]) == builtin + assert query(""" + SELECT count(*) FROM mz_cluster_replicas r + JOIN mz_clusters c ON c.id = r.cluster_id + WHERE c.name = 'protected_refresh' + """) == [(0,)] + for name in ("protected_live", "protected_eliminated"): + assert inspect(ids[name])["since"][0] <= first_refresh, name + assert record(bound_kind, ids[name])["frontier"] <= first_refresh, name + eliminated = inspect(ids["protected_eliminated"]) + control = inspect(ids["protected_control"]) + # Both tables receive the same revisions. Fewer updates on the control + # demonstrates consolidation of history that the eliminated input retains. + checks = { + "control_since_advanced": control["since"][0] > first_refresh, + "control_compacted": compacted_past(control, first_refresh), + "eliminated_compacted": compacted_past(eliminated, eliminated_birth), + "control_reclaims_more": updates(control) < updates(eliminated), + } + print( + json.dumps( + { + "probe": checks, + "first_refresh": first_refresh, + "eliminated_birth": eliminated_birth, + "control_since": control["since"], + "eliminated_since": eliminated["since"], + "control_updates": updates(control), + "eliminated_updates": updates(eliminated), + "control_batches": [ + { + key: batch[key] + for key in ("lower", "upper", "since", "len") + } + for batch in batches(control) + ], + "eliminated_batches": [ + { + key: batch[key] + for key in ("lower", "upper", "since", "len") + } + for batch in batches(eliminated) + ], + } + ), + flush=True, + ) + return all(checks.values()) + + verify_plan() + before = sample("pending-before-writes") + td(""" + > UPDATE protected_live SET a = 2; + > UPDATE protected_eliminated SET a = 2; + > UPDATE protected_control SET a = 2; + > ALTER TABLE protected_catalog_probe RENAME TO protected_catalog_probe_renamed; + """) + await_state("control physically compacted past first refresh", verify_pending) + after = sample("pending-after-compaction") + elapsed = after["monotonic_seconds"] - before["monotonic_seconds"] + # These are measured catalog-shard totals, including builtin maintenance, + # not an attribution of every persist command to bound publication. + print( + json.dumps( + { + "publication_sample_seconds": elapsed, + "catalog_counter_deltas": { + metric: value - before["catalog_counters"][metric] + for metric, value in after["catalog_counters"].items() + }, + "catalog_diff_bytes_per_second": ( + after["catalog_counters"]["mz_persist_shard_diff_size_bytes"] + - before["catalog_counters"]["mz_persist_shard_diff_size_bytes"] + ) + / elapsed, + }, + sort_keys=True, + ) + ) + + def rejected_while_physically_readable() -> bool: + permission = record(bound_kind, ids["protected_control"])["frontier"] + before_since = inspect(ids["protected_control"])["since"][0] + if before_since >= permission: + return False + historical = permission - 1 + td(f""" + ! CREATE MATERIALIZED VIEW protected_rejected + IN CLUSTER protected_refresh WITH (REFRESH AT {historical}) + AS SELECT a FROM protected_control WHERE false; + contains: REFRESH AT requested for a time where not all the inputs are readable + """) + after_since = inspect(ids["protected_control"])["since"][0] + # Persist since is monotonic. Both observations must bracket the + # rejection while H is physically readable, otherwise try another gap. + print( + json.dumps( + { + "admission_timestamp": historical, + "permission": permission, + "persist_since_before": before_since, + "persist_since_after": after_since, + } + ) + ) + return after_since <= historical + + await_state( + "historical admission rejected during physical compaction lag", + rejected_while_physically_readable, + ) + + c.kill("materialized") + c.up("materialized") + verify_plan() + assert verify_pending() + sample("recovered-without-replica") + td(""" + > ALTER CLUSTER protected_refresh SET (REPLICATION FACTOR 1); + > SELECT a FROM protected_once; + 1 + > SELECT a FROM protected_live; + 2 + > SELECT name FROM protected_builtin; + protected_catalog_probe + """) + for name in ("protected_once", "protected_builtin"): + td(f""" + > SELECT v->>'frontier' IS NULL + FROM ({record_sql(requirement_kind, ids[name])}) AS r(v); + true + """) + assert inspect(ids[name])["upper"] == [], name + + def history_released() -> bool: + eliminated = inspect(ids["protected_eliminated"]) + return ( + eliminated["since"][0] > first_refresh + and inspect(ids["protected_live"])["since"][0] > first_refresh + and compacted_past(eliminated, first_refresh) + ) + + await_state("completed requirement releasing input history", history_released) + sample("completed-without-dropping-mv") + + td(""" + > CREATE MATERIALIZED VIEW protected_ongoing IN CLUSTER protected_refresh + AS SELECT a FROM protected_logical; + > SELECT a FROM protected_ongoing; + 2 + """) + ids["protected_ongoing"] = gid("protected_ongoing") + ongoing = record(requirement_kind, ids["protected_ongoing"]) + assert sorted(ongoing["inputs"], key=str) == sorted( + expected_inputs, key=str + ), ongoing + start = ongoing["frontier"] + assert isinstance(start, int), ongoing + td(""" + > UPDATE protected_live SET a = 3; + > SELECT a FROM protected_ongoing; + 3 + > SELECT a FROM protected_once; + 1 + """) + + def ongoing_advanced() -> bool: + eliminated = inspect(ids["protected_eliminated"]) + requirement = record(requirement_kind, ids["protected_ongoing"]) + upper = inspect(ids["protected_ongoing"])["upper"] + frontier = requirement["frontier"] + assert frontier is not None and upper, (requirement, upper) + assert eliminated["since"][0] <= frontier <= upper[0] - 1 + return frontier > start and compacted_past(eliminated, start) + + await_state( + "ongoing requirement advancing with durable output", ongoing_advanced + ) + sample("ongoing-not-completed") + + # Pending replacements can make shared-shard registration groups cyclic + # even though the catalog's query dependency graph is acyclic. + td(""" + > CREATE MATERIALIZED VIEW protected_a IN CLUSTER protected_refresh + AS SELECT a FROM protected_live; + > CREATE MATERIALIZED VIEW protected_b IN CLUSTER protected_refresh + AS SELECT a FROM protected_live; + > CREATE REPLACEMENT MATERIALIZED VIEW protected_ar FOR protected_a + IN CLUSTER protected_refresh AS SELECT a FROM protected_b; + > CREATE REPLACEMENT MATERIALIZED VIEW protected_br FOR protected_b + IN CLUSTER protected_refresh AS SELECT a FROM protected_a; + > CREATE MATERIALIZED VIEW protected_downstream IN CLUSTER protected_refresh + AS SELECT a FROM protected_a JOIN protected_b USING (a); + > SELECT a FROM protected_downstream; + 3 + """) + c.kill("materialized") + c.up("materialized") + td(""" + > SELECT a FROM protected_downstream; + 3 + > SELECT a FROM protected_ongoing; + 3 + > SELECT a FROM protected_once; + 1 + > SELECT name FROM protected_builtin; + protected_catalog_probe + > DROP MATERIALIZED VIEW protected_ar; + > DROP MATERIALIZED VIEW protected_br; + """) + c.down(destroy_volumes=True) + + def workflow_index_compute_dependencies(c: Composition) -> None: """ Assert that materialized views and index catalog items see and use only @@ -1402,6 +1893,13 @@ def wait_for(sql: str, expected: list[tuple], what: str) -> None: c.down(destroy_volumes=True) c.up("materialized") + # Keep reclamation WAL entries observable until the assertions below. + c.sql( + "ALTER SYSTEM SET enable_storage_shard_finalization = false", + port=6877, + user="mz_system", + ) + # Two sessions create temporary items of the same name. Name uniqueness is # scoped by the owning session, so both must coexist, and mz_tables and # mz_views report every item regardless of owner. @@ -1507,10 +2005,7 @@ def wait_for(sql: str, expected: list[tuple], what: str) -> None: # The temp table's storage mapping must have moved to the finalization # WAL in the same reclamation, else the metadata row and its persist - # shard would leak forever. Both rows are stable to assert on here: the - # metadata deletion is permanent, and the WAL row survives until the - # next committed catalog transaction, which cannot have happened because - # nothing has run DDL since the restart. + # shard would leak forever. metadata = c.sql_query( f"""SELECT count(*) FROM mz_internal.mz_catalog_raw WHERE data->>'kind' = 'StorageCollectionMetadata' @@ -1531,6 +2026,11 @@ def wait_for(sql: str, expected: list[tuple], what: str) -> None: assert unfinalized == [ (1,) ], f"temp table's shard was not enqueued for finalization: {temp_shard}" + c.sql( + "ALTER SYSTEM RESET enable_storage_shard_finalization", + port=6877, + user="mz_system", + ) # The comment row dies with its item. comments = c.sql_query(temp_comment_count, port=6877, user="mz_system") diff --git a/test/testdrive-old-kafka-src-syntax/audit-log.td b/test/testdrive-old-kafka-src-syntax/audit-log.td index 0a24ad4db14d5..22993506fa14c 100644 --- a/test/testdrive-old-kafka-src-syntax/audit-log.td +++ b/test/testdrive-old-kafka-src-syntax/audit-log.td @@ -33,7 +33,10 @@ $ kafka-create-topic topic=test # and `replica_id` fields are dropped for versions where the audit-log struct # uses `#[serde(skip_serializing_if)]`, and source `external_type` is # exposed as `type`. -> SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events ORDER BY id DESC LIMIT 4 +# Exclude consistency-checker interval changes from this fixture's newest DDL events. +> SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events + WHERE (object_type = 'system' AND details->>'name' = 'catalog_read_protection_publish_interval') IS NOT TRUE + ORDER BY id DESC LIMIT 4 create source "{\"cluster_id\":\"\",\"database\":\"materialize\",\"id\":\"\",\"item\":\"kafka_src\",\"schema\":\"public\",\"type\":\"kafka\"}" materialize create source "{\"cluster_id\":null,\"database\":\"materialize\",\"id\":\"\",\"item\":\"kafka_src_progress\",\"schema\":\"public\",\"type\":\"progress\"}" materialize create cluster-replica "{\"cluster_id\":\"\",\"cluster_name\":\"audit_log_kafka_src\",\"logical_size\":\"${arg.default-storage-size}\",\"replica_name\":\"r1\"}" materialize @@ -45,7 +48,9 @@ create cluster "{\"id\":\"\",\"name\":\"audit_log_kafka_src\"}" materialize IN CLUSTER audit_log_counter_src FROM LOAD GENERATOR COUNTER (UP TO 100); -> SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events ORDER BY id DESC LIMIT 4 +> SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events + WHERE (object_type = 'system' AND details->>'name' = 'catalog_read_protection_publish_interval') IS NOT TRUE + ORDER BY id DESC LIMIT 4 create source "{\"cluster_id\":\"\",\"database\":\"materialize\",\"id\":\"\",\"item\":\"counter_src\",\"schema\":\"public\",\"type\":\"load-generator\"}" materialize create source "{\"cluster_id\":null,\"database\":\"materialize\",\"id\":\"\",\"item\":\"counter_src_progress\",\"schema\":\"public\",\"type\":\"progress\"}" materialize create cluster-replica "{\"cluster_id\":\"\",\"cluster_name\":\"audit_log_counter_src\",\"logical_size\":\"${arg.default-storage-size}\",\"replica_name\":\"r1\"}" materialize diff --git a/test/testdrive/audit-log.td b/test/testdrive/audit-log.td index 534060dfca7b7..496f5fc607d99 100644 --- a/test/testdrive/audit-log.td +++ b/test/testdrive/audit-log.td @@ -36,7 +36,10 @@ $ kafka-create-topic topic=test # and `replica_id` fields are dropped for versions where the audit-log struct # uses `#[serde(skip_serializing_if)]`, and source `external_type` is # exposed as `type`. -> SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events ORDER BY id DESC LIMIT 3 +# Exclude consistency-checker interval changes so newest-event assertions see this fixture's DDL. +> SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events + WHERE (object_type = 'system' AND details->>'name' = 'catalog_read_protection_publish_interval') IS NOT TRUE + ORDER BY id DESC LIMIT 3 create source "{\"cluster_id\":\"\",\"database\":\"materialize\",\"id\":\"\",\"item\":\"kafka_src\",\"schema\":\"public\",\"type\":\"kafka\"}" materialize create cluster-replica "{\"cluster_id\":\"\",\"cluster_name\":\"audit_log_kafka_src\",\"logical_size\":\"${arg.default-storage-size}\",\"replica_name\":\"r1\"}" materialize create table "{\"database\":\"materialize\",\"id\":\"\",\"item\":\"kafka_src_tbl\",\"schema\":\"public\"}" materialize @@ -47,7 +50,9 @@ create table "{\"database\":\"materialize\",\"id\":\"\",\"item\":\"kafka_sr IN CLUSTER audit_log_counter_src FROM LOAD GENERATOR COUNTER (UP TO 100); -> SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events ORDER BY id DESC LIMIT 3 +> SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events + WHERE (object_type = 'system' AND details->>'name' = 'catalog_read_protection_publish_interval') IS NOT TRUE + ORDER BY id DESC LIMIT 3 create source "{\"cluster_id\":\"\",\"database\":\"materialize\",\"id\":\"\",\"item\":\"counter_src\",\"schema\":\"public\",\"type\":\"load-generator\"}" materialize create cluster-replica "{\"cluster_id\":\"\",\"cluster_name\":\"audit_log_counter_src\",\"logical_size\":\"${arg.default-storage-size}\",\"replica_name\":\"r1\"}" materialize create cluster "{\"id\":\"\",\"name\":\"audit_log_counter_src\"}" materialize From ccd720ad0e4dca1e97398b12430f4ddb6d3b5d49 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 01:55:52 +0000 Subject: [PATCH 25/36] doc: Steer coordination work toward scalable independent lifecycle Record the architectural review priorities in the implementer prompt: publication scaling, consistency-check coupling, and remaining ownership dependencies. Keep the design unchanged and retire resolved steering rather than accumulating a permanent checklist. --- .../20260903_decoupled_coordination_prompt.md | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index 302687fe3040b..43e89287086c5 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -15,6 +15,33 @@ the next coherent piece toward its observable outcome, briefly explain that choice, then implement and verify it. Historical proposals and next steps are context, not a cumulative task list. +Current steering + +Check which of these review findings remain unresolved, then choose one coherent +change. Remove resolved steering from this prompt. These are implementation +priorities, not new design requirements or a reason to reopen agreed decisions. + +- Publication scaling: publish_read_protection emits one catalog op per record, + while Transaction::get_op_updates scans accumulated pending updates after each + op. This creates quadratic work on the coordinator loop. Address that path and + measure publication cost at representative collection counts, including DDL + latency and retained history. Do not infer scalability from the small recovery + demonstration or make a general catalog redesign a prerequisite. +- Consistency-check coupling: check_catalog_state_quiesced changes the publication + interval, and ddl_revision introduces a production conflict exception for that + setting. Revisit whether the checker is forcing unnecessary production semantics. + Preserve full catalog comparison, including protection records, without adding + further special cases to make the harness pass. +- Ownership transition: finish the protected-MV milestone's production validation, + then prioritize a real catalog subscriber over more standalone APIs. In that + transition, address prepare_state's reliance on locally installed collections + and make the writer-side responsibility for complete maintained requirements + explicit. An MV's requirement is a separate sequencer op today. These are + transitional dependencies, not evidence that the current single-owner path fails. + +Keep the draft PR description accurate about what is implemented and what remains, +with validation status in the PR rather than the design log. + Prefer connecting existing pieces through the active milestone's production path over adding further standalone APIs. Preparatory work is appropriate when it unblocks that path. Let integration evidence refine intermediate interfaces From f9cd1b2c3aef290d745b6e0bb5a34dd212dfd24f Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 02:42:52 +0000 Subject: [PATCH 26/36] doc: Clarify maintained-protection milestone coverage Keep milestone 1 active beyond the protected-MV checkpoint. Cover source and sink recovery requirements and compute compaction before moving to independent lifecycle execution. Retain catalog-local authorization and its traffic tradeoff without prescribing duplicate records or stronger historical recovery guarantees. --- .../design/20260903_decoupled_coordination.md | 27 ++++++++++++++++--- .../20260903_decoupled_coordination_prompt.md | 18 ++++++++----- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 88acd136342ea..e2e83f6377a10 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -251,10 +251,16 @@ records the active milestone and progress toward it. #### 1. Catalog-backed recovery protection Maintained requirements and compaction permission are coordinated through the -catalog. Use an MV as the first complete example: logical-input protection is -established before installation depends on it, survives uncached recovery, -preserves a pending first refresh, and advances with durable output progress -rather than retaining creation-time history forever. +catalog across maintained storage and compute object types. Use an MV as the +first complete example: logical-input protection is established before installation +depends on it, survives uncached recovery, preserves a pending first refresh, and +advances with durable output progress rather than retaining creation-time history +forever. + +Derive requirements from existing catalog definitions and durable progress where +possible. Separate records per object type are not prescribed. Preserve existing +recovery semantics without introducing creation-time history guarantees for indexes +or metric sinks. Evidence includes actual compaction and an input eliminated by optimization. Measure publication and retained-history costs as the real path becomes available. @@ -612,3 +618,16 @@ Publication cadence is configurable. Planning-cache freshness and DDL conflict tracking have distinct revisions, so cadence changes do not abort open DDL. Next: milestone 2's catalog-driven maintained lifecycle and builtin schema migration. MV compute installation still uses the sequencer closure. + +### 2026-09-09: Maintained-protection coverage agreed with Aljoscha + +Milestone 1 remains active. The protected-MV path is an implementation checkpoint, +not completion across maintained object types. Source/sink recovery protection +still depends on installed controller accounting, and compute compaction has no +catalog permission cap. Current recovery ordering protects the single-owner path, +but does not establish complete catalog-local authorization. + +Retain the explicit-bounds decision and its accepted catalog traffic cost rather +than reopen delegated admission and reclamation. Next: complete source/sink +requirements at the catalog authorization boundary and maintained compute permission. +Milestone 2 remains the independent lifecycle subscriber and builtin migration. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index 43e89287086c5..af5269180cda8 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -21,6 +21,12 @@ Check which of these review findings remain unresolved, then choose one coherent change. Remove resolved steering from this prompt. These are implementation priorities, not new design requirements or a reason to reopen agreed decisions. +- Maintained protection: milestone 1 remains active. The protected-MV path is an + implementation checkpoint, not completion across maintained object types. Make + source/sink recovery requirements constrain catalog authorization even when + local controller accounting is absent, and bring maintained compute compaction + under committed permission. Follow the milestone's requirement-derivation and + recovery-semantics boundaries rather than prescribing new records for every type. - Publication scaling: publish_read_protection emits one catalog op per record, while Transaction::get_op_updates scans accumulated pending updates after each op. This creates quadratic work on the coordinator loop. Address that path and @@ -32,12 +38,12 @@ priorities, not new design requirements or a reason to reopen agreed decisions. setting. Revisit whether the checker is forcing unnecessary production semantics. Preserve full catalog comparison, including protection records, without adding further special cases to make the harness pass. -- Ownership transition: finish the protected-MV milestone's production validation, - then prioritize a real catalog subscriber over more standalone APIs. In that - transition, address prepare_state's reliance on locally installed collections - and make the writer-side responsibility for complete maintained requirements - explicit. An MV's requirement is a separate sequencer op today. These are - transitional dependencies, not evidence that the current single-owner path fails. +- Ownership transition: after milestone 1, prioritize a real catalog subscriber + over more standalone APIs. In that transition, address prepare_state's reliance + on locally installed collections and make the writer-side responsibility for + complete maintained requirements explicit. An MV's requirement is a separate + sequencer op today. These are transitional dependencies, not evidence that the + current single-owner path fails. Keep the draft PR description accurate about what is implemented and what remains, with validation status in the PR rather than the design log. From 95afe9961b9498a6a6545b6c2a366d28647ff448 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 02:46:30 +0000 Subject: [PATCH 27/36] doc: Defer migration work beyond decoupling milestones Keep builtin schema migration and protected-environment version upgrades outside the fresh-environment decoupling deliverable, alongside existing environment conversion. Demonstrate the ownership model before tackling those follow-on concerns. Preserve fresh builtin initialization, same-version recovery, and ownership handover within the milestones. --- .../design/20260903_decoupled_coordination.md | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index e2e83f6377a10..3d177de29eb88 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -18,11 +18,15 @@ clients without another ownership redesign. Enabling concurrent catalog writers and deploying multiple adapters are not required here. Initial implementation and validation target environments initialized under the -new protection rules. Conversion of existing environments is deferred and is not -a prerequisite for the [milestones](#milestones). Enabling the new ownership model -for existing environments requires a separate conversion and rollout decision -that preserves their promised results. Fresh environments must still survive -restarts, replanning, and ownership handover. +new protection rules. Conversion of existing environments, builtin schema migration, +and version-upgrade support for protected environments are deferred until after +demonstrating fresh-environment decoupling. They are not prerequisites for the +[milestones](#milestones). + +Enabling the new ownership model for existing environments requires a separate +conversion and rollout decision that preserves their promised results. Fresh +environments must still survive same-version restarts, replanning, and ownership +handover. Query-local dataflows that do not go through the catalog, including slow-path SELECTs, SUBSCRIBEs, and COPY TO, remain on the fast protocol. Their creation, @@ -267,8 +271,7 @@ Measure publication and retained-history costs as the real path becomes availabl This milestone can use the current single-writer arrangement. Include fresh builtin initialization and same-version recovery, including MVs -reading system-catalog collections. Builtin schema migration belongs to milestone -2. Milestone 1 does not establish version-upgrade support for protected environments. +reading system-catalog collections. #### 2. Catalog-driven maintained lifecycle @@ -631,3 +634,12 @@ Retain the explicit-bounds decision and its accepted catalog traffic cost rather than reopen delegated admission and reclamation. Next: complete source/sink requirements at the catalog authorization boundary and maintained compute permission. Milestone 2 remains the independent lifecycle subscriber and builtin migration. + +### 2026-09-09: Migration follow-on scope agreed with Aljoscha + +Builtin schema migration and protected-environment version upgrades join +existing-environment conversion as follow-on work after demonstrating +fresh-environment decoupling, not milestone 2 requirements. Fresh builtin +initialization, same-version recovery, and ownership handover remain in scope. +Milestone 1 remains active. Next: complete maintained protection, then move +lifecycle execution to the independent catalog subscriber in milestone 2. From 283d75d1c2918ce6328aaefcefd60b28b1bf2400 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 10 Sep 2026 02:27:09 +0000 Subject: [PATCH 28/36] doc: Consolidate coordination guidance and implementation handoffs --- .../design/20260903_decoupled_coordination.md | 461 +++--------------- .../20260903_decoupled_coordination_log.md | 456 +++++++++++++++++ .../20260903_decoupled_coordination_prompt.md | 132 +++-- 3 files changed, 614 insertions(+), 435 deletions(-) create mode 100644 doc/developer/design/20260903_decoupled_coordination_log.md diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 3d177de29eb88..020d3f7ed1091 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -8,7 +8,8 @@ state, rather than depending on an adapter to send lifecycle commands. Adapters write catalog changes and use a separate fast protocol for query execution. The goal is decoupling, preserving consistency guarantees, performance, and -existing SQL behavior except for the [maintained-creation admission rules](#admission-and-conversion). +existing SQL behavior except for the [maintained-creation admission rules](#admission), +including the [fresh sink cutoff](#fresh-sink-cutoff). Losing an adapter must not disrupt maintained dataflows or other clients. Its own queries may fail. Transparent query or session failover is out of scope. @@ -24,9 +25,11 @@ demonstrating fresh-environment decoupling. They are not prerequisites for the [milestones](#milestones). Enabling the new ownership model for existing environments requires a separate -conversion and rollout decision that preserves their promised results. Fresh -environments must still survive same-version restarts, replanning, and ownership -handover. +conversion and rollout decision that preserves their promised results. Conversion +to logical-input protection becomes active only once remaining recovery requirements +are protected across all logical inputs. It must not skip pending results by moving +the recovery timestamp forward. Fresh environments must still survive same-version +restarts, replanning, and ownership handover. Query-local dataflows that do not go through the catalog, including slow-path SELECTs, SUBSCRIBEs, and COPY TO, remain on the fast protocol. Their creation, @@ -52,13 +55,17 @@ client ownership of the cluster's maintained lifecycle. Responses, cancellation, and disconnect cleanup belong to the relevant client. This is a semantic split, not simply another listener for the same controller protocol. -## Catalog-backed compaction bounds +## Boundary contracts + +### Compaction permission Store explicit compaction bounds for maintained collections in the catalog. Storage and compute must not compact those collections beyond committed permission. Applied compaction may lag, retaining extra history. Bounds advance monotonically within a collection's lifetime. Their representation and granularity -remain implementation choices. +remain implementation choices. A record is justified by information that cannot +be derived from existing catalog state or durable progress, not by uniformity +across object types. Introducing or strengthening a maintained read requirement and advancing the affected bounds must be coordinated at the catalog transaction boundary. A @@ -70,18 +77,13 @@ independently of the creating adapter's lifetime. This places maintained read requirements and permission to discard history under the same durable authority. Cluster-side components have an explicit limit to enforce and recover, without treating a process's local accounting as the -authority to advance beyond it. - -The cost is ongoing catalog traffic proportional to the number of changing bounds -and their publication cadence, together with catalog processing and subscriber -work. Publication competes with DDL, and further advancement depends on catalog -write availability. Delaying publication retains more history, with storage and -compute resource costs even when the metadata bandwidth is modest. - -Avoid coupling ordinary query throughput to catalog writes for every hold change. -Coalescing or rate-limiting bound advancement may retain extra history, but must -not delay protection until after it is needed. Choose cadence and batching from -measured catalog load, DDL latency, and retention cost. +authority to advance beyond it. The cost is ongoing catalog traffic and processing +proportional to changing bounds and publication cadence, accepted over the +[delegated alternative](#delegated-compaction-advancement). Coalescing or +rate-limiting advancement may retain extra history, but must not delay protection +until after it is needed. Choose cadence and batching from measured catalog load, +DDL latency, and retention cost. Publication work scales with what changed, not +with catalog size. ### Applying committed permission @@ -113,8 +115,6 @@ We accept extra retention on inputs the running plan does not read, especially for slow or suspended consumers, to preserve reconstruction from catalog SQL independently of optimizer choices. -## Boundary contracts - ### Readability and compaction A reader must secure protection before relying on a timestamp. Observing a @@ -123,10 +123,12 @@ valid, compute and persist compaction must respect it, including through dependencies, installation, and ownership handover. One client cannot release another's protection. -Keep compaction bounds distinct from a dataflow's installation `as_of` and an -MV's initial storage visibility boundary unless the implementation establishes -how they fit. The representation and accounting mechanisms for individual read -requirements remain implementation choices. +Committed permission bounds a dataflow's installation `as_of` from above. Installing +a maintained collection at an `as_of` beyond its committed permission discards +history without authorization, so `as_of` selection treats permission as a hard +constraint rather than waiting for permission to catch up. An MV's initial storage +visibility boundary remains a distinct concept. The representation and accounting +mechanisms for individual read requirements remain implementation choices. Propagation to persist critical since handles must respect all valid read requirements. Those handles are the durable backstop, not a substitute for @@ -156,7 +158,7 @@ creating clients. Query execution and transient dataflows remain ephemeral and require their own readiness and execution protection. This does not introduce transparent session or query failover. -### Admission and conversion +### Admission Admission of maintained read requirements respects committed compaction permission for all logical inputs, rather than relying on lagging physical compaction. @@ -164,12 +166,6 @@ Automatically selected creation timestamps must be compatible with all those inputs. Explicit historical refresh requests are rejected if any logical input cannot support them, even when optimization removes that input. -Existing objects retain their promised results. Their conversion to logical-input -protection becomes active only once their remaining recovery requirements are -protected across all logical inputs. Conversion must not skip pending results by -moving the recovery timestamp forward. Conversion is deferred under the -[initial scope](#outcome-and-scope). - ### Visibility and execution Catalog commit, cluster application, and query readiness are different events. @@ -182,6 +178,34 @@ maintained dataflows. Lifecycle ownership and permission to perform external writes must remain safe across restarts and handover, independently of query connection lifetime. +## Selected implementation decisions + +These choices resolve implementation questions raised by the requirements above. +They constrain the relevant paths without prescribing their internal mechanisms. + +### Fresh sink cutoff + +Fresh sinks select their automatic cutoff against committed input permission, +including with `SNAPSHOT = false`. They cannot rely on history merely because +physical compaction lags. Existing sinks retain pending output through alteration +and recovery. Their requirements advance with durable output progress, not merely +with input compaction permission. + +### Index reconstruction + +A saved index compaction bound is not a historical reconstruction guarantee. +Recovery selects installation frontiers from actual readability and all valid read +requirements, within committed permission. Reconciliation that discards existing +history requires committed permission, even when performed as part of reconstruction. + +### Read-only prewarming + +Preserve prewarming by following committed catalog permission independently of the +SQL savepoint. Read-only bootstrap installs within that permission and does not +wait for the writer to advance it. A local savepoint write grants no compaction +permission, and there is no startup-specific writer protocol or reconstruction +exemption. + ## Alternatives ### Plan-specific recovery protection @@ -228,29 +252,17 @@ metadata and reclamation costs. ## Implementation and verification -Implementers choose the smallest coherent mechanism and the order of work. -This document does not prescribe catalog fields, leases, protocol messages, -planning placement, or process topology. An incremental implementation is fine, -but an intermediate step is not completion of the outcome above. - -Flag implementation discoveries, tradeoffs, and scope growth to Aljoscha. Pause -affected work when guidance is needed, especially when correctness, user-visible -behavior, or an agreed boundary would change. Do not silently narrow capability -or expand the design to resolve a difficulty. Seek independent review where -the risk warrants it. - Existing CI, nightly, and performance suites are the broad acceptance signal. Ensure coverage actually exercises the changed boundaries, particularly independent query clients, catalog/query ordering, and read protection during compaction and recovery. Add targeted tests or experiments where existing coverage leaves uncertainty. Measure catalog traffic and retained-history cost -as well as query performance. Record what ran, failures, and remaining gaps. +as well as query performance. ### Milestones These are outcome checkpoints, not a rigid implementation sequence or one-commit -tasks. Work can cross their boundaries when necessary. The implementation log -records the active milestone and progress toward it. +tasks. Work can cross their boundaries when necessary. #### 1. Catalog-backed recovery protection @@ -266,12 +278,11 @@ possible. Separate records per object type are not prescribed. Preserve existing recovery semantics without introducing creation-time history guarantees for indexes or metric sinks. -Evidence includes actual compaction and an input eliminated by optimization. -Measure publication and retained-history costs as the real path becomes available. -This milestone can use the current single-writer arrangement. - -Include fresh builtin initialization and same-version recovery, including MVs -reading system-catalog collections. +Evidence includes actual compaction and an input eliminated by optimization, +fresh builtin initialization, and same-version recovery including MVs reading +system-catalog collections. Measure publication and retained-history costs as the +real path becomes available. This milestone can use the current single-writer +arrangement. #### 2. Catalog-driven maintained lifecycle @@ -298,348 +309,6 @@ compaction, and an expired client returning. Together, these milestones complete fresh-environment decoupling outcome, subject to the correctness and performance acceptance criteria above. -### Starting points - -- [Catalog implications](../../../src/adapter/src/coord/catalog_implications.rs) - derive effects from committed changes, including sink creation, index - installation, and MV storage registration. MV and metric-sink compute - installation remain sequencer-side. -- [Compute protocol](../../../src/compute-client/src/protocol/command.rs) mixes - lifecycle and query commands. [Transport](../../../src/service/src/transport.rs) - replaces the active client on a new connection. -- [StorageCollections](../../../src/storage-client/src/storage_collections.rs) - owns storage capability accounting and critical since handles. The fixed - critical-reader identity and epoch fencing support handover, not independent - owners aggregating their local holds. - -## Implementation log (append-only) - -Append dated entries with findings, decisions or pending questions, and the next -useful step. Validation status lives in the PR, not here. Clearly distinguish an -implementer's proposal from a decision reviewed with Aljoscha. Do not rewrite -earlier entries. Keep the main text concise and update its agreed boundaries -only following review. - -### 2026-09-03: Scope agreed with Aljoscha - -Multi-adapter operation is the end goal, not the immediate deliverable. -Query-local dataflows remain on the fast protocol. Adapter loss must not harm -other clients or maintained dataflows, but transparent failover is not required. -Completing relevant catalog implication paths is within scope. - -### 2026-09-03: Sink creation from committed implications - -Sink additions now install exports through catalog implications. - -Controllers still live in the adapter. Direct committed-update tests and -same-batch MV/sink creation remain gaps. Index parsing leaves plans absent and -the expression cache has no runtime read API. - -### 2026-09-03: Reusable index reconstruction - -Extracted bootstrap's uncached index planning and notice rendering without -changing cache policy, ordering, or installation. Next: runtime expression-cache -reads and index-add implications using reconstruction on cache misses. Cache -validity must account for committed dependencies, and installation must follow -same-batch prerequisites. - -### 2026-09-03: Runtime index implications - -Index additions now acquire cached or reconstructed plans and install through -implications. Runtime cache reads are best-effort and validated against committed -dependencies and compute availability. Review identified stale session notices -after cache rejection, addressed by filtering dropped dependencies. - -No existing harness can exercise cluster/table/index creation in one committed -batch without the sequencer. Same-batch MV/index creation still depends on moving -MV storage creation into implications. - -### 2026-09-03: System-boundary verification - -Agreed with Aljoscha to proceed with system-level coverage, without a test-only -coordinator command. Added cache-disabled SQL creation and restart coverage for -index use, EXPLAIN, notices, and drop cleanup. The no-sequencer same-batch runtime -case remains uncovered until a production catalog subscriber exists. External -catalog writers fence the adapter today, and the existing read-only catalog -harness does not apply controller effects. - -### 2026-09-03: MV storage registration from committed implications - -MV additions register storage before dependent sinks/indexes, then initialize -read policies through the deferred batch. Runtime and bootstrap share descriptor -construction, including replacement ownership and the initial storage frontier. -Compute installation remains sequencer-side. - -### 2026-09-03: Reusable MV reconstruction - -Extracted bootstrap MV reconstruction without changing cache policy or timestamp -selection. Extended cache-disabled restart coverage to MV results, EXPLAIN, and -continued maintenance. - -Finding: a cache miss during runtime MV reconstruction can choose imports outside -the creator's holds, and acquiring fresh holds after commit cannot recover history -needed for the committed first refresh. Input protection must be established -before moving compute installation. - -### 2026-09-03: Pending first-refresh recovery coverage - -Added cache-disabled restart coverage for an unexecuted first refresh after its -input changes. - -Code inspection ruled out merely broadening creator holds: timestamp selection -joins every hold, changing historical MV readability, while -[`sufficient_collections`](../../../src/adapter/src/coord/indexes.rs) stops at -available indexes and does not cover sibling indexes exposed by same-batch drops. -Compute holds do protect actual transitive dependencies. Production MV installation -paused pending a decision between lifecycle-owned protection and a temporary -protected-plan reconstruction bridge. - -### 2026-09-03: Lifecycle protection prioritized with Aljoscha - -Proceed with lifecycle-owned protection rather than a temporary adapter bridge. - -The durable writer is already shared behind a mutex, so an independent lifecycle -component need not introduce concurrent catalog writers. Existing hold accounting -and epoch fencing are reusable, but neither recovers pending maintained read -requirements from catalog state. Open question before choosing a schema: does -catalog authority require committed frontier bounds, or can advancement be -delegated to a fenced lifecycle owner under catalog-derived policies? - -### 2026-09-03: Delegation failure-scenario exploration - -Worked cases require atomic ordering of creation commit versus hold reclamation, -physical fencing, and a recovery barrier before compaction resumes. Snapshot absence -and catalog fencing alone are insufficient. Small abstract interleaving models -checked these orderings, not production behavior. Delegation shifts coordination -to read-requiring DDL and owner handover rather than ongoing frontier publication. -The shared writer mutex only serializes one in-process handle, not independent -writers or subscriber delivery. - -### 2026-09-03: Catalog-bound traffic estimate - -For compact records advancing once per minute, a planning budget of 300-500 bytes -per retraction/insertion pair gives 0.5-0.83 MB/s at 100,000 changing bounds. -This estimates uncompressed logical updates, not measured persist traffic. Current -structured JSONB encoding retains whole-record JSON text. Whole-item rewrites, -batch sizes, catalog CPU/DDL latency, persist maintenance, subscriber fanout, and -retained history need measurement. - -### 2026-09-03: Explicit bounds agreed with Aljoscha - -Choose catalog-backed compaction bounds and document delegated advancement as an -alternative. Schema, granularity, batching, and publication cadence remain open. -The one-minute traffic estimate is not a chosen cadence. - -### 2026-09-04: Admission and recovery scope exploration - -The proposed validation seam is durable `Transaction` state before -[`into_parts`](../../../src/catalog/src/durable/transaction.rs) extracts the batch. -MV creation protects physical imports but persists only the storage visibility -timestamp. Requirement identity must account for recovery and access-path changes, -not just creation admission. Protecting all logical dependencies risks retaining -unneeded history and rejecting historical creation on unused inputs. Protecting -selected imports requires a recoverable path through replanning and index drops. - -### 2026-09-04: Recovery cases traced - -Existing dependency holds retain dropped indexes and their storage inputs. Live -replanning lacks timestamp-aware access-path selection, while bootstrap can rebuild -indexes with different installation frontiers. Transitive storage protection handles -access-path loss but does not guarantee reconstruction from SQL: [optimizer goldens](../../../test/sqllogictest/transform/union_cancel.slt) -show whole logical inputs eliminated from nonconstant results. A reconstructed plan -can require their discarded history. The [expression cache](../../../src/catalog/src/expr_cache.rs) -explicitly has no cross-build representation compatibility contract. - -Retaining a logical recovery computation over protected storage inputs would add -durable expression compatibility and dependency-version obligations. An IDs-only -alternative using empty-input substitution has unestablished error and -semantic-assumption contracts. - -### 2026-09-04: Logical-input protection agreed with Aljoscha - -Choose logical-input protection based on code inspection, accepting conservative -retention without making optimized expressions authoritative. The catalog already -preserves [logical dependencies for drop safety](../../../test/sqllogictest/materialized_views.slt), -but installed holds and [bootstrap recovery constraints](../../../src/compute-client/src/as_of_selection.rs) -follow physical imports. Bootstrap freezes recovered storage sinces, not missing -history. Historical creation admission and -conversion of existing objects are the next design questions. - -### 2026-09-04: Admission and conversion boundaries agreed with Aljoscha - -Automatic creation timestamps account for all logical inputs. Explicit historical -refresh requests incompatible with their committed compaction permission are -rejected. Existing objects keep their promised results, with conversion active only -once remaining recovery requirements are protected. The conversion mechanism and -rollout policy for objects unable to satisfy that condition remain open. - -### 2026-09-08: Logical collection input discovery - -Added catalog traversal and tests for logical inputs, preserving collection -versions and stopping at upstream MV outputs without selecting indexes. Review -found that resolved collection IDs also include functions, addressed by filtering -name references to relations while retaining raw-HIR reads. This is discovery only, -with no runtime protection or admission change. - -Next useful step: derive maintained requirements from their query definitions -and enforce admission together with bound advancement at the durable transaction -boundary. Conversion policy remains open. - -### 2026-09-08: Fresh-environment milestones agreed with Aljoscha - -Focus initial implementation on fresh environments and defer existing-environment -conversion and rollout. Preserve the rejected alternatives as decision context. -Milestone 1 is active. Sink/index implications, MV storage registration, reusable -reconstruction, and logical-input discovery are implemented. Admission and -catalog-backed protection remain unwired, and independent lifecycle following -and query clients remain unimplemented. - -Next useful step: connect maintained requirements, admission, and bound enforcement -toward the first protected MV creation and recovery example. Conversion is not a -blocker for this work. - -Implementation finding: the [runtime index cache-miss path](../../../src/adapter/src/coord/catalog_implications.rs) -optimizes synchronously after commit, following precommit optimization. Account -for coordinator blocking and the postcommit failure boundary before extending -that pattern to MVs, without making a broad cache redesign a prerequisite. - -### 2026-09-08: Storage-side compaction permission - -Storage consumes explicit bounds independently of retention policies and execution -holds, including initialization, shared-shard versions, and drops. Recovery must -use protected readability rather than the policy frontier. Initialization must -not reuse a handle after a failed fencing compare refreshes its owner token. - -Milestone 1 remains active. There is no durable producer for these bounds yet. -Next useful step: persist bounds and maintained requirements, enforce their -compatibility at the catalog transaction boundary, and supply committed bounds -to storage before relying on them for MV installation and recovery. - -### 2026-09-08: Logical-input MV admission - -MV creation accounts for logical inputs in refresh preparation and timestamp -selection, including eliminated references and planner-introduced reads. Query -references are kept separate from statement dependencies because a replacement's -`FOR` target is not an input read. Existing early holds are preserved when adding -inputs discovered during planning. - -This constrains live admission using held readability, not committed permission. -Milestone 1 still needs a durable producer for bounds and maintained requirements, -transaction-boundary compatibility checks, and committed delivery to storage. - -### 2026-09-08: Catalog integration prioritized with Aljoscha - -Milestone 1's next target is the catalog authority connecting admission to storage: -maintained requirements and compaction permission are checked together in the -catalog transaction, and committed permission reaches storage. This can take -several commits. If blocked, identify the concrete obstacle to this integration. - -The decisive check must reject an incompatible maintained requirement even when -the history remains physically readable. Creator-held readability is not durable -protection for logical inputs eliminated from the installed plan. - -Account for mixed drop/create batches: [storage creation](../../../src/storage-client/src/storage_collections.rs) -requires metadata for every still-governed collection, but [catalog implications](../../../src/adapter/src/coord/catalog_implications.rs) -create collections before processing drops whose metadata is already removed. -Resolve this mismatch as part of integration, preserving valid read protection. - -### 2026-09-08: Mixed-batch storage protection - -Storage creation accepts committed removal of collection metadata while retaining -the installed bound until the drop is applied. Still-cataloged collections must -continue supplying bounds. This preserves create-before-drop implication ordering -and shared-shard protection without another lifecycle API. - -Milestone 1 remains active. Next: persist requirements and bounds, check their -compatibility in the catalog transaction, and deliver committed bounds to storage. - -### 2026-09-08: Durable protection and committed delivery - -Storage-backed bounds and maintained requirements have separate durable records. -Final transaction state checks their compatibility and bound monotonicity, with -recoverable adapter admission and committed delivery to storage. Governance begins -at collection birth, not adoption of a live ungoverned collection. - -Milestone 1 remains active. SQL creation does not yet produce these records. Next: -wire initial bounds and MV logical-input requirements into creation, accounting for -registration timestamps and actual readability when reusing shards. Recovery must -consume the requirements, and durable output progress must advance them and permit -bound publication before this becomes the complete protected-MV path. - -### 2026-09-08: Durable client protection agreed with Aljoscha - -Choose client-incarnation-scoped durable protection that survives replacement of -the components enforcing compaction, without durable query execution or session -failover. Schema, grouping, and reclamation mechanics remain implementation choices. -No separate component called a lifecycle owner is prescribed. - -Milestone 1 remains active. Next: complete the protected-MV production path. -Integration cases identified in review include table commit before initialization, -read-only bootstrap racing a drop, and intermediate bound updates removed by a -same-transaction drop. Independent-client protection is part of milestone 3, not -a prerequisite for the first maintained-recovery example. - -### 2026-09-08: Non-exclusive compaction application agreed with Aljoscha - -Applying committed permission does not require a fenced or exclusive executor. -Separate this responsibility from authorization and read-protection accounting. -Milestone 1 remains active. Existing controller fencing cannot simply be removed -while advancement still depends on private hold accounting. - -### 2026-09-08: Production integration scope question - -Table initialization must respect committed birth permission independently of -later transaction-WAL registration. Secondary MV visibility must not advance the -shared shard's physical since. Source exports and sinks also inherit storage -dependencies, so zero is not a generally valid initial bound. - -The protected-MV path reaches builtin initialization and the catalog's own shard. -Pending question for Aljoscha: include builtin schema evolution in milestone 1, -or leave it with milestone 2's lifecycle changes? The implementer's recommendation -is to include system-catalog inputs rather than silently use legacy protection. -Record production is in progress. Progress publication and the production -compaction/restart demonstration remain unwired. - -### 2026-09-08: Builtin milestone scope agreed with Aljoscha - -Include fresh builtin initialization and same-version recovery in milestone 1. -Defer builtin schema migration and protected-environment version-upgrade support -to milestone 2, separately from conversion of unprotected environments. Builtin -shard replacement reuses GlobalIds while discarding contents, so its interaction -with protected dependent history needs an explicit lifetime/recovery treatment. - -### 2026-09-09: Catalog-backed MV recovery path - -Milestone 1 is complete for fresh environments and same-version recovery. -Creation commits logical requirements with collection permission, including builtin -inputs. Publication follows durable output progress and respects early creation -holds. Finite persist downgrades retain pending delivery through rate limits and -primary handoff. Read-only bootstrap relies on leased readability, not stale -snapshot permission. - -Publication cadence is configurable. Planning-cache freshness and DDL conflict -tracking have distinct revisions, so cadence changes do not abort open DDL. -Next: milestone 2's catalog-driven maintained lifecycle and builtin schema -migration. MV compute installation still uses the sequencer closure. - -### 2026-09-09: Maintained-protection coverage agreed with Aljoscha - -Milestone 1 remains active. The protected-MV path is an implementation checkpoint, -not completion across maintained object types. Source/sink recovery protection -still depends on installed controller accounting, and compute compaction has no -catalog permission cap. Current recovery ordering protects the single-owner path, -but does not establish complete catalog-local authorization. - -Retain the explicit-bounds decision and its accepted catalog traffic cost rather -than reopen delegated admission and reclamation. Next: complete source/sink -requirements at the catalog authorization boundary and maintained compute permission. -Milestone 2 remains the independent lifecycle subscriber and builtin migration. - -### 2026-09-09: Migration follow-on scope agreed with Aljoscha - -Builtin schema migration and protected-environment version upgrades join -existing-environment conversion as follow-on work after demonstrating -fresh-environment decoupling, not milestone 2 requirements. Fresh builtin -initialization, same-version recovery, and ownership handover remain in scope. -Milestone 1 remains active. Next: complete maintained protection, then move -lifecycle execution to the independent catalog subscriber in milestone 2. +Implementation history is in the [log](20260903_decoupled_coordination_log.md). +Workflow and current steering are in the +[implementer prompt](20260903_decoupled_coordination_prompt.md). diff --git a/doc/developer/design/20260903_decoupled_coordination_log.md b/doc/developer/design/20260903_decoupled_coordination_log.md new file mode 100644 index 0000000000000..48037d57d1fbf --- /dev/null +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -0,0 +1,456 @@ +# Decoupled coordination: implementation log + +Historical decisions, findings, and handoffs. The +[design](20260903_decoupled_coordination.md) owns the agreed contracts, and the +[implementer prompt](20260903_decoupled_coordination_prompt.md) owns current steering +and the append-only handoff instructions. + +### 2026-09-03: Scope agreed with Aljoscha + +Multi-adapter operation is the end goal, not the immediate deliverable. +Query-local dataflows remain on the fast protocol. Adapter loss must not harm +other clients or maintained dataflows, but transparent failover is not required. +Completing relevant catalog implication paths is within scope. + +### 2026-09-03: Sink creation from committed implications + +Sink additions now install exports through catalog implications. + +Controllers still live in the adapter. Direct committed-update tests and +same-batch MV/sink creation remain gaps. Index parsing leaves plans absent and +the expression cache has no runtime read API. + +### 2026-09-03: Reusable index reconstruction + +Extracted bootstrap's uncached index planning and notice rendering without +changing cache policy, ordering, or installation. Next: runtime expression-cache +reads and index-add implications using reconstruction on cache misses. Cache +validity must account for committed dependencies, and installation must follow +same-batch prerequisites. + +### 2026-09-03: Runtime index implications + +Index additions now acquire cached or reconstructed plans and install through +implications. Runtime cache reads are best-effort and validated against committed +dependencies and compute availability. Review identified stale session notices +after cache rejection, addressed by filtering dropped dependencies. + +No existing harness can exercise cluster/table/index creation in one committed +batch without the sequencer. Same-batch MV/index creation still depends on moving +MV storage creation into implications. + +### 2026-09-03: System-boundary verification + +Agreed with Aljoscha to proceed with system-level coverage, without a test-only +coordinator command. Added cache-disabled SQL creation and restart coverage for +index use, EXPLAIN, notices, and drop cleanup. The no-sequencer same-batch runtime +case remains uncovered until a production catalog subscriber exists. External +catalog writers fence the adapter today, and the existing read-only catalog +harness does not apply controller effects. + +### 2026-09-03: MV storage registration from committed implications + +MV additions register storage before dependent sinks/indexes, then initialize +read policies through the deferred batch. Runtime and bootstrap share descriptor +construction, including replacement ownership and the initial storage frontier. +Compute installation remains sequencer-side. + +### 2026-09-03: Reusable MV reconstruction + +Extracted bootstrap MV reconstruction without changing cache policy or timestamp +selection. Extended cache-disabled restart coverage to MV results, EXPLAIN, and +continued maintenance. + +Finding: a cache miss during runtime MV reconstruction can choose imports outside +the creator's holds, and acquiring fresh holds after commit cannot recover history +needed for the committed first refresh. Input protection must be established +before moving compute installation. + +### 2026-09-03: Pending first-refresh recovery coverage + +Added cache-disabled restart coverage for an unexecuted first refresh after its +input changes. + +Code inspection ruled out merely broadening creator holds: timestamp selection +joins every hold, changing historical MV readability, while +[`sufficient_collections`](../../../src/adapter/src/coord/indexes.rs) stops at +available indexes and does not cover sibling indexes exposed by same-batch drops. +Compute holds do protect actual transitive dependencies. Production MV installation +paused pending a decision between lifecycle-owned protection and a temporary +protected-plan reconstruction bridge. + +### 2026-09-03: Lifecycle protection prioritized with Aljoscha + +Proceed with lifecycle-owned protection rather than a temporary adapter bridge. + +The durable writer is already shared behind a mutex, so an independent lifecycle +component need not introduce concurrent catalog writers. Existing hold accounting +and epoch fencing are reusable, but neither recovers pending maintained read +requirements from catalog state. Open question before choosing a schema: does +catalog authority require committed frontier bounds, or can advancement be +delegated to a fenced lifecycle owner under catalog-derived policies? + +### 2026-09-03: Delegation failure-scenario exploration + +Worked cases require atomic ordering of creation commit versus hold reclamation, +physical fencing, and a recovery barrier before compaction resumes. Snapshot absence +and catalog fencing alone are insufficient. Small abstract interleaving models +checked these orderings, not production behavior. Delegation shifts coordination +to read-requiring DDL and owner handover rather than ongoing frontier publication. +The shared writer mutex only serializes one in-process handle, not independent +writers or subscriber delivery. + +### 2026-09-03: Catalog-bound traffic estimate + +For compact records advancing once per minute, a planning budget of 300-500 bytes +per retraction/insertion pair gives 0.5-0.83 MB/s at 100,000 changing bounds. +This estimates uncompressed logical updates, not measured persist traffic. Current +structured JSONB encoding retains whole-record JSON text. Whole-item rewrites, +batch sizes, catalog CPU/DDL latency, persist maintenance, subscriber fanout, and +retained history need measurement. + +### 2026-09-03: Explicit bounds agreed with Aljoscha + +Choose catalog-backed compaction bounds and document delegated advancement as an +alternative. Schema, granularity, batching, and publication cadence remain open. +The one-minute traffic estimate is not a chosen cadence. + +### 2026-09-04: Admission and recovery scope exploration + +The proposed validation seam is durable `Transaction` state before +[`into_parts`](../../../src/catalog/src/durable/transaction.rs) extracts the batch. +MV creation protects physical imports but persists only the storage visibility +timestamp. Requirement identity must account for recovery and access-path changes, +not just creation admission. Protecting all logical dependencies risks retaining +unneeded history and rejecting historical creation on unused inputs. Protecting +selected imports requires a recoverable path through replanning and index drops. + +### 2026-09-04: Recovery cases traced + +Existing dependency holds retain dropped indexes and their storage inputs. Live +replanning lacks timestamp-aware access-path selection, while bootstrap can rebuild +indexes with different installation frontiers. Transitive storage protection handles +access-path loss but does not guarantee reconstruction from SQL: [optimizer goldens](../../../test/sqllogictest/transform/union_cancel.slt) +show whole logical inputs eliminated from nonconstant results. A reconstructed plan +can require their discarded history. The [expression cache](../../../src/catalog/src/expr_cache.rs) +explicitly has no cross-build representation compatibility contract. + +Retaining a logical recovery computation over protected storage inputs would add +durable expression compatibility and dependency-version obligations. An IDs-only +alternative using empty-input substitution has unestablished error and +semantic-assumption contracts. + +### 2026-09-04: Logical-input protection agreed with Aljoscha + +Choose logical-input protection based on code inspection, accepting conservative +retention without making optimized expressions authoritative. The catalog already +preserves [logical dependencies for drop safety](../../../test/sqllogictest/materialized_views.slt), +but installed holds and [bootstrap recovery constraints](../../../src/compute-client/src/as_of_selection.rs) +follow physical imports. Bootstrap freezes recovered storage sinces, not missing +history. Historical creation admission and +conversion of existing objects are the next design questions. + +### 2026-09-04: Admission and conversion boundaries agreed with Aljoscha + +Automatic creation timestamps account for all logical inputs. Explicit historical +refresh requests incompatible with their committed compaction permission are +rejected. Existing objects keep their promised results, with conversion active only +once remaining recovery requirements are protected. The conversion mechanism and +rollout policy for objects unable to satisfy that condition remain open. + +### 2026-09-08: Logical collection input discovery + +Added catalog traversal and tests for logical inputs, preserving collection +versions and stopping at upstream MV outputs without selecting indexes. Review +found that resolved collection IDs also include functions, addressed by filtering +name references to relations while retaining raw-HIR reads. This is discovery only, +with no runtime protection or admission change. + +Next useful step: derive maintained requirements from their query definitions +and enforce admission together with bound advancement at the durable transaction +boundary. Conversion policy remains open. + +### 2026-09-08: Fresh-environment milestones agreed with Aljoscha + +Focus initial implementation on fresh environments and defer existing-environment +conversion and rollout. Preserve the rejected alternatives as decision context. +Milestone 1 is active. Sink/index implications, MV storage registration, reusable +reconstruction, and logical-input discovery are implemented. Admission and +catalog-backed protection remain unwired, and independent lifecycle following +and query clients remain unimplemented. + +Next useful step: connect maintained requirements, admission, and bound enforcement +toward the first protected MV creation and recovery example. Conversion is not a +blocker for this work. + +Implementation finding: the [runtime index cache-miss path](../../../src/adapter/src/coord/catalog_implications.rs) +optimizes synchronously after commit, following precommit optimization. Account +for coordinator blocking and the postcommit failure boundary before extending +that pattern to MVs, without making a broad cache redesign a prerequisite. + +### 2026-09-08: Storage-side compaction permission + +Storage consumes explicit bounds independently of retention policies and execution +holds, including initialization, shared-shard versions, and drops. Recovery must +use protected readability rather than the policy frontier. Initialization must +not reuse a handle after a failed fencing compare refreshes its owner token. + +Milestone 1 remains active. There is no durable producer for these bounds yet. +Next useful step: persist bounds and maintained requirements, enforce their +compatibility at the catalog transaction boundary, and supply committed bounds +to storage before relying on them for MV installation and recovery. + +### 2026-09-08: Logical-input MV admission + +MV creation accounts for logical inputs in refresh preparation and timestamp +selection, including eliminated references and planner-introduced reads. Query +references are kept separate from statement dependencies because a replacement's +`FOR` target is not an input read. Existing early holds are preserved when adding +inputs discovered during planning. + +This constrains live admission using held readability, not committed permission. +Milestone 1 still needs a durable producer for bounds and maintained requirements, +transaction-boundary compatibility checks, and committed delivery to storage. + +### 2026-09-08: Catalog integration prioritized with Aljoscha + +Milestone 1's next target is the catalog authority connecting admission to storage: +maintained requirements and compaction permission are checked together in the +catalog transaction, and committed permission reaches storage. This can take +several commits. If blocked, identify the concrete obstacle to this integration. + +The decisive check must reject an incompatible maintained requirement even when +the history remains physically readable. Creator-held readability is not durable +protection for logical inputs eliminated from the installed plan. + +Account for mixed drop/create batches: [storage creation](../../../src/storage-client/src/storage_collections.rs) +requires metadata for every still-governed collection, but [catalog implications](../../../src/adapter/src/coord/catalog_implications.rs) +create collections before processing drops whose metadata is already removed. +Resolve this mismatch as part of integration, preserving valid read protection. + +### 2026-09-08: Mixed-batch storage protection + +Storage creation accepts committed removal of collection metadata while retaining +the installed bound until the drop is applied. Still-cataloged collections must +continue supplying bounds. This preserves create-before-drop implication ordering +and shared-shard protection without another lifecycle API. + +Milestone 1 remains active. Next: persist requirements and bounds, check their +compatibility in the catalog transaction, and deliver committed bounds to storage. + +### 2026-09-08: Durable protection and committed delivery + +Storage-backed bounds and maintained requirements have separate durable records. +Final transaction state checks their compatibility and bound monotonicity, with +recoverable adapter admission and committed delivery to storage. Governance begins +at collection birth, not adoption of a live ungoverned collection. + +Milestone 1 remains active. SQL creation does not yet produce these records. Next: +wire initial bounds and MV logical-input requirements into creation, accounting for +registration timestamps and actual readability when reusing shards. Recovery must +consume the requirements, and durable output progress must advance them and permit +bound publication before this becomes the complete protected-MV path. + +### 2026-09-08: Durable client protection agreed with Aljoscha + +Choose client-incarnation-scoped durable protection that survives replacement of +the components enforcing compaction, without durable query execution or session +failover. Schema, grouping, and reclamation mechanics remain implementation choices. +No separate component called a lifecycle owner is prescribed. + +Milestone 1 remains active. Next: complete the protected-MV production path. +Integration cases identified in review include table commit before initialization, +read-only bootstrap racing a drop, and intermediate bound updates removed by a +same-transaction drop. Independent-client protection is part of milestone 3, not +a prerequisite for the first maintained-recovery example. + +### 2026-09-08: Non-exclusive compaction application agreed with Aljoscha + +Applying committed permission does not require a fenced or exclusive executor. +Separate this responsibility from authorization and read-protection accounting. +Milestone 1 remains active. Existing controller fencing cannot simply be removed +while advancement still depends on private hold accounting. + +### 2026-09-08: Production integration scope question + +Table initialization must respect committed birth permission independently of +later transaction-WAL registration. Secondary MV visibility must not advance the +shared shard's physical since. Source exports and sinks also inherit storage +dependencies, so zero is not a generally valid initial bound. + +The protected-MV path reaches builtin initialization and the catalog's own shard. +Pending question for Aljoscha: include builtin schema evolution in milestone 1, +or leave it with milestone 2's lifecycle changes? The implementer's recommendation +is to include system-catalog inputs rather than silently use legacy protection. +Record production is in progress. Progress publication and the production +compaction/restart demonstration remain unwired. + +### 2026-09-08: Builtin milestone scope agreed with Aljoscha + +Include fresh builtin initialization and same-version recovery in milestone 1. +Defer builtin schema migration and protected-environment version-upgrade support +to milestone 2, separately from conversion of unprotected environments. Builtin +shard replacement reuses GlobalIds while discarding contents, so its interaction +with protected dependent history needs an explicit lifetime/recovery treatment. + +### 2026-09-09: Catalog-backed MV recovery path + +Milestone 1 is complete for fresh environments and same-version recovery. +Creation commits logical requirements with collection permission, including builtin +inputs. Publication follows durable output progress and respects early creation +holds. Finite persist downgrades retain pending delivery through rate limits and +primary handoff. Read-only bootstrap relies on leased readability, not stale +snapshot permission. + +Publication cadence is configurable. Planning-cache freshness and DDL conflict +tracking have distinct revisions, so cadence changes do not abort open DDL. +Next: milestone 2's catalog-driven maintained lifecycle and builtin schema +migration. MV compute installation still uses the sequencer closure. + +### 2026-09-09: Maintained-protection coverage agreed with Aljoscha + +Milestone 1 remains active. The protected-MV path is an implementation checkpoint, +not completion across maintained object types. Source/sink recovery protection +still depends on installed controller accounting, and compute compaction has no +catalog permission cap. Current recovery ordering protects the single-owner path, +but does not establish complete catalog-local authorization. + +Retain the explicit-bounds decision and its accepted catalog traffic cost rather +than reopen delegated admission and reclamation. Next: complete source/sink +requirements at the catalog authorization boundary and maintained compute permission. +Milestone 2 remains the independent lifecycle subscriber and builtin migration. + +### 2026-09-09: Migration follow-on scope agreed with Aljoscha + +Builtin schema migration and protected-environment version upgrades join +existing-environment conversion as follow-on work after demonstrating +fresh-environment decoupling, not milestone 2 requirements. Fresh builtin +initialization, same-version recovery, and ownership handover remain in scope. +Milestone 1 remains active. Next: complete maintained protection, then move +lifecycle execution to the independent catalog subscriber in milestone 2. + +### 2026-09-09: Publication batching and remaining scope questions + +Publication stages changed requirements and bounds as one catalog operation, +avoiding per-record scans of accumulated transaction updates. End-to-end +publication, DDL latency, and retention measurement remain necessary. + +Pending guidance from Aljoscha: using committed permission for a fresh sink's +automatic cutoff can omit changes that lagging physical compaction still retains, +including with `SNAPSHOT = false`. The implementer's proposal is to apply the +agreed creation-admission rule to that cutoff while preserving pending output +through alteration and recovery. A second proposal is to cover catalog metric +sinks in milestone 1 and address the ownership of curated, transient-ID, +per-replica metric dataflows with milestone 2's subscriber. + +Milestone 1 remains active. Next: resolve these boundaries and complete source/sink +authorization and maintained compute permission. + +### 2026-09-09: Maintained-protection integration checkpoint + +The metric-sink discussion clarified that protection belongs to their inputs and +execution holds. Their process-local output needs no compaction bound or historical +recovery requirement. The curated definition list is empty. + +The local implementation connects source/sink requirements to their definitions and +durable progress, including a committed remap floor carried into ingestion setup. +Catalog comparison uses frozen read-only reconstruction rather than changing +publication settings. Index bound ownership and controller accounting are present, +but index publication, committed delivery, and pre-install authorization are unwired. + +Pending decision: may fenced index reconstruction establish readability later than +the saved bound once all valid read requirements are protected? Same-replica +reconciliation can compact or replace an existing trace, so it is not universally +a fresh installation. No permission bypass is implemented. + +Milestone 1 remains active. Next: resolve that boundary, connect the index path, +and complete production recovery and publication-cost evidence. An adjacent issue +remains separate: `alter_export` retains the old descriptor after an input change, +which a subsequent connection alteration can consult. + +### 2026-09-09: Recovery authority and sink admission agreed with Aljoscha + +Preserve committed compaction permission without making saved index bounds a +historical reconstruction guarantee. Recovery must preserve all valid read +requirements and authorize destructive reconciliation. Preserve read-only +prewarming by bringing forward the necessary catalog-following work, rather than +introducing a startup-specific writer protocol or a reconstruction exemption. + +Fresh sinks select their automatic cutoff against committed input permission, +including with `SNAPSHOT = false`. Existing sinks retain pending output through +alteration and recovery. Their requirements advance with durable output progress, +not merely with input compaction permission. + +Milestone 1 remains active. Next: connect maintained compute permission and +recovery authorization, then establish production recovery and publication-cost +evidence across maintained object types. + +### 2026-09-09: Maintained compute permission and recovery evidence + +Index bounds publish with storage progress and reach compute before installation. +Writable recovery commits the selected installation frontier. Read-only prewarming +follows committed bounds through an independent durable catalog reader beside its +SQL savepoint, and waits for permission rather than installing beyond it. A dropped +sibling index no longer blocks delivery to surviving lifetimes. + +Findings: physical history release for sources is bounded by crash-surviving +persist reader leases and a fixed ingestion resumption hold, not only by catalog +permission. Consistency checking needed an exact committed prefix because +publication removes any quiet catalog window. Catalog history growth at a +one-second cadence made `mz_catalog_raw` observers slow even at 100 collections. + +Open question for Aljoscha: a same-version replan can select an index `as_of` +beyond the writer's bound when a suspended cluster's warmup hold caps publication. +Read-only bootstrap then waits before installing any dataflow. This is a liveness +tradeoff of strict permission, not a safety gap, and needs a decision on whether +prewarming should proceed per cluster or keep waiting. + +Milestone 1 evidence: protected recovery demo and read-only index proof pass on +published binaries. Representative publication measurement is in progress. + +### 2026-09-09: Design consolidation and steering agreed with Aljoscha + +Keep the architectural choices. Selected implementation decisions belong in their +own design section, historical handoffs in this sibling log, and current steering +in the prompt. + +Milestone 1 remains active. A blocked cluster should not prevent unrelated, +otherwise-ready work from prewarming. The affected cluster's eventual progress +and promotion-readiness behavior remain open. Next: resolve those questions and +concurrent-drop behavior, finish publication evidence including real subscriber +costs, and advance the production maintained-lifecycle subscriber. + +### 2026-09-09: Permission as an installation constraint agreed with Aljoscha + +Committed permission is a hard upper constraint on installation `as_of`, applied +in as-of selection. Installation never waits for the writer to advance permission. +This supersedes the partial-prewarming direction in the previous entry: no cluster +blocks, so per-cluster isolation and the affected cluster's promotion readiness +need no separate treatment. + +Review of the branch against the design found the protection contract implemented +across maintained types, with machinery beyond the design in three places: index +bound birth records threaded through eleven transaction mutation paths with +whole-catalog scans in validation, test-only surface on the durable catalog trait +and coordinator command enum for the consistency checker, and publication work +proportional to catalog size. The design now states that a record must carry +non-derivable information and that publication scales with what changed. + +Milestone 1 remains active. Next: remove the wait loop and index birth records, +trim the checker surface, make publication proportional to changed records, then +record measurements and squash the 09-09 history before milestone 2. + +### 2026-09-09: Index installation feasibility and checker boundary + +Removing index birth records leaves first-installation authority unresolved. If +absence means MIN, an index over an already-compacted input cannot install. A +saved index bound can also precede actual readability when replanning introduces +an input outside the installed plan's protection. A hard upper constraint cannot +make those frontiers compatible. Index cleanup is paused for a decision on initial +permission and the outcome of infeasible read-only reconstruction. + +The consistency checker needs independent reconstruction at an aligned immutable +catalog version under ongoing publication. Moving that comparison into the existing +diagnostic check is a proposal, not an agreed decision. It removes the external +prefix certificate but adds server-side reconstruction cost. Next: settle these +two boundaries, then resume index simplification and changed-record publication. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index af5269180cda8..0aac8d1301aa3 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -7,61 +7,101 @@ Working PR: https://github.com/MaterializeInc/materialize/pull/38696 Bookmark: decoupled-coordination Remote: origin, pointing to the contributor fork, not upstream -Read the design and its implementation log, then inspect the current code, -worktree, and remote bookmark. Preserve existing work and account for progress -from other sessions. Use the latest handoff and current code to identify the -active milestone in the design's Implementation and verification section. Choose -the next coherent piece toward its observable outcome, briefly explain that -choice, then implement and verify it. Historical proposals and next steps are -context, not a cumulative task list. +Read the design and doc/developer/design/20260903_decoupled_coordination_log.md, +then inspect the current code, worktree, and remote bookmark. Preserve existing +work and account for progress from other sessions. Use the latest handoff and +current code to identify the active milestone in the design's Implementation and +verification section. Choose the next coherent piece toward its observable outcome, +briefly explain that choice, then implement and verify it. Historical proposals +and next steps are context, not a cumulative task list. Current steering Check which of these review findings remain unresolved, then choose one coherent change. Remove resolved steering from this prompt. These are implementation -priorities, not new design requirements or a reason to reopen agreed decisions. - -- Maintained protection: milestone 1 remains active. The protected-MV path is an - implementation checkpoint, not completion across maintained object types. Make - source/sink recovery requirements constrain catalog authorization even when - local controller accounting is absent, and bring maintained compute compaction - under committed permission. Follow the milestone's requirement-derivation and - recovery-semantics boundaries rather than prescribing new records for every type. -- Publication scaling: publish_read_protection emits one catalog op per record, - while Transaction::get_op_updates scans accumulated pending updates after each - op. This creates quadratic work on the coordinator loop. Address that path and - measure publication cost at representative collection counts, including DDL - latency and retained history. Do not infer scalability from the small recovery - demonstration or make a general catalog redesign a prerequisite. -- Consistency-check coupling: check_catalog_state_quiesced changes the publication - interval, and ddl_revision introduces a production conflict exception for that - setting. Revisit whether the checker is forcing unnecessary production semantics. - Preserve full catalog comparison, including protection records, without adding - further special cases to make the harness pass. -- Ownership transition: after milestone 1, prioritize a real catalog subscriber - over more standalone APIs. In that transition, address prepare_state's reliance - on locally installed collections and make the writer-side responsibility for - complete maintained requirements explicit. An MV's requirement is a separate - sequencer op today. These are transitional dependencies, not evidence that the - current single-owner path fails. +priorities, not additional design requirements. + +Milestone 1 remains active. Source/sink authorization and maintained compute +permission are connected. What remains is removing machinery that grew beyond +the design, recovery liveness, and performance evidence. Items 1 and 2 remove +code and should go first. + +Items 1 and 2 are paused for the first-installation authority and infeasible +reconstruction decisions described in the latest handoff. A hard upper bound +does not by itself guarantee a readable installation timestamp. + +1. Permission as an as_of constraint: the design now settles that committed + permission is a hard upper constraint on installation `as_of`, applied in + as-of selection. Remove the read-only wait loop in index authorization and + the per-second re-delivery of all index bounds. Read-only bootstrap then + installs within permission on every cluster without waiting on the writer, + which also resolves the blocked-cluster prewarming question. Keep concurrent + drops during bootstrap correct. +2. Index bound birth records: an index bound record at birth always says MIN and + carries no information. Let an index's bound appear at first publication, and + remove the lifecycle threading through item, system-mapping, introspection, + and cluster mutation paths together with the whole-catalog `index_ids` scans + in validation. Fold as-of authorization into ordinary publication so CREATE + INDEX is one durable commit and `ship_new_dataflow` no longer commits from + inside an implication batch. Keep monotonicity and no-regression. +3. Test-only production surface: `freeze`/`freeze_at`, `FrozenReadonly`, + `Command::CatalogDumpSnapshot`, `dump_upper`, the `mz-catalog-upper` header, + and `publish_interval = 0` as a pause switch exist for the testdrive + consistency checker and test workflows. Decide once, with Aljoscha, what the + checker actually needs, then remove the rest. Do not add further diagnostic + surface to production traits or the coordinator command enum. +4. Publication scaling: per-tick work and transaction validation must scale with + changed records, not with all collections or items. Then finish representative + measurements using the existing workflow, including DDL latency, subscriber + lag, and retained history, and record the numbers in the PR. Distinguish + publication overhead from workload and observer costs. Do not make a general + catalog redesign a prerequisite. +5. Add a targeted test for final sink execution as-of selection using aggregate + readability rather than policy permission, including ungoverned collections. +6. History: the 09-09 commits, including `wip: Integrate maintained recovery + protection`, are not yet one coherent story. Squash them before milestone 2 + work begins. +7. Ownership transition: prioritize a production maintained-lifecycle subscriber + over more standalone APIs. The bound-only `CatalogSubscriber` is not that + outcome and should either grow into it or take a narrower name. Address + creator-local plans, prepare_state's reliance on locally installed collections, + and writer-side responsibility for complete maintained requirements: the + sequencer supplies an MV's requirement as its own op, and the transaction + validates but does not derive it. These are transitional dependencies, not + evidence that the current single-owner path fails. Keep the draft PR description accurate about what is implemented and what remains, with validation status in the PR rather than the design log. -Prefer connecting existing pieces through the active milestone's production path -over adding further standalone APIs. Preparatory work is appropriate when it -unblocks that path. Let integration evidence refine intermediate interfaces -rather than adding machinery to preserve them. +Standing rules + +- No production trait methods, coordinator commands, or system-variable semantics + whose only consumer is a test harness. Bring the need to Aljoscha first. +- A durable record is added only when it carries information that cannot be + derived from existing catalog state or durable progress. A record whose value + is constant at birth is a signal to look again. +- Milestone 2 starts with MV compute installation from committed state and a real + catalog subscriber. Client protection stays in milestone 3. Do not pull its + machinery forward. + +Prefer changes that remove a dependency on the originating adapter and demonstrate +that through a production path. Preparatory work is appropriate when it unblocks +that path. Temporary interfaces may be replaced as integration clarifies the +boundary. Do not add parallel machinery merely to preserve them or keep milestone +boundaries tidy. Keep maintained requirements object-owned and client protection +incarnation-scoped, without durable per-query or per-SQL-session bookkeeping. Treat the design as the agreed boundaries, not a prescribed mechanism. Prefer the smallest coherent solution that preserves the full capability. Incremental progress is fine, but do not mistake an intermediate step for completion. Implementation choices within the agreed boundaries do not require renewed -design approval. +design approval. Catalog fields, leases, protocol messages, planning placement, +and process topology remain implementation choices. Bring discoveries, consequential tradeoffs, and scope growth to me, Aljoscha. Pause affected work when guidance is needed rather than silently narrowing -scope, adding machinery, or changing an agreed boundary. +scope, adding machinery, or changing an agreed boundary. Bring disproportionate +implementation cost to me even when the implementation conforms to the design. Follow repository instructions and skills. Verify at the changed boundaries, and seek independent review when the risk warrants it. @@ -87,7 +127,8 @@ message and change description explaining the outcome, rationale, and validation status, not the chronology of attempts. If blocked, report the blocker rather than claiming completion. -Append only a minimal dated handoff to the design's log: consequential findings +Append only a minimal dated handoff to +doc/developer/design/20260903_decoupled_coordination_log.md: consequential findings or decisions, unresolved questions, and the next useful step. Do not record validation status there at all: CI results, pending checks, formatting or compile checks, tool availability, and review outcomes are reconstructible from @@ -107,3 +148,16 @@ that others have built on. Report validation failures honestly. Keep the existing PR as a draft. Do not merge, mark it ready, or push to upstream or other branches without asking. ``` + +## Code navigation + +- [Catalog implications](../../../src/adapter/src/coord/catalog_implications.rs) + derive effects from committed changes. MV and metric-sink compute installation + still have sequencer-side paths. +- [Compute protocol](../../../src/compute-client/src/protocol/command.rs) mixes + lifecycle and query commands. [Transport](../../../src/service/src/transport.rs) + replaces the active client on a new connection. +- [StorageCollections](../../../src/storage-client/src/storage_collections.rs) + owns storage capability accounting and critical since handles. The fixed + critical-reader identity and epoch fencing support handover, not independent + owners aggregating their local holds. From a604a3b990947aabfb4d46b45cfc5160c2137883 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 21:31:03 +0000 Subject: [PATCH 29/36] doc: Settle index permission as published since and the checker boundary A fresh index has no bound until first publication, and recovery installs at the least readable frontier, capped by permission only where permission is at or above readability. When it is below, nothing durable depends on the gap because durable protection of an index protects its inputs, so the index is replaced and its bound follows through publication. Installation never waits for a catalog write. Record that client requirements on indexes protect the index's inputs, which is what makes this safe under milestone 3. Agree to move the consistency checker's reconstruction behind the existing environmentd diagnostic check and remove the dedicated command, header, and frozen catalog mode. Merge the paused steering items accordingly. --- .../design/20260903_decoupled_coordination.md | 16 ++++-- .../20260903_decoupled_coordination_log.md | 17 ++++++ .../20260903_decoupled_coordination_prompt.md | 57 +++++++++---------- 3 files changed, 55 insertions(+), 35 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 020d3f7ed1091..d51a001177e18 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -144,7 +144,9 @@ have equivalent history. Client protection is scoped to a client incarnation and the collections and frontiers it requires. A client's read protection remains valid across restarts or replacement of the components enforcing compaction. Durable client requirements -participate in compaction accounting alongside maintained requirements. +participate in compaction accounting alongside maintained requirements. A durable +requirement on an index protects the index's inputs at that frontier, since +arrangements do not survive compute restart and must be reconstructible. Clients may aggregate query and transaction needs locally under established protection. Ordinary query execution must not require a durable write for each @@ -193,10 +195,14 @@ with input compaction permission. ### Index reconstruction -A saved index compaction bound is not a historical reconstruction guarantee. -Recovery selects installation frontiers from actual readability and all valid read -requirements, within committed permission. Reconciliation that discards existing -history requires committed permission, even when performed as part of reconstruction. +An index's compaction bound is its published since, not a historical reconstruction +guarantee. A fresh index has no bound until first publication. Recovery installs at +the least readable frontier, capped by committed permission where permission is at +or above readability. Where permission is below readability, no durable requirement +depends on the gap, because durable protection of an index protects its inputs. The +index is replaced at readability and its bound follows through publication. +Installation never waits for a catalog write. Live readers of an existing trace +remain protected by execution holds. ### Read-only prewarming diff --git a/doc/developer/design/20260903_decoupled_coordination_log.md b/doc/developer/design/20260903_decoupled_coordination_log.md index 48037d57d1fbf..dacd079a58d69 100644 --- a/doc/developer/design/20260903_decoupled_coordination_log.md +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -454,3 +454,20 @@ catalog version under ongoing publication. Moving that comparison into the exist diagnostic check is a proposal, not an agreed decision. It removes the external prefix certificate but adds server-side reconstruction cost. Next: settle these two boundaries, then resume index simplification and changed-record publication. + +### 2026-09-09: Index permission and checker boundary agreed with Aljoscha + +An index's bound is its published since. A fresh index has no bound until first +publication, so first installation has no cap. When a saved bound is below actual +readability, nothing durable depends on the gap, because a durable requirement on +an index protects its inputs. The index is replaced at readability and its bound +follows through publication. Installation never waits for a catalog write. This +resolves both halves of the previous entry's question without a pre-install commit. + +The consistency checker's durable-to-memory reconstruction moves behind the +existing environmentd diagnostic check, off the coordinator thread, using an +ordinary read-only open synced to the snapshot's upper. The dedicated command, +header, and frozen catalog mode are removed once no test needs them. + +Milestone 1 remains active. Next: resume index simplification under these rules, +then the checker move and changed-record publication. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index 0aac8d1301aa3..b3a23aa677541 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -23,45 +23,42 @@ priorities, not additional design requirements. Milestone 1 remains active. Source/sink authorization and maintained compute permission are connected. What remains is removing machinery that grew beyond -the design, recovery liveness, and performance evidence. Items 1 and 2 remove -code and should go first. - -Items 1 and 2 are paused for the first-installation authority and infeasible -reconstruction decisions described in the latest handoff. A hard upper bound -does not by itself guarantee a readable installation timestamp. - -1. Permission as an as_of constraint: the design now settles that committed - permission is a hard upper constraint on installation `as_of`, applied in - as-of selection. Remove the read-only wait loop in index authorization and - the per-second re-delivery of all index bounds. Read-only bootstrap then - installs within permission on every cluster without waiting on the writer, - which also resolves the blocked-cluster prewarming question. Keep concurrent - drops during bootstrap correct. -2. Index bound birth records: an index bound record at birth always says MIN and - carries no information. Let an index's bound appear at first publication, and - remove the lifecycle threading through item, system-mapping, introspection, - and cluster mutation paths together with the whole-catalog `index_ids` scans - in validation. Fold as-of authorization into ordinary publication so CREATE - INDEX is one durable commit and `ship_new_dataflow` no longer commits from - inside an implication batch. Keep monotonicity and no-regression. -3. Test-only production surface: `freeze`/`freeze_at`, `FrozenReadonly`, +the design, recovery liveness, and performance evidence. Item 1 removes code and +goes first. + +1. Index permission as published since: the design's Index reconstruction + decision now settles both halves of the paused question. A fresh index has no + bound record until first publication, so the MIN birth records and their + lifecycle threading through item, system-mapping, introspection, and cluster + mutation paths go, together with the whole-catalog `index_ids` scans in + validation. Installation selects the least readable frontier, capped by + permission where permission is at or above readability, and otherwise replaces + the index at readability with the bound following through publication. Remove + the read-only wait loop and the per-second re-delivery of all index bounds. + CREATE INDEX becomes one durable commit and `ship_new_dataflow` no longer + commits from inside an implication batch. Keep monotonicity and no-regression + for published bounds, and keep concurrent drops during bootstrap correct. +2. Consistency checker: agreed to move the durable-to-memory reconstruction + behind the existing diagnostic check in environmentd, where a memory snapshot + and its durable upper are available together. Reconstruction is an ordinary + read-only open synced to that upper and then discarded. Run it off the + coordinator thread so a check does not stall DDL, and reuse the existing + snapshot path rather than adding a command variant. Then remove `Command::CatalogDumpSnapshot`, `dump_upper`, the `mz-catalog-upper` header, - and `publish_interval = 0` as a pause switch exist for the testdrive - consistency checker and test workflows. Decide once, with Aljoscha, what the - checker actually needs, then remove the rest. Do not add further diagnostic - surface to production traits or the coordinator command enum. -4. Publication scaling: per-tick work and transaction validation must scale with + `freeze`/`freeze_at`, `FrozenReadonly`, and the `publish_interval = 0` pause + semantics if no test still needs them. +3. Publication scaling: per-tick work and transaction validation must scale with changed records, not with all collections or items. Then finish representative measurements using the existing workflow, including DDL latency, subscriber lag, and retained history, and record the numbers in the PR. Distinguish publication overhead from workload and observer costs. Do not make a general catalog redesign a prerequisite. -5. Add a targeted test for final sink execution as-of selection using aggregate +4. Add a targeted test for final sink execution as-of selection using aggregate readability rather than policy permission, including ungoverned collections. -6. History: the 09-09 commits, including `wip: Integrate maintained recovery +5. History: the 09-09 commits, including `wip: Integrate maintained recovery protection`, are not yet one coherent story. Squash them before milestone 2 work begins. -7. Ownership transition: prioritize a production maintained-lifecycle subscriber +6. Ownership transition: prioritize a production maintained-lifecycle subscriber over more standalone APIs. The bound-only `CatalogSubscriber` is not that outcome and should either grow into it or take a narrower name. Address creator-local plans, prepare_state's reliance on locally installed collections, From ae3eaf805854b0d27758ee97758b143dd7345674 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 10 Sep 2026 02:27:22 +0000 Subject: [PATCH 30/36] catalog: Integrate sparse recovery protection and changed-record publication Publish index permission from installed readability, without birth records, installation-time catalog writes, or read-only waits. Preserve feasible saved permission and replace indexes at readability when it is not feasible. Track changed controller frontiers and maintained input requirements. Validate affected durable records using structurally shared projections, and retire sparse bounds from final lifetime changes. Reconstruct independent durable snapshots through the existing diagnostic endpoint off the coordinator thread. Preserve ordinary invariant checks and legacy compatibility without a frozen mode or dedicated snapshot command. Add controlled recovery and publication-cost workflows, held-readability coverage, and bounded-kind counters for committed catalog row payload. Keep experiment dispatch out of the regular pipeline. The large identical-index stress case exposes quadratic notice and dependency work plus repeated uncached planning. Its scope split remains unresolved. Milestone 1 is not declared complete. --- AGENTS.md | 1 - Cargo.lock | 1 + .../design/20260903_decoupled_coordination.md | 18 +- .../20260903_decoupled_coordination_log.md | 16 + .../20260903_decoupled_coordination_prompt.md | 63 +- src/adapter/benches/catalog.rs | 128 +- src/adapter/src/catalog.rs | 251 +++- src/adapter/src/catalog/apply.rs | 53 +- src/adapter/src/catalog/consistency.rs | 47 + src/adapter/src/catalog/open.rs | 208 ++- src/adapter/src/catalog/state.rs | 466 ++++++- src/adapter/src/catalog/transact.rs | 679 ++++++++-- src/adapter/src/client.rs | 73 +- src/adapter/src/command.rs | 5 +- src/adapter/src/coord.rs | 70 +- src/adapter/src/coord/catalog_implications.rs | 17 +- src/adapter/src/coord/command_handler.rs | 15 +- .../src/coord/compaction_bound_subscriber.rs | 252 ++++ src/adapter/src/coord/ddl.rs | 22 +- src/adapter/src/coord/info_metrics.rs | 7 +- src/adapter/src/coord/read_protection.rs | 453 ++++++- src/adapter/src/coord/sequencer/inner.rs | 41 +- .../inner/create_materialized_view.rs | 12 +- src/adapter/src/error.rs | 8 +- src/adapter/src/peek_client.rs | 7 +- src/adapter/src/session.rs | 8 +- src/adapter/src/util.rs | 1 + src/catalog/Cargo.toml | 1 + src/catalog/src/durable.rs | 12 +- src/catalog/src/durable/metrics.rs | 24 +- src/catalog/src/durable/objects.rs | 91 ++ .../src/durable/objects/state_update.rs | 2 +- src/catalog/src/durable/persist.rs | 165 ++- src/catalog/src/durable/transaction.rs | 502 +++++-- src/catalog/tests/compute-read-protection.rs | 750 +++++++++++ src/catalog/tests/open.rs | 1 + src/catalog/tests/read-write.rs | 426 ++++++ src/compute-client/src/as_of_selection.rs | 77 +- src/compute-client/src/controller.rs | 149 ++- .../src/controller/compaction_tests.rs | 866 ++++++++++++ src/compute-client/src/controller/error.rs | 14 + src/compute-client/src/controller/instance.rs | 154 ++- src/controller/src/lib.rs | 2 + src/environmentd/src/http/catalog.rs | 23 +- src/environmentd/src/lib.rs | 35 +- src/sql/src/session/vars/definitions.rs | 7 +- src/storage-client/src/client.rs | 5 + src/storage-client/src/storage_collections.rs | 452 ++++++- src/storage-controller/src/history.rs | 19 + src/storage-controller/src/instance.rs | 36 +- src/storage-controller/src/lib.rs | 311 ++++- src/storage/src/storage_state.rs | 39 +- .../src/storage_state/async_storage_worker.rs | 318 ++++- src/testdrive/src/action.rs | 77 +- src/testdrive/src/action/consistency.rs | 260 ++-- test/0dt/mzcompose.py | 286 +++- test/restart/mzcompose.py | 1185 ++++++++++++++++- .../audit-log.td | 3 - test/testdrive/audit-log.td | 3 - 59 files changed, 8257 insertions(+), 960 deletions(-) create mode 100644 src/adapter/src/coord/compaction_bound_subscriber.rs create mode 100644 src/catalog/tests/compute-read-protection.rs create mode 100644 src/compute-client/src/controller/compaction_tests.rs diff --git a/AGENTS.md b/AGENTS.md index 16def78e95123..fc78ff106dcf5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,4 +25,3 @@ Never regenerate full Cargo.lock. When changing deps: ## Licensing Two files control license policy, **keep in sync**: `deny.toml` (`[licenses].allow`) and `about.toml` (`accepted`). New dep with new license not already allowed: add SPDX identifier to both. - diff --git a/Cargo.lock b/Cargo.lock index 4a9c30a7f0ebb..c52f401e386ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6670,6 +6670,7 @@ dependencies = [ "derivative", "differential-dataflow", "futures", + "imbl", "insta", "ipnet", "itertools 0.14.0", diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index d51a001177e18..8069feb3c299b 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -123,12 +123,11 @@ valid, compute and persist compaction must respect it, including through dependencies, installation, and ownership handover. One client cannot release another's protection. -Committed permission bounds a dataflow's installation `as_of` from above. Installing -a maintained collection at an `as_of` beyond its committed permission discards -history without authorization, so `as_of` selection treats permission as a hard -constraint rather than waiting for permission to catch up. An MV's initial storage -visibility boundary remains a distinct concept. The representation and accounting -mechanisms for individual read requirements remain implementation choices. +Installation `as_of` selection respects committed permission, with the replacement +semantics for indexes described in [Index reconstruction](#index-reconstruction). +An MV's initial storage visibility boundary remains a distinct concept. The +representation and accounting mechanisms for individual read requirements remain +implementation choices. Propagation to persist critical since handles must respect all valid read requirements. Those handles are the durable backstop, not a substitute for @@ -207,10 +206,9 @@ remain protected by execution holds. ### Read-only prewarming Preserve prewarming by following committed catalog permission independently of the -SQL savepoint. Read-only bootstrap installs within that permission and does not -wait for the writer to advance it. A local savepoint write grants no compaction -permission, and there is no startup-specific writer protocol or reconstruction -exemption. +SQL savepoint. Read-only bootstrap follows [Index reconstruction](#index-reconstruction) +without waiting for the writer. A local savepoint write grants no compaction +permission, and there is no startup-specific writer protocol. ## Alternatives diff --git a/doc/developer/design/20260903_decoupled_coordination_log.md b/doc/developer/design/20260903_decoupled_coordination_log.md index dacd079a58d69..ffd0c49ffd9bb 100644 --- a/doc/developer/design/20260903_decoupled_coordination_log.md +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -471,3 +471,19 @@ header, and frozen catalog mode are removed once no test needs them. Milestone 1 remains active. Next: resume index simplification under these rules, then the checker move and changed-record publication. + +### 2026-09-09: Measurement accounting and index-fixture scope + +Persist's shard diff-byte counter measures consensus state metadata, not catalog +row payload. Separate committed packed-row counters classify protection and other +catalog traffic without adding durable records or another catalog mode. + +A large identical-index population on one view produces quadratic notice work, +retained notices, and dependency checking. Cache-miss implication reconstruction +also repeats optimization on the coordinator. Proposed scope split, awaiting +Aljoscha: use independent constant-view inputs for publication measurements, retain +the duplicate-index stress case, and address the broader planning/notice cost with +lifecycle work. Do not suppress slow-message diagnostics to make that case pass. + +Milestone 1 remains active. Next: resolve that scope split and finish payload-aware +CI measurements. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index b3a23aa677541..95958be1846d9 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -21,46 +21,29 @@ Check which of these review findings remain unresolved, then choose one coherent change. Remove resolved steering from this prompt. These are implementation priorities, not additional design requirements. -Milestone 1 remains active. Source/sink authorization and maintained compute -permission are connected. What remains is removing machinery that grew beyond -the design, recovery liveness, and performance evidence. Item 1 removes code and -goes first. - -1. Index permission as published since: the design's Index reconstruction - decision now settles both halves of the paused question. A fresh index has no - bound record until first publication, so the MIN birth records and their - lifecycle threading through item, system-mapping, introspection, and cluster - mutation paths go, together with the whole-catalog `index_ids` scans in - validation. Installation selects the least readable frontier, capped by - permission where permission is at or above readability, and otherwise replaces - the index at readability with the bound following through publication. Remove - the read-only wait loop and the per-second re-delivery of all index bounds. - CREATE INDEX becomes one durable commit and `ship_new_dataflow` no longer - commits from inside an implication batch. Keep monotonicity and no-regression - for published bounds, and keep concurrent drops during bootstrap correct. -2. Consistency checker: agreed to move the durable-to-memory reconstruction - behind the existing diagnostic check in environmentd, where a memory snapshot - and its durable upper are available together. Reconstruction is an ordinary - read-only open synced to that upper and then discarded. Run it off the - coordinator thread so a check does not stall DDL, and reuse the existing - snapshot path rather than adding a command variant. Then remove - `Command::CatalogDumpSnapshot`, `dump_upper`, the `mz-catalog-upper` header, - `freeze`/`freeze_at`, `FrozenReadonly`, and the `publish_interval = 0` pause - semantics if no test still needs them. -3. Publication scaling: per-tick work and transaction validation must scale with - changed records, not with all collections or items. Then finish representative - measurements using the existing workflow, including DDL latency, subscriber - lag, and retained history, and record the numbers in the PR. Distinguish - publication overhead from workload and observer costs. Do not make a general - catalog redesign a prerequisite. -4. Add a targeted test for final sink execution as-of selection using aggregate - readability rather than policy permission, including ungoverned collections. -5. History: the 09-09 commits, including `wip: Integrate maintained recovery +Milestone 1 remains active. Sparse index permission, no-wait reconstruction, +changed-record publication and validation, and owned diagnostic reconstruction +are integrated. Complete their production evidence before declaring the milestone. + +1. Production proof: run the restart and read-only index workflows in draft PR + CI, resolve integration findings, and demonstrate protection through actual + compaction and uncached recovery across maintained types. Keep claims about + bootstrap-drop ordering limited to the ordering the evidence establishes. + The large sparse fixture's identical indexes expose quadratic notice and + dependency work, amplified by cache-miss reconstruction on the coordinator. + Its scope split is awaiting Aljoscha's decision in the latest handoff. Do not + suppress the slow-message diagnostics or silently substitute a smaller cohort. +2. Publication costs: finish representative measurements using the existing + workflow, including DDL latency, follower lag, and retained history. Separate + changed-record work from baseline catalog snapshot and storage-metadata cloning + costs. Distinguish publication, workload, and observer costs with controlled + comparisons, and record the numbers in the PR. Do not make a general catalog + redesign a prerequisite. +3. History: the 09-09 commits, including `wip: Integrate maintained recovery protection`, are not yet one coherent story. Squash them before milestone 2 work begins. -6. Ownership transition: prioritize a production maintained-lifecycle subscriber - over more standalone APIs. The bound-only `CatalogSubscriber` is not that - outcome and should either grow into it or take a narrower name. Address +4. Ownership transition: prioritize a production maintained-lifecycle subscriber + over more standalone APIs. The bound subscriber is not that outcome. Address creator-local plans, prepare_state's reliance on locally installed collections, and writer-side responsibility for complete maintained requirements: the sequencer supplies an MV's requirement as its own op, and the transaction @@ -110,6 +93,10 @@ prerequisite for pushing. Follow the mz-debug-ci skill when investigating CI. Report pending or failed checks explicitly rather than treating a push as successful validation. +Use draft PR CI for Docker/mzcompose demos and performance workloads. Avoid +running them locally when CI can run them. Local verification should focus on +formatting, compilation, and targeted Rust tests. + Locally, `bin/fmt`, `cargo check`, and the Rust parts of `bin/lint` (check-cargo, check-formatting, check-python-docs) work. `bin/lint` also runs checks whose tools are not installed here (npm, helm-docs, trufflehog, zizmor); diff --git a/src/adapter/benches/catalog.rs b/src/adapter/benches/catalog.rs index 76bc3edcc19c5..ef7372ce50ba9 100644 --- a/src/adapter/benches/catalog.rs +++ b/src/adapter/benches/catalog.rs @@ -7,11 +7,19 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0. -use criterion::{Criterion, criterion_group, criterion_main}; +use std::collections::BTreeSet; +use std::time::{Duration, Instant}; + +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use mz_adapter::catalog::{Catalog, Op}; -use mz_catalog::durable::test_bootstrap_args; -use mz_persist_client::PersistClient; +use mz_catalog::durable::objects::{CollectionCompactionBound, MaintainedReadRequirement}; +use mz_catalog::durable::{TestCatalogStateBuilder, test_bootstrap_args}; +use mz_ore::now::SYSTEM_TIME; +use mz_persist_client::{PersistClient, ShardId}; +use mz_repr::{GlobalId, Timestamp}; use mz_sql::session::user::MZ_SYSTEM_ROLE_ID; +use mz_storage_client::controller::StorageTxn; +use timely::progress::Antichain; use tokio::runtime::Runtime; use uuid::Uuid; @@ -47,5 +55,117 @@ fn bench_transact(c: &mut Criterion) { }); } -criterion_group!(benches, bench_transact); +fn bench_read_protection(c: &mut Criterion) { + let mut group = c.benchmark_group("catalog_read_protection"); + for count in [100_u64, 1_000, 10_000] { + group.throughput(Throughput::Elements(count + count / 2)); + group.bench_with_input(BenchmarkId::new("batch", count), &count, |b, &count| { + let runtime = Runtime::new().unwrap(); + // Count is total collections, split into disjoint input/output pairs. + let pairs: Vec<_> = (0..count / 2) + .map(|i| (GlobalId::User(2 * i + 1), GlobalId::User(2 * i + 2))) + .collect(); + let records = |frontier| { + let requirements = pairs + .iter() + .map(|&(input, output)| MaintainedReadRequirement { + id: output, + inputs: BTreeSet::from([input]), + frontier: Some(frontier), + }) + .collect(); + let bounds = pairs + .iter() + .flat_map(|&(input, output)| [input, output]) + .map(|id| CollectionCompactionBound { + id, + frontier: Some(frontier), + }) + .collect(); + (requirements, bounds) + }; + // Seed catalog metadata only. This isolates transaction processing + // from controller work, physical retention, and concurrent DDL. + let mut catalog = runtime.block_on(async { + let persist_client = PersistClient::new_for_tests().await; + let organization_id = Uuid::new_v4(); + let bootstrap_args = test_bootstrap_args(); + let mut storage = TestCatalogStateBuilder::new(persist_client.clone()) + .with_organization_id(organization_id) + .with_default_deploy_generation() + .build() + .await + .unwrap() + .open(SYSTEM_TIME().into(), &bootstrap_args) + .await + .unwrap(); + // Acknowledge setup updates before transacting. The debug catalog + // reconstructs in-memory state from the committed seed below. + let _ = storage.sync_to_current_updates().await.unwrap(); + let mut tx = storage.transaction().await.unwrap(); + tx.insert_collection_metadata( + pairs + .iter() + .flat_map(|&(input, output)| [input, output]) + .map(|id| (id, ShardId::new())) + .collect(), + ) + .unwrap(); + let (requirements, bounds) = records(Timestamp::new(0)); + tx.set_read_protection(requirements, bounds).unwrap(); + let _ = tx.get_and_commit_op_updates(); + let commit_ts = tx.upper(); + tx.commit(commit_ts).await.unwrap(); + storage.expire().await; + Catalog::open_debug_catalog(persist_client, organization_id, &bootstrap_args) + .await + .unwrap() + }); + let mut frontier = 0_u64; + // Only Catalog::transact is timed, not record construction or upper lookup. + // Updating the same keys keeps cardinality fixed across warmup and samples. + b.iter_custom(|iterations| { + runtime.block_on(async { + let mut elapsed = Duration::ZERO; + for _ in 0..iterations { + frontier = frontier.checked_add(1).unwrap(); + let (requirements, bounds) = records(Timestamp::new(frontier)); + let ops = vec![Op::SetReadProtection { + requirements, + bounds, + }]; + let commit_ts = catalog.current_upper().await; + let start = Instant::now(); + let result = catalog.transact(None, commit_ts, None, ops).await; + elapsed += start.elapsed(); + result.unwrap(); + } + elapsed + }) + }); + let state = catalog.state(); + assert_eq!(state.maintained_read_requirements().len(), pairs.len()); + assert_eq!( + state.storage_metadata().compaction_bounds.len(), + 2 * pairs.len() + ); + for &(input, output) in &pairs { + assert_eq!( + state.maintained_read_requirements()[&output].frontier, + Some(Timestamp::new(frontier)) + ); + for id in [input, output] { + assert_eq!( + state.storage_metadata().compaction_bounds[&id], + Antichain::from_elem(Timestamp::new(frontier)) + ); + } + } + runtime.block_on(catalog.expire()); + }); + } + group.finish(); +} + +criterion_group!(benches, bench_transact, bench_read_protection); criterion_main!(benches); diff --git a/src/adapter/src/catalog.rs b/src/adapter/src/catalog.rs index 04fb807a70d80..51028b35cf6de 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -145,7 +145,8 @@ pub struct Catalog { expr_cache_handle: Option, storage: Arc>>, transient_revision: u64, - ddl_revision: u64, + /// Opening context needed to reconstruct persisted state independently of this catalog. + diagnostic_config: Arc, /// The latest `transient_revision`, shared by all clones of this catalog. /// While `transient_revision` is this clone's own revision, frozen when /// the snapshot was taken, this field always tracks the latest revision @@ -186,7 +187,7 @@ impl Clone for Catalog { expr_cache_handle: self.expr_cache_handle.clone(), storage: Arc::clone(&self.storage), transient_revision: self.transient_revision, - ddl_revision: self.ddl_revision, + diagnostic_config: Arc::clone(&self.diagnostic_config), shared_transient_revision: Arc::clone(&self.shared_transient_revision), } } @@ -365,14 +366,6 @@ impl Catalog { self.transient_revision } - /// Returns the revision used to detect conflicts with open DDL transactions. - /// It starts at 1 on every load and advances on the same changes as - /// [`Self::transient_revision`], except changes to - /// `catalog_read_protection_publish_interval`. - pub fn ddl_revision(&self) -> u64 { - self.ddl_revision - } - /// Reports whether this catalog's transient revision is still the latest, /// i.e., whether its planning-visible state is equivalent to the current /// catalog's. Can be called on a snapshot from off-thread, without a @@ -478,7 +471,11 @@ impl Catalog { bootstrap_args: &BootstrapArgs, ) -> Result { let now = SYSTEM_TIME.clone(); - let environment_id = None; + let environment_id = Some( + format!("local-az1-{organization_id}-0") + .parse() + .expect("valid debug environment ID"), + ); let openable_storage = TestCatalogStateBuilder::new(persist_client.clone()) .with_organization_id(organization_id) .with_default_deploy_generation() @@ -515,7 +512,11 @@ impl Catalog { aws_context: Option, ) -> Result { let now = SYSTEM_TIME.clone(); - let environment_id = None; + let environment_id = Some( + format!("local-az1-{organization_id}-0") + .parse() + .expect("valid debug environment ID"), + ); let openable_storage = TestCatalogStateBuilder::new(persist_client.clone()) .with_organization_id(organization_id) .with_default_deploy_generation() @@ -547,7 +548,11 @@ impl Catalog { bootstrap_args: &BootstrapArgs, ) -> Result { let now = SYSTEM_TIME.clone(); - let environment_id = None; + let environment_id = Some( + format!("local-az1-{organization_id}-0") + .parse() + .expect("valid debug environment ID"), + ); let openable_storage = TestCatalogStateBuilder::new(persist_client.clone()) .with_organization_id(organization_id) .build() @@ -609,7 +614,10 @@ impl Catalog { .await } - async fn open_debug_catalog_inner( + /// Reconstructs a debug catalog from caller-prepared durable storage without reopening it. + /// + /// Storage must retain its initial update stream. + pub async fn open_debug_catalog_inner( persist_client: PersistClient, storage: Box, now: NowFn, @@ -746,6 +754,21 @@ impl Catalog { self.storage().await.current_upper().await } + /// Certifies a durable prefix while the caller serializes catalog snapshot capture. + pub(crate) async fn current_upper_if_in_sync( + &self, + ) -> Result { + let mut storage = self.storage().await; + if storage.is_savepoint() || storage.is_read_only() { + return Err(AdapterError::ReadOnly); + } + let upper = storage.current_upper().await; + // Allocations and empty upper advancement can run off-loop. Neither may + // certify unapplied catalog content as part of the memory snapshot. + storage.ensure_not_out_of_sync(upper).await?; + Ok(upper) + } + /// Allocates and returns both a user [`CatalogItemId`] and [`GlobalId`], delegating to /// [`DurableCatalogState::allocate_user_id`]. pub async fn allocate_user_id( @@ -1326,6 +1349,27 @@ impl Catalog { Ok(CatalogDump::new(self.state.dump(None)?)) } + pub(crate) async fn open_diagnostic_reader( + &self, + ) -> Result { + let config = &self.diagnostic_config; + let bootstrap = BootstrapArgs { + cluster_replica_size_map: config.cluster_replica_sizes.clone(), + default_cluster_replica_size: config.builtin_system_cluster_config.size.clone(), + default_cluster_replication_factor: config + .builtin_system_cluster_config + .replication_factor, + bootstrap_role: None, + }; + Ok(mz_catalog::durable::CatalogSnapshotReader::open( + config.persist_client.clone(), + config.environment_id.organization_id(), + config.build_info.semver_version(), + &bootstrap, + ) + .await?) + } + /// Checks the [`Catalog`]s internal consistency. /// /// Returns a JSON object describing the inconsistencies, if there are any. @@ -2653,7 +2697,6 @@ mod tests { .await .expect("unable to open debug catalog"); assert_eq!(catalog.transient_revision(), 1); - assert_eq!(catalog.ddl_revision(), 1); assert!(catalog.transient_revision_is_current()); let snapshot = catalog.clone(); let commit_ts = catalog.current_upper().await; @@ -2670,12 +2713,11 @@ mod tests { .await .expect("failed to transact"); assert_eq!(catalog.transient_revision(), 2); - assert_eq!(catalog.ddl_revision(), 2); assert!(catalog.transient_revision_is_current()); // The pre-transaction snapshot detects its own staleness through // the shared latest revision. assert!(!snapshot.transient_revision_is_current()); - assert_eq!(snapshot.ddl_revision(), 1); + assert_eq!(snapshot.transient_revision(), 1); catalog.expire().await; } { @@ -2684,11 +2726,184 @@ mod tests { .await .expect("unable to open debug catalog"); assert_eq!(catalog.transient_revision(), 1); - assert_eq!(catalog.ddl_revision(), 1); catalog.expire().await; } } + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] + async fn owned_catalog_reconstruction_preserves_pending_replica() { + Catalog::with_debug(|mut catalog| async move { + let replica = catalog + .user_cluster_replicas() + .next() + .expect("bootstrap user replica") + .clone(); + let mut config = replica.config; + let mz_controller::clusters::ReplicaLocation::Managed(location) = &mut config.location + else { + panic!("bootstrap replica must be managed"); + }; + location.pending = true; + let ts = catalog.current_upper().await; + let replica_id = catalog + .allocate_user_replica_ids(1, ts) + .await + .expect("can allocate pending replica ID")[0]; + let ts = catalog.current_upper().await; + catalog + .transact( + None, + ts, + None, + vec![Op::CreateClusterReplica { + cluster_id: replica.cluster_id, + replica_id, + name: "pending_replica".into(), + config, + owner_id: replica.owner_id, + reason: super::ReplicaCreateDropReason::GracefulReconfiguration, + }], + ) + .await + .expect("can create pending replica"); + + let expected = catalog.state().dump(None).expect("can dump catalog state"); + let reader = catalog + .open_diagnostic_reader() + .await + .expect("can open diagnostic catalog reader"); + let upper = catalog.current_upper().await; + let input = reader + .into_snapshot_at(upper) + .await + .expect("can extract catalog snapshot"); + let reconstructed = catalog + .reconstruct_state(input) + .await + .expect("can reconstruct catalog state"); + assert_eq!( + expected, + reconstructed + .dump(None) + .expect("can dump reconstructed catalog state") + ); + catalog.expire().await; + }) + .await; + } + + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] + async fn owned_catalog_reconstruction_preserves_protection_snapshot() { + use mz_catalog::durable::TestCatalogStateBuilder; + use mz_catalog::durable::objects::{CollectionCompactionBound, MaintainedReadRequirement}; + use mz_ore::now::SYSTEM_TIME; + use mz_persist_client::ShardId; + use mz_storage_client::controller::StorageTxn; + + let persist = PersistClient::new_for_tests().await; + let organization = Uuid::new_v4(); + let bootstrap = test_bootstrap_args(); + let input = GlobalId::User(100_000); + let output = GlobalId::User(100_001); + let mut seed = TestCatalogStateBuilder::new(persist.clone()) + .with_organization_id(organization) + .with_default_deploy_generation() + .build() + .await + .expect("failed to build seed catalog") + .open(SYSTEM_TIME().into(), &bootstrap) + .await + .expect("failed to open seed catalog"); + let _ = seed + .sync_to_current_updates() + .await + .expect("failed to sync seed catalog"); + let mut tx = seed + .transaction() + .await + .expect("failed to start seed transaction"); + tx.insert_collection_metadata(BTreeMap::from([ + (input, ShardId::new()), + (output, ShardId::new()), + ])) + .expect("failed to insert seed collection metadata"); + tx.set_collection_compaction_bound(input, Some(Timestamp::from(10))) + .expect("failed to set seed compaction bound"); + tx.set_maintained_read_requirement( + output, + BTreeSet::from([input]), + Some(Timestamp::from(10)), + ) + .expect("failed to set seed read requirement"); + let _ = tx.get_and_commit_op_updates(); + let ts = tx.upper(); + tx.commit(ts) + .await + .expect("failed to commit seed transaction"); + seed.expire().await; + + let mut writer = Catalog::open_debug_catalog(persist.clone(), organization, &bootstrap) + .await + .expect("failed to open writer catalog"); + let expected = writer + .state() + .dump(None) + .expect("failed to dump initial writer catalog"); + let reader = writer + .open_diagnostic_reader() + .await + .expect("failed to open readonly catalog"); + let upper = writer.current_upper().await; + let memory = writer.clone(); + + let ts = writer.current_upper().await; + writer + .transact( + None, + ts, + None, + vec![Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: output, + inputs: BTreeSet::from([input]), + frontier: Some(Timestamp::from(20)), + }], + bounds: vec![CollectionCompactionBound { + id: input, + frontier: Some(Timestamp::from(20)), + }], + }], + ) + .await + .expect("failed to update writer read protection"); + assert_ne!( + expected, + writer + .state() + .dump(None) + .expect("failed to dump updated writer catalog") + ); + + let input = reader + .into_snapshot_at(upper) + .await + .expect("failed to extract catalog prefix"); + let reconstructed = memory + .reconstruct_state(input) + .await + .expect("failed to reconstruct catalog"); + assert_eq!( + expected, + reconstructed + .dump(None) + .expect("can dump reconstructed catalog state") + ); + drop(memory); + writer.expire().await; + } + #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` on OS `linux` async fn test_effective_search_path() { diff --git a/src/adapter/src/catalog/apply.rs b/src/adapter/src/catalog/apply.rs index 2262a1dca9777..e209a7b80e405 100644 --- a/src/adapter/src/catalog/apply.rs +++ b/src/adapter/src/catalog/apply.rs @@ -366,6 +366,9 @@ impl CatalogState { ); } StateUpdateKind::Item(item) => { + self.read_protection_changes.insert(item.global_id); + self.read_protection_changes + .extend(item.extra_versions.values().copied()); self.apply_item_update(item, diff, retractions, local_expression_cache)?; } StateUpdateKind::Comment(comment) => { @@ -386,16 +389,36 @@ impl CatalogState { } StateUpdateKind::CollectionCompactionBound(bound) => { apply_inverted_lookup( - &mut Arc::make_mut(&mut self.storage_metadata).compaction_bounds, + &mut self.collection_compaction_bounds, &bound.id, bound.frontier.into_iter().collect(), diff, ); + self.read_protection_changes.insert(bound.id); + self.update_storage_compaction_bound(bound.id); } StateUpdateKind::MaintainedReadRequirement(requirement) => { let id = requirement.id; + self.read_protection_changes.insert(id); + self.read_protection_changes + .extend(requirement.inputs.iter().copied()); + if let Some(frontier) = requirement.frontier { + for input in &requirement.inputs { + let edge = (*input, frontier, id); + match diff { + StateDiff::Addition => { + let prev = self.maintained_input_requirements.insert(edge); + assert!(prev.is_none(), "requirement edge already exists"); + } + StateDiff::Retraction => { + let prev = self.maintained_input_requirements.remove(&edge); + assert!(prev.is_some(), "requirement edge does not exist"); + } + } + } + } apply_inverted_lookup( - Arc::make_mut(&mut self.maintained_read_requirements), + &mut self.maintained_read_requirements, &id, requirement, diff, @@ -1416,6 +1439,32 @@ impl CatalogState { storage_collection_metadata.shard, diff, ); + self.read_protection_changes + .insert(storage_collection_metadata.id); + self.update_storage_compaction_bound(storage_collection_metadata.id); + } + + fn update_storage_compaction_bound(&mut self, id: GlobalId) { + // Bounds and shard mappings can arrive in either order. Storage receives + // only the shard-backed projection, never permissions for compute traces. + let bound = self + .storage_metadata + .collection_metadata + .contains_key(&id) + .then(|| self.collection_compaction_bounds.get(&id)) + .flatten(); + if self.storage_metadata.compaction_bounds.get(&id) == bound { + return; + } + let metadata = Arc::make_mut(&mut self.storage_metadata); + match bound { + Some(bound) => { + metadata.compaction_bounds.insert(id, bound.clone()); + } + None => { + metadata.compaction_bounds.remove(&id); + } + } } #[instrument(level = "debug")] diff --git a/src/adapter/src/catalog/consistency.rs b/src/adapter/src/catalog/consistency.rs index 2a02d09477641..7f0a103aba352 100644 --- a/src/adapter/src/catalog/consistency.rs +++ b/src/adapter/src/catalog/consistency.rs @@ -58,6 +58,53 @@ impl CatalogInconsistencies { } } +impl super::Catalog { + pub(crate) async fn check_durable_consistency( + &self, + input: mz_catalog::durable::CatalogSnapshot, + ) -> Result<(), crate::AdapterError> { + self.check_consistency() + .map_err(|error| crate::AdapterError::Internal(error.to_string()))?; + let reconstructed = self.reconstruct_state(input).await?; + // Shard finalization is asynchronous and is not applied to CatalogState. + let unfinalized_shards = self + .state + .storage_metadata + .unfinalized_shards + .iter() + .map(ToString::to_string) + .collect(); + let memory = self.state.dump(None)?; + let disk = reconstructed.dump(Some(unfinalized_shards))?; + if memory != disk { + let memory_lines: Vec<_> = memory.lines().collect(); + let disk_lines: Vec<_> = disk.lines().collect(); + let common_len = memory_lines.len().min(disk_lines.len()); + let first = (0..common_len) + .find(|&i| memory_lines[i] != disk_lines[i]) + .unwrap_or(common_len); + let start = first.saturating_sub(5); + return Err(crate::AdapterError::Internal(format!( + "in-memory catalog differs from durable reconstruction at line {}\nmemory:\n{}\ndurable:\n{}", + first + 1, + memory_lines[start..] + .iter() + .take(20) + .copied() + .collect::>() + .join("\n"), + disk_lines[start..] + .iter() + .take(20) + .copied() + .collect::>() + .join("\n"), + ))); + } + Ok(()) + } +} + impl CatalogState { /// Checks the [`CatalogState`] to make sure we're internally consistent. pub fn check_consistency(&self) -> Result<(), Box> { diff --git a/src/adapter/src/catalog/open.rs b/src/adapter/src/catalog/open.rs index 33bc0a3e34c9e..406fe4107aa51 100644 --- a/src/adapter/src/catalog/open.rs +++ b/src/adapter/src/catalog/open.rs @@ -123,6 +123,71 @@ pub struct OpenCatalogResult { } impl Catalog { + fn diagnostic_state_config(config: &StateConfig) -> StateConfig { + StateConfig { + unsafe_mode: config.unsafe_mode, + all_features: config.all_features, + build_info: config.build_info, + environment_id: config.environment_id.clone(), + read_only: config.read_only, + now: config.now.clone(), + boot_ts: config.boot_ts, + skip_migrations: true, + cluster_replica_sizes: config.cluster_replica_sizes.clone(), + builtin_system_cluster_config: config.builtin_system_cluster_config.clone(), + builtin_catalog_server_cluster_config: config + .builtin_catalog_server_cluster_config + .clone(), + builtin_probe_cluster_config: config.builtin_probe_cluster_config.clone(), + builtin_support_cluster_config: config.builtin_support_cluster_config.clone(), + builtin_analytics_cluster_config: config.builtin_analytics_cluster_config.clone(), + system_parameter_defaults: config.system_parameter_defaults.clone(), + remote_system_parameters: None, + availability_zones: config.availability_zones.clone(), + egress_addresses: config.egress_addresses.clone(), + aws_principal_context: config.aws_principal_context.clone(), + aws_privatelink_availability_zones: config.aws_privatelink_availability_zones.clone(), + http_host_name: config.http_host_name.clone(), + connection_context: config.connection_context.clone(), + builtin_item_migration_config: mz_catalog::config::BuiltinItemMigrationConfig { + persist_client: config.persist_client.clone(), + read_only: config.builtin_item_migration_config.read_only, + force_migration: None, + }, + persist_client: config.persist_client.clone(), + enable_expression_cache_override: Some(false), + helm_chart_version: config.helm_chart_version.clone(), + external_login_password_mz_system: None, + license_key: config.license_key.clone(), + } + } + + pub(crate) async fn reconstruct_state( + &self, + input: mz_catalog::durable::CatalogSnapshot, + ) -> Result { + let mut config = Self::diagnostic_state_config(&self.diagnostic_config); + config.system_parameter_defaults = self.state.system_config().defaults(); + config.boot_ts = input.upper; + let before = input.snapshot; + let mut txn = mz_catalog::durable::DryRunTransaction::from_snapshot( + before.clone(), + input.upper, + input.is_bootstrap_complete, + false, + )?; + let (result, cleanup) = + Self::initialize_state_from_updates(config, input.updates, txn.transaction_mut(), None) + .await?; + drop(cleanup); + if txn.current_snapshot() != before { + return Err(AdapterError::Internal( + "catalog reconstruction requires durable changes".into(), + )); + } + Ok(result.state) + } + /// Initializes a CatalogState. Separate from [`Catalog::open`] to avoid depending on state /// external to a [mz_catalog::durable::DurableCatalogState] /// (for example: no [mz_secrets::SecretsReader]). @@ -130,6 +195,26 @@ impl Catalog { config: StateConfig, storage: &'a mut Box, ) -> Result { + let deploy_generation = storage.get_deployment_generation().await?; + let updates = storage.sync_to_current_updates().await?; + let mut txn = storage.transaction().await?; + let boot_ts = config.boot_ts; + let (result, cleanup) = + Self::initialize_state_from_updates(config, updates, &mut txn, Some(deploy_generation)) + .await?; + txn.commit(boot_ts).await?; + cleanup.await; + Ok(result) + } + + /// Reconstructs the full catalog using owned input and a caller-owned transaction. + /// A missing deployment generation replays committed live state without restart migrations. + async fn initialize_state_from_updates( + config: StateConfig, + mut updates: Vec, + txn: &mut Transaction<'_>, + deploy_generation: Option, + ) -> Result<(InitializeStateResult, BoxFuture<'static, ()>), AdapterError> { for builtin_role in BUILTIN_ROLES { assert!( is_reserved_name(builtin_role.name), @@ -172,7 +257,10 @@ impl Catalog { comments: Arc::new(CommentsMap::default()), source_references: imbl::OrdMap::new(), storage_metadata: Arc::new(StorageMetadata::default()), + collection_compaction_bounds: Default::default(), maintained_read_requirements: Default::default(), + maintained_input_requirements: Default::default(), + read_protection_changes: Default::default(), catalog_read_protection_enabled: false, temporary_namespaces: Default::default(), mock_authentication_nonce: Default::default(), @@ -200,16 +288,13 @@ impl Catalog { license_key: config.license_key, }; - let deploy_generation = storage.get_deployment_generation().await?; - - let mut updates: Vec<_> = storage.sync_to_current_updates().await?; assert!(!updates.is_empty(), "initial catalog snapshot is missing"); - let mut txn = storage.transaction().await?; - // Migrate/update durable data before we start loading the in-memory catalog. - let new_builtin_collections = { + // Diagnostic replay must preserve pending replicas and builtin desired state, + // which restart reconciliation is allowed to change. + let new_builtin_collections = if deploy_generation.is_some() { migrate::durable_migrate( - &mut txn, + txn, state.config.environment_id.organization_id(), config.boot_ts, )?; @@ -222,7 +307,7 @@ impl Catalog { txn.set_system_config_synced_once()?; } // Add any new builtin objects and remove old ones. - let new_builtin_collections = add_new_remove_old_builtin_items_migration(&mut txn)?; + let new_builtin_collections = add_new_remove_old_builtin_items_migration(txn)?; let builtin_bootstrap_cluster_config_map = BuiltinBootstrapClusterConfigMap { system_cluster: config.builtin_system_cluster_config, catalog_server_cluster: config.builtin_catalog_server_cluster_config, @@ -231,21 +316,48 @@ impl Catalog { analytics_cluster: config.builtin_analytics_cluster_config, }; add_new_remove_old_builtin_clusters_migration( - &mut txn, + txn, &builtin_bootstrap_cluster_config_map, config.boot_ts, )?; - add_new_remove_old_builtin_introspection_source_migration(&mut txn)?; + add_new_remove_old_builtin_introspection_source_migration(txn)?; reconcile_builtin_cluster_replicas( - &mut txn, + txn, &builtin_bootstrap_cluster_config_map, config.boot_ts, )?; - add_new_remove_old_builtin_roles_migration(&mut txn)?; - remove_invalid_config_param_role_defaults_migration(&mut txn)?; - remove_pending_cluster_replicas_migration(&mut txn, config.boot_ts)?; + add_new_remove_old_builtin_roles_migration(txn)?; + remove_invalid_config_param_role_defaults_migration(txn)?; + remove_pending_cluster_replicas_migration(txn, config.boot_ts)?; new_builtin_collections + } else { + let mut mappings: BTreeMap<_, _> = txn + .get_system_object_mappings() + .map(|mapping| (mapping.description, mapping.unique_identifier.fingerprint)) + .collect(); + let compatible = BUILTINS::iter().all(|builtin| { + let description = SystemObjectDescription { + schema_name: builtin.schema().to_string(), + object_type: builtin.catalog_item_type(), + object_name: builtin.name().to_string(), + }; + let fingerprint = if builtin.runtime_alterable() { + RUNTIME_ALTERABLE_FINGERPRINT_SENTINEL.into() + } else { + builtin.fingerprint() + }; + mappings.remove(&description).as_ref() == Some(&fingerprint) + }); + if !compatible + || !mappings.is_empty() + || get_migration_version(txn).as_ref() != Some(&config.build_info.semver_version()) + { + return Err(AdapterError::Internal( + "catalog reconstruction requires a builtin schema migration".into(), + )); + } + Vec::new() }; let op_updates = txn.get_and_commit_op_updates(); @@ -453,7 +565,7 @@ impl Catalog { .await; builtin_table_updates.extend(builtin_table_update); - let last_seen_version = get_migration_version(&txn); + let last_seen_version = get_migration_version(txn); // Latch the mode at environment birth. Changing startup defaults must not // adopt existing collections whose required history may already be gone. @@ -479,7 +591,7 @@ impl Catalog { let (builtin_table_update, _catalog_updates) = if !config.skip_migrations { let migrate_result = migrate::migrate( &mut state, - &mut txn, + txn, &mut local_expr_cache, item_updates, config.now, @@ -542,14 +654,20 @@ impl Catalog { } // Migrate builtin items. - let schema_migration_result = builtin_schema_migration::run( - config.build_info, - deploy_generation, - &mut txn, - config.builtin_item_migration_config, - ) - .await?; + let schema_migration_result = match deploy_generation { + Some(deploy_generation) => { + builtin_schema_migration::run( + config.build_info, + deploy_generation, + txn, + config.builtin_item_migration_config, + ) + .await? + } + None => Default::default(), + }; + txn.finalize_index_compaction_bounds(); let state_updates = txn.get_and_commit_op_updates(); // When initializing/bootstrapping, we don't use the catalog updates but @@ -564,23 +682,21 @@ impl Catalog { let builtin_table_updates = state.resolve_builtin_table_updates(builtin_table_updates); // Bump the migration version immediately before committing. - set_migration_version(&mut txn, config.build_info.semver_version())?; - - txn.commit(config.boot_ts).await?; - - // Now that the migration is durable, run any requested deferred cleanup. - schema_migration_result.cleanup_action.await; - - Ok(InitializeStateResult { - state, - migrated_storage_collections_0dt: schema_migration_result.replaced_items, - new_builtin_collections: new_builtin_collections.into_iter().collect(), - builtin_table_updates, - last_seen_version, - expr_cache_handle, - cached_global_exprs, - uncached_local_exprs: local_expr_cache.into_uncached_exprs(), - }) + set_migration_version(txn, config.build_info.semver_version())?; + + Ok(( + InitializeStateResult { + state, + migrated_storage_collections_0dt: schema_migration_result.replaced_items, + new_builtin_collections: new_builtin_collections.into_iter().collect(), + builtin_table_updates, + last_seen_version, + expr_cache_handle, + cached_global_exprs, + uncached_local_exprs: local_expr_cache.into_uncached_exprs(), + }, + schema_migration_result.cleanup_action, + )) } /// Opens or creates a catalog that stores data at `path`. @@ -597,6 +713,7 @@ impl Catalog { pub fn open(config: Config<'_>) -> BoxFuture<'static, Result> { async move { let mut storage = config.storage; + let diagnostic_config = Arc::new(Self::diagnostic_state_config(&config.state)); let InitializeStateResult { state, @@ -619,8 +736,8 @@ impl Catalog { let catalog = Catalog { state, expr_cache_handle, + diagnostic_config, transient_revision: 1, - ddl_revision: 1, shared_transient_revision: Arc::new(AtomicU64::new(1)), storage: Arc::new(tokio::sync::Mutex::new(storage)), }; @@ -800,7 +917,14 @@ impl Catalog { let read_only_tx = storage.transaction().await?; - mz_controller::Controller::new(config, envd_epoch, read_only, &read_only_tx).await + mz_controller::Controller::new( + config, + envd_epoch, + read_only, + self.state().catalog_read_protection_enabled(), + &read_only_tx, + ) + .await }; self.initialize_storage_state(&controller.storage_collections) diff --git a/src/adapter/src/catalog/state.rs b/src/adapter/src/catalog/state.rs index 2b291db975c91..7c1b6aeb615eb 100644 --- a/src/adapter/src/catalog/state.rs +++ b/src/adapter/src/catalog/state.rs @@ -57,7 +57,7 @@ use mz_repr::network_policy_id::NetworkPolicyId; use mz_repr::optimize::{OptimizerFeatureOverrides, OptimizerFeatures, OverrideFrom}; use mz_repr::role_id::RoleId; use mz_repr::{ - CatalogItemId, GlobalId, RelationDesc, RelationVersion, RelationVersionSelector, + CatalogItemId, GlobalId, RelationDesc, RelationVersion, RelationVersionSelector, Timestamp, VersionedRelationDesc, }; use mz_secrets::InMemorySecretsController; @@ -156,8 +156,16 @@ pub struct CatalogState { #[serde(serialize_with = "mz_ore::serde::map_key_to_string")] pub(super) source_references: imbl::OrdMap, pub(super) storage_metadata: Arc, + #[serde(serialize_with = "serialize_collection_compaction_bounds")] + pub(super) collection_compaction_bounds: imbl::OrdMap>, #[serde(serialize_with = "serialize_maintained_read_requirements")] - pub(super) maintained_read_requirements: Arc>, + pub(super) maintained_read_requirements: imbl::OrdMap, + /// Non-completed requirement edges, ordered by input, frontier, and owner. + #[serde(skip)] + pub(super) maintained_input_requirements: imbl::OrdSet<(GlobalId, Timestamp, GlobalId)>, + /// IDs whose read protection must be reconsidered after applied catalog changes. + #[serde(skip)] + pub(super) read_protection_changes: imbl::OrdSet, pub(super) catalog_read_protection_enabled: bool, pub(super) mock_authentication_nonce: Option, @@ -483,7 +491,10 @@ impl CatalogState { comments: Arc::new(CommentsMap::default()), source_references: Default::default(), storage_metadata: Arc::new(StorageMetadata::default()), + collection_compaction_bounds: Default::default(), maintained_read_requirements: Default::default(), + maintained_input_requirements: Default::default(), + read_protection_changes: Default::default(), catalog_read_protection_enabled: false, license_key: ValidatedLicenseKey::for_tests(), mock_authentication_nonce: Default::default(), @@ -2901,11 +2912,31 @@ impl CatalogState { &self.storage_metadata } + /// Returns committed compaction permission for maintained storage and compute collections. + pub fn collection_compaction_bounds(&self) -> &imbl::OrdMap> { + &self.collection_compaction_bounds + } + /// Returns the durable maintained read requirements, keyed by output ID. - pub fn maintained_read_requirements(&self) -> &BTreeMap { + pub fn maintained_read_requirements( + &self, + ) -> &imbl::OrdMap { &self.maintained_read_requirements } + /// Returns the earliest committed requirement for an input, excluding the given owners. + pub fn maintained_read_frontier( + &self, + input: GlobalId, + excluding: &BTreeSet, + ) -> Option { + // System(0) is the minimum GlobalId, so the range includes every owner. + self.maintained_input_requirements + .range((input, Timestamp::MIN, GlobalId::System(0))..) + .take_while(|(id, _, _)| *id == input) + .find_map(|(_, frontier, owner)| (!excluding.contains(owner)).then_some(*frontier)) + } + /// Whether this environment was initialized with catalog-backed recovery protection. pub fn catalog_read_protection_enabled(&self) -> bool { self.catalog_read_protection_enabled @@ -3118,13 +3149,27 @@ impl OptimizerCatalog for Catalog { } impl Catalog { + /// Drains IDs whose read protection may have changed since the last drain. + pub fn take_read_protection_changes(&mut self) -> BTreeSet { + std::mem::take(&mut self.state.read_protection_changes) + .into_iter() + .collect() + } + pub fn as_optimizer_catalog(self: Arc) -> Arc { self } } +fn serialize_collection_compaction_bounds( + bounds: &imbl::OrdMap>, + serializer: S, +) -> Result { + mz_ore::serde::map_key_to_string(bounds, serializer) +} + fn serialize_maintained_read_requirements( - requirements: &BTreeMap, + requirements: &imbl::OrdMap, serializer: S, ) -> Result { let entries = requirements @@ -3137,6 +3182,154 @@ fn serialize_maintained_read_requirements( mod tests { use super::*; + #[mz_ore::test(tokio::test)] + async fn storage_permission_projects_only_shard_backed_bounds() { + use mz_catalog::durable::objects::{CollectionCompactionBound, StorageCollectionMetadata}; + use mz_catalog::memory::objects::{StateDiff, StateUpdate, StateUpdateKind}; + use mz_ore::cast::CastFrom; + use mz_persist_client::ShardId; + + let storage = GlobalId::User(1); + let compute = GlobalId::User(2); + let mapping = StorageCollectionMetadata { + id: storage, + shard: ShardId::new(), + }; + let permission = |id| { + StateUpdateKind::CollectionCompactionBound(CollectionCompactionBound { + id, + frontier: Some(Timestamp::from(10)), + }) + }; + for mapping_first in [false, true] { + let mut state = CatalogState::empty_test(); + let mut updates = vec![ + permission(storage), + StateUpdateKind::StorageCollectionMetadata(mapping.clone()), + ]; + if mapping_first { + updates.reverse(); + } + updates.push(permission(compute)); + for (i, kind) in updates.into_iter().enumerate() { + let _ = state + .apply_updates( + vec![StateUpdate { + kind, + ts: Timestamp::from(u64::cast_from(i) + 1), + diff: StateDiff::Addition, + }], + &mut LocalExpressionCache::Closed, + ) + .await; + } + assert_eq!(state.collection_compaction_bounds().len(), 2); + assert_eq!( + state.storage_metadata().compaction_bounds, + BTreeMap::from([(storage, Antichain::from_elem(Timestamp::from(10)))]) + ); + assert_eq!( + std::mem::take(&mut state.read_protection_changes), + imbl::OrdSet::from_iter([storage, compute]) + ); + assert_eq!( + serialize_collection_compaction_bounds( + state.collection_compaction_bounds(), + serde_json::value::Serializer, + ) + .expect("can serialize collection compaction bounds"), + serde_json::to_value(BTreeMap::from([ + ( + storage.to_string(), + Antichain::from_elem(Timestamp::from(10)) + ), + ( + compute.to_string(), + Antichain::from_elem(Timestamp::from(10)) + ), + ])) + .expect("can serialize expected collection compaction bounds") + ); + let snapshot = state.clone(); + let _ = state + .apply_updates( + vec![StateUpdate { + kind: StateUpdateKind::StorageCollectionMetadata(mapping.clone()), + ts: Timestamp::from(4), + diff: StateDiff::Retraction, + }], + &mut LocalExpressionCache::Closed, + ) + .await; + assert!(state.storage_metadata().compaction_bounds.is_empty()); + assert_eq!(state.collection_compaction_bounds().len(), 2); + assert_eq!( + std::mem::take(&mut state.read_protection_changes), + imbl::OrdSet::from_iter([storage]) + ); + assert!( + snapshot + .storage_metadata() + .compaction_bounds + .contains_key(&storage) + ); + let mut old = permission(compute); + for frontier in [Some(Timestamp::from(20)), None] { + let new = StateUpdateKind::CollectionCompactionBound(CollectionCompactionBound { + id: compute, + frontier, + }); + let _ = state + .apply_updates( + vec![ + StateUpdate { + kind: old, + ts: Timestamp::from(5), + diff: StateDiff::Retraction, + }, + StateUpdate { + kind: new.clone(), + ts: Timestamp::from(5), + diff: StateDiff::Addition, + }, + ], + &mut LocalExpressionCache::Closed, + ) + .await; + assert_eq!( + state.collection_compaction_bounds().get(&compute), + Some(&frontier.into_iter().collect()) + ); + assert_eq!( + snapshot.collection_compaction_bounds().get(&compute), + Some(&Antichain::from_elem(Timestamp::from(10))) + ); + assert!(state.storage_metadata().compaction_bounds.is_empty()); + old = new; + } + let _ = state + .apply_updates( + vec![StateUpdate { + kind: old, + ts: Timestamp::from(6), + diff: StateDiff::Retraction, + }], + &mut LocalExpressionCache::Closed, + ) + .await; + assert!(!state.collection_compaction_bounds().contains_key(&compute)); + assert!( + snapshot + .collection_compaction_bounds() + .contains_key(&compute) + ); + assert_eq!( + state.read_protection_changes, + imbl::OrdSet::from_iter([compute]) + ); + } + } + #[mz_ore::test(tokio::test)] async fn maintained_read_requirements_apply_snapshot_update_drop() { use mz_catalog::memory::objects::{StateDiff, StateUpdate, StateUpdateKind}; @@ -3169,8 +3362,23 @@ mod tests { .await; assert_eq!( state.maintained_read_requirements(), - &BTreeMap::from([(initial.id, initial.clone())]) + &imbl::OrdMap::from_iter([(initial.id, initial.clone())]) ); + assert_eq!( + serialize_maintained_read_requirements( + state.maintained_read_requirements(), + serde_json::value::Serializer, + ) + .expect("can serialize maintained read requirements"), + serde_json::to_value(BTreeMap::from([( + initial.id.to_string(), + (&initial.inputs, initial.frontier), + )])) + .expect("can serialize expected maintained read requirements") + ); + let serialized = serde_json::to_value(&state).expect("can serialize catalog state"); + assert!(serialized.get("maintained_input_requirements").is_none()); + assert!(serialized.get("read_protection_changes").is_none()); let snapshot = state.clone(); let _ = state @@ -3186,12 +3394,22 @@ mod tests { .await; assert_eq!( state.maintained_read_requirements(), - &BTreeMap::from([(completed.id, completed.clone())]) + &imbl::OrdMap::from_iter([(completed.id, completed.clone())]) ); assert_eq!( snapshot.maintained_read_requirements(), - &BTreeMap::from([(initial.id, initial)]) + &imbl::OrdMap::from_iter([(initial.id, initial.clone())]) ); + for input in &initial.inputs { + assert_eq!( + state.maintained_read_frontier(*input, &BTreeSet::new()), + None + ); + assert_eq!( + snapshot.maintained_read_frontier(*input, &BTreeSet::new()), + initial.frontier + ); + } let _ = state .apply_updates( @@ -3202,6 +3420,175 @@ mod tests { assert!(state.maintained_read_requirements().is_empty()); } + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` + async fn read_protection_changes_and_requirement_projection() { + use mz_catalog::memory::objects::{StateDiff, StateUpdate, StateUpdateKind}; + + Catalog::with_debug(|mut catalog| async move { + catalog.take_read_protection_changes(); + let input = GlobalId::User(100); + let other_input = GlobalId::User(101); + let first = MaintainedReadRequirement { + id: GlobalId::User(102), + inputs: BTreeSet::from([input]), + frontier: Some(Timestamp::from(10)), + }; + let second = MaintainedReadRequirement { + id: GlobalId::User(103), + ..first.clone() + }; + let later = MaintainedReadRequirement { + id: GlobalId::User(104), + frontier: Some(Timestamp::from(20)), + ..first.clone() + }; + let update = |requirement, diff| StateUpdate { + kind: StateUpdateKind::MaintainedReadRequirement(requirement), + ts: Timestamp::MIN, + diff, + }; + let _ = catalog + .state + .apply_updates( + vec![ + update(first.clone(), StateDiff::Addition), + update(second.clone(), StateDiff::Addition), + update(later.clone(), StateDiff::Addition), + ], + &mut LocalExpressionCache::Closed, + ) + .await; + let snapshot = catalog.state.clone(); + assert_eq!( + catalog.take_read_protection_changes(), + BTreeSet::from([input, first.id, second.id, later.id]) + ); + assert!(catalog.take_read_protection_changes().is_empty()); + for (excluding, expected) in [ + (BTreeSet::new(), first.frontier), + (BTreeSet::from([first.id]), second.frontier), + (BTreeSet::from([first.id, second.id]), later.frontier), + (BTreeSet::from([first.id, second.id, later.id]), None), + ] { + assert_eq!( + catalog.state.maintained_read_frontier(input, &excluding), + expected + ); + } + assert_eq!( + catalog + .state + .maintained_read_frontier(other_input, &BTreeSet::new()), + None + ); + + let moved = MaintainedReadRequirement { + inputs: BTreeSet::from([other_input]), + frontier: Some(Timestamp::from(30)), + ..first.clone() + }; + let _ = catalog + .state + .apply_updates( + vec![ + update(first.clone(), StateDiff::Retraction), + update(moved.clone(), StateDiff::Addition), + ], + &mut LocalExpressionCache::Closed, + ) + .await; + assert_eq!( + catalog.take_read_protection_changes(), + BTreeSet::from([input, other_input, first.id]) + ); + assert_eq!( + catalog + .state + .maintained_read_frontier(input, &BTreeSet::new()), + second.frontier + ); + assert_eq!( + catalog + .state + .maintained_read_frontier(other_input, &BTreeSet::new()), + moved.frontier + ); + assert_eq!( + snapshot.maintained_read_requirements().get(&first.id), + Some(&first) + ); + assert_eq!( + snapshot.maintained_read_frontier(other_input, &BTreeSet::new()), + None + ); + assert_eq!( + snapshot.read_protection_changes.len(), + 4, + "draining does not mutate snapshots" + ); + + let completed = MaintainedReadRequirement { + frontier: None, + ..moved.clone() + }; + for (old, new) in [(moved, Some(completed.clone())), (completed, None)] { + let mut updates = vec![update(old, StateDiff::Retraction)]; + if let Some(new) = new { + updates.push(update(new, StateDiff::Addition)); + } + let _ = catalog + .state + .apply_updates(updates, &mut LocalExpressionCache::Closed) + .await; + assert_eq!( + catalog.take_read_protection_changes(), + BTreeSet::from([other_input, first.id]) + ); + assert_eq!( + catalog + .state + .maintained_read_frontier(other_input, &BTreeSet::new()), + None + ); + } + let _ = catalog + .state + .apply_updates( + vec![update(second.clone(), StateDiff::Retraction)], + &mut LocalExpressionCache::Closed, + ) + .await; + assert_eq!( + catalog + .state + .maintained_read_frontier(input, &BTreeSet::new()), + later.frontier + ); + assert_eq!( + catalog.take_read_protection_changes(), + BTreeSet::from([input, second.id]) + ); + + let _ = catalog + .state + .apply_updates( + vec![ + update(later.clone(), StateDiff::Retraction), + update(later, StateDiff::Addition), + ], + &mut LocalExpressionCache::Closed, + ) + .await; + assert!( + catalog.take_read_protection_changes().is_empty(), + "consolidated no-op is unchanged" + ); + catalog.expire().await; + }) + .await; + } + async fn create_collection_input_item( catalog: &mut Catalog, name: &str, @@ -3248,6 +3635,71 @@ mod tests { global_id } + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` + async fn read_protection_changes_include_item_versions() { + use mz_catalog::memory::objects::{StateDiff, StateUpdate, StateUpdateKind}; + + Catalog::with_debug(|mut catalog| async move { + catalog.take_read_protection_changes(); + let id = create_collection_input_item( + &mut catalog, + "t", + "CREATE TABLE materialize.public.t (a int)", + ) + .await; + assert_eq!(catalog.take_read_protection_changes(), BTreeSet::from([id])); + let original = catalog + .state + .durable_item(catalog.state.get_entry_by_global_id(&id).entry) + .expect("can convert test table to durable item"); + let (_, extra_id) = catalog + .allocate_user_id_for_test() + .await + .expect("can allocate test table version ID"); + let mut versioned = original.clone(); + versioned.create_sql = + "CREATE TABLE materialize.public.t (a int, b int VERSION ADDED 1)".into(); + versioned + .extra_versions + .insert(RelationVersion::root().bump(), extra_id); + for (old, new, expected) in [ + ( + original.clone(), + Some(versioned.clone()), + BTreeSet::from([id, extra_id]), + ), + ( + versioned, + Some(original.clone()), + BTreeSet::from([id, extra_id]), + ), + (original, None, BTreeSet::from([id])), + ] { + let mut updates = vec![StateUpdate { + kind: StateUpdateKind::Item(old), + ts: Timestamp::MIN, + diff: StateDiff::Retraction, + }]; + if let Some(new) = new { + updates.push(StateUpdate { + kind: StateUpdateKind::Item(new), + ts: Timestamp::MIN, + diff: StateDiff::Addition, + }); + } + let _ = catalog + .state + .apply_updates(updates, &mut LocalExpressionCache::Closed) + .await; + assert_eq!(catalog.take_read_protection_changes(), expected); + assert!(catalog.take_read_protection_changes().is_empty()); + } + catalog.expire().await; + }) + .await; + } + #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `TLS_client_method` async fn logical_collection_inputs_follow_names_not_plans() { diff --git a/src/adapter/src/catalog/transact.rs b/src/adapter/src/catalog/transact.rs index c574dc09c94cf..28a7f9b645803 100644 --- a/src/adapter/src/catalog/transact.rs +++ b/src/adapter/src/catalog/transact.rs @@ -34,6 +34,7 @@ use mz_audit_log::{ }; use mz_catalog::SYSTEM_CONN_ID; use mz_catalog::builtin::BuiltinLog; +use mz_catalog::durable::objects::{CollectionCompactionBound, MaintainedReadRequirement}; use mz_catalog::durable::{DryRunTransaction, NetworkPolicy, Snapshot, Transaction}; use mz_catalog::expr_cache::{LocalExpressions, latest_item_version}; use mz_catalog::memory::error::{AmbiguousRename, Error, ErrorKind}; @@ -68,7 +69,7 @@ use mz_sql::names::{ use mz_sql::plan::{NetworkPolicyRule, PlanError}; use mz_sql::session::user::{MZ_SUPPORT_ROLE_ID, MZ_SYSTEM_ROLE_ID}; use mz_sql::session::vars::OwnedVarInput; -use mz_sql::session::vars::{self, Value as VarValue, VarInput}; +use mz_sql::session::vars::{Value as VarValue, VarInput}; use mz_sql::{DEFAULT_SCHEMA, rbac}; use mz_sql_parser::ast::{QualifiedReplica, Value}; use mz_storage_client::controller::StorageTxn; @@ -276,14 +277,13 @@ pub enum Op { scoped: ScopedParameters, prune_scope: ScopedParametersScope, }, - SetCollectionCompactionBound { - id: GlobalId, - frontier: Option, - }, - SetMaintainedReadRequirement { - id: GlobalId, - inputs: BTreeSet, - frontier: Option, + /// Stages changed read requirements and compaction bounds. + /// + /// Either vector may be empty. See [`Transaction::set_read_protection`] for + /// the readability and recovery contracts. + SetReadProtection { + requirements: Vec, + bounds: Vec, }, /// Injects audit events into the catalog. /// @@ -450,7 +450,6 @@ pub struct TransactionResult { struct TransactInnerResult { state: CatalogState, planning_changed: bool, - ddl_changed: bool, } #[derive(Debug, Clone, Copy)] @@ -790,7 +789,6 @@ impl Catalog { if let Some(TransactInnerResult { state, planning_changed, - ddl_changed, }) = new_state { if planning_changed { @@ -801,9 +799,6 @@ impl Catalog { self.shared_transient_revision .store(self.transient_revision, atomic::Ordering::SeqCst); } - if ddl_changed { - self.ddl_revision += 1; - } self.state = state; } @@ -1067,6 +1062,7 @@ impl Catalog { // Admission failures must return before entering the fatal commit path. // Batch extraction repeats this check for other durable callers. + tx.finalize_index_compaction_bounds(); tx.validate_read_protection()?; // Storage preparation can retract permission staged by an earlier op @@ -1075,28 +1071,15 @@ impl Catalog { updates.extend(tx.get_and_commit_op_updates()); // Classify raw updates, not parsed implications, which omit // planning-visible changes. - let mut planning_changed = false; - let mut ddl_changed = false; - for update in &updates { - match &update.kind { + let planning_changed = updates.iter().any(|update| { + !matches!( + &update.kind, StateUpdateKind::CollectionCompactionBound(_) - | StateUpdateKind::MaintainedReadRequirement(_) - | StateUpdateKind::UnfinalizedShard(_) - | StateUpdateKind::AuditLog(_) => {} - StateUpdateKind::SystemConfiguration(config) - if config.name - == vars::CATALOG_READ_PROTECTION_PUBLISH_INTERVAL.name.as_str() => - { - // current_setting is folded to a literal during optimization, - // so changing publication cadence must invalidate cached plans. - planning_changed = true; - } - _ => { - planning_changed = true; - ddl_changed = true; - } - } - } + | StateUpdateKind::MaintainedReadRequirement(_) + | StateUpdateKind::UnfinalizedShard(_) + | StateUpdateKind::AuditLog(_) + ) + }); if !updates.is_empty() { let mut local_expr_cache = LocalExpressionCache::new(cached_exprs.clone()); let (op_builtin_table_updates, op_catalog_updates) = state @@ -1114,7 +1097,6 @@ impl Catalog { Cow::Owned(state) => Ok(Some(TransactInnerResult { state, planning_changed, - ddl_changed, })), Cow::Borrowed(_) => Ok(None), } @@ -1753,15 +1735,22 @@ impl Catalog { } }; tx.set_collection_compaction_bound(gids[0], bound)?; + if let Some((id, inputs)) = storage_recovery_inputs(state, &item)? + { + tx.set_maintained_read_requirement(id, inputs, bound)?; + } } storage_collections_to_create.extend(gids); } CatalogItem::Source(source) => { if state.catalog_read_protection_enabled() { - tx.set_collection_compaction_bound( - source.global_id(), - source_initial_compaction_bound(state, &source.data_source)?, - )?; + let bound = + source_initial_compaction_bound(state, &source.data_source)?; + tx.set_collection_compaction_bound(source.global_id(), bound)?; + if let Some((id, inputs)) = storage_recovery_inputs(state, &item)? + { + tx.set_maintained_read_requirement(id, inputs, bound)?; + } } storage_collections_to_create.insert(source.global_id()); } @@ -1798,9 +1787,12 @@ impl Catalog { } CatalogItem::Sink(sink) => { if state.catalog_read_protection_enabled() { - tx.set_collection_compaction_bound( + let bound = dependency_compaction_bound(state, sink.from)?; + tx.set_collection_compaction_bound(sink.global_id(), bound)?; + tx.set_maintained_read_requirement( sink.global_id(), - dependency_compaction_bound(state, sink.from)?, + BTreeSet::from([sink.global_id(), sink.from]), + bound, )?; } storage_collections_to_create.insert(sink.global_id()); @@ -3070,6 +3062,22 @@ impl Catalog { entry.name = name.clone(); entry.item = to_item.clone(); tx.update_item(id, state.durable_item(entry)?)?; + if state.catalog_read_protection_enabled() + && let Some((owner, inputs)) = storage_recovery_inputs(state, &to_item)? + { + // Preliminary state includes explicit progress ops preceding this update. + // Changing inputs alone cannot release history still needed for recovery. + let requirement = state + .maintained_read_requirements() + .get(&owner) + .ok_or_else(|| { + AdapterError::internal( + "update storage item", + format!("missing read requirement for {owner}"), + ) + })?; + tx.set_maintained_read_requirement(owner, inputs, requirement.frontier)?; + } if Self::should_audit_log_item(&to_item) { let mut full_name = Self::full_name_detail( @@ -3248,15 +3256,11 @@ impl Catalog { } } } - Op::SetCollectionCompactionBound { id, frontier } => { - tx.set_collection_compaction_bound(id, frontier)?; - } - Op::SetMaintainedReadRequirement { - id, - inputs, - frontier, + Op::SetReadProtection { + requirements, + bounds, } => { - tx.set_maintained_read_requirement(id, inputs, frontier)?; + tx.set_read_protection(requirements, bounds)?; } Op::InjectAuditEvents { events } => { for event in events { @@ -3356,6 +3360,16 @@ fn source_initial_compaction_bound( state: &CatalogState, desc: &DataSourceDesc, ) -> Result, AdapterError> { + match source_initialization_dependency(state, desc)? { + Some(id) => dependency_compaction_bound(state, id), + None => Ok(Some(mz_repr::Timestamp::MIN)), + } +} + +fn source_initialization_dependency( + state: &CatalogState, + desc: &DataSourceDesc, +) -> Result, AdapterError> { let dependency = match desc { DataSourceDesc::IngestionExport { ingestion_id, @@ -3382,23 +3396,54 @@ fn source_initial_compaction_bound( | DataSourceDesc::Webhook { .. } | DataSourceDesc::Catalog => None, }; - match dependency { - Some(id) => dependency_compaction_bound(state, state.get_entry(&id).latest_global_id()), - None => Ok(Some(mz_repr::Timestamp::MIN)), + Ok(dependency.map(|id| state.get_entry(&id).latest_global_id())) +} + +/// Returns the exact storage versions whose history a source or sink needs for recovery. +fn storage_recovery_inputs( + state: &CatalogState, + item: &CatalogItem, +) -> Result)>, AdapterError> { + let (id, desc) = match item { + CatalogItem::Source(source) => (source.global_id(), &source.data_source), + CatalogItem::Table(table) => match &table.data_source { + TableDataSource::DataSource { desc, .. } => (table.global_id_writes(), desc), + TableDataSource::TableWrites { .. } => return Ok(None), + }, + CatalogItem::Sink(sink) => { + return Ok(Some(( + sink.global_id(), + BTreeSet::from([sink.global_id(), sink.from]), + ))); + } + _ => return Ok(None), + }; + match desc { + DataSourceDesc::Ingestion { .. } + | DataSourceDesc::OldSyntaxIngestion { .. } + | DataSourceDesc::IngestionExport { .. } => { + let mut inputs = BTreeSet::from([id]); + inputs.extend(source_initialization_dependency(state, desc)?); + Ok(Some((id, inputs))) + } + DataSourceDesc::Introspection(_) + | DataSourceDesc::Progress + | DataSourceDesc::Webhook { .. } + | DataSourceDesc::Catalog => Ok(None), } } -/// Returns the committed permission of a governed storage dependency. +/// Returns the compaction permission of a governed storage dependency. fn dependency_compaction_bound( state: &CatalogState, id: GlobalId, ) -> Result, AdapterError> { // Storage initializes a dependent at least as far as its dependency's since. // Governance proves since <= bound without making physical readability the - // authority. The preliminary state includes permissions from preceding ops. + // authority. The preliminary state includes permissions from preceding ops, + // even before their shard metadata is allocated. state - .storage_metadata() - .compaction_bounds + .collection_compaction_bounds() .get(&id) .map(|bound| bound.as_option().copied()) .ok_or_else(|| { @@ -3734,6 +3779,7 @@ mod tests { use std::sync::{Arc, Mutex}; use mz_catalog::SYSTEM_CONN_ID; + use mz_catalog::durable::objects::{CollectionCompactionBound, MaintainedReadRequirement}; use mz_catalog::memory::error::{Error, ErrorKind}; use mz_catalog::memory::objects::{CatalogItem, Table, TableDataSource}; use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem, PrivilegeMap}; @@ -3754,9 +3800,10 @@ mod tests { #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] async fn test_storage_birth_inherits_dependency_permission() { - use std::collections::BTreeMap; + use std::collections::{BTreeMap, BTreeSet}; use mz_proto::RustType; + use mz_repr::{CatalogItemId, GlobalId}; use mz_storage_types::sources::SourceExportStatementDetails; use mz_storage_types::sources::load_generator::LoadGeneratorOutput; use prost::Message; @@ -3790,11 +3837,28 @@ mod tests { base.catalog_read_protection_enabled = true; let mut state = base.clone(); let mut ops = Vec::new(); + let mut identities: BTreeMap<&str, (CatalogItemId, GlobalId)> = BTreeMap::new(); for (name, sql) in [ ( "birth_source", format!("CREATE SOURCE {prefix}.birth_source IN CLUSTER quickstart FROM LOAD GENERATOR COUNTER"), ), + ( + "birth_progress", + format!("CREATE SUBSOURCE {prefix}.birth_progress (\"offset\" uint8) WITH (PROGRESS)"), + ), + ( + "birth_old_source", + format!("CREATE SOURCE {prefix}.birth_old_source IN CLUSTER quickstart FROM LOAD GENERATOR COUNTER EXPOSE PROGRESS AS [__PROGRESS_ID__ AS {prefix}.birth_progress]"), + ), + ( + "birth_old_export", + format!("CREATE SUBSOURCE {prefix}.birth_old_export (counter bigint) OF SOURCE {prefix}.birth_old_source WITH (EXTERNAL REFERENCE counter, DETAILS '{details}')"), + ), + ( + "birth_writable", + format!("CREATE TABLE {prefix}.birth_writable (counter bigint)"), + ), ( "birth_connection", format!("CREATE CONNECTION {prefix}.birth_connection TO KAFKA (BROKER 'localhost:9092', SECURITY PROTOCOL PLAINTEXT)"), @@ -3812,10 +3876,16 @@ mod tests { format!("CREATE SINK {prefix}.birth_sink IN CLUSTER quickstart FROM {prefix}.birth_table INTO KAFKA CONNECTION {prefix}.birth_connection (TOPIC 'birth-permission') FORMAT JSON ENVELOPE DEBEZIUM"), ), ] { + // Catalog SQL contains the progress ID resolved by purification. + let sql = match identities.get("birth_progress") { + Some((id, _)) => sql.replace("__PROGRESS_ID__", &id.to_string()), + None => sql, + }; let (id, global_id) = catalog .allocate_user_id_for_test() .await .expect("allocate collection identity"); + identities.insert(name, (id, global_id)); let item = state .with_enable_for_item_parsing(|state| state.parse_item( global_id, @@ -3843,23 +3913,206 @@ mod tests { .transact_incremental_dry_run(&base, ops.clone(), None, None, oracle_write_ts) .await .unwrap_or_else(|err| panic!("create {name}: {err}")); + if matches!(name, "birth_connection" | "birth_writable" | "birth_progress") { + assert!(!state.maintained_read_requirements().contains_key(&global_id)); + if name == "birth_progress" { + ops.push(Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: global_id, + frontier: Some(permission), + }], + requirements: vec![], + }); + } + continue; + } + let inputs = match name { + "birth_source" => BTreeSet::from([global_id]), + "birth_table" | "birth_export" => { + BTreeSet::from([global_id, identities["birth_source"].1]) + } + "birth_old_source" | "birth_old_export" => { + BTreeSet::from([global_id, identities["birth_progress"].1]) + } + "birth_sink" => BTreeSet::from([global_id, identities["birth_table"].1]), + _ => unreachable!(), + }; + let frontier = if name == "birth_source" { + mz_repr::Timestamp::MIN + } else { + permission + }; + assert_eq!( + state.maintained_read_requirements()[&global_id], + MaintainedReadRequirement { + id: global_id, + inputs: inputs.clone(), + frontier: Some(frontier), + }, + ); + for input in &inputs { + let mut invalid = ops.clone(); + for other in state.maintained_read_requirements().values() { + if other.id != global_id { + invalid.push(Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: other.id, + inputs: other.inputs.clone(), + frontier: Some(frontier.step_forward()), + }], + bounds: vec![], + }); + } + } + invalid.push(Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: *input, + frontier: Some(frontier.step_forward()), + }], + requirements: vec![], + }); + let err = catalog + .transact_incremental_dry_run(&base, invalid, None, None, oracle_write_ts) + .await + .expect_err("live storage consumers protect every input, including self"); + assert!( + err.to_string() + .contains(&format!("read requirement {global_id}")), + "{err}" + ); + } if name == "birth_source" { assert_eq!( - state.storage_metadata().compaction_bounds[&global_id], + state.collection_compaction_bounds()[&global_id], Antichain::from_elem(mz_repr::Timestamp::MIN), ); - ops.push(Op::SetCollectionCompactionBound { - id: global_id, - frontier: Some(permission), + ops.push(Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: global_id, + inputs, + frontier: Some(permission), + }], + bounds: vec![], + }); + ops.push(Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: global_id, + frontier: Some(permission), + }], + requirements: vec![], }); - } else if name != "birth_connection" { + } else { assert_eq!( - state.storage_metadata().compaction_bounds[&global_id], + state.collection_compaction_bounds()[&global_id], Antichain::from_elem(permission), "{name} must inherit dependency permission, not MIN or oracle time", ); } } + + let (sink_id, sink_gid) = identities["birth_sink"]; + let export_gid = identities["birth_export"].1; + let source_gid = identities["birth_source"].1; + let progress = permission.step_forward(); + ops.extend([ + Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: export_gid, + inputs: BTreeSet::from([export_gid, source_gid]), + frontier: Some(progress), + }], + bounds: vec![], + }, + Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: export_gid, + frontier: Some(progress), + }], + requirements: vec![], + }, + ]); + let export_sql = format!("CREATE SUBSOURCE {prefix}.birth_export (counter bigint) OF SOURCE {prefix}.birth_old_source WITH (EXTERNAL REFERENCE counter, DETAILS '{details}')"); + let export = state + .with_enable_for_item_parsing(|state| state.parse_item( + export_gid, + &export_sql, + &BTreeMap::new(), + None, + false, + None, + &mut LocalExpressionCache::Closed, + None, + )) + .expect("parse export with a different remap dependency"); + ops.push(Op::UpdateItem { + id: identities["birth_export"].0, + name: QualifiedItemName { + qualifiers: qualifiers.clone(), + item: "birth_export".to_string(), + }, + to_item: export, + }); + let sql = format!("CREATE SINK {prefix}.birth_sink IN CLUSTER quickstart FROM {prefix}.birth_export INTO KAFKA CONNECTION {prefix}.birth_connection (TOPIC 'birth-permission') FORMAT JSON ENVELOPE DEBEZIUM"); + let CatalogItem::Sink(mut sink) = state.get_entry(&sink_id).item().clone() else { + unreachable!(); + }; + sink.create_sql = sql; + sink.from = export_gid; + sink.resolved_ids.remove_item(&identities["birth_table"].0); + sink.resolved_ids.add_item(identities["birth_export"].0); + let update = Op::UpdateItem { + id: sink_id, + name: QualifiedItemName { + qualifiers, + item: "birth_sink".to_string(), + }, + to_item: CatalogItem::Sink(sink), + }; + let mut invalid = ops.clone(); + invalid.push(update.clone()); + let err = catalog + .transact_incremental_dry_run(&base, invalid, None, None, oracle_write_ts) + .await + .expect_err("changing inputs cannot advance the committed recovery frontier"); + assert!(err.to_string().contains("read requirement"), "{err}"); + + ops.push(Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: sink_gid, + inputs: BTreeSet::from([sink_gid, identities["birth_table"].1]), + frontier: Some(progress), + }], + bounds: vec![], + }); + ops.push(update.clone()); + let (state, _) = catalog + .transact_incremental_dry_run(&base, ops.clone(), None, None, oracle_write_ts) + .await + .expect("progress and input change must be admitted atomically"); + let expected = MaintainedReadRequirement { + id: sink_gid, + inputs: BTreeSet::from([sink_gid, export_gid]), + frontier: Some(progress), + }; + assert_eq!(state.maintained_read_requirements()[&sink_gid], expected); + let CatalogItem::Sink(sink) = state.get_entry(&sink_id).item() else { + unreachable!(); + }; + assert_eq!(sink.from, export_gid); + ops.push(update); + let (replayed, _) = catalog + .transact_incremental_dry_run(&base, ops, None, None, oracle_write_ts) + .await + .expect("repeated UpdateItem must preserve progress and exact inputs"); + assert_eq!(replayed.maintained_read_requirements()[&sink_gid], expected); + assert_eq!( + replayed.maintained_read_requirements()[&export_gid], + MaintainedReadRequirement { + id: export_gid, + inputs: BTreeSet::from([export_gid, identities["birth_progress"].1]), + frontier: Some(progress), + }, + ); }) .await; } @@ -3867,7 +4120,7 @@ mod tests { #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] async fn test_ddl_replay_inherits_fresh_dependency_permission() { - use std::collections::BTreeMap; + use std::collections::{BTreeMap, BTreeSet}; use mz_persist_client::ShardId; use mz_proto::RustType; @@ -3978,7 +4231,7 @@ mod tests { .await .expect("table birth dry run should succeed"); assert_eq!( - dry_run_state.storage_metadata().compaction_bounds[&global_id], + dry_run_state.collection_compaction_bounds()[&global_id], Antichain::from_elem(Timestamp::MIN) ); seed_collection(&mut catalog, global_id).await; @@ -3989,10 +4242,23 @@ mod tests { None, oracle_write_ts, None, - vec![Op::SetCollectionCompactionBound { - id: source_id, - frontier: Some(fresh_bound), - }], + vec![ + Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: source_id, + inputs: BTreeSet::from([source_id]), + frontier: Some(fresh_bound), + }], + bounds: vec![], + }, + Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: source_id, + frontier: Some(fresh_bound), + }], + requirements: vec![], + }, + ], ) .await .expect("dependency permission should advance during the DDL transaction"); @@ -4006,9 +4272,17 @@ mod tests { .expect("DDL replay should use current birth permission"); assert_eq!(catalog.transient_revision(), revision + 1); assert_eq!( - catalog.state().storage_metadata().compaction_bounds[&global_id], + catalog.state().collection_compaction_bounds()[&global_id], Antichain::from_elem(fresh_bound) ); + assert_eq!( + catalog.state().maintained_read_requirements()[&global_id], + MaintainedReadRequirement { + id: global_id, + inputs: BTreeSet::from([global_id, source_id]), + frontier: Some(fresh_bound), + }, + ); assert!(catalog.state().try_get_entry(&id).is_some()); } else { seed_collection(&mut catalog, global_id).await; @@ -4042,6 +4316,8 @@ mod tests { Catalog::with_debug(|mut catalog| async move { let input = GlobalId::User(100_000); let output = GlobalId::User(100_001); + let completed_input = GlobalId::User(100_002); + let completed_output = GlobalId::User(100_003); let ts = |value| Timestamp::new(value); // Seed collection birth through the durable transaction. This harness // does not run storage shard allocation or controller implications. @@ -4054,10 +4330,20 @@ mod tests { tx.insert_collection_metadata(BTreeMap::from([ (input, ShardId::new()), (output, ShardId::new()), + (completed_input, ShardId::new()), + (completed_output, ShardId::new()), ])) .expect("input and output collection metadata should be inserted"); tx.set_collection_compaction_bound(input, Some(ts(10))) .expect("initial input compaction bound should be accepted"); + tx.set_collection_compaction_bound(completed_input, Some(ts(10))) + .expect("initial completion input bound should be accepted"); + tx.set_maintained_read_requirement( + completed_output, + BTreeSet::from([completed_input]), + Some(ts(10)), + ) + .expect("initial completion requirement should be accepted"); let updates = tx.get_and_commit_op_updates(); let commit_ts = tx.upper(); tx.commit(commit_ts) @@ -4070,19 +4356,24 @@ mod tests { .apply_updates(updates, &mut LocalExpressionCache::Closed) .await; - let requirement = |frontier| Op::SetMaintainedReadRequirement { - id: output, - inputs: BTreeSet::from([input]), - frontier, + let requirement = |frontier| Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: output, + inputs: BTreeSet::from([input]), + frontier, + }], + bounds: vec![], }; - let bound = |frontier| Op::SetCollectionCompactionBound { - id: input, - frontier, + let bound = |frontier| Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: input, + frontier, + }], + requirements: vec![], }; let revision = catalog.transient_revision(); - let ddl_revision = catalog.ddl_revision(); let snapshot = catalog.clone(); - let before = catalog.state().storage_metadata().compaction_bounds.clone(); + let before = catalog.state().collection_compaction_bounds().clone(); let oracle_write_ts = catalog.current_upper().await; let result = catalog .transact(None, oracle_write_ts, None, vec![requirement(Some(ts(9)))]) @@ -4091,7 +4382,7 @@ mod tests { result.is_err(), "admission must return an error, not terminate" ); - assert_eq!(catalog.state().storage_metadata().compaction_bounds, before); + assert_eq!(catalog.state().collection_compaction_bounds(), &before); let base_state = catalog.state().clone(); assert!( catalog @@ -4113,11 +4404,11 @@ mod tests { assert_eq!(catalog.transient_revision(), revision); assert!(snapshot.transient_revision_is_current()); assert_eq!( - catalog.state().storage_metadata().compaction_bounds[&input], + catalog.state().collection_compaction_bounds()[&input], Antichain::from_elem(ts(15)) ); assert_eq!( - snapshot.state().storage_metadata().compaction_bounds[&input], + snapshot.state().collection_compaction_bounds()[&input], Antichain::from_elem(ts(10)), "planning-equivalent snapshots need not have identical protection authority" ); @@ -4133,38 +4424,143 @@ mod tests { assert_eq!(committed_requirement.inputs, BTreeSet::from([input])); assert_eq!(committed_requirement.frontier, Some(ts(19))); + let publication = |required| Op::SetReadProtection { + requirements: vec![ + MaintainedReadRequirement { + id: output, + inputs: BTreeSet::from([input]), + frontier: Some(ts(required)), + }, + MaintainedReadRequirement { + id: completed_output, + inputs: BTreeSet::from([completed_input]), + frontier: None, + }, + ], + bounds: vec![ + CollectionCompactionBound { + id: input, + frontier: Some(ts(20)), + }, + CollectionCompactionBound { + id: completed_input, + frontier: None, + }, + ], + }; let oracle_write_ts = catalog.current_upper().await; - let result = catalog - .transact( + let before = catalog.state().clone(); + assert!( + catalog + .transact(None, oracle_write_ts, None, vec![publication(19)]) + .await + .is_err() + ); + assert_eq!( + catalog.state().collection_compaction_bounds(), + before.collection_compaction_bounds(), + "an incompatible batch must not advance any bound" + ); + assert_eq!( + catalog.state().maintained_read_requirements(), + before.maintained_read_requirements(), + "an incompatible batch must not complete any requirement" + ); + assert!( + catalog + .transact_incremental_dry_run( + &before, + vec![publication(19)], + None, + None, + oracle_write_ts, + ) + .await + .is_err() + ); + let (speculative, speculative_snapshot) = catalog + .transact_incremental_dry_run( + &before, + vec![publication(20)], + None, None, oracle_write_ts, + ) + .await + .expect("compatible publication should pass speculative admission"); + let (replayed, _) = catalog + .transact_incremental_dry_run( + &speculative, + vec![publication(20)], None, - vec![bound(Some(ts(20))), requirement(Some(ts(20)))], + Some(speculative_snapshot), + oracle_write_ts, ) .await + .expect("publication replay should be idempotent"); + let result = catalog + .transact(None, oracle_write_ts, None, vec![publication(20)]) + .await .expect("matching compaction bound and read requirement should commit"); + for state in [&speculative, &replayed] { + assert_eq!( + state.collection_compaction_bounds(), + catalog.state().collection_compaction_bounds() + ); + assert_eq!( + state.maintained_read_requirements(), + catalog.state().maintained_read_requirements() + ); + } + assert_eq!( + catalog.state().collection_compaction_bounds()[&completed_input], + Antichain::new() + ); + assert_eq!( + catalog.state().maintained_read_requirements()[&completed_output].frontier, + None + ); + let mut bound_updates: Vec<_> = result + .catalog_updates + .iter() + .filter_map(|update| match &update.kind { + ParsedStateUpdateKind::CollectionCompactionBound(bound) => { + Some((bound.id, bound.frontier, update.diff)) + } + _ => None, + }) + .collect(); + bound_updates.sort_unstable(); + assert_eq!( + bound_updates, + vec![ + (input, Some(ts(15)), StateDiff::Retraction), + (input, Some(ts(20)), StateDiff::Addition), + (completed_input, None, StateDiff::Addition), + (completed_input, Some(ts(10)), StateDiff::Retraction), + ], + "each changed bound has one retraction and one addition for implications" + ); assert_eq!(catalog.transient_revision(), revision); assert_eq!( - catalog.state().storage_metadata().compaction_bounds[&input], + catalog.state().collection_compaction_bounds()[&input], Antichain::from_elem(ts(20)) ); - assert!(result.catalog_updates.iter().any(|update| { - matches!( - &update.kind, - ParsedStateUpdateKind::CollectionCompactionBound(bound) - if bound.id == input && bound.frontier == Some(ts(20)) - && update.diff == StateDiff::Addition - ) - })); - assert_eq!(catalog.ddl_revision(), ddl_revision); + let oracle_write_ts = catalog.current_upper().await; + let unchanged = catalog + .transact(None, oracle_write_ts, None, vec![publication(20)]) + .await + .expect("unchanged publication should commit without implications"); + assert!(unchanged.catalog_updates.is_empty()); + assert_eq!(catalog.transient_revision(), revision); let interval = vars::CATALOG_READ_PROTECTION_PUBLISH_INTERVAL.name.as_str(); let default_interval = catalog .system_config() .catalog_read_protection_publish_interval(); assert!(!default_interval.is_zero()); let oracle_write_ts = catalog.current_upper().await; - catalog + let error = catalog .transact( None, oracle_write_ts, @@ -4175,18 +4571,33 @@ mod tests { }], ) .await - .expect("pausing publication should commit"); + .err() + .expect("publication requires a positive cadence"); + assert!(error.to_string().contains("non-zero durations"), "{error}"); + assert_eq!(catalog.transient_revision(), revision); + + let oracle_write_ts = catalog.current_upper().await; + catalog + .transact( + None, + oracle_write_ts, + None, + vec![Op::UpdateSystemConfiguration { + name: interval.into(), + value: OwnedVarInput::Flat("2s".into()), + }], + ) + .await + .expect("changing publication cadence should commit"); assert_eq!(catalog.transient_revision(), revision + 1); - assert_eq!(catalog.ddl_revision(), ddl_revision); assert!(!snapshot.transient_revision_is_current()); - let paused_snapshot = catalog.clone(); - assert!(paused_snapshot.transient_revision_is_current()); - assert_eq!(paused_snapshot.ddl_revision(), ddl_revision); - assert!( - paused_snapshot + let cadence_snapshot = catalog.clone(); + assert!(cadence_snapshot.transient_revision_is_current()); + assert_eq!( + cadence_snapshot .system_config() - .catalog_read_protection_publish_interval() - .is_zero() + .catalog_read_protection_publish_interval(), + std::time::Duration::from_secs(2), ); let oracle_write_ts = catalog.current_upper().await; catalog @@ -4199,10 +4610,9 @@ mod tests { }], ) .await - .expect("resuming publication should commit"); + .expect("resetting publication cadence should commit"); assert_eq!(catalog.transient_revision(), revision + 2); - assert_eq!(catalog.ddl_revision(), ddl_revision); - assert!(!paused_snapshot.transient_revision_is_current()); + assert!(!cadence_snapshot.transient_revision_is_current()); let snapshot = catalog.clone(); assert!(snapshot.transient_revision_is_current()); assert_eq!( @@ -4243,7 +4653,6 @@ mod tests { .await .expect("schema and protection changes should commit together"); assert_eq!(catalog.transient_revision(), revision + 3); - assert_eq!(catalog.ddl_revision(), ddl_revision + 1); assert!(!snapshot.transient_revision_is_current()); assert!(catalog.transient_revision_is_current()); assert!( @@ -4260,11 +4669,7 @@ mod tests { None ); assert_eq!( - catalog - .state() - .storage_metadata() - .compaction_bounds - .get(&input), + catalog.state().collection_compaction_bounds().get(&input), Some(&Antichain::new()), "completed permission remains governed" ); @@ -4849,14 +5254,20 @@ mod tests { let op_t1 = make_table_op(id_t1, global_id_t1, "t1"); let op_t2 = make_table_op(id_t2, global_id_t2, "t2"); - let bound = Op::SetCollectionCompactionBound { - id: global_id_t1, - frontier: Some(10.into()), + let bound = Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: global_id_t1, + frontier: Some(10.into()), + }], + requirements: vec![], }; - let requirement = Op::SetMaintainedReadRequirement { - id: global_id_t2, - inputs: [global_id_t1].into_iter().collect(), - frontier: Some(10.into()), + let requirement = Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: global_id_t2, + inputs: [global_id_t1].into_iter().collect(), + frontier: Some(10.into()), + }], + bounds: vec![], }; let base_state = catalog.state().clone(); @@ -4874,7 +5285,7 @@ mod tests { .await .expect("table birth establishes committed permission"); assert_eq!( - born_state.storage_metadata().compaction_bounds[&global_id_t1], + born_state.collection_compaction_bounds()[&global_id_t1], timely::progress::Antichain::from_elem(oracle_write_ts) ); let historical_ts = oracle_write_ts.step_back().expect("nonzero catalog upper"); @@ -4883,10 +5294,13 @@ mod tests { &born_state, vec![ op_t2.clone(), - Op::SetMaintainedReadRequirement { - id: global_id_t2, - inputs: [global_id_t1].into_iter().collect(), - frontier: Some(historical_ts), + Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: global_id_t2, + inputs: [global_id_t1].into_iter().collect(), + frontier: Some(historical_ts), + }], + bounds: vec![], }, ], None, @@ -4991,9 +5405,12 @@ mod tests { &state_incremental, vec![ Op::DropObjects(vec![crate::catalog::DropObjectInfo::Item(id_t2)]), - Op::SetCollectionCompactionBound { - id: global_id_t1, - frontier: Some(20.into()), + Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: global_id_t1, + frontier: Some(20.into()), + }], + requirements: vec![], }, ], None, @@ -5005,7 +5422,7 @@ mod tests { assert!(dropped_state.try_get_entry(&id_t2).is_none()); assert!(dropped_snapshot.maintained_read_requirements.is_empty()); assert_eq!( - dropped_state.storage_metadata().compaction_bounds[&global_id_t1], + dropped_state.collection_compaction_bounds()[&global_id_t1], timely::progress::Antichain::from_elem(20.into()) ); diff --git a/src/adapter/src/client.rs b/src/adapter/src/client.rs index 8b86043a8b6c7..aa49880c184d2 100644 --- a/src/adapter/src/client.rs +++ b/src/adapter/src/client.rs @@ -593,8 +593,11 @@ Issue a SQL query to get started. Need help? /// which serves from the session's snapshot cache. pub async fn catalog_snapshot_expensive(&self) -> Arc { let (tx, rx) = oneshot::channel(); - self.send(Command::CatalogSnapshot { tx }); - let CatalogSnapshot { catalog } = rx.await.expect("coordinator unexpectedly gone"); + self.send(Command::CatalogSnapshot { + tx, + include_durable_upper: false, + }); + let CatalogSnapshot { catalog, .. } = rx.await.expect("coordinator unexpectedly gone"); catalog } @@ -1118,28 +1121,60 @@ impl SessionClient { .enable_extended_protocol_implicit_transaction() } - /// Dumps the catalog to a JSON string. - /// - /// No authorization is performed, so access to this function must be limited to internal - /// servers or superusers. + /// Dumps the catalog to JSON. Access must be limited to internal servers or superusers. pub async fn dump_catalog(&mut self) -> Result { - // Dumps include protection authority, which does not invalidate the planning cache. - let catalog = self.inner().catalog_snapshot_expensive().await; - catalog.dump().map_err(AdapterError::from) + let snapshot = self + .send_without_session(|tx| Command::CatalogSnapshot { + tx, + include_durable_upper: false, + }) + .await; + Ok(snapshot.catalog.dump()?) } - /// Checks the catalog for internal consistency, returning a JSON object describing the - /// inconsistencies, if there are any. - /// - /// No authorization is performed, so access to this function must be limited to internal - /// servers or superusers. - pub async fn check_catalog(&mut self) -> Result<(), serde_json::Value> { - let catalog = self.catalog_snapshot("check_catalog").await; - catalog.check_consistency() + /// Checks internal invariants, optionally reconstructing the durable catalog independently. + /// Access must be limited to internal servers or superusers. + pub async fn check_catalog(&mut self, durable: bool) -> Result<(), serde_json::Value> { + if !durable { + let snapshot = self + .send_without_session(|tx| Command::CatalogSnapshot { + tx, + include_durable_upper: false, + }) + .await; + return snapshot.catalog.check_consistency(); + } + let result = async { + let initial = self + .send_without_session(|tx| Command::CatalogSnapshot { + tx, + include_durable_upper: false, + }) + .await; + let reader = initial.catalog.open_diagnostic_reader().await?; + // Capture uncached state after acquiring the continuous durable reader. + let snapshot = self + .send_without_session(|tx| Command::CatalogSnapshot { + tx, + include_durable_upper: true, + }) + .await; + let upper = match snapshot.durable_upper.expect("requested durable prefix") { + Ok(upper) => upper, + Err(error) => { + reader.expire().await; + return Err(error); + } + }; + let input = reader.into_snapshot_at(upper).await?; + snapshot.catalog.check_durable_consistency(input).await + } + .await; + result.map_err(|error: AdapterError| serde_json::json!(error.to_string())) } - /// Checks the coordinator for internal consistency, returning a JSON object describing the - /// inconsistencies, if there are any. This is a superset of checks that check_catalog performs, + /// Checks coordinator and in-memory catalog invariants, without durable reconstruction. + /// Returns a JSON object describing any inconsistencies. /// /// No authorization is performed, so access to this function must be limited to internal /// servers or superusers. diff --git a/src/adapter/src/command.rs b/src/adapter/src/command.rs index 0d43ae267ce2c..f7f5bf6ec001d 100644 --- a/src/adapter/src/command.rs +++ b/src/adapter/src/command.rs @@ -84,12 +84,15 @@ pub struct CopyFromStdinWriter { #[derive(Debug)] pub struct CatalogSnapshot { pub catalog: Arc, + /// A certified durable prefix, when explicitly requested. + pub durable_upper: Option>, } #[derive(Debug)] pub enum Command { CatalogSnapshot { tx: oneshot::Sender, + include_durable_upper: bool, }, Startup { @@ -632,7 +635,7 @@ impl Transmittable for StartupResponse { } } -/// The response to [`SessionClient::dump_catalog`](crate::SessionClient::dump_catalog). +/// A JSON-encoded catalog snapshot. #[derive(Debug, Clone)] pub struct CatalogDump(String); diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index 82a456443497d..d56bbd6cdaf49 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -169,6 +169,7 @@ use mz_storage_types::connections::Connection as StorageConnection; use mz_storage_types::connections::ConnectionContext; use mz_storage_types::connections::inline::{IntoInlineConnection, ReferencedConnection}; use mz_storage_types::read_holds::ReadHold; +use mz_storage_types::read_policy::ReadPolicy; use mz_storage_types::sinks::{S3SinkFormat, StorageSinkDesc}; use mz_storage_types::sources::kafka::KAFKA_PROGRESS_DESC; use mz_storage_types::sources::{IngestionDescription, SourceExport, Timeline}; @@ -202,10 +203,12 @@ use crate::coord::appends::{ PendingWriteTxn, }; use crate::coord::caught_up::CaughtUpCheckContext; +use crate::coord::compaction_bound_subscriber::CompactionBoundSubscriber; use crate::coord::id_bundle::CollectionIdBundle; use crate::coord::introspection::IntrospectionSubscribe; use crate::coord::metric_sink::{CuratedMetricSink, InstalledMetricSink, PlannedMetricSink}; use crate::coord::peek::PendingPeek; +use crate::coord::read_protection::CATALOG_SUBSCRIPTION_INTERVAL; use crate::coord::statement_logging::StatementLogging; use crate::coord::timeline::{TimelineContext, TimelineState}; use crate::coord::timestamp_selection::{TimestampContext, TimestampDetermination}; @@ -242,6 +245,7 @@ pub(crate) mod timestamp_selection; pub mod catalog_implications; mod caught_up; mod command_handler; +mod compaction_bound_subscriber; mod ddl; pub(crate) mod group_sync; mod hydration_history; @@ -1319,6 +1323,7 @@ pub struct Config { pub controller_config: ControllerConfig, pub controller_envd_epoch: NonZeroI64, pub storage: Box, + pub compaction_bound_subscriber: Option>, pub timestamp_oracle_url: Option, pub unsafe_mode: bool, pub all_features: bool, @@ -2063,6 +2068,9 @@ pub struct Coordinator { /// read their catalog as long as needed. In the future we would like this /// to be a pTVC, but for now this is sufficient. catalog: Arc, + compaction_bound_subscriber: Option, + /// Changed records retained until publication succeeds, including failed attempts. + read_protection_pending: BTreeSet, /// A client for persist. Initially, this is only used for reading stashed /// peek responses out of batches. @@ -2651,6 +2659,7 @@ impl Coordinator { .catalog() .system_config() .enable_storage_introspection_logs(); + self.restore_compute_read_protection().await?; for instance in self.catalog.clusters() { self.controller.create_cluster( instance.id, @@ -2735,7 +2744,7 @@ impl Coordinator { // `bootstrap_dataflow_plans`. let bootstrap_as_ofs_start = Instant::now(); info!("startup: coordinator init: bootstrap: dataflow as-of bootstrapping beginning"); - let dataflow_read_holds = self.bootstrap_dataflow_as_ofs().await; + let dataflow_read_holds = self.bootstrap_dataflow_as_ofs().await?; info!( "startup: coordinator init: bootstrap: dataflow as-of bootstrapping complete in {:?}", bootstrap_as_ofs_start.elapsed() @@ -3488,6 +3497,14 @@ impl Coordinator { .relation_desc() .expect("sinks can only be built on items with descs") .into_owned(); + let as_of = if self.catalog().state().catalog_read_protection_enabled() { + self.catalog().state().maintained_read_requirements()[&sink.global_id()] + .frontier + .into_iter() + .collect() + } else { + Antichain::from_elem(Timestamp::minimum()) + }; let collection_desc = CollectionDescription { // TODO(sinks): make generic once we have more than one sink type. desc: KAFKA_PROGRESS_DESC.clone(), @@ -3501,7 +3518,7 @@ impl Coordinator { .clone() .into_inline_connection(self.catalog().state()), envelope: sink.envelope, - as_of: Antichain::from_elem(Timestamp::minimum()), + as_of, with_snapshot: sink.with_snapshot, version: sink.version, from_storage_metadata: (), @@ -4060,10 +4077,12 @@ impl Coordinator { /// This method expects all storage collections and dataflow plans to be available, so it must /// run after [`Coordinator::bootstrap_storage_collections`] and /// [`Coordinator::bootstrap_dataflow_plans`]. - async fn bootstrap_dataflow_as_ofs(&mut self) -> BTreeMap { + async fn bootstrap_dataflow_as_ofs( + &mut self, + ) -> Result, AdapterError> { let mut catalog_ids = Vec::new(); let mut dataflows = Vec::new(); - let mut read_policies = BTreeMap::new(); + let mut read_policies: BTreeMap = BTreeMap::new(); for entry in self.catalog.entries() { let gid = match entry.item() { CatalogItem::Index(idx) => idx.global_id(), @@ -4089,10 +4108,27 @@ impl Coordinator { } } + self.sync_compute_read_protection().await?; + let mut index_bounds: BTreeMap<_, _> = self + .catalog() + .state() + .collection_compaction_bounds() + .iter() + .map(|(&id, bound)| (id, bound.clone())) + .collect(); + if let Some(subscriber) = &self.compaction_bound_subscriber { + index_bounds.extend( + subscriber + .bounds() + .iter() + .map(|(&id, bound)| (id, bound.clone())), + ); + } let read_ts = self.get_local_read_ts().await; let read_holds = as_of_selection::run( &mut dataflows, &read_policies, + &index_bounds, &*self.controller.storage_collections, read_ts, self.controller.read_only(), @@ -4103,7 +4139,7 @@ impl Coordinator { catalog.set_physical_plan(id, plan); } - read_holds + Ok(read_holds) } /// Serves the coordinator, receiving commands from users over `cmd_rx` @@ -4221,6 +4257,8 @@ impl Coordinator { .catalog_read_protection_publish_interval(); let publication_timer = tokio::time::sleep(publication_delay); tokio::pin!(publication_timer); + let subscription_timer = tokio::time::sleep(CATALOG_SUBSCRIPTION_INTERVAL); + tokio::pin!(subscription_timer); loop { let delay = self @@ -4241,12 +4279,22 @@ impl Coordinator { // before receiving a new batch of commands. biased; + // Polling a pinned Sleep is cancellation-safe. Following committed permission + // does not depend on the savepoint's publication setting. + _ = subscription_timer.as_mut(), + if self.compaction_bound_subscriber.is_some() => { + if let Err(error) = self.sync_compute_read_protection().await { + warn!(%error, "unable to follow catalog read protection"); + } + subscription_timer.set(tokio::time::sleep(CATALOG_SUBSCRIPTION_INTERVAL)); + } + // Polling a pinned Sleep is cancellation-safe. Bootstrap restores execution holds // before this runs. Give publication a turn even under continuous load, // but schedule from completion so a slow commit cannot monopolize us. _ = publication_timer.as_mut(), if self.catalog().state().catalog_read_protection_enabled() - && !self.controller.read_only() && !publication_delay.is_zero() => { + && !self.controller.read_only() => { if let Err(error) = self.publish_read_protection().await { warn!(%error, "unable to publish catalog read protection"); } @@ -4450,6 +4498,9 @@ impl Coordinator { if let Some(sweep) = self.hydration_history_sweep.take() { sweep.abort_and_wait().await; } + if let Some(subscriber) = self.compaction_bound_subscriber.take() { + subscriber.expire().await; + } // Try and cleanup as a best effort. There may be some async tasks out there holding a // reference that prevents us from cleaning up. @@ -4687,8 +4738,7 @@ impl Coordinator { ) { let read_holds = self.acquire_read_holds(id_bundle); let since = read_holds.least_valid_read(); - df_desc.set_as_of(since); - + df_desc.set_as_of(since.clone()); self.ship_dataflow_and_notice_builtin_table_updates( df_desc, instance, @@ -5101,6 +5151,7 @@ pub fn serve( controller_config, controller_envd_epoch, mut storage, + compaction_bound_subscriber, timestamp_oracle_url, unsafe_mode, all_features, @@ -5513,6 +5564,9 @@ pub fn serve( let mut coord = Coordinator { controller, catalog, + compaction_bound_subscriber: compaction_bound_subscriber + .map(CompactionBoundSubscriber::new), + read_protection_pending: BTreeSet::new(), internal_cmd_tx, group_commit_tx, reconcile_now: Arc::new(Notify::new()), diff --git a/src/adapter/src/coord/catalog_implications.rs b/src/adapter/src/coord/catalog_implications.rs index b139c777a68c3..6a1949796acf8 100644 --- a/src/adapter/src/coord/catalog_implications.rs +++ b/src/adapter/src/coord/catalog_implications.rs @@ -257,6 +257,19 @@ impl Coordinator { self.catalog().system_config().notify_all_callbacks(); } + // Logging indexes are installed with their cluster, so stage compute permission + // before applying any creates. Storage receives its bounds after registration. + let storage_metadata = self.catalog().state().storage_metadata(); + let (storage_bounds, compute_bounds): (BTreeMap<_, _>, BTreeMap<_, _>) = compaction_bounds + .into_iter() + .partition(|(id, _)| storage_metadata.collection_metadata.contains_key(id)); + for (id, bound) in compute_bounds { + self.controller + .compute + .apply_compaction_bound(id, bound) + .map_err(|error| AdapterError::Unstructured(error.into()))?; + } + let mut tables_to_drop = BTreeSet::new(); let mut sources_to_drop = vec![]; let mut replication_slots_to_drop: Vec<(PostgresConnection, String)> = vec![]; @@ -851,10 +864,10 @@ impl Coordinator { // New collections already enforce their committed bounds during creation. // Deliver advancements after same-batch creates, before drops release protection. - if !compaction_bounds.is_empty() { + if !storage_bounds.is_empty() { self.controller .storage_collections - .apply_compaction_bounds(compaction_bounds)?; + .apply_compaction_bounds(storage_bounds)?; } // Create VPC endpoints for AWS PrivateLink connections diff --git a/src/adapter/src/coord/command_handler.rs b/src/adapter/src/coord/command_handler.rs index 0ac1328385067..da218c17d304d 100644 --- a/src/adapter/src/coord/command_handler.rs +++ b/src/adapter/src/coord/command_handler.rs @@ -353,9 +353,18 @@ impl Coordinator { drop(retire_notify); } - Command::CatalogSnapshot { tx } => { + Command::CatalogSnapshot { + tx, + include_durable_upper, + } => { + let durable_upper = if include_durable_upper { + Some(self.catalog().current_upper_if_in_sync().await) + } else { + None + }; let _ = tx.send(CatalogSnapshot { catalog: self.owned_catalog(), + durable_upper, }); } @@ -1411,7 +1420,7 @@ impl Coordinator { | Statement::CreateTableFromSource(_) | Statement::CreateSource(_) => { let state = self.catalog().for_session(ctx.session()).state().clone(); - let ddl_revision = self.catalog().ddl_revision(); + let transient_revision = self.catalog().transient_revision(); // Initialize our transaction with a set of empty ops, or return an error // if we can't run a DDL transaction @@ -1419,7 +1428,7 @@ impl Coordinator { if let Err(err) = txn_status.add_ops(TransactionOps::DDL { ops: vec![], state, - ddl_revision, + transient_revision, side_effects: vec![], snapshot: None, }) { diff --git a/src/adapter/src/coord/compaction_bound_subscriber.rs b/src/adapter/src/coord/compaction_bound_subscriber.rs new file mode 100644 index 0000000000000..908b9db8e2021 --- /dev/null +++ b/src/adapter/src/coord/compaction_bound_subscriber.rs @@ -0,0 +1,252 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +use std::collections::BTreeMap; + +use mz_catalog::durable::{CatalogError, DurableCatalogState}; +use mz_catalog::memory::objects::{StateDiff, StateUpdateKind}; +use mz_repr::{GlobalId, Timestamp}; +use timely::progress::Antichain; + +/// Tracks committed compaction bounds independently of the SQL catalog. +#[derive(Debug)] +pub(super) struct CompactionBoundSubscriber { + catalog: Box, + bounds: BTreeMap>, +} + +impl CompactionBoundSubscriber { + /// Takes a dedicated, live readonly handle whose initial updates have not been consumed. + pub(super) fn new(catalog: Box) -> Self { + assert!(catalog.is_read_only()); + Self { + catalog, + bounds: BTreeMap::new(), + } + } + + /// Consumes committed updates and returns changed surviving bounds. + /// The first call includes the initial durable state. + pub(super) async fn sync( + &mut self, + ) -> Result>, CatalogError> { + // Opening queues the durable initial state. Draining the entire stream, rather + // than reading snapshots, preserves its continuity and bounds queue growth. + let updates = self.catalog.sync_to_current_updates().await?; + let mut bounds: Vec<_> = updates + .into_iter() + .filter_map(|update| match update.kind { + StateUpdateKind::CollectionCompactionBound(bound) => { + Some((update.ts, update.diff, bound)) + } + _ => None, + }) + .collect(); + // A call can span transactions. Retractions precede additions only within + // each timestamp, and no await exposes a partially applied transaction. + bounds.sort_by_key(|(ts, diff, _)| (*ts, *diff)); + let mut changed = BTreeMap::new(); + for (_, diff, bound) in bounds { + match diff { + StateDiff::Retraction => { + self.bounds.remove(&bound.id); + changed.remove(&bound.id); + } + StateDiff::Addition => { + changed.insert(bound.id, Antichain::from_iter(bound.frontier)); + self.bounds + .insert(bound.id, Antichain::from_iter(bound.frontier)); + } + } + } + Ok(changed) + } + + /// Returns committed bounds, not permission to read or to drop a collection. + /// + /// Record absence is not a drop notification. Fresh indexes have no record, + /// and retractions must not release execution protection in a frozen catalog. + pub(super) fn bounds(&self) -> &BTreeMap> { + &self.bounds + } + + /// Releases the durable catalog handle. + pub(super) async fn expire(self) { + self.catalog.expire().await; + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeSet; + + use mz_catalog::durable::{TestCatalogStateBuilder, test_bootstrap_args}; + use mz_ore::now::SYSTEM_TIME; + use mz_persist_client::{PersistClient, ShardId}; + use mz_storage_client::controller::StorageTxn; + + use super::*; + + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] + async fn committed_bounds_follow_writer_independently_of_savepoint() { + let builder = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation(); + let mut writer = builder + .clone() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .expect("failed to open writer catalog"); + let _ = writer + .sync_to_current_updates() + .await + .expect("failed to sync writer catalog"); + + let id = GlobalId::User(1000); + let completed = GlobalId::User(1001); + let mut txn = writer + .transaction() + .await + .expect("failed to start initial bounds transaction"); + txn.insert_collection_metadata(BTreeMap::from([ + (id, ShardId::new()), + (completed, ShardId::new()), + ])) + .expect("failed to insert collection metadata"); + txn.set_collection_compaction_bound(id, Some(10.into())) + .expect("failed to set initial compaction bound"); + txn.set_collection_compaction_bound(completed, None) + .expect("failed to set completed compaction bound"); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts) + .await + .expect("failed to commit initial bounds transaction"); + + let mut savepoint = builder + .clone() + .unwrap_build() + .await + .open_savepoint(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .expect("failed to open savepoint catalog"); + let _ = savepoint + .sync_to_current_updates() + .await + .expect("failed to sync initial savepoint catalog"); + let frozen_snapshot = savepoint + .snapshot() + .await + .expect("failed to snapshot initial savepoint catalog"); + let reader = builder + .unwrap_build() + .await + .open_read_only(&test_bootstrap_args()) + .await + .expect("failed to open subscriber catalog"); + let mut subscriber = CompactionBoundSubscriber::new(reader); + assert!(subscriber.bounds().is_empty()); + let changed = subscriber + .sync() + .await + .expect("failed to sync initial subscriber bounds"); + assert_eq!(&changed, subscriber.bounds()); + assert_eq!( + subscriber.bounds(), + &BTreeMap::from([ + (id, Antichain::from_elem(10.into())), + (completed, Antichain::new()), + ]) + ); + + // Multiple replacements between syncs must not retract the final addition. + for frontier in [20, 30] { + let mut txn = writer + .transaction() + .await + .expect("failed to start bound replacement transaction"); + txn.set_collection_compaction_bound(id, Some(frontier.into())) + .expect("failed to replace compaction bound"); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts) + .await + .expect("failed to commit bound replacement transaction"); + } + let changed = subscriber + .sync() + .await + .expect("failed to sync replaced subscriber bounds"); + assert_eq!( + changed, + BTreeMap::from([(id, Antichain::from_elem(30.into()))]) + ); + assert_eq!(subscriber.bounds()[&id], Antichain::from_elem(30.into())); + + let mut txn = writer + .transaction() + .await + .expect("failed to start pre-deletion bound transaction"); + txn.set_collection_compaction_bound(id, Some(40.into())) + .expect("failed to advance compaction bound before deletion"); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts) + .await + .expect("failed to commit pre-deletion bound transaction"); + let mut txn = writer + .transaction() + .await + .expect("failed to start collection deletion transaction"); + txn.delete_collection_metadata(BTreeSet::from([id])); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts) + .await + .expect("failed to commit collection deletion transaction"); + let changed = subscriber + .sync() + .await + .expect("failed to sync subscriber after collection deletion"); + assert!(changed.is_empty(), "a dropped lifetime is not delivered"); + assert_eq!( + subscriber.bounds(), + &BTreeMap::from([(completed, Antichain::new())]) + ); + let changed = subscriber + .sync() + .await + .expect("failed to sync subscriber without new updates"); + assert!(changed.is_empty(), "unchanged bounds are not redelivered"); + assert_eq!( + subscriber.bounds(), + &BTreeMap::from([(completed, Antichain::new())]) + ); + assert!( + savepoint + .sync_to_current_updates() + .await + .expect("failed to sync savepoint after writer updates") + .is_empty() + ); + assert_eq!( + savepoint + .snapshot() + .await + .expect("failed to snapshot savepoint after writer updates"), + frozen_snapshot + ); + + subscriber.expire().await; + savepoint.expire().await; + writer.expire().await; + } +} diff --git a/src/adapter/src/coord/ddl.rs b/src/adapter/src/coord/ddl.rs index 0e946dcf18ae3..645e3e2bd1b4b 100644 --- a/src/adapter/src/coord/ddl.rs +++ b/src/adapter/src/coord/ddl.rs @@ -15,6 +15,7 @@ use std::pin::Pin; use std::sync::Arc; use std::time::{Duration, Instant}; +use differential_dataflow::lattice::Lattice; use fail::fail_point; use maplit::{btreemap, btreeset}; use mz_adapter_types::compaction::SINCE_GRANULARITY; @@ -272,7 +273,7 @@ impl Coordinator { ops: TransactionOps::DDL { ops: txn_ops, - ddl_revision: txn_revision, + transient_revision: txn_revision, state: txn_state, snapshot: txn_snapshot, side_effects: _, @@ -290,7 +291,7 @@ impl Coordinator { return result; }; - if self.catalog().ddl_revision() != *txn_revision { + if self.catalog().transient_revision() != *txn_revision { self.metrics .catalog_transact_seconds .with_label_values(&["catalog_transact_with_ddl_transaction"]) @@ -379,7 +380,7 @@ impl Coordinator { ops: combined_ops, state: new_state, side_effects: vec![Box::new(side_effect)], - ddl_revision: self.catalog().ddl_revision(), + transient_revision: self.catalog().transient_revision(), snapshot: Some(new_snapshot), }); @@ -1104,9 +1105,6 @@ impl Coordinator { // The AsOf is used to determine at what time to snapshot reading from // the persist collection. This is primarily relevant when we do _not_ // want to include the snapshot in the sink. - // - // We choose the smallest as_of that is legal, according to the sinked - // collection's since. let id_bundle = crate::CollectionIdBundle { storage_ids: btreeset! {sink.from}, compute_ids: btreemap! {}, @@ -1120,7 +1118,14 @@ impl Coordinator { // TODO: Maybe in the future, pass those holds on to storage, to hold on // to them and downgrade when possible? let read_holds = self.acquire_read_holds(&id_bundle); - let as_of = read_holds.least_valid_read(); + let mut as_of = read_holds.least_valid_read(); + if self.catalog().state().catalog_read_protection_enabled() { + let requirement = &self.catalog().state().maintained_read_requirements()[&id]; + let committed = requirement.frontier.into_iter().collect(); + // A read-only catalog snapshot can lag the leased since. Actual holds, + // not the snapshot's permission, determine what remains executable. + as_of.join_assign(&committed); + } let storage_sink_from_entry = self.catalog().get_entry_by_global_id(&sink.from); let storage_sink_desc = mz_storage_types::sinks::StorageSinkDesc { @@ -1396,8 +1401,7 @@ impl Coordinator { | Op::ResetSystemConfiguration { .. } | Op::ResetAllSystemConfiguration { .. } | Op::UpdateScopedSystemParameters { .. } - | Op::SetCollectionCompactionBound { .. } - | Op::SetMaintainedReadRequirement { .. } + | Op::SetReadProtection { .. } | Op::Comment { .. } | Op::CheckClusterState { .. } | Op::InjectAuditEvents { .. } => {} diff --git a/src/adapter/src/coord/info_metrics.rs b/src/adapter/src/coord/info_metrics.rs index 20ac3d6694b3c..fdfff41f95831 100644 --- a/src/adapter/src/coord/info_metrics.rs +++ b/src/adapter/src/coord/info_metrics.rs @@ -286,13 +286,16 @@ impl Coordinator { let (tx, rx) = oneshot::channel(); let send = internal_cmd_tx.send(Message::Command( OpenTelemetryContext::obtain(), - Command::CatalogSnapshot { tx }, + Command::CatalogSnapshot { + tx, + include_durable_upper: false, + }, )); // Bail if the coordinator has gone away. if send.is_err() { break; } - let Ok(CatalogSnapshot { catalog }) = rx.await else { + let Ok(CatalogSnapshot { catalog, .. }) = rx.await else { break; }; diff --git a/src/adapter/src/coord/read_protection.rs b/src/adapter/src/coord/read_protection.rs index 5b5bffeb8774b..921bb0d197dd0 100644 --- a/src/adapter/src/coord/read_protection.rs +++ b/src/adapter/src/coord/read_protection.rs @@ -9,8 +9,8 @@ //! Publishes durable recovery requirements and compaction permission together. -use std::collections::BTreeMap; -use std::time::Instant; +use std::collections::{BTreeMap, BTreeSet}; +use std::time::{Duration, Instant}; use differential_dataflow::lattice::Lattice; use mz_catalog::durable::objects::{CollectionCompactionBound, MaintainedReadRequirement}; @@ -23,7 +23,53 @@ use crate::AdapterError; use crate::catalog::Op; use crate::coord::Coordinator; +pub(super) const CATALOG_SUBSCRIPTION_INTERVAL: Duration = Duration::from_secs(1); + impl Coordinator { + /// Restores published compute bounds before installing clusters and dataflows. + pub(super) async fn restore_compute_read_protection(&mut self) -> Result<(), AdapterError> { + if !self.catalog().state().catalog_read_protection_enabled() { + return Ok(()); + } + let state = self.catalog().state(); + let storage = &state.storage_metadata().collection_metadata; + let bounds = state + .collection_compaction_bounds() + .iter() + .filter(|(id, _)| !storage.contains_key(id)) + .map(|(&id, bound)| (id, bound.clone())) + .collect::>(); + for (id, bound) in bounds { + self.controller + .compute + .apply_compaction_bound(id, bound) + .map_err(|error| AdapterError::Unstructured(error.into()))?; + } + self.sync_compute_read_protection().await + } + + /// Delivers changed published bounds to compute lifetimes in the local catalog. + pub(super) async fn sync_compute_read_protection(&mut self) -> Result<(), AdapterError> { + let Some(subscriber) = &mut self.compaction_bound_subscriber else { + return Ok(()); + }; + let changed = subscriber.sync().await?; + for (id, bound) in changed { + if !self + .catalog() + .try_get_entry_by_global_id(&id) + .is_some_and(|entry| matches!(entry.item(), CatalogItem::Index(_))) + { + continue; + } + self.controller + .compute + .apply_compaction_bound(id, bound) + .map_err(|error| AdapterError::Unstructured(error.into()))?; + } + Ok(()) + } + /// Publishes recovery progress and compatible bounds in enabled, writable environments. pub(super) async fn publish_read_protection(&mut self) -> Result<(), AdapterError> { if self.controller.read_only() || !self.catalog().state().catalog_read_protection_enabled() @@ -32,55 +78,107 @@ impl Coordinator { } let start = Instant::now(); - let frontiers = self + let catalog_changes = self.catalog_mut().take_read_protection_changes(); + self.read_protection_pending.extend(catalog_changes); + let mut storage = self .controller .storage_collections - .active_collection_frontiers(); - // Coordinator handlers serialize new creation holds with this publication. - let compaction_frontiers = self.controller.storage_collections.compaction_frontiers(); + .take_read_protection_frontiers(&self.read_protection_pending); + let inputs = storage + .keys() + .filter_map(|id| { + self.catalog() + .state() + .maintained_read_requirements() + .get(id) + }) + .flat_map(|requirement| requirement.inputs.iter().copied()) + .collect(); + storage.extend( + self.controller + .storage_collections + .take_read_protection_frontiers(&inputs), + ); + let frontiers = storage + .values() + .map(|(frontiers, _)| frontiers.clone()) + .collect::>(); + let compaction_frontiers = storage + .iter() + .map(|(&id, (_, frontier))| (id, frontier.clone())) + .collect(); + let compute_changes = self + .controller + .compute + .take_compaction_bound_proposals(&self.read_protection_pending); + self.read_protection_pending.extend(storage.keys().copied()); + self.read_protection_pending + .extend(compute_changes.keys().copied()); + let bounds = self.catalog().state().collection_compaction_bounds(); + let compute_proposals = compute_changes + .into_iter() + .filter_map(|(id, proposal)| { + let entry = self.catalog().try_get_entry_by_global_id(&id)?; + if !matches!(entry.item(), CatalogItem::Index(_)) { + return None; + } + let frontier = if bounds.contains_key(&id) { + proposal + } else { + // First publication records installed readability. + self.controller + .compute + .collection_frontiers(id, None) + .expect("proposal belongs to installed index") + .read_frontier + }; + Some((id, frontier)) + }) + .collect(); let candidates = publication_candidates( self.catalog().state().maintained_read_requirements(), - &self.catalog().state().storage_metadata().compaction_bounds, + self.catalog().state().collection_compaction_bounds(), &frontiers, &compaction_frontiers, + &compute_proposals, |id| { let Some(entry) = self.catalog().try_get_entry_by_global_id(&id) else { return false; }; - let CatalogItem::MaterializedView(mv) = entry.item() else { - return false; - }; - // A pending replacement observes the target's shared upper, not its own - // progress. Retired writers' requirements are completed by replacement. - mv.replacement_target.is_none() && mv.global_id_writes() == id + match entry.item() { + // A pending replacement observes the target's shared upper, not its own + // progress. Retired writers' requirements are completed by replacement. + CatalogItem::MaterializedView(mv) => { + mv.replacement_target.is_none() && mv.global_id_writes() == id + } + CatalogItem::Source(_) | CatalogItem::Table(_) | CatalogItem::Sink(_) => true, + _ => false, + } + }, + |id, excluding| { + self.catalog() + .state() + .maintained_read_frontier(id, excluding) }, ); let requirement_updates = candidates.requirements.len(); let bound_updates = candidates.bounds.len(); if requirement_updates == 0 && bound_updates == 0 { + self.read_protection_pending.clear(); return Ok(()); } - let ops = - candidates - .requirements - .into_iter() - .map(|requirement| Op::SetMaintainedReadRequirement { - id: requirement.id, - inputs: requirement.inputs, - frontier: requirement.frontier, - }) - .chain(candidates.bounds.into_iter().map(|bound| { - Op::SetCollectionCompactionBound { - id: bound.id, - frontier: bound.frontier, - } - })) - .collect(); + // Applying each record as a separate op would rescan accumulated pending + // catalog updates for every record on the coordinator loop. + let ops = vec![Op::SetReadProtection { + requirements: candidates.requirements, + bounds: candidates.bounds, + }]; self.catalog_transact(None, ops).await?; + self.read_protection_pending.clear(); tracing::info!( - installed_collections = frontiers.len(), + changed_storage_collections = frontiers.len(), requirement_updates, bound_updates, duration_seconds = start.elapsed().as_secs_f64(), @@ -102,43 +200,47 @@ struct PublicationCandidates { unbounded_policy_lag: usize, } -/// Computes changed records from committed protection and installed storage frontiers. +/// Computes changed records from committed protection and installed controller frontiers. +/// Compute proposals contain only live governed catalog indexes, using actual readability +/// for indexes without a published bound. fn publication_candidates( - requirements: &BTreeMap, - bounds: &BTreeMap>, + requirements: &imbl::OrdMap, + bounds: &imbl::OrdMap>, frontiers: &[CollectionFrontiers], compaction_frontiers: &BTreeMap>, - is_active_mv_writer: impl Fn(GlobalId) -> bool, + compute_proposals: &BTreeMap>, + owns_durable_progress: impl Fn(GlobalId) -> bool, + committed_input_limit: impl Fn(GlobalId, &BTreeSet) -> Option, ) -> PublicationCandidates { let mut candidates = PublicationCandidates::default(); - let installed: BTreeMap<_, _> = frontiers.iter().map(|f| (f.id, f)).collect(); let mut input_limits: BTreeMap = BTreeMap::new(); + let mut advancing = BTreeSet::new(); - for requirement in requirements.values() { - let mut frontier: Antichain<_> = requirement.frontier.into_iter().collect(); - if is_active_mv_writer(requirement.id) - && let Some(output) = installed.get(&requirement.id) - { - // An upper at a refresh timestamp does not complete that refresh. - // Its predecessor preserves the pending input snapshot. At MIN, - // saturating subtraction leaves the committed requirement intact. - let predecessor = output - .write_frontier - .iter() - .map(|t| t.saturating_sub(1)) - .collect(); - frontier.join_assign(&predecessor); + for output in frontiers { + let Some(requirement) = requirements.get(&output.id) else { + continue; + }; + if !owns_durable_progress(requirement.id) { + continue; } + let mut frontier: Antichain<_> = requirement.frontier.into_iter().collect(); + // An upper at a refresh timestamp does not complete that refresh. + // Its predecessor retains the pending input snapshot, including at MIN. + let predecessor = output + .write_frontier + .iter() + .map(|t| t.saturating_sub(1)) + .collect(); + frontier.join_assign(&predecessor); let frontier = frontier.into_option(); - if frontier != requirement.frontier { - candidates.requirements.push(MaintainedReadRequirement { - frontier, - ..requirement.clone() - }); + if frontier == requirement.frontier { + continue; } - - // Every remaining requirement limits its exact committed input versions, - // including consumers that are uninstalled or ineligible to advance. + advancing.insert(requirement.id); + candidates.requirements.push(MaintainedReadRequirement { + frontier, + ..requirement.clone() + }); if let Some(frontier) = frontier { for input in &requirement.inputs { input_limits @@ -149,19 +251,35 @@ fn publication_candidates( } } - for collection in frontiers { - let Some(old_bound) = bounds.get(&collection.id) else { - continue; - }; - let Some(mut bound) = compaction_frontiers.get(&collection.id).cloned() else { + let storage_proposals = frontiers.iter().filter_map(|collection| { + compaction_frontiers.get(&collection.id).map(|proposal| { + ( + collection.id, + proposal, + Some(&collection.implied_capability), + ) + }) + }); + let compute_proposals = compute_proposals + .iter() + .map(|(&id, proposal)| (id, proposal, None)); + for (id, proposal, policy) in storage_proposals.chain(compute_proposals) { + let old_bound = bounds.get(&id); + if old_bound.is_none() && policy.is_some() { continue; - }; - // Keep early creation holds authoritative during optimization. The storage - // proposal excludes only its permission cap, not those read requirements. - bound.extend(input_limits.get(&collection.id).copied()); - bound.join_assign(old_bound); - if let Some(bound_ts) = bound.as_option() { - match collection.implied_capability.as_option() { + } + let mut bound = proposal.clone(); + // Controller proposals exclude only catalog permission. Early creation and + // execution holds remain authoritative alongside durable input requirements. + bound.extend(committed_input_limit(id, &advancing)); + bound.extend(input_limits.get(&id).copied()); + if let Some(old_bound) = old_bound { + bound.join_assign(old_bound); + } + if let Some(bound_ts) = bound.as_option() + && let Some(policy) = policy + { + match policy.as_option() { Some(policy_ts) => { candidates.max_policy_lag_ts = candidates .max_policy_lag_ts @@ -170,9 +288,9 @@ fn publication_candidates( None => candidates.unbounded_policy_lag += 1, } } - if &bound != old_bound { + if Some(&bound) != old_bound { candidates.bounds.push(CollectionCompactionBound { - id: collection.id, + id, frontier: bound.into_option(), }); } @@ -184,6 +302,39 @@ fn publication_candidates( mod tests { use super::*; + fn publication_candidates( + requirements: &BTreeMap, + bounds: &BTreeMap>, + frontiers: &[CollectionFrontiers], + compaction_frontiers: &BTreeMap>, + compute_proposals: &BTreeMap>, + owns_durable_progress: impl Fn(GlobalId) -> bool, + ) -> PublicationCandidates { + super::publication_candidates( + &requirements + .iter() + .map(|(&id, value)| (id, value.clone())) + .collect(), + &bounds + .iter() + .map(|(&id, value)| (id, value.clone())) + .collect(), + frontiers, + compaction_frontiers, + compute_proposals, + owns_durable_progress, + |input, excluding| { + requirements + .values() + .filter(|requirement| { + requirement.inputs.contains(&input) && !excluding.contains(&requirement.id) + }) + .filter_map(|requirement| requirement.frontier) + .min() + }, + ) + } + fn frontier(t: u64) -> Antichain { Antichain::from_elem(Timestamp::from(t)) } @@ -238,6 +389,7 @@ mod tests { &bounds, &frontiers, &policy_frontiers(&frontiers), + &BTreeMap::new(), |_| true, ); let expected = expected.map(Timestamp::from); @@ -274,6 +426,7 @@ mod tests { &bounds, &[collection(output, Some(20), Some(0))], &BTreeMap::new(), + &BTreeMap::new(), |_| true, ); assert!(candidates.requirements.is_empty(), "completion is final"); @@ -313,6 +466,7 @@ mod tests { &bounds, &frontiers, &policy_frontiers(&frontiers), + &BTreeMap::new(), |id| id != pending, ); assert_eq!( @@ -336,6 +490,57 @@ mod tests { } } + #[mz_ore::test] + fn storage_consumers_protect_self_and_dependencies_until_durable_progress() { + let remap = GlobalId::User(1); + let source = GlobalId::User(2); + let sink = GlobalId::User(3); + let requirements = BTreeMap::from([ + (remap, requirement(remap, &[remap], 10)), + (source, requirement(source, &[source, remap], 10)), + (sink, requirement(sink, &[sink, source], 10)), + ]); + let bounds = BTreeMap::from([ + (remap, frontier(10)), + (source, frontier(10)), + (sink, frontier(10)), + ]); + for sink_installed in [false, true] { + let mut frontiers = vec![ + collection(remap, Some(101), Some(200)), + collection(source, Some(51), Some(200)), + ]; + if sink_installed { + frontiers.push(collection(sink, Some(21), Some(200))); + } + let candidates = publication_candidates( + &requirements, + &bounds, + &frontiers, + &policy_frontiers(&frontiers), + &BTreeMap::new(), + |_| true, + ); + let mut expected = vec![ + requirement(remap, &[remap], 100), + requirement(source, &[source, remap], 50), + ]; + let mut expected_bounds = vec![CollectionCompactionBound { + id: remap, + frontier: Some(Timestamp::from(50)), + }]; + if sink_installed { + expected.push(requirement(sink, &[sink, source], 20)); + expected_bounds.extend([source, sink].map(|id| CollectionCompactionBound { + id, + frontier: Some(Timestamp::from(20)), + })); + } + assert_eq!(candidates.requirements, expected); + assert_eq!(candidates.bounds, expected_bounds); + } + } + #[mz_ore::test] fn local_creation_holds_limit_permission() { let input = GlobalId::User(1); @@ -345,6 +550,7 @@ mod tests { &BTreeMap::from([(input, frontier(10))]), &frontiers, &BTreeMap::from([(input, frontier(20))]), + &BTreeMap::new(), |_| false, ); assert_eq!( @@ -356,6 +562,109 @@ mod tests { ); } + #[mz_ore::test] + fn compute_publication_preserves_existing_bounds() { + let index = GlobalId::User(1); + let uninstalled = GlobalId::User(3); + for (old, proposal, expected) in [ + (Some(10), Some(20), Some(Some(20))), + (Some(10), Some(10), None), + (Some(10), Some(5), None), + (Some(10), None, Some(None)), + (None, Some(20), None), + ] { + let bounds = BTreeMap::from([ + (index, old.map(Timestamp::from).into_iter().collect()), + (uninstalled, frontier(10)), + ]); + let proposals = + BTreeMap::from([(index, proposal.map(Timestamp::from).into_iter().collect())]); + let candidates = publication_candidates( + &BTreeMap::new(), + &bounds, + &[], + &BTreeMap::new(), + &proposals, + |_| false, + ); + assert!(candidates.requirements.is_empty()); + assert_eq!( + candidates.bounds, + expected + .into_iter() + .map(|frontier| CollectionCompactionBound { + id: index, + frontier: frontier.map(Timestamp::from), + }) + .collect::>() + ); + } + } + + #[mz_ore::test] + fn first_compute_publication_needs_no_birth_record() { + let index = GlobalId::User(1); + let candidates = publication_candidates( + &BTreeMap::new(), + &BTreeMap::new(), + &[], + &BTreeMap::new(), + &BTreeMap::from([(index, frontier(20))]), + |_| false, + ); + assert_eq!( + candidates.bounds, + vec![CollectionCompactionBound { + id: index, + frontier: Some(20.into()), + }] + ); + } + + #[mz_ore::test] + fn mixed_publication_batches_progress_and_independently_held_bounds() { + let input = GlobalId::User(1); + let output = GlobalId::User(2); + let index = GlobalId::User(3); + let requirements = BTreeMap::from([(output, requirement(output, &[input], 10))]); + let bounds = BTreeMap::from([(input, frontier(10)), (index, frontier(10))]); + let frontiers = [ + collection(input, Some(100), Some(90)), + collection(output, Some(51), Some(0)), + ]; + // Proposals already include live holds. Releasing one controller's hold + // must not release the other's or the logical storage input requirement. + for (storage_proposal, compute_proposal, expected_storage) in + [(20, 30, 20), (90, 30, 50), (20, 90, 20)] + { + let candidates = publication_candidates( + &requirements, + &bounds, + &frontiers, + &BTreeMap::from([(input, frontier(storage_proposal))]), + &BTreeMap::from([(index, frontier(compute_proposal))]), + |_| true, + ); + assert_eq!( + candidates.requirements, + vec![requirement(output, &[input], 50)] + ); + assert_eq!( + candidates.bounds, + vec![ + CollectionCompactionBound { + id: input, + frontier: Some(Timestamp::from(expected_storage)), + }, + CollectionCompactionBound { + id: index, + frontier: Some(Timestamp::from(compute_proposal)), + }, + ] + ); + } + } + #[mz_ore::test] fn bounds_require_installation_and_governance_and_never_regress() { let advancing = GlobalId::User(1); @@ -380,6 +689,7 @@ mod tests { &bounds, &frontiers, &policy_frontiers(&frontiers), + &BTreeMap::new(), |_| false, ); assert_eq!( @@ -398,6 +708,7 @@ mod tests { &bounds, &frontiers, &policy_frontiers(&frontiers), + &BTreeMap::new(), |_| false, ); assert!(candidates.requirements.is_empty()); diff --git a/src/adapter/src/coord/sequencer/inner.rs b/src/adapter/src/coord/sequencer/inner.rs index 9b8122878b406..b561c333fb595 100644 --- a/src/adapter/src/coord/sequencer/inner.rs +++ b/src/adapter/src/coord/sequencer/inner.rs @@ -15,6 +15,7 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use anyhow::anyhow; +use differential_dataflow::lattice::Lattice; use futures::future::{BoxFuture, FutureExt}; use futures::{Future, StreamExt, future}; use itertools::Itertools; @@ -24,6 +25,7 @@ use mz_adapter_types::dyncfgs::{ ENABLE_EXPRESSION_CACHE, ENABLE_PASSWORD_AUTH, FRONTEND_READ_THEN_WRITE, READ_THEN_WRITE_MAX_DEPENDENCIES, }; +use mz_catalog::durable::objects::MaintainedReadRequirement; use mz_catalog::memory::error::ErrorKind; use mz_catalog::memory::objects::{ CatalogItem, Connection, DataSourceDesc, Sink, Source, Table, TableDataSource, Type, @@ -2272,10 +2274,10 @@ impl Coordinator { ops, state: _, side_effects, - ddl_revision, + transient_revision, snapshot: _, } => { - if *ddl_revision != self.catalog().ddl_revision() { + if *transient_revision != self.catalog().transient_revision() { return Err(AdapterError::DDLTransactionRace); } // Commit all of our queued ops. @@ -3436,12 +3438,22 @@ impl Coordinator { storage_ids: BTreeSet::from_iter([plan.sink.from]), compute_ids: BTreeMap::new(), }; - let read_hold = self.acquire_read_holds(&id_bundle); + let mut read_hold = self.acquire_read_holds(&id_bundle); + let mut threshold = read_hold.least_valid_read(); + if self.catalog().state().catalog_read_protection_enabled() { + let metadata = self.catalog().state().storage_metadata(); + let Some(bound) = metadata.compaction_bounds.get(&plan.sink.from) else { + ctx.retire(Err(AdapterError::UnreadableSinkCollection)); + return; + }; + threshold.join_assign(bound); + } - let Some(read_ts) = read_hold.least_valid_read().into_option() else { + let Some(read_ts) = threshold.into_option() else { ctx.retire(Err(AdapterError::UnreadableSinkCollection)); return; }; + read_hold.downgrade(read_ts); let otel_ctx = OpenTelemetryContext::obtain(); let from_item_id = self.catalog().resolve_item_id(&plan.sink.from); @@ -3547,6 +3559,23 @@ impl Coordinator { &*as_of, &**write_frontier ); + let mut ops = Vec::new(); + if self.catalog().state().catalog_read_protection_enabled() { + let requirement = &self.catalog().state().maintained_read_requirements()[&global_id]; + let mut frontier: Antichain<_> = requirement.frontier.into_iter().collect(); + // Kafka reports this frontier only after committing its progress shard. + // Retain the predecessor so no pending output timestamp is skipped. + let predecessor = write_frontier.iter().map(|t| t.saturating_sub(1)).collect(); + frontier.join_assign(&predecessor); + ops.push(catalog::Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: global_id, + inputs: requirement.inputs.clone(), + frontier: frontier.as_option().copied(), + }], + bounds: vec![], + }); + } // Parse the `create_sql` so we can update it to the new sink definition. // @@ -3612,11 +3641,11 @@ impl Coordinator { commit_interval: sink_plan.commit_interval, }; - let ops = vec![catalog::Op::UpdateItem { + ops.push(catalog::Op::UpdateItem { id: item_id, name: entry.name().clone(), to_item: CatalogItem::Sink(new_sink), - }]; + }); match self .catalog_transact(Some(ctx.ctx().session_mut()), ops) diff --git a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs index 0b2c03425b70e..8330f84e3a1d0 100644 --- a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs +++ b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs @@ -10,6 +10,7 @@ use anyhow::anyhow; use differential_dataflow::lattice::Lattice; use maplit::btreemap; +use mz_catalog::durable::objects::MaintainedReadRequirement; use mz_catalog::memory::objects::{CatalogItem, MaterializedView}; use mz_expr::{CollectionPlan, ResultSpec}; use mz_ore::collections::CollectionExt; @@ -770,10 +771,13 @@ impl Coordinator { }, ]; if self.catalog().state().catalog_read_protection_enabled() { - ops.push(catalog::Op::SetMaintainedReadRequirement { - id: global_id, - inputs: logical_inputs.storage_ids, - frontier: dataflow_as_of.as_option().copied(), + ops.push(catalog::Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: global_id, + inputs: logical_inputs.storage_ids, + frontier: dataflow_as_of.as_option().copied(), + }], + bounds: vec![], }); } diff --git a/src/adapter/src/error.rs b/src/adapter/src/error.rs index 6f5dc48798faa..fbd36f6085849 100644 --- a/src/adapter/src/error.rs +++ b/src/adapter/src/error.rs @@ -1262,9 +1262,11 @@ impl AdapterError { dependency_kind: "replica", dependency_id: id.to_string(), }, - MissingAsOf | SinceViolation(..) | EmptyAsOfForSubscribe | EmptyAsOfForCopyTo => { - AdapterError::internal("dataflow creation error", e) - } + MissingAsOf + | SinceViolation(..) + | CompactionBoundViolation(..) + | EmptyAsOfForSubscribe + | EmptyAsOfForCopyTo => AdapterError::internal("dataflow creation error", e), } } } diff --git a/src/adapter/src/peek_client.rs b/src/adapter/src/peek_client.rs index 81e08e18b8bf2..39f5ac47a7f7e 100644 --- a/src/adapter/src/peek_client.rs +++ b/src/adapter/src/peek_client.rs @@ -253,8 +253,11 @@ impl PeekClient { // Session clients keep the coordinator loop alive. A dropped response // here therefore indicates an internal lifecycle bug, unlike a // background compute lookup racing coordinator shutdown. - let CatalogSnapshot { catalog } = self - .call_coordinator(|tx| Command::CatalogSnapshot { tx }) + let CatalogSnapshot { catalog, .. } = self + .call_coordinator(|tx| Command::CatalogSnapshot { + tx, + include_durable_upper: false, + }) .await .expect("coordinator unexpectedly dropped catalog snapshot response"); let metrics = self.coordinator_client.metrics(); diff --git a/src/adapter/src/session.rs b/src/adapter/src/session.rs index ceefa45b254c7..d9397daa2880b 100644 --- a/src/adapter/src/session.rs +++ b/src/adapter/src/session.rs @@ -1453,14 +1453,14 @@ impl TransactionStatus { } TransactionOps::DDL { ops: og_ops, - ddl_revision: og_revision, + transient_revision: og_revision, state: og_state, side_effects, snapshot: og_snapshot, } => match add_ops { TransactionOps::DDL { ops: new_ops, - ddl_revision: new_revision, + transient_revision: new_revision, side_effects: mut net_new_side_effects, state: new_state, snapshot: new_snapshot, @@ -1661,8 +1661,8 @@ pub enum TransactionOps { + Sync, >, >, - /// [`crate::catalog::Catalog::ddl_revision`] when this transaction started. - ddl_revision: u64, + /// [`crate::catalog::Catalog::transient_revision`] when this transaction started. + transient_revision: u64, /// Snapshot of the durable transaction state after the last dry run. /// Used to initialize the next dry run's transaction so it starts /// in sync with the accumulated `state`. `None` for the first diff --git a/src/adapter/src/util.rs b/src/adapter/src/util.rs index c9c237d1e8222..557d0f1ca6e95 100644 --- a/src/adapter/src/util.rs +++ b/src/adapter/src/util.rs @@ -378,6 +378,7 @@ impl ShouldTerminateGracefully for DataflowCreationError { fn should_terminate_gracefully(&self) -> bool { match self { DataflowCreationError::SinceViolation(_) + | DataflowCreationError::CompactionBoundViolation(_) | DataflowCreationError::InstanceMissing(_) | DataflowCreationError::CollectionMissing(_) | DataflowCreationError::ReplicaMissing(_) diff --git a/src/catalog/Cargo.toml b/src/catalog/Cargo.toml index b4f8762e28c99..c83935a913b11 100644 --- a/src/catalog/Cargo.toml +++ b/src/catalog/Cargo.toml @@ -20,6 +20,7 @@ chrono.workspace = true derivative.workspace = true differential-dataflow.workspace = true futures.workspace = true +imbl.workspace = true ipnet.workspace = true itertools.workspace = true mz-adapter-types = { path = "../adapter-types" } diff --git a/src/catalog/src/durable.rs b/src/catalog/src/durable.rs index 32fb47d562874..570476f9755d6 100644 --- a/src/catalog/src/durable.rs +++ b/src/catalog/src/durable.rs @@ -39,7 +39,7 @@ pub use crate::durable::objects::{ SourceReference, SourceReferences, StorageCollectionMetadata, SystemConfiguration, SystemObjectDescription, SystemObjectMapping, UnfinalizedShard, managed_cluster_replica_name, }; -pub use crate::durable::persist::shard_id; +pub use crate::durable::persist::{CatalogSnapshotReader, shard_id}; use crate::durable::persist::{Timestamp, UnopenedPersistCatalogState}; use crate::durable::transaction::TransactionBatch; pub use crate::durable::transaction::{DryRunTransaction, Transaction}; @@ -284,6 +284,16 @@ pub trait ReadOnlyDurableCatalogState: Debug + Send + Sync { async fn current_upper(&mut self) -> Timestamp; } +/// Owned durable input for independent catalog reconstruction, with no persist handles. +#[derive(Debug)] +pub struct CatalogSnapshot { + pub snapshot: Snapshot, + pub updates: Vec, + pub upper: Timestamp, + pub deployment_generation: u64, + pub is_bootstrap_complete: bool, +} + /// A read-write API for the durable catalog state. #[async_trait] #[allow(mismatched_lifetime_syntaxes)] diff --git a/src/catalog/src/durable/metrics.rs b/src/catalog/src/durable/metrics.rs index 8a583e66351a1..6946affce2916 100644 --- a/src/catalog/src/durable/metrics.rs +++ b/src/catalog/src/durable/metrics.rs @@ -12,7 +12,7 @@ use mz_ore::metric; use mz_ore::metrics::{IntCounter, MetricsRegistry}; use mz_ore::stats::histogram_seconds_buckets; -use prometheus::{Histogram, IntGauge, IntGaugeVec}; +use prometheus::{Histogram, IntCounterVec, IntGauge, IntGaugeVec}; #[derive(Debug, Clone)] pub struct Metrics { @@ -27,12 +27,34 @@ pub struct Metrics { pub allocate_id_seconds: Histogram, pub snapshot_consolidations: IntCounter, pub snapshot_max_entries: IntGauge, + /// Acknowledged row traffic through the catalog compare-and-append path, including bootstrap. + /// Each pair counts updates and packed row bytes, ordered by compaction bound, maintained + /// read requirement, and other kind. Each submitted row counts once regardless of its diff. + pub(crate) committed_row_traffic: [(IntCounter, IntCounter); 3], } impl Metrics { /// Returns a new [Metrics] instance connected to the given registry. pub fn new(registry: &MetricsRegistry) -> Self { + let updates: IntCounterVec = registry.register(metric!( + name: "mz_catalog_committed_updates", + help: "Number of catalog row updates in acknowledged compare-and-appends, including retractions and bootstrap writes.", + var_labels: ["kind"], + )); + let bytes: IntCounterVec = registry.register(metric!( + name: "mz_catalog_committed_update_bytes", + help: "Packed SourceData row bytes in acknowledged catalog compare-and-appends, including retractions and bootstrap writes. Excludes timestamps, diffs, Persist encoding, compression, and network framing. Not blob or network bytes.", + var_labels: ["kind"], + )); + let committed_row_traffic = ["compaction_bound", "maintained_read_requirement", "other"] + .map(|kind| { + ( + updates.with_label_values(&[kind]), + bytes.with_label_values(&[kind]), + ) + }); Self { + committed_row_traffic, transactions_started: registry.register(metric!( name: "mz_catalog_transactions_started", help: "Total number of started transactions.", diff --git a/src/catalog/src/durable/objects.rs b/src/catalog/src/durable/objects.rs index 607f1e59fe3a4..f8df59e6b69a5 100644 --- a/src/catalog/src/durable/objects.rs +++ b/src/catalog/src/durable/objects.rs @@ -36,6 +36,7 @@ use mz_audit_log::VersionedEvent; use mz_controller::clusters::ReplicaLogging; use mz_controller_types::{ClusterId, ReplicaId}; use mz_persist_types::ShardId; +use mz_proto::RustType; use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem}; use mz_repr::network_policy_id::NetworkPolicyId; use mz_repr::role_id::RoleId; @@ -1397,9 +1398,99 @@ impl DurableType for UnfinalizedShard { // Structs used internally to represent on-disk state. +/// Structurally shared, derived lookups for changed-record read protection validation. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct ReadProtectionIndex { + pub(crate) index_identity_counts: imbl::OrdMap, + pub(crate) active_consumers: imbl::OrdMap>, +} + +impl ReadProtectionIndex { + /// Applies one committed typed update, exactly once, including during replay. + /// The projection is queryable after all updates at a timestamp have been applied. + pub(crate) fn apply_update(&mut self, update: &state_update::StateUpdate) { + use state_update::StateUpdateKind; + let diff = update.diff.into_inner(); + match &update.kind { + StateUpdateKind::Item(_, value) => { + let value = ItemValue::from_proto(value.clone()).expect("valid item"); + if value.item_type() == CatalogItemType::Index { + self.update_identity(value.global_id, diff); + } + } + StateUpdateKind::SystemObjectMapping(key, value) => { + let key = GidMappingKey::from_proto(key.clone()).expect("valid mapping key"); + if key.object_type == CatalogItemType::Index { + let value = GidMappingValue::from_proto(value.clone()).expect("valid mapping"); + self.update_identity(value.global_id.into(), diff); + } + } + StateUpdateKind::IntrospectionSourceIndex(_, value) => { + let value = ClusterIntrospectionSourceIndexValue::from_proto(value.clone()) + .expect("valid introspection index"); + self.update_identity(value.global_id.into(), diff); + } + StateUpdateKind::MaintainedReadRequirement(key, value) => { + let key = MaintainedReadRequirementKey::from_proto(key.clone()) + .expect("valid requirement key"); + let value = MaintainedReadRequirementValue::from_proto(value.clone()) + .expect("valid requirement"); + self.update_requirement(key.id, &value, diff); + } + _ => {} + } + } + + pub(crate) fn update_identity(&mut self, id: GlobalId, diff: i64) { + let count = self.index_identity_counts.get(&id).copied().unwrap_or(0) + diff; + if count == 0 { + self.index_identity_counts.remove(&id); + } else { + self.index_identity_counts.insert(id, count); + } + } + + pub(crate) fn update_requirement( + &mut self, + owner: GlobalId, + value: &MaintainedReadRequirementValue, + diff: i64, + ) { + if value.frontier.is_none() { + return; + } + for input in &value.inputs { + let mut owners = self + .active_consumers + .get(input) + .cloned() + .unwrap_or_default(); + // Counts tolerate either ordering of replacement insertions and retractions. + let count = owners.get(&owner).copied().unwrap_or(0) + diff; + if count == 0 { + owners.remove(&owner); + } else { + owners.insert(owner, count); + } + if owners.is_empty() { + self.active_consumers.remove(input); + } else { + self.active_consumers.insert(*input, owners); + } + } + } + + pub(crate) fn contains_index(&self, id: GlobalId) -> bool { + self.index_identity_counts + .get(&id) + .is_some_and(|count| *count > 0) + } +} + /// A snapshot of the current on-disk state. #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Snapshot { + pub read_protection_index: ReadProtectionIndex, pub databases: BTreeMap, pub schemas: BTreeMap, pub roles: BTreeMap, diff --git a/src/catalog/src/durable/objects/state_update.rs b/src/catalog/src/durable/objects/state_update.rs index fd50063049475..6d57c0ebcb325 100644 --- a/src/catalog/src/durable/objects/state_update.rs +++ b/src/catalog/src/durable/objects/state_update.rs @@ -362,7 +362,7 @@ impl StateUpdateKindJson { serde_json::from_value::(serde_value) } - fn kind(&self) -> &str { + pub(crate) fn kind(&self) -> &str { let row = self.0.row(); let mut iter = row.unpack_first().unwrap_map().iter(); let datum = iter diff --git a/src/catalog/src/durable/persist.rs b/src/catalog/src/durable/persist.rs index 5b825fd3cb78b..219a824444a1b 100644 --- a/src/catalog/src/durable/persist.rs +++ b/src/catalog/src/durable/persist.rs @@ -58,7 +58,7 @@ use crate::durable::objects::state_update::{ IntoStateUpdateKindJson, StateUpdate, StateUpdateKind, StateUpdateKindJson, TryIntoStateUpdateKind, }; -use crate::durable::objects::{AuditLogKey, FenceToken, Snapshot}; +use crate::durable::objects::{AuditLogKey, FenceToken, ReadProtectionIndex, Snapshot}; use crate::durable::transaction::TransactionBatch; use crate::durable::upgrade::upgrade; use crate::durable::{ @@ -94,7 +94,7 @@ pub const _BUILTIN_MIGRATION_SEED: usize = 3; /// Legacy seed used to generate the persist shard ID for the expression cache. DO NOT REUSE. pub const _EXPRESSION_CACHE_SEED: usize = 4; -/// Durable catalog mode that dictates the effect of mutable operations. +/// Durable catalog read and write behavior. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub(crate) enum Mode { /// Mutable operations are prohibited. @@ -339,7 +339,7 @@ pub(crate) trait ApplyUpdate { /// respectively. #[derive(Debug)] pub(crate) struct PersistHandle> { - /// The [`Mode`] that this catalog was opened in. + /// The catalog's read and write mode. pub(crate) mode: Mode, /// Since handle to control compaction. since_handle: SinceHandle, @@ -400,18 +400,31 @@ impl> PersistHandle { updates: Vec<(S, Diff)>, commit_ts: Timestamp, ) -> Result { + let mut traffic = [(0u64, 0u64); 3]; let updates = updates.into_iter().map(|(kind, diff)| { let kind: StateUpdateKindJson = kind.into(); - ( - (Into::::into(kind), ()), - commit_ts, - diff.into_inner(), - ) + let index = match kind.kind() { + "CollectionCompactionBound" => 0, + "MaintainedReadRequirement" => 1, + _ => 2, + }; + let row = SourceData::from(kind); + traffic[index].0 += 1; + traffic[index].1 += u64::try_from(row.0.as_ref().expect("catalog row").byte_len()) + .expect("row length fits in u64"); + ((row, ()), commit_ts, diff.into_inner()) }); let next_upper = commit_ts.step_forward(); // Upper mismatches are classified by the commit and advance callers. self.compare_and_append_inner(updates, next_upper).await?; + // Publication succeeded even if the subsequent local sync fails. + for ((updates, bytes), (update_count, byte_count)) in + self.metrics.committed_row_traffic.iter().zip_eq(traffic) + { + updates.inc_by(update_count); + bytes.inc_by(byte_count); + } self.sync(next_upper).await?; Ok(next_upper) } @@ -533,16 +546,27 @@ impl> PersistHandle { /// Returns an error if this instance has been fenced out. #[mz_ore::instrument(level = "debug")] pub(crate) async fn sync(&mut self, target_upper: Timestamp) -> Result<(), FenceError> { + self.sync_with_limit(target_upper, false).await + } + + fn sync_with_limit( + &mut self, + target_upper: Timestamp, + terminal: bool, + ) -> impl std::future::Future> + '_ { self.metrics.syncs.inc(); let histogram = self.metrics.sync_latency_seconds.clone(); - self.sync_inner(target_upper) + self.sync_inner(target_upper, terminal) .wall_time() .observe(histogram) - .await } #[mz_ore::instrument(level = "debug")] - async fn sync_inner(&mut self, target_upper: Timestamp) -> Result<(), FenceError> { + async fn sync_inner( + &mut self, + target_upper: Timestamp, + terminal: bool, + ) -> Result<(), FenceError> { self.fenceable_token.validate()?; // Savepoint catalogs do not yet know how to update themselves in response to concurrent @@ -565,6 +589,9 @@ impl> PersistHandle { ListenEvent::Progress(upper) => { debug!("synced up to {upper:?}"); self.upper = antichain_to_timestamp(upper); + if terminal { + self.upper = self.upper.min(target_upper); + } // Attempt to apply updates in batches of a single timestamp. If another // catalog wrote a fence token at one timestamp and then updates in a new // format at a later timestamp, then we want to apply the fence token @@ -585,10 +612,18 @@ impl> PersistHandle { self.apply_updates(updates)?; self.maybe_consolidate(); } + if terminal && self.upper == target_upper { + break; + } } ListenEvent::Updates(batch_updates) => { for update in batch_updates { let update: StateUpdate = update.into(); + // Terminal synchronization discards the suffix of this continuous + // listen, since the consumed handle must never follow it again. + if terminal && update.ts >= target_upper { + continue; + } updates.entry(update.ts).or_default().push(update); } } @@ -750,7 +785,7 @@ impl> PersistHandle { } } -impl> PersistHandle { +impl PersistCatalogState { /// Execute and return the results of `f` on the current catalog snapshot. /// /// Will return an error if the catalog has been fenced out. @@ -758,6 +793,11 @@ impl> PersistHandle { &mut self, f: impl FnOnce(Snapshot) -> Result, ) -> Result { + self.sync_to_current_upper().await?; + f(self.cached_snapshot()?) + } + + fn cached_snapshot(&self) -> Result { fn apply(map: &mut BTreeMap, key: &K, value: &V, diff: Diff) where K: Ord + Clone, @@ -781,9 +821,10 @@ impl> PersistHandle { } } - self.with_trace(|trace| { + { let mut snapshot = Snapshot::empty(); - for (kind, ts, diff) in trace { + snapshot.read_protection_index = self.update_applier.read_protection_index.clone(); + for (kind, ts, diff) in &self.snapshot { let diff = *diff; if diff != Diff::ONE && diff != Diff::MINUS_ONE { panic!("invalid update in consolidated trace: ({kind:?}, {ts:?}, {diff:?})"); @@ -883,9 +924,8 @@ impl> PersistHandle { } } } - f(snapshot) - }) - .await + Ok(snapshot) + } } /// Generates an iterator of [`StateUpdate`] that contain all updates to the catalog @@ -1145,7 +1185,7 @@ impl UnopenedPersistCatalogState { mode: Mode, initial_ts: Timestamp, bootstrap_args: &BootstrapArgs, - ) -> Result, CatalogError> { + ) -> Result, CatalogError> { // It would be nice to use `initial_ts` here, but it comes from the system clock, not the // timestamp oracle. let mut commit_ts = self.upper; @@ -1473,9 +1513,10 @@ impl OpenableDurableCatalogState for UnopenedPersistCatalogState { initial_ts: Timestamp, bootstrap_args: &BootstrapArgs, ) -> Result, CatalogError> { - self.open_inner(Mode::Savepoint, initial_ts, bootstrap_args) + Ok(self + .open_inner(Mode::Savepoint, initial_ts, bootstrap_args) .boxed() - .await + .await?) } #[mz_ore::instrument] @@ -1483,9 +1524,10 @@ impl OpenableDurableCatalogState for UnopenedPersistCatalogState { mut self: Box, bootstrap_args: &BootstrapArgs, ) -> Result, CatalogError> { - self.open_inner(Mode::Readonly, EpochMillis::MIN.into(), bootstrap_args) + Ok(self + .open_inner(Mode::Readonly, EpochMillis::MIN.into(), bootstrap_args) .boxed() - .await + .await?) } #[mz_ore::instrument] @@ -1494,9 +1536,10 @@ impl OpenableDurableCatalogState for UnopenedPersistCatalogState { initial_ts: Timestamp, bootstrap_args: &BootstrapArgs, ) -> Result, CatalogError> { - self.open_inner(Mode::Writable, initial_ts, bootstrap_args) + Ok(self + .open_inner(Mode::Writable, initial_ts, bootstrap_args) .boxed() - .await + .await?) } #[mz_ore::instrument(level = "debug")] @@ -1612,12 +1655,14 @@ impl OpenableDurableCatalogState for UnopenedPersistCatalogState { struct CatalogStateInner { /// A trace of all catalog updates that can be consumed by some higher layer. updates: VecDeque, + read_protection_index: ReadProtectionIndex, } impl CatalogStateInner { fn new() -> CatalogStateInner { CatalogStateInner { updates: VecDeque::new(), + read_protection_index: ReadProtectionIndex::default(), } } } @@ -1629,6 +1674,7 @@ impl ApplyUpdate for CatalogStateInner { current_fence_token: &mut FenceableToken, metrics: &Arc, ) -> Result>, FenceError> { + self.read_protection_index.apply_update(&update); if let Some(collection_type) = update.kind.collection_type() { metrics .collection_entries @@ -1669,6 +1715,75 @@ impl ApplyUpdate for CatalogStateInner { /// so that it can expire its leases. If/when rust gets AsyncDrop, this will be done automatically. type PersistCatalogState = PersistHandle; +/// An independent readonly acquisition consumed by exact-prefix reconstruction. +#[derive(Debug)] +pub struct CatalogSnapshotReader { + state: Box, +} + +impl CatalogSnapshotReader { + /// Opens an ordinary readonly catalog without a deployment generation override. + pub async fn open( + persist_client: PersistClient, + organization_id: Uuid, + version: semver::Version, + bootstrap_args: &BootstrapArgs, + ) -> Result { + let state = UnopenedPersistCatalogState::new( + persist_client, + organization_id, + version, + None, + Arc::new(Metrics::new(&mz_ore::metrics::MetricsRegistry::new())), + ) + .await? + .open_inner(Mode::Readonly, EpochMillis::MIN.into(), bootstrap_args) + .await?; + Ok(Self { state }) + } + + /// Consumes the acquisition at exactly `exclusive_upper`, releasing resources on success or error. + /// + /// Open before capturing memory. The prefix excludes updates at the upper and later, + /// including later fences. Backwards requests and observed fences are errors. + pub async fn into_snapshot_at( + self, + exclusive_upper: Timestamp, + ) -> Result { + let mut state = self.state; + let result = async { + state.fenceable_token.validate()?; + if exclusive_upper < state.upper { + return Err(DurableCatalogError::Internal(format!( + "catalog prefix {exclusive_upper} predates acquisition upper {}", + state.upper, + )) + .into()); + } + state.sync_with_limit(exclusive_upper, true).await?; + Ok(crate::durable::CatalogSnapshot { + snapshot: state.cached_snapshot()?, + updates: state.update_applier.updates.drain(..).collect(), + upper: exclusive_upper, + deployment_generation: state + .fenceable_token + .token() + .expect("opened catalog has a fence token") + .deploy_generation, + is_bootstrap_complete: state.bootstrap_complete, + }) + } + .await; + state.expire().await; + result + } + + /// Releases the acquisition without extracting a snapshot. + pub async fn expire(self) { + self.state.expire().await; + } +} + impl PersistHandle { /// Creates a transaction without validating pending catalog updates. async fn transaction_unchecked(&mut self) -> Result, CatalogError> { @@ -1887,7 +2002,7 @@ impl DurableCatalogState for PersistCatalogState { // If the transaction is empty then we don't error, even in read-only mode. // This is mostly for legacy reasons (i.e. with enough elbow grease this // behavior can be changed without breaking any fundamental assumptions). - if catalog.mode == Mode::Readonly { + if catalog.is_read_only() { let updates: Vec<_> = StateUpdate::from_txn_batch(txn_batch).collect(); if !updates.is_empty() { let collection_types: Vec<_> = updates diff --git a/src/catalog/src/durable/transaction.rs b/src/catalog/src/durable/transaction.rs index f2fd8c32149d4..5aad91daf572f 100644 --- a/src/catalog/src/durable/transaction.rs +++ b/src/catalog/src/durable/transaction.rs @@ -52,18 +52,19 @@ use crate::durable::objects::{ AuditLogKey, Cluster, ClusterConfig, ClusterIntrospectionSourceIndexKey, ClusterIntrospectionSourceIndexValue, ClusterKey, ClusterReplica, ClusterReplicaKey, ClusterReplicaValue, ClusterSystemConfiguration, ClusterSystemConfigurationKey, - ClusterSystemConfigurationValue, ClusterValue, CollectionCompactionBoundKey, - CollectionCompactionBoundValue, CommentKey, CommentValue, Config, ConfigKey, ConfigValue, - Database, DatabaseKey, DatabaseValue, DefaultPrivilegesKey, DefaultPrivilegesValue, - DurableType, GidMappingKey, GidMappingValue, IdAllocKey, IdAllocValue, - IntrospectionSourceIndex, Item, ItemKey, ItemValue, MaintainedReadRequirementKey, - MaintainedReadRequirementValue, NetworkPolicyKey, NetworkPolicyValue, ReplicaConfig, - ReplicaSystemConfiguration, ReplicaSystemConfigurationKey, ReplicaSystemConfigurationValue, - Role, RoleKey, RoleValue, Schema, SchemaKey, SchemaValue, ServerConfigurationKey, - ServerConfigurationValue, SettingKey, SettingValue, SourceReference, SourceReferencesKey, - SourceReferencesValue, StorageCollectionMetadataKey, StorageCollectionMetadataValue, - SystemObjectDescription, SystemObjectMapping, SystemPrivilegesKey, SystemPrivilegesValue, - TxnWalShardValue, UnfinalizedShardKey, + ClusterSystemConfigurationValue, ClusterValue, CollectionCompactionBound, + CollectionCompactionBoundKey, CollectionCompactionBoundValue, CommentKey, CommentValue, Config, + ConfigKey, ConfigValue, Database, DatabaseKey, DatabaseValue, DefaultPrivilegesKey, + DefaultPrivilegesValue, DurableType, GidMappingKey, GidMappingValue, IdAllocKey, IdAllocValue, + IntrospectionSourceIndex, Item, ItemKey, ItemValue, MaintainedReadRequirement, + MaintainedReadRequirementKey, MaintainedReadRequirementValue, NetworkPolicyKey, + NetworkPolicyValue, ReadProtectionIndex, ReplicaConfig, ReplicaSystemConfiguration, + ReplicaSystemConfigurationKey, ReplicaSystemConfigurationValue, Role, RoleKey, RoleValue, + Schema, SchemaKey, SchemaValue, ServerConfigurationKey, ServerConfigurationValue, SettingKey, + SettingValue, SourceReference, SourceReferencesKey, SourceReferencesValue, + StorageCollectionMetadataKey, StorageCollectionMetadataValue, SystemObjectDescription, + SystemObjectMapping, SystemPrivilegesKey, SystemPrivilegesValue, TxnWalShardValue, + UnfinalizedShardKey, }; use crate::durable::{ AUDIT_LOG_ID_ALLOC_KEY, BUILTIN_MIGRATION_SHARD_KEY, CATALOG_CONTENT_VERSION_KEY, CatalogError, @@ -87,7 +88,10 @@ struct CommitCapability; pub struct Transaction<'a> { #[derivative(Debug = "ignore")] #[derivative(PartialEq = "ignore")] - durable_catalog: &'a mut dyn DurableCatalogState, + durable_catalog: Option<&'a mut dyn DurableCatalogState>, + is_bootstrap_complete: bool, + is_savepoint: bool, + read_protection_index: ReadProtectionIndex, databases: TableTransaction, schemas: TableTransaction, items: TableTransaction, @@ -156,10 +160,45 @@ impl<'a> DryRunTransaction<'a> { } } +impl DryRunTransaction<'static> { + /// Constructs non-committable state from an owned snapshot and its opening context. + pub fn from_snapshot( + snapshot: Snapshot, + upper: mz_repr::Timestamp, + is_bootstrap_complete: bool, + is_savepoint: bool, + ) -> Result { + Ok(Self { + transaction: Transaction::from_snapshot( + snapshot, + upper, + is_bootstrap_complete, + is_savepoint, + )?, + }) + } +} + impl<'a> Transaction<'a> { pub(super) fn new( durable_catalog: &'a mut dyn DurableCatalogState, + snapshot: Snapshot, + upper: mz_repr::Timestamp, + ) -> Result, CatalogError> { + let mut transaction = Self::from_snapshot( + snapshot, + upper, + durable_catalog.is_bootstrap_complete(), + durable_catalog.is_savepoint(), + )?; + transaction.durable_catalog = Some(durable_catalog); + transaction.commit_capability = Some(CommitCapability); + Ok(transaction) + } + + fn from_snapshot( Snapshot { + read_protection_index, databases, schemas, roles, @@ -187,6 +226,8 @@ impl<'a> Transaction<'a> { txn_wal_shard, }: Snapshot, upper: mz_repr::Timestamp, + is_bootstrap_complete: bool, + is_savepoint: bool, ) -> Result, CatalogError> { // For these collections uniqueness is plain equality of the name fields, so the same // predicate answers both "do these two conflict?" and "did this update keep the same key?". @@ -202,7 +243,10 @@ impl<'a> Transaction<'a> { |a, b| a.cluster_id == b.cluster_id && a.name == b.name; Ok(Transaction { - durable_catalog, + durable_catalog: None, + is_bootstrap_complete, + is_savepoint, + read_protection_index, databases: TableTransaction::new_with_uniqueness_fn( databases, database_unique_fn, @@ -280,7 +324,7 @@ impl<'a> Transaction<'a> { audit_log_updates: Vec::new(), upper, op_id: 0, - commit_capability: Some(CommitCapability), + commit_capability: None, }) } @@ -792,24 +836,23 @@ impl<'a> Transaction<'a> { extra_versions: BTreeMap, ephemeral_owner_session: Option, ) -> Result<(), CatalogError> { - match self.items.insert( - ItemKey { id }, - ItemValue { - schema_id, - name: item_name.to_string(), - create_sql, - owner_id, - privileges, - oid, - global_id, - extra_versions, - ephemeral_owner_session, - }, - self.op_id, - ) { - Ok(_) => Ok(()), - Err(_) => Err(SqlCatalogError::ItemAlreadyExists(id, item_name.to_owned()).into()), - } + let value = ItemValue { + schema_id, + name: item_name.to_string(), + create_sql, + owner_id, + privileges, + oid, + global_id, + extra_versions, + ephemeral_owner_session, + }; + self.items + .insert(ItemKey { id }, value, self.op_id) + .map_err(|_| { + CatalogError::from(SqlCatalogError::ItemAlreadyExists(id, item_name.to_owned())) + })?; + Ok(()) } /// Removes every item owned by an ephemeral session from the transaction, @@ -909,7 +952,7 @@ impl<'a> Transaction<'a> { amount: u64, ) -> Result, CatalogError> { assert!( - key != SYSTEM_ITEM_ALLOC_KEY || !self.durable_catalog.is_bootstrap_complete(), + key != SYSTEM_ITEM_ALLOC_KEY || !self.is_bootstrap_complete, "system item IDs cannot be allocated outside of bootstrap" ); @@ -941,7 +984,7 @@ impl<'a> Transaction<'a> { amount: u64, ) -> Result, CatalogError> { assert!( - !self.durable_catalog.is_bootstrap_complete(), + !self.is_bootstrap_complete, "we can only allocate system item IDs during bootstrap" ); Ok(self @@ -1190,6 +1233,7 @@ impl<'a> Transaction<'a> { /// accumulated `CatalogState`. pub fn current_snapshot(&self) -> Snapshot { Snapshot { + read_protection_index: self.current_read_protection_index(), databases: self.databases.current_items_proto(), schemas: self.schemas.current_items_proto(), roles: self.roles.current_items_proto(), @@ -1533,7 +1577,8 @@ impl<'a> Transaction<'a> { } let ks: Vec<_> = ids.clone().into_iter().map(|id| ItemKey { id }).collect(); - let n = self.items.delete_by_keys(ks, self.op_id).len(); + let deleted = self.items.delete_by_keys(ks, self.op_id); + let n = deleted.len(); if n == ids.len() { Ok(()) } else { @@ -1566,7 +1611,8 @@ impl<'a> Transaction<'a> { object_name: desc.object_name, }) .collect(); - let n = self.system_gid_mapping.delete_by_keys(ks, self.op_id).len(); + let deleted = self.system_gid_mapping.delete_by_keys(ks, self.op_id); + let n = deleted.len(); if n == descriptions.len() { Ok(()) @@ -1610,10 +1656,8 @@ impl<'a> Transaction<'a> { .into_iter() .map(|(cluster_id, name)| ClusterIntrospectionSourceIndexKey { cluster_id, name }) .collect(); - let n = self - .introspection_sources - .delete_by_keys(ks, self.op_id) - .len(); + let deleted = self.introspection_sources.delete_by_keys(ks, self.op_id); + let n = deleted.len(); if n == introspection_source_indexes.len() { Ok(()) } else { @@ -1637,9 +1681,10 @@ impl<'a> Transaction<'a> { /// Runtime is linear with respect to the total number of items in the catalog. /// DO NOT call this function in a loop, use [`Self::update_items`] instead. pub fn update_item(&mut self, id: CatalogItemId, item: Item) -> Result<(), CatalogError> { - let updated = - self.items - .update_by_key(ItemKey { id }, item.into_key_value().1, self.op_id)?; + let value = item.into_key_value().1; + let updated = self + .items + .update_by_key(ItemKey { id }, value, self.op_id)?; if updated { Ok(()) } else { @@ -1664,7 +1709,6 @@ impl<'a> Transaction<'a> { let update_ids: BTreeSet<_> = items.keys().cloned().collect(); let kvs: Vec<_> = items - .clone() .into_iter() .map(|(id, item)| (ItemKey { id }, item.into_key_value().1)) .collect(); @@ -2086,7 +2130,7 @@ impl<'a> Transaction<'a> { return Ok(()); } - let mappings = mappings + let mappings: BTreeMap<_, _> = mappings .into_iter() .map(DurableType::into_key_value) .map(|(k, v)| (k, Some(v))) @@ -2569,6 +2613,9 @@ impl<'a> Transaction<'a> { let Transaction { durable_catalog: _, + is_bootstrap_complete: _, + is_savepoint: _, + read_protection_index: _, databases, schemas, items, @@ -2723,7 +2770,7 @@ impl<'a> Transaction<'a> { } pub fn is_savepoint(&self) -> bool { - self.durable_catalog.is_savepoint() + self.is_savepoint } fn commit_op(&mut self) { @@ -2738,11 +2785,12 @@ impl<'a> Transaction<'a> { self.upper } - /// Stages a storage collection's compaction permission. + /// Stages a storage collection's or index's compaction permission. /// - /// A first bound must accompany collection birth. The caller must secure actual - /// readability at that bound, including when reusing a shard. Validation checks - /// committed permission and read requirements, not physical storage frontiers. + /// A first storage bound must accompany collection birth. The caller must + /// secure actual readability at that bound, including when reusing a shard. + /// Validation checks committed permission and read requirements, not physical + /// frontiers. Protected indexes may publish their first bound after birth. /// `None` denotes the empty frontier, not an ungoverned collection. pub fn set_collection_compaction_bound( &mut self, @@ -2775,86 +2823,258 @@ impl<'a> Transaction<'a> { Ok(()) } - /// Validates the final storage-backed read protection state without committing it. - /// Call before entering a commit path that treats errors as fatal. - pub fn validate_read_protection(&self) -> Result<(), CatalogError> { - if self.storage_collection_metadata.pending.is_empty() - && self.collection_compaction_bounds.pending.is_empty() - && self.maintained_read_requirements.pending.is_empty() - { - return Ok(()); + /// Stages protection records in bulk, checked against the final state at commit. + /// + /// The caller must satisfy the readability and recovery contracts of + /// [`Self::set_collection_compaction_bound`] and [`Self::set_maintained_read_requirement`]. + /// For repeated IDs in either vector, the last record wins. + pub fn set_read_protection( + &mut self, + requirements: Vec, + bounds: Vec, + ) -> Result<(), CatalogError> { + self.maintained_read_requirements.set_many( + requirements + .into_iter() + .map(|requirement| { + let (key, value) = requirement.into_key_value(); + (key, Some(value)) + }) + .collect(), + self.op_id, + )?; + self.collection_compaction_bounds.set_many( + bounds + .into_iter() + .map(|bound| { + let (key, value) = bound.into_key_value(); + (key, Some(value)) + }) + .collect(), + self.op_id, + )?; + Ok(()) + } + + fn pending_index_identities(&self) -> (ReadProtectionIndex, BTreeSet) { + let mut index = self.read_protection_index.clone(); + let mut touched = BTreeSet::new(); + // Only pending owner keys are visited. Include intermediate identities for + // retirement, but apply only initial/final identities to the projection. + macro_rules! project { + ($table:expr, $identity:expr) => { + for (key, updates) in &$table.pending { + let identity = $identity; + for value in $table + .initial + .get(key) + .into_iter() + .chain(updates.iter().map(|u| &u.value)) + { + if let Some(id) = identity(key, value) { + touched.insert(id); + } + } + if let Some(id) = $table.initial.get(key).and_then(|v| identity(key, v)) { + index.update_identity(id, -1); + } + if let Some(id) = $table.get(key).and_then(|v| identity(key, v)) { + index.update_identity(id, 1); + } + } + }; } + project!(self.items, |_: &ItemKey, v: &ItemValue| { + (v.item_type() == CatalogItemType::Index).then_some(v.global_id) + }); + project!( + self.system_gid_mapping, + |k: &GidMappingKey, v: &GidMappingValue| { + (k.object_type == CatalogItemType::Index).then_some(GlobalId::from(v.global_id)) + } + ); + project!( + self.introspection_sources, + |_: &ClusterIntrospectionSourceIndexKey, v: &ClusterIntrospectionSourceIndexValue| { + Some(GlobalId::from(v.global_id)) + } + ); + (index, touched) + } + + fn current_read_protection_index(&self) -> ReadProtectionIndex { + let (mut index, _) = self.pending_index_identities(); + for key in self.maintained_read_requirements.pending.keys() { + if let Some(value) = self.maintained_read_requirements.initial.get(key) { + index.update_requirement(key.id, value, -1); + } + if let Some(value) = self.maintained_read_requirements.get(key) { + index.update_requirement(key.id, value, 1); + } + } + index + } + + /// Retires sparse bounds for index identities absent from the final batch. + /// + /// Call after staging all lifetime changes, before extracting adapter updates, + /// exporting a dry-run snapshot, or validating/committing the transaction. + /// Repeated calls are safe without intervening lifetime changes. Storage bound + /// cleanup belongs to `delete_collection_metadata`. + pub fn finalize_index_compaction_bounds(&mut self) { + let (index, touched) = self.pending_index_identities(); + let retired = touched + .into_iter() + .filter(|id| { + let key = StorageCollectionMetadataKey { id: *id }; + !index.contains_index(*id) + && self.storage_collection_metadata.get(&key).is_none() + && !self.storage_collection_metadata.initial.contains_key(&key) + }) + .map(|id| CollectionCompactionBoundKey { id }) + .collect::>(); + self.collection_compaction_bounds + .delete_by_keys(retired, self.op_id); + } + + /// Validates changed protection records and affected unchanged consumers. + /// + /// Requires a valid initial snapshot and its matching derived projection. + /// Call after finalization and before entering a commit path that treats errors as fatal. + pub fn validate_read_protection(&self) -> Result<(), CatalogError> { let invalid = |message| CatalogError::from(DurableCatalogError::InvalidReadProtection(message)); - let storage = self.storage_collection_metadata.items(); - let bounds = self.collection_compaction_bounds.items(); - let requirements = self.maintained_read_requirements.items(); - - // Compare against the initial snapshot, not the preceding setter, so a - // delete/reinsert of a surviving GlobalId cannot erase its bound history. - for (key, initial) in &self.collection_compaction_bounds.initial { - if !storage.contains_key(&StorageCollectionMetadataKey { id: key.id }) { - continue; + let (indexes, mut touched) = self.pending_index_identities(); + touched.extend( + self.storage_collection_metadata + .pending + .keys() + .map(|k| k.id), + ); + touched.extend( + self.collection_compaction_bounds + .pending + .keys() + .map(|k| k.id), + ); + let storage_exists = |id| { + self.storage_collection_metadata + .get(&StorageCollectionMetadataKey { id }) + .is_some() + }; + let owns_bound = |id| storage_exists(id) || indexes.contains_index(id); + let protected_initially = self + .configs + .initial + .get(&ConfigKey { + key: "catalog_read_protection_enabled".to_string(), + }) + .is_some_and(|v| v.value == 1); + + // Baseline comparison preserves history across delete/reinsert and ID swaps. + for id in &touched { + let key = CollectionCompactionBoundKey { id: *id }; + let initial = self.collection_compaction_bounds.initial.get(&key); + let bound = self.collection_compaction_bounds.get(&key); + if owns_bound(*id) { + if let Some(initial) = initial { + let Some(bound) = bound else { + return Err(invalid(format!( + "collection {id} lost its compaction bound" + ))); + }; + let monotonic = match (initial.frontier, bound.frontier) { + (_, None) => true, + (Some(initial), Some(final_frontier)) => initial <= final_frontier, + (None, Some(_)) => false, + }; + if !monotonic { + return Err(invalid(format!( + "collection {id} compaction bound regressed" + ))); + } + } } - let Some(bound) = bounds.get(key) else { - return Err(invalid(format!( - "collection {} lost its compaction bound", - key.id - ))); - }; - let monotonic = match (initial.frontier, bound.frontier) { - (_, None) => true, - (Some(initial), Some(final_frontier)) => initial <= final_frontier, - (None, Some(_)) => false, - }; - if !monotonic { - return Err(invalid(format!( - "collection {} compaction bound regressed", - key.id - ))); + if bound.is_some() { + if initial.is_none() + && (self + .storage_collection_metadata + .initial + .contains_key(&StorageCollectionMetadataKey { id: *id }) + || (self.read_protection_index.contains_index(*id) && !protected_initially)) + { + return Err(invalid(format!( + "cannot introduce a compaction bound for existing collection {id}" + ))); + } + if !owns_bound(*id) { + return Err(invalid(format!( + "compaction bound owner {id} has neither storage metadata nor an index identity" + ))); + } } } - for key in bounds.keys() { - // An already-live ungoverned collection has no durable readability - // guarantee and storage cannot adopt a bound without conversion. - if self - .storage_collection_metadata - .initial - .contains_key(&StorageCollectionMetadataKey { id: key.id }) - && !self.collection_compaction_bounds.initial.contains_key(key) - { - return Err(invalid(format!( - "cannot introduce a compaction bound for existing collection {}", - key.id - ))); + + let check_input = |owner, input, required| { + let readable = storage_exists(input) + && self + .collection_compaction_bounds + .get(&CollectionCompactionBoundKey { id: input }) + .and_then(|bound| bound.frontier) + .is_some_and(|bound| bound <= required); + if readable { + Ok(()) + } else { + Err(invalid(format!( + "read requirement {owner} needs input {input} readable at {required}" + ))) } - if !storage.contains_key(&StorageCollectionMetadataKey { id: key.id }) { + }; + let changed_requirements = self + .maintained_read_requirements + .pending + .keys() + .map(|k| k.id) + .chain( + self.storage_collection_metadata + .pending + .keys() + .map(|k| k.id), + ) + .collect::>(); + for id in &changed_requirements { + let Some(requirement) = self + .maintained_read_requirements + .get(&MaintainedReadRequirementKey { id: *id }) + else { + continue; + }; + if !storage_exists(*id) { return Err(invalid(format!( - "compaction bound owner {} has no storage metadata", - key.id + "read requirement owner {id} has no storage metadata" ))); } - } - for (key, requirement) in requirements { - if !storage.contains_key(&StorageCollectionMetadataKey { id: key.id }) { - return Err(invalid(format!( - "read requirement owner {} has no storage metadata", - key.id - ))); + if let Some(required) = requirement.frontier { + for input in &requirement.inputs { + check_input(*id, *input, required)?; + } } - let Some(required) = requirement.frontier else { - continue; - }; - for input in &requirement.inputs { - let readable = bounds - .get(&CollectionCompactionBoundKey { id: *input }) - .and_then(|bound| bound.frontier) - .is_some_and(|bound| bound <= required); - if !readable { - return Err(invalid(format!( - "read requirement {} needs input {input} readable at {required}", - key.id - ))); + } + for input in touched { + if let Some(consumers) = self.read_protection_index.active_consumers.get(&input) { + for (owner, _) in consumers { + if changed_requirements.contains(owner) { + continue; + } + let requirement = self + .maintained_read_requirements + .get(&MaintainedReadRequirementKey { id: *owner }) + .expect("reverse edge has an unchanged requirement"); + check_input( + *owner, + input, + requirement.frontier.expect("reverse edge is active"), + )?; } } } @@ -2871,13 +3091,16 @@ impl<'a> Transaction<'a> { /// Verifies that this process has not missed catalog content updates. pub(super) async fn ensure_not_out_of_sync(&mut self) -> Result<(), CatalogError> { self.durable_catalog + .as_mut() + .ok_or(DurableCatalogError::DryRunTransaction)? .ensure_not_out_of_sync(self.upper) .await } pub(crate) fn into_parts( - self, + mut self, ) -> Result<(TransactionBatch, &'a mut dyn DurableCatalogState), CatalogError> { + self.finalize_index_compaction_bounds(); self.validate_read_protection()?; let commit_capability = self .commit_capability @@ -2918,7 +3141,11 @@ impl<'a> Transaction<'a> { upper: self.upper, _commit_capability: commit_capability, }; - Ok((txn_batch, self.durable_catalog)) + Ok(( + txn_batch, + self.durable_catalog + .ok_or(DurableCatalogError::DryRunTransaction)?, + )) } /// Commits the storage transaction to durable storage. @@ -3031,8 +3258,9 @@ impl<'a> Transaction<'a> { /// after committing and only then apply the updates in-memory. While this removes assumptions /// about the caller in this method, in practice it results in duplicate work on every commit. #[mz_ore::instrument(level = "debug")] - pub async fn commit(self, commit_ts: mz_repr::Timestamp) -> Result<(), CatalogError> { + pub async fn commit(mut self, commit_ts: mz_repr::Timestamp) -> Result<(), CatalogError> { self.ensure_committable()?; + self.finalize_index_compaction_bounds(); let op_updates = self.get_op_updates(); assert!( op_updates.is_empty(), @@ -3107,22 +3335,26 @@ impl StorageTxn for Transaction<'_> { } fn delete_collection_metadata(&mut self, ids: BTreeSet) -> Vec<(GlobalId, ShardId)> { + let deleted = self.storage_collection_metadata.delete_by_keys( + ids.into_iter() + .map(|id| StorageCollectionMetadataKey { id }), + self.op_id, + ); + // Only actual storage owners authorize cleanup. An unknown owner must + // remain visible to protection validation. self.collection_compaction_bounds.delete_by_keys( - ids.iter() - .map(|id| CollectionCompactionBoundKey { id: *id }), + deleted + .iter() + .map(|(key, _)| CollectionCompactionBoundKey { id: key.id }), self.op_id, ); self.maintained_read_requirements.delete_by_keys( - ids.iter() - .map(|id| MaintainedReadRequirementKey { id: *id }), + deleted + .iter() + .map(|(key, _)| MaintainedReadRequirementKey { id: key.id }), self.op_id, ); - let ks: Vec<_> = ids - .into_iter() - .map(|id| StorageCollectionMetadataKey { id }) - .collect(); - self.storage_collection_metadata - .delete_by_keys(ks, self.op_id) + deleted .into_iter() .map( |( @@ -3726,9 +3958,9 @@ where /// /// Prefer using [`Self::update_by_key`] or [`Self::update_by_keys`], which generally have /// better performance. - fn update Option>( + fn update Option>( &mut self, - f: F, + mut f: F, ts: Timestamp, ) -> Result { let mut changed = Diff::ZERO; diff --git a/src/catalog/tests/compute-read-protection.rs b/src/catalog/tests/compute-read-protection.rs new file mode 100644 index 0000000000000..f9b58c62fa8ea --- /dev/null +++ b/src/catalog/tests/compute-read-protection.rs @@ -0,0 +1,750 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +#![recursion_limit = "256"] + +use std::collections::{BTreeMap, BTreeSet}; + +use mz_catalog::builtin::BUILTINS; +use mz_catalog::durable::objects::{ + ClusterConfig, ClusterVariant, CollectionCompactionBound, DurableType, SystemObjectDescription, + SystemObjectMapping, SystemObjectUniqueIdentifier, +}; +use mz_catalog::durable::{ + CatalogError, DurableCatalogError, Snapshot, TestCatalogStateBuilder, Transaction, + test_bootstrap_args, +}; +use mz_controller_types::ClusterId; +use mz_ore::now::SYSTEM_TIME; +use mz_persist_client::{PersistClient, ShardId}; +use mz_proto::RustType; +use mz_repr::role_id::RoleId; +use mz_repr::{CatalogItemId, GlobalId, Timestamp}; +use mz_sql::catalog::CatalogItemType; +use mz_sql::names::SchemaId; +use mz_storage_client::controller::StorageTxn; + +#[derive(Clone, Copy, Debug)] +enum Owner { + Ordinary, + Builtin, + Logging, +} + +const OWNERS: [Owner; 3] = [Owner::Ordinary, Owner::Builtin, Owner::Logging]; + +impl Owner { + fn id(self, n: u64) -> GlobalId { + match self { + Self::Ordinary => GlobalId::User(n), + Self::Builtin => GlobalId::System(n), + Self::Logging => GlobalId::IntrospectionSourceIndex(n), + } + } + + fn item_id(self, n: u64) -> CatalogItemId { + match self { + Self::Ordinary => CatalogItemId::User(n), + Self::Builtin => CatalogItemId::System(n), + Self::Logging => CatalogItemId::IntrospectionSourceIndex(n), + } + } + + fn mapping(self, n: u64) -> SystemObjectMapping { + SystemObjectMapping { + description: SystemObjectDescription { + schema_name: "mz_catalog".to_string(), + object_type: CatalogItemType::Index, + object_name: format!("governed_idx_{n}"), + }, + unique_identifier: SystemObjectUniqueIdentifier { + catalog_id: self.item_id(n), + global_id: self.id(n), + fingerprint: "index definition".to_string(), + }, + } + } + + fn insert(self, txn: &mut Transaction<'_>, n: u64) { + match self { + Self::Ordinary => txn.insert_item( + self.item_id(n), + 20000 + u32::try_from(n).unwrap(), + self.id(n), + SchemaId::User(1), + &format!("governed_idx_{n}"), + format!("CREATE INDEX governed_idx_{n} ON t (a)"), + RoleId::User(1), + Vec::new(), + BTreeMap::new(), + None, + ), + Self::Builtin => txn.set_system_object_mappings(vec![self.mapping(n)]), + Self::Logging => txn.insert_introspection_source_indexes( + vec![( + cluster_id(), + format!("log_{n}"), + self.item_id(n), + self.id(n), + )], + &Default::default(), + ), + } + .unwrap(); + } + + fn remove(self, txn: &mut Transaction<'_>, n: u64) { + match self { + Self::Ordinary => txn.remove_item(self.item_id(n)), + Self::Builtin => { + txn.remove_system_object_mappings(BTreeSet::from([self.mapping(n).description])) + } + Self::Logging => txn.remove_introspection_source_indexes(BTreeSet::from([( + cluster_id(), + format!("log_{n}"), + )])), + } + .unwrap(); + } +} + +fn cluster_id() -> ClusterId { + ClusterId::user(1000).unwrap() +} + +fn bounds(snapshot: Snapshot) -> BTreeMap> { + snapshot + .collection_compaction_bounds + .into_iter() + .map(|entry| { + let (key, value) = RustType::from_proto(entry).unwrap(); + let bound = CollectionCompactionBound::from_key_value(key, value); + (bound.id, bound.frontier) + }) + .collect() +} + +async fn commit(mut txn: Transaction<'_>) { + txn.finalize_index_compaction_bounds(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); +} + +async fn reject(mut txn: Transaction<'_>, message: &str) { + txn.finalize_index_compaction_bounds(); + match txn.validate_read_protection().unwrap_err() { + CatalogError::Durable(DurableCatalogError::InvalidReadProtection(detail)) => { + assert!(detail.contains(message), "{detail}"); + } + error => panic!("{error}"), + } + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + assert!(matches!( + txn.commit(ts).await, + Err(CatalogError::Durable( + DurableCatalogError::InvalidReadProtection(_) + )) + )); +} + +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn index_birth_bootstrap_and_drop() { + let builder = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation(); + let mut state = builder + .clone() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + + let mut txn = state.transaction().await.unwrap(); + for owner in OWNERS { + owner.insert(&mut txn, 1000); + } + assert!(bounds(txn.current_snapshot()).is_empty()); + txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) + .unwrap(); + txn.finalize_index_compaction_bounds(); + for owner in OWNERS { + owner.insert(&mut txn, 1001); + } + assert!(bounds(txn.current_snapshot()).is_empty()); + commit(txn).await; + Box::new(state).expire().await; + + let mut state = builder + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + assert!(bounds(state.snapshot().await.unwrap()).is_empty()); + let expected: BTreeMap<_, _> = OWNERS + .into_iter() + .flat_map(|owner| [1000, 1001].map(|n| (owner.id(n), Some(10.into())))) + .collect(); + let mut txn = state.transaction().await.unwrap(); + for (id, frontier) in &expected { + txn.set_collection_compaction_bound(*id, *frontier).unwrap(); + } + commit(txn).await; + assert_eq!(bounds(state.snapshot().await.unwrap()), expected); + let mut txn = state.transaction().await.unwrap(); + let mut item = txn.get_item(&Owner::Ordinary.item_id(1001)).unwrap(); + item.global_id = Owner::Ordinary.id(1002); + txn.update_items(BTreeMap::from([(item.id, item)])).unwrap(); + let mut mapping = Owner::Builtin.mapping(1001); + mapping.unique_identifier.global_id = Owner::Builtin.id(1002); + txn.update_system_object_mappings(BTreeMap::from([(Owner::Builtin.item_id(1001), mapping)])) + .unwrap(); + txn.update_introspection_source_index_gids(std::iter::once(( + cluster_id(), + std::iter::once(( + "log_1001".to_string(), + Owner::Logging.item_id(1001), + Owner::Logging.id(1002), + 22000, + )), + ))) + .unwrap(); + txn.finalize_index_compaction_bounds(); + for owner in OWNERS { + let current = bounds(txn.current_snapshot()); + assert!(!current.contains_key(&owner.id(1001))); + assert!(!current.contains_key(&owner.id(1002))); + } + commit(txn).await; + let mut txn = state.transaction().await.unwrap(); + for owner in OWNERS { + owner.remove(&mut txn, 1000); + } + txn.remove_items(&BTreeSet::from([Owner::Ordinary.item_id(1001)])) + .unwrap(); + Owner::Builtin.remove(&mut txn, 1001); + Owner::Logging.remove(&mut txn, 1001); + commit(txn).await; + assert!(bounds(state.snapshot().await.unwrap()).is_empty()); + Box::new(state).expire().await; +} + +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn surviving_index_permissions_are_monotonic() { + let mut state = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + let mut txn = state.transaction().await.unwrap(); + txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) + .unwrap(); + for owner in OWNERS { + owner.insert(&mut txn, 1000); + txn.set_collection_compaction_bound(owner.id(1000), Some(10.into())) + .unwrap(); + } + commit(txn).await; + let expected = bounds(state.snapshot().await.unwrap()); + + for owner in OWNERS { + let mut txn = state.transaction().await.unwrap(); + txn.set_collection_compaction_bound(owner.id(1000), Some(9.into())) + .unwrap(); + reject(txn, "regressed").await; + + let mut txn = state.transaction().await.unwrap(); + owner.remove(&mut txn, 1000); + owner.insert(&mut txn, 1000); + txn.set_collection_compaction_bound(owner.id(1000), Some(9.into())) + .unwrap(); + reject(txn, "regressed").await; + + let mut txn = state.transaction().await.unwrap(); + owner.remove(&mut txn, 1000); + txn.set_config("catalog_read_protection_enabled".to_string(), Some(0)) + .unwrap(); + owner.insert(&mut txn, 1000); + txn.finalize_index_compaction_bounds(); + txn.validate_read_protection().unwrap(); + assert_eq!(bounds(txn.current_snapshot()), expected); + drop(txn); + assert_eq!(bounds(state.snapshot().await.unwrap()), expected); + } + + let mut txn = state.transaction().await.unwrap(); + let mut mapping = Owner::Builtin.mapping(1000); + mapping.unique_identifier.fingerprint = "changed definition".to_string(); + txn.set_system_object_mappings(vec![mapping.clone()]) + .unwrap(); + txn.update_system_object_mappings(BTreeMap::from([(Owner::Builtin.item_id(1000), mapping)])) + .unwrap(); + txn.finalize_index_compaction_bounds(); + assert_eq!(bounds(txn.current_snapshot()), expected); + for owner in OWNERS { + owner.remove(&mut txn, 1000); + owner.insert(&mut txn, 1000); + txn.set_collection_compaction_bound(owner.id(1000), Some(20.into())) + .unwrap(); + } + commit(txn).await; + let mut txn = state.transaction().await.unwrap(); + for owner in OWNERS { + txn.set_collection_compaction_bound(owner.id(1000), None) + .unwrap(); + } + commit(txn).await; + for owner in OWNERS { + let mut txn = state.transaction().await.unwrap(); + txn.set_collection_compaction_bound(owner.id(1000), Some(20.into())) + .unwrap(); + reject(txn, "regressed").await; + } + Box::new(state).expire().await; +} + +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn index_id_swaps_preserve_bounds() { + let mut state = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + let mut txn = state.transaction().await.unwrap(); + txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) + .unwrap(); + for owner in OWNERS { + for (n, frontier) in [(1000, Some(10.into())), (1001, None)] { + owner.insert(&mut txn, n); + txn.set_collection_compaction_bound(owner.id(n), frontier) + .unwrap(); + } + } + commit(txn).await; + let expected = bounds(state.snapshot().await.unwrap()); + + for enabled in [false, true] { + let mut txn = state.transaction().await.unwrap(); + txn.set_config( + "catalog_read_protection_enabled".to_string(), + Some(u64::from(enabled)), + ) + .unwrap(); + let items = [1000, 1001].map(|n| { + let mut item = txn.get_item(&Owner::Ordinary.item_id(n)).unwrap(); + item.global_id = Owner::Ordinary.id(2001 - n); + (item.id, item) + }); + txn.update_items(BTreeMap::from(items)).unwrap(); + + let mappings = [1000, 1001].map(|n| { + let mut mapping = Owner::Builtin.mapping(n); + mapping.unique_identifier.global_id = Owner::Builtin.id(2001 - n); + (Owner::Builtin.item_id(n), mapping) + }); + txn.update_system_object_mappings(BTreeMap::from(mappings)) + .unwrap(); + assert_eq!(bounds(txn.current_snapshot()), expected); + let mut intermediate = Owner::Builtin.mapping(1000); + intermediate.unique_identifier.global_id = Owner::Builtin.id(1002); + txn.set_system_object_mappings(vec![ + intermediate, + Owner::Builtin.mapping(1000), + Owner::Builtin.mapping(1001), + ]) + .unwrap(); + + let mut intermediate = Owner::Builtin.mapping(1000); + intermediate.unique_identifier.global_id = Owner::Builtin.id(1002); + txn.set_system_object_mappings(vec![intermediate]).unwrap(); + txn.set_collection_compaction_bound(Owner::Builtin.id(1002), None) + .unwrap(); + txn.set_system_object_mappings(vec![Owner::Builtin.mapping(1000)]) + .unwrap(); + + // Intermediate identities must not mask a surviving ID or retain bounds. + txn.update_introspection_source_index_gids(std::iter::once(( + cluster_id(), + [(1000, 1002), (1001, 1000), (1000, 1001)] + .into_iter() + .map(|(n, gid)| { + ( + format!("log_{n}"), + Owner::Logging.item_id(n), + Owner::Logging.id(gid), + 22000 + u32::try_from(n).unwrap(), + ) + }), + ))) + .unwrap(); + txn.finalize_index_compaction_bounds(); + assert_eq!(bounds(txn.current_snapshot()), expected); + txn.validate_read_protection().unwrap(); + } + let mut txn = state.transaction().await.unwrap(); + let mut item = txn.get_item(&Owner::Ordinary.item_id(1001)).unwrap(); + item.global_id = Owner::Ordinary.id(1000); + txn.update_item(item.id, item).unwrap(); + commit(txn).await; + let mut txn = state.transaction().await.unwrap(); + Owner::Ordinary.remove(&mut txn, 1000); + txn.finalize_index_compaction_bounds(); + assert_eq!( + bounds(txn.current_snapshot())[&Owner::Ordinary.id(1000)], + Some(10.into()) + ); + commit(txn).await; + Box::new(state).expire().await; +} + +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn cannot_adopt_live_ungoverned_indices() { + let mut state = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + let mut txn = state.transaction().await.unwrap(); + for owner in OWNERS { + owner.insert(&mut txn, 1000); + } + commit(txn).await; + + for owner in OWNERS { + for frontier in [Some(Timestamp::MIN), None] { + let mut txn = state.transaction().await.unwrap(); + txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) + .unwrap(); + txn.set_collection_compaction_bound(owner.id(1000), frontier) + .unwrap(); + reject(txn, "cannot introduce").await; + } + let mut txn = state.transaction().await.unwrap(); + txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) + .unwrap(); + owner.remove(&mut txn, 1000); + owner.insert(&mut txn, 1000); + txn.set_collection_compaction_bound(owner.id(1000), Some(10.into())) + .unwrap(); + reject(txn, "cannot introduce").await; + } + Box::new(state).expire().await; +} + +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn index_permission_is_not_a_recovery_boundary() { + let mut state = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + let mut txn = state.transaction().await.unwrap(); + txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) + .unwrap(); + for owner in OWNERS { + owner.insert(&mut txn, 1000); + txn.set_collection_compaction_bound(owner.id(1000), Some(10.into())) + .unwrap(); + } + let output = GlobalId::User(2000); + txn.insert_collection_metadata(BTreeMap::from([(output, ShardId::new())])) + .unwrap(); + commit(txn).await; + for owner in OWNERS { + let mut txn = state.transaction().await.unwrap(); + txn.set_maintained_read_requirement( + output, + BTreeSet::from([owner.id(1000)]), + Some(10.into()), + ) + .unwrap(); + reject(txn, "needs input").await; + } + + for (sql, object_type) in [ + ( + "CREATE MATERIALIZED VIEW v AS SELECT 1", + CatalogItemType::MaterializedView, + ), + ( + "CREATE METRIC SINK s FROM v INTO CONNECTION c", + CatalogItemType::MetricSink, + ), + ("CREATE VIEW v AS SELECT 1", CatalogItemType::View), + ] { + let mut txn = state.transaction().await.unwrap(); + txn.insert_item( + CatalogItemId::User(3000), + 23000, + GlobalId::User(3000), + SchemaId::User(1), + "not_an_index", + sql.to_string(), + RoleId::User(1), + Vec::new(), + BTreeMap::new(), + None, + ) + .unwrap(); + txn.set_collection_compaction_bound(GlobalId::User(3000), None) + .unwrap(); + txn.delete_collection_metadata(BTreeSet::from([GlobalId::User(3000)])); + reject(txn, "neither storage metadata nor an index identity").await; + + let mut txn = state.transaction().await.unwrap(); + let mut mapping = Owner::Builtin.mapping(2000); + mapping.description.object_type = object_type; + txn.set_system_object_mappings(vec![mapping]).unwrap(); + assert!(!bounds(txn.current_snapshot()).contains_key(&Owner::Builtin.id(2000))); + txn.set_collection_compaction_bound(Owner::Builtin.id(2000), None) + .unwrap(); + reject(txn, "neither storage metadata nor an index identity").await; + } + Box::new(state).expire().await; +} + +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn cluster_and_ephemeral_cleanup() { + let mut state = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + let mut txn = state.transaction().await.unwrap(); + txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) + .unwrap(); + txn.insert_user_cluster( + cluster_id(), + "governed_cluster", + vec![( + BUILTINS::logs().next().unwrap(), + Owner::Logging.item_id(1000), + Owner::Logging.id(1000), + )], + RoleId::User(1), + Vec::new(), + ClusterConfig { + variant: ClusterVariant::Unmanaged, + workload_class: None, + }, + &Default::default(), + ) + .unwrap(); + Owner::Ordinary.insert(&mut txn, 1000); + let mut item = txn.get_item(&Owner::Ordinary.item_id(1000)).unwrap(); + item.ephemeral_owner_session = Some(uuid::Uuid::new_v4()); + txn.update_item(item.id, item).unwrap(); + for owner in [Owner::Ordinary, Owner::Logging] { + txn.set_collection_compaction_bound(owner.id(1000), Some(10.into())) + .unwrap(); + } + assert_eq!(bounds(txn.current_snapshot()).len(), 2); + Owner::Ordinary.insert(&mut txn, 1001); + let mut table = txn.get_item(&Owner::Ordinary.item_id(1001)).unwrap(); + table.create_sql = "CREATE TABLE t (a INT)".to_string(); + txn.update_item(table.id, table).unwrap(); + let storage = Owner::Ordinary.id(1001); + txn.insert_collection_metadata(BTreeMap::from([(storage, ShardId::new())])) + .unwrap(); + txn.set_collection_compaction_bound(storage, Some(10.into())) + .unwrap(); + commit(txn).await; + let mut txn = state.transaction().await.unwrap(); + txn.remove_clusters(&BTreeSet::from([cluster_id()])) + .unwrap(); + txn.remove_ephemeral_items(); + txn.remove_items(&BTreeSet::from([Owner::Ordinary.item_id(1001)])) + .unwrap(); + txn.finalize_index_compaction_bounds(); + assert_eq!( + bounds(txn.current_snapshot()), + BTreeMap::from([(storage, Some(10.into()))]) + ); + txn.delete_collection_metadata(BTreeSet::from([storage])); + commit(txn).await; + assert!(bounds(state.snapshot().await.unwrap()).is_empty()); + Box::new(state).expire().await; +} + +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn unchanged_consumers_follow_bound_and_lifetime_changes() { + let builder = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation(); + let mut state = builder + .clone() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + let input = GlobalId::User(2000); + let output = GlobalId::User(2001); + let mut txn = state.transaction().await.unwrap(); + txn.insert_collection_metadata(BTreeMap::from([ + (input, ShardId::new()), + (output, ShardId::new()), + ])) + .unwrap(); + txn.set_collection_compaction_bound(input, Some(10.into())) + .unwrap(); + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), Some(20.into())) + .unwrap(); + commit(txn).await; + Box::new(state).expire().await; + + let mut state = builder + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + for bound in [Some(21.into()), None] { + let mut txn = state.transaction().await.unwrap(); + txn.set_collection_compaction_bound(input, bound).unwrap(); + reject(txn, "needs input").await; + } + let mut txn = state.transaction().await.unwrap(); + txn.delete_collection_metadata(BTreeSet::from([input])); + reject(txn, "needs input").await; + + let mut txn = state.transaction().await.unwrap(); + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), None) + .unwrap(); + txn.delete_collection_metadata(BTreeSet::from([input])); + commit(txn).await; + let mut txn = state.transaction().await.unwrap(); + let unknown = GlobalId::User(3000); + txn.set_maintained_read_requirement(unknown, BTreeSet::new(), None) + .unwrap(); + txn.delete_collection_metadata(BTreeSet::from([unknown])); + reject(txn, "has no storage metadata").await; + Box::new(state).expire().await; +} + +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn incremental_dry_run_projects_final_identities_and_edges() { + let mut state = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + let initial = state.snapshot().await.unwrap(); + let input = GlobalId::User(2000); + let other_input = GlobalId::User(2001); + let output = GlobalId::User(2002); + let mut dry_run = state.transaction_from_snapshot(initial.clone()).unwrap(); + let txn = dry_run.transaction_mut(); + txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) + .unwrap(); + for owner in OWNERS { + owner.insert(txn, 1000); + } + txn.insert_collection_metadata(BTreeMap::from([ + (input, ShardId::new()), + (other_input, ShardId::new()), + (output, ShardId::new()), + ])) + .unwrap(); + for id in [input, other_input] { + txn.set_collection_compaction_bound(id, Some(10.into())) + .unwrap(); + } + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), Some(20.into())) + .unwrap(); + txn.finalize_index_compaction_bounds(); + txn.validate_read_protection().unwrap(); + let snapshot = dry_run.current_snapshot(); + drop(dry_run); + + let mut dry_run = state.transaction_from_snapshot(snapshot.clone()).unwrap(); + let txn = dry_run.transaction_mut(); + txn.set_collection_compaction_bound(input, Some(21.into())) + .unwrap(); + assert!(txn.validate_read_protection().is_err()); + drop(dry_run); + + let mut dry_run = state.transaction_from_snapshot(snapshot).unwrap(); + let txn = dry_run.transaction_mut(); + for owner in OWNERS { + txn.set_collection_compaction_bound(owner.id(1000), Some(10.into())) + .unwrap(); + owner.remove(txn, 1000); + owner.insert(txn, 1000); + } + txn.set_maintained_read_requirement(output, BTreeSet::from([other_input]), Some(20.into())) + .unwrap(); + txn.finalize_index_compaction_bounds(); + txn.validate_read_protection().unwrap(); + let snapshot = dry_run.current_snapshot(); + drop(dry_run); + + let mut dry_run = state.transaction_from_snapshot(snapshot).unwrap(); + let txn = dry_run.transaction_mut(); + txn.delete_collection_metadata(BTreeSet::from([input])); + txn.validate_read_protection().unwrap(); + txn.set_collection_compaction_bound(other_input, Some(21.into())) + .unwrap(); + assert!(txn.validate_read_protection().is_err()); + txn.set_maintained_read_requirement(output, BTreeSet::from([other_input]), None) + .unwrap(); + for owner in OWNERS { + owner.remove(txn, 1000); + } + txn.finalize_index_compaction_bounds(); + txn.validate_read_protection().unwrap(); + let snapshot = dry_run.current_snapshot(); + for owner in OWNERS { + assert!(!bounds(snapshot.clone()).contains_key(&owner.id(1000))); + } + drop(dry_run); + for owner in OWNERS { + let mut dry_run = state.transaction_from_snapshot(snapshot.clone()).unwrap(); + let txn = dry_run.transaction_mut(); + txn.set_collection_compaction_bound(other_input, Some(22.into())) + .unwrap(); + txn.validate_read_protection().unwrap(); + txn.set_collection_compaction_bound(owner.id(1000), Some(10.into())) + .unwrap(); + assert!(txn.validate_read_protection().is_err()); + } + assert_eq!(state.snapshot().await.unwrap(), initial); + Box::new(state).expire().await; +} diff --git a/src/catalog/tests/open.rs b/src/catalog/tests/open.rs index c9048c961f2ac..aaf699993480a 100644 --- a/src/catalog/tests/open.rs +++ b/src/catalog/tests/open.rs @@ -82,6 +82,7 @@ impl StableSnapshot<'_> { impl Debug for StableSnapshot<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let Snapshot { + read_protection_index: _, databases, schemas, roles, diff --git a/src/catalog/tests/read-write.rs b/src/catalog/tests/read-write.rs index 07c36497bcf45..d0922367b988d 100644 --- a/src/catalog/tests/read-write.rs +++ b/src/catalog/tests/read-write.rs @@ -883,6 +883,183 @@ async fn test_non_writer_commits(state_builder: TestCatalogStateBuilder) { } } +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn test_persist_exact_prefix() { + let persist = PersistClient::new_for_tests().await; + let organization_id = Uuid::new_v4(); + let builder = TestCatalogStateBuilder::new(persist.clone()) + .with_organization_id(organization_id) + .with_default_deploy_generation(); + let mut writer = builder + .clone() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = writer.sync_to_current_updates().await.unwrap(); + let shard = writer.shard_id(); + async fn resources(persist: &PersistClient, shard: ShardId) -> (usize, usize) { + let state = serde_json::to_value( + persist + .inspect_shard::(&shard) + .await + .unwrap(), + ) + .unwrap(); + ( + state["leased_readers"].as_object().unwrap().len(), + state["writers"].as_object().unwrap().len(), + ) + } + let initial_resources = resources(&persist, shard).await; + let reader = |persist: PersistClient| async move { + mz_catalog::durable::CatalogSnapshotReader::open( + persist, + organization_id, + semver::Version::new(0, 0, 0), + &test_bootstrap_args(), + ) + .await + .unwrap() + }; + let exact = reader(persist.clone()).await; + let before_fence = reader(persist.clone()).await; + let at_fence = reader(persist.clone()).await; + let live = reader(persist.clone()).await; + let backwards = reader(persist.clone()).await; + let opened_upper = writer.current_upper().await; + assert!( + backwards + .into_snapshot_at(opened_upper.saturating_sub(1)) + .await + .is_err() + ); + + let input = GlobalId::User(1000); + let output = GlobalId::User(1001); + let mut txn = writer.transaction().await.unwrap(); + txn.insert_collection_metadata( + [input, output] + .into_iter() + .map(|id| (id, ShardId::new())) + .collect(), + ) + .unwrap(); + txn.set_collection_compaction_bound(input, Some(10.into())) + .unwrap(); + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), Some(10.into())) + .unwrap(); + txn.insert_item( + CatalogItemId::User(2000), + 22_000, + GlobalId::User(2000), + SchemaId::User(0), + "temporary_view", + "CREATE VIEW temporary_view AS SELECT 1".into(), + RoleId::User(1), + vec![], + BTreeMap::new(), + Some(Uuid::new_v4()), + ) + .unwrap(); + let expected = txn.current_snapshot(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + + let expected_upper = writer.current_upper().await; + let replayed = reader(persist.clone()).await; + assert_eq!( + replayed + .into_snapshot_at(expected_upper) + .await + .unwrap() + .snapshot, + expected + ); + let mut txn = writer.transaction().await.unwrap(); + txn.set_collection_compaction_bound(input, Some(20.into())) + .unwrap(); + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), None) + .unwrap(); + txn.remove_ephemeral_items(); + txn.get_and_increment_id(USER_ITEM_ALLOC_KEY.into()) + .unwrap(); + let changed = txn.current_snapshot(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + let later_upper = writer.current_upper().await; + assert!(later_upper > expected_upper); + + // Keep publishing while extraction consumes the earlier exclusive prefix. + let (stop, mut stopped) = tokio::sync::oneshot::channel::<()>(); + let (published, publishing) = tokio::sync::oneshot::channel(); + let publisher = mz_ore::task::spawn(|| "catalog-prefix-publisher", async move { + let mut published = Some(published); + while matches!( + stopped.try_recv(), + Err(tokio::sync::oneshot::error::TryRecvError::Empty) + ) { + let upper = writer.current_upper().await.step_forward(); + writer.advance_upper(upper).await.unwrap(); + if let Some(published) = published.take() { + let _ = published.send(()); + } + tokio::task::yield_now().await; + } + writer + }); + publishing.await.unwrap(); + let owned = tokio::time::timeout( + std::time::Duration::from_secs(30), + exact.into_snapshot_at(expected_upper), + ) + .await; + let _ = stop.send(()); + let writer = publisher.await; + let owned = owned + .expect("extraction must not wait for publication to stop") + .unwrap(); + assert_eq!(owned.upper, expected_upper); + assert_eq!(owned.snapshot, expected); + assert!(!owned.updates.is_empty()); + assert!( + owned + .updates + .iter() + .all(|update| update.ts < expected_upper) + ); + assert_eq!( + live.into_snapshot_at(later_upper).await.unwrap().snapshot, + changed + ); + let mut next_writer = builder + .clone() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let fenced_upper = next_writer.current_upper().await; + assert!(matches!( + at_fence.into_snapshot_at(fenced_upper).await, + Err(CatalogError::Durable(DurableCatalogError::Fence(_))) + )); + let before_fence = before_fence.into_snapshot_at(expected_upper).await.unwrap(); + assert_eq!(before_fence.snapshot, expected); + assert_eq!(before_fence.updates, owned.updates); + next_writer.expire().await; + assert_eq!(resources(&persist, shard).await, initial_resources); + writer.expire().await; + + let unused = reader(persist.clone()).await; + unused.expire().await; + assert_eq!(resources(&persist, shard).await, (0, 0)); +} + /// Verifies that computing next IDs from max existing catalog items gives /// the correct baseline for DDL detection, even when the allocator counter /// has been advanced far ahead by batch allocation (as IdPool does). @@ -1163,6 +1340,255 @@ async fn test_persist_sync_snapshot_stays_bounded_under_churn() { Box::new(reader).expire().await; } +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn test_persist_committed_row_traffic() { + use mz_repr::adt::jsonb::Jsonb; + use mz_storage_types::sources::SourceData; + + fn traffic(registry: &MetricsRegistry) -> BTreeMap<(String, String), f64> { + registry + .gather() + .into_iter() + .filter(|family| { + matches!( + family.name(), + "mz_catalog_committed_updates" | "mz_catalog_committed_update_bytes" + ) + }) + .flat_map(|family| { + family + .get_metric() + .iter() + .map(|metric| { + assert_eq!(metric.get_label().len(), 1); + let label = &metric.get_label()[0]; + assert_eq!(label.name(), "kind"); + ( + (family.name().to_owned(), label.value().to_owned()), + metric.get_counter().as_ref().unwrap().value(), + ) + }) + .collect::>() + }) + .collect() + } + + fn packed_bytes(kind: proto::StateUpdateKind, tag: &str) -> f64 { + let json = serde_json::to_value(kind).unwrap(); + assert_eq!(json["kind"], tag); + let source = SourceData(Ok(Jsonb::from_serde_json(json).unwrap().into_row())); + let bytes = source.0.unwrap().byte_len(); + assert!(bytes > 0); + f64::from(u32::try_from(bytes).unwrap()) + } + + let registry = MetricsRegistry::new(); + let metrics = Arc::new(Metrics::new(®istry)); + let zero = traffic(®istry); + assert_eq!(zero.len(), 6); + for kind in ["compaction_bound", "maintained_read_requirement", "other"] { + for metric in [ + "mz_catalog_committed_updates", + "mz_catalog_committed_update_bytes", + ] { + assert_eq!(zero[&(metric.to_owned(), kind.to_owned())], 0.0); + } + } + let builder = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) + .with_default_deploy_generation() + .with_metrics(metrics); + let mut state = builder + .clone() + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = state.sync_to_current_updates().await.unwrap(); + let input = GlobalId::User(1000); + let output = GlobalId::User(1001); + + let mut previous_bytes = [0.0; 2]; + for frontier in [10u64, 20] { + let before = traffic(®istry); + let mut txn = state.transaction().await.unwrap(); + // A storage collection's first bound must be published with its metadata. + if frontier == 10 { + txn.insert_collection_metadata( + [input, output] + .into_iter() + .map(|id| (id, ShardId::new())) + .collect(), + ) + .unwrap(); + } + txn.set_collection_compaction_bound(input, Some(frontier.into())) + .unwrap(); + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), Some(frontier.into())) + .unwrap(); + let snapshot = txn.current_snapshot(); + let metadata_bytes = if frontier == 10 { + snapshot + .storage_collection_metadata + .into_iter() + .map(|(key, value)| { + packed_bytes( + proto::StateUpdateKind::StorageCollectionMetadata( + proto::StorageCollectionMetadata { key, value }, + ), + "StorageCollectionMetadata", + ) + }) + .sum::() + } else { + 0.0 + }; + let (key, value) = snapshot + .collection_compaction_bounds + .into_iter() + .next() + .unwrap(); + let bound_bytes = packed_bytes( + proto::StateUpdateKind::CollectionCompactionBound(proto::CollectionCompactionBound { + key, + value, + }), + "CollectionCompactionBound", + ); + let (key, value) = snapshot + .maintained_read_requirements + .into_iter() + .next() + .unwrap(); + let requirement_bytes = packed_bytes( + proto::StateUpdateKind::MaintainedReadRequirement(proto::MaintainedReadRequirement { + key, + value, + }), + "MaintainedReadRequirement", + ); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + let after = traffic(®istry); + for (i, (kind, bytes)) in [ + ("compaction_bound", bound_bytes), + ("maintained_read_requirement", requirement_bytes), + ] + .into_iter() + .enumerate() + { + let updates_key = ("mz_catalog_committed_updates".to_owned(), kind.to_owned()); + let bytes_key = ( + "mz_catalog_committed_update_bytes".to_owned(), + kind.to_owned(), + ); + assert_eq!( + after[&updates_key] - before[&updates_key], + if frontier == 10 { 1.0 } else { 2.0 } + ); + assert_eq!( + after[&bytes_key] - before[&bytes_key], + bytes + previous_bytes[i] + ); + previous_bytes[i] = bytes; + } + for (metric, expected) in [ + ( + "mz_catalog_committed_updates", + if frontier == 10 { 2.0 } else { 0.0 }, + ), + ("mz_catalog_committed_update_bytes", metadata_bytes), + ] { + let key = (metric.to_owned(), "other".to_owned()); + assert_eq!(after[&key] - before[&key], expected); + } + } + + let before = traffic(®istry); + let mut txn = state.transaction().await.unwrap(); + txn.insert_user_database("traffic_db", RoleId::User(1), Vec::new(), &HashSet::new()) + .unwrap(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + let after = traffic(®istry); + for (key, value) in &before { + if key.1 == "other" { + assert!(after[key] > *value); + } else { + assert_eq!(after[key], *value); + } + } + + let mut txn = state.transaction().await.unwrap(); + txn.set_collection_compaction_bound(input, Some(20.into())) + .unwrap(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + assert_eq!(traffic(®istry), after); + + let mut reader = builder + .clone() + .unwrap_build() + .await + .open_read_only(&test_bootstrap_args()) + .await + .unwrap(); + let _ = reader.sync_to_current_updates().await.unwrap(); + let mut txn = reader.transaction().await.unwrap(); + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), Some(30.into())) + .unwrap(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + assert!(matches!( + txn.commit(ts).await.unwrap_err(), + CatalogError::Durable(DurableCatalogError::NotWritable(_)) + )); + assert_eq!(traffic(®istry), after); + + let mut savepoint = builder + .clone() + .unwrap_build() + .await + .open_savepoint(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let _ = savepoint.sync_to_current_updates().await.unwrap(); + let mut txn = savepoint.transaction().await.unwrap(); + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), Some(30.into())) + .unwrap(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + assert_eq!(traffic(®istry), after); + + // Fence a transaction after it is prepared, so its durable append fails. + let mut txn = state.transaction().await.unwrap(); + txn.set_maintained_read_requirement(output, BTreeSet::from([input]), Some(30.into())) + .unwrap(); + let _ = txn.get_and_commit_op_updates(); + let replacement = builder + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + let before_failure = traffic(®istry); + let ts = txn.upper(); + assert!(matches!( + txn.commit(ts).await.unwrap_err(), + CatalogError::Durable(DurableCatalogError::Fence(FenceError::Epoch { .. })) + )); + assert_eq!(traffic(®istry), before_failure); + Box::new(replacement).expire().await; + Box::new(savepoint).expire().await; + Box::new(reader).expire().await; + Box::new(state).expire().await; +} + #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] async fn test_persist_read_protection() { diff --git a/src/compute-client/src/as_of_selection.rs b/src/compute-client/src/as_of_selection.rs index 6d5cbb516f056..a77741264c1b1 100644 --- a/src/compute-client/src/as_of_selection.rs +++ b/src/compute-client/src/as_of_selection.rs @@ -81,6 +81,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::fmt; use std::rc::Rc; +use differential_dataflow::lattice::Lattice; use mz_compute_types::dataflows::DataflowDescription; use mz_compute_types::plan::LirRelationExpr; use mz_ore::collections::CollectionExt; @@ -101,6 +102,7 @@ use tracing::{info, warn}; pub fn run( dataflows: &mut [DataflowDescription], read_policies: &BTreeMap, + committed_index_bounds: &BTreeMap>, storage_collections: &dyn StorageCollections, current_time: Timestamp, read_only_mode: bool, @@ -138,6 +140,7 @@ pub fn run( // Apply hard constraints from upstream and downstream storage collections. ctx.apply_upstream_storage_constraints(&storage_read_holds); ctx.apply_downstream_storage_constraints(); + ctx.apply_committed_index_bounds(committed_index_bounds); // At this point all collections have as-of bounds that reflect what is required for // correctness. The current state isn't very usable though. In particular, most of the upper @@ -500,6 +503,32 @@ impl<'a> Context<'a> { self.propagate_bounds_upstream(BoundType::Upper); } + fn apply_committed_index_bounds(&self, bounds: &BTreeMap>) { + for (id, collection) in &self.collections { + if !collection.is_index { + continue; + } + let Some(bound) = bounds.get(id) else { + continue; + }; + // A saved permission below actual readability cannot be recovered. Replacement + // installs at the readable lower bound, never at a later soft preference, and + // seeds local governance there until durable publication catches up. + let lower = collection.bounds.borrow().lower.clone(); + let upper = bound.join(&lower); + self.apply_constraint( + *id, + Constraint { + type_: ConstraintType::Hard, + bound_type: BoundType::Upper, + frontier: &upper, + reason: "committed index compaction bound", + }, + ); + } + self.propagate_bounds_upstream(BoundType::Upper); + } + /// Apply as-of constraints to ensure collections can hydrate immediately. /// /// A collection's as-of _should_ be < the write frontier of each of its (transitive) storage @@ -928,7 +957,10 @@ mod tests { unimplemented!() } - fn compaction_frontiers(&self) -> BTreeMap> { + fn take_read_protection_frontiers( + &self, + _additional_ids: &BTreeSet, + ) -> BTreeMap)> { unimplemented!() } @@ -1043,6 +1075,16 @@ mod tests { unimplemented!() } + fn compaction_bound( + &self, + id: GlobalId, + ) -> Result>, StorageError> { + self.0 + .contains_key(&id) + .then_some(None) + .ok_or(StorageError::IdentifierMissing(id)) + } + fn acquire_read_holds( &self, desired_holds: Vec, @@ -1168,6 +1210,7 @@ mod tests { dataflows: [ $( $export_id:literal <- $inputs:expr => $as_of:expr, )* ], current_time: $current_time:literal, $( read_policies: { $( $policy_id:literal: $policy:expr, )* }, )? + $( committed_bounds: { $( $bound_id:literal: $bound:expr, )* }, )? $( read_only: $read_only:expr, )? }) => { #[mz_ore::test] @@ -1200,6 +1243,9 @@ mod tests { super::run( &mut dataflows, &read_policies, + &BTreeMap::from([ + $($( ($bound_id.parse().unwrap(), ts_to_frontier($bound)), )*)? + ]), &storage_frontiers, $current_time.into(), read_only, @@ -1216,6 +1262,35 @@ mod tests { }; } + testcase!(committed_index_caps, { + storage: { "s1": (10, 100), }, + dataflows: [ + "u1" <- ["s1"] => 30, + "u2" <- ["u1"] => 30, + "u3" <- ["s1"] => 90, + ], + current_time: 90, + committed_bounds: { "u2": 30, }, + }); + + testcase!(committed_index_replacement, { + storage: { "s1": (20, 100), }, + dataflows: [ + "u1" <- ["s1"] => 20, + "u2" <- ["u1"] => 20, + ], + current_time: 90, + committed_bounds: { "u2": 10, }, + }); + + testcase!(committed_index_dropped_input, { + storage: { "s1": (SEALED, SEALED), }, + dataflows: [ "u1" <- ["s1"] => SEALED, ], + current_time: 90, + committed_bounds: { "u1": 10, }, + read_only: true, + }); + testcase!(upstream_storage_constraints, { storage: { "s1": (10, 20), diff --git a/src/compute-client/src/controller.rs b/src/compute-client/src/controller.rs index bfd41301888a8..d6f40b50be7dc 100644 --- a/src/compute-client/src/controller.rs +++ b/src/compute-client/src/controller.rs @@ -36,6 +36,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::sync::{Arc, Mutex}; use std::time::Duration; +use differential_dataflow::lattice::Lattice; use mz_build_info::BuildInfo; use mz_cluster_client::client::ClusterReplicaLocation; use mz_cluster_client::metrics::ControllerMetrics; @@ -71,9 +72,9 @@ use tokio::time::{self, MissedTickBehavior}; use uuid::Uuid; use crate::controller::error::{ - CollectionLookupError, CollectionMissing, CollectionUpdateError, DataflowCreationError, - HydrationCheckBadTarget, InstanceExists, InstanceMissing, PeekError, ReadPolicyError, - ReplicaCreationError, ReplicaDropError, + CollectionLookupError, CollectionMissing, CollectionUpdateError, CompactionBoundError, + DataflowCreationError, HydrationCheckBadTarget, InstanceExists, InstanceMissing, PeekError, + ReadPolicyError, ReplicaCreationError, ReplicaDropError, }; use crate::controller::instance::{Instance, SharedCollectionState}; use crate::controller::introspection::{IntrospectionUpdates, spawn_introspection_sink}; @@ -181,6 +182,9 @@ impl PeekNotification { /// A controller for the compute layer. pub struct ComputeController { instances: BTreeMap, + /// Committed permissions for collections not yet installed. + staged_compaction_bounds: BTreeMap>, + dirty_compaction_proposals: Arc>>, /// A map from an instance ID to an arbitrary string that describes the /// class of the workload that compute instance is running (e.g., /// `production` or `staging`). @@ -196,6 +200,7 @@ pub struct ComputeController { /// controlled by it are allowed to affect changes to external systems /// (largely persist). read_only: bool, + catalog_read_protection_enabled: bool, /// Compute configuration to apply to new instances. config: ComputeParameters, /// The persist location where we can stash large peek results. @@ -240,10 +245,14 @@ pub struct ComputeController { impl ComputeController { /// Construct a new [`ComputeController`]. + /// + /// Catalog read protection governs permanent readable exports from installation, + /// using their installation `as_of` until a committed bound is available. pub fn new( build_info: &'static BuildInfo, storage_collections: StorageCollections, read_only: bool, + catalog_read_protection_enabled: bool, metrics_registry: &MetricsRegistry, peek_stash_persist_location: PersistLocation, controller_metrics: ControllerMetrics, @@ -302,11 +311,14 @@ impl ComputeController { Self { instances: BTreeMap::new(), + staged_compaction_bounds: BTreeMap::new(), + dirty_compaction_proposals: Arc::default(), instance_workload_classes, build_info, storage_collections, initialized: false, read_only, + catalog_read_protection_enabled, config: Default::default(), peek_stash_persist_location, stashed_response: None, @@ -467,11 +479,14 @@ impl ComputeController { // Destructure `self` here so we don't forget to consider dumping newly added fields. let Self { instances, + staged_compaction_bounds, + dirty_compaction_proposals: _, instance_workload_classes, build_info: _, storage_collections: _, initialized, read_only, + catalog_read_protection_enabled, config: _, peek_stash_persist_location: _, stashed_response, @@ -503,9 +518,11 @@ impl ComputeController { Ok(serde_json::json!({ "instances": instances_dump, + "staged_compaction_bounds": format!("{staged_compaction_bounds:?}"), "instance_workload_classes": instance_workload_classes, "initialized": initialized, "read_only": read_only, + "catalog_read_protection_enabled": catalog_read_protection_enabled, "stashed_response": format!("{stashed_response:?}"), "maintenance_scheduled": maintenance_scheduled, })) @@ -527,7 +544,14 @@ impl ComputeController { let mut collections = BTreeMap::new(); let mut logs = Vec::with_capacity(arranged_logs.len()); for (&log, &id) in &arranged_logs { - let collection = Collection::new_log(); + let bound = self.staged_compaction_bounds.remove(&id).or_else(|| { + (self.catalog_read_protection_enabled && (id.is_user() || id.is_system())) + .then(|| Antichain::from_elem(Timestamp::MIN)) + }); + let collection = Collection::new_log(bound); + collection + .shared + .track_compaction_proposals(id, Arc::clone(&self.dirty_compaction_proposals)); let shared = collection.shared.clone(); collections.insert(id, collection); logs.push((log, id, shared)); @@ -815,6 +839,11 @@ impl ComputeController { /// into its own registry. The coordinator's curated metric sinks are installed per replica and /// do target one, so each replica's series are attributable to it. /// + /// A staged compaction bound caps each index export's `as_of`, unless the inputs are + /// no longer readable there. Such an export must install at exactly their least readable + /// frontier, with local permission seeded there pending durable publication. + /// Write-only exports discard any staged bound without installing permission. + /// /// Panics if called with a dataflow description that has index exports /// when `target_replica` is set. pub fn create_dataflow( @@ -893,10 +922,22 @@ impl ComputeController { let read_hold = instance.acquire_read_hold(id)?; import_read_holds.push(read_hold); } + let mut least_readable = Antichain::from_elem(Timestamp::MIN); for hold in &import_read_holds { if PartialOrder::less_than(as_of, hold.since()) { return Err(SinceViolation(hold.id())); } + least_readable.join_assign(hold.since()); + } + for id in dataflow.exported_index_ids() { + if let Some(bound) = self.staged_compaction_bounds.get(&id) { + if !PartialOrder::less_equal(as_of, bound) + && !(as_of == &least_readable + && PartialOrder::less_than(bound, &least_readable)) + { + return Err(CompactionBoundViolation(id)); + } + } } // Validation: storage sink collections @@ -909,13 +950,24 @@ impl ComputeController { .determine_time_dependence(instance_id, &dataflow, &used_imports) .expect("must exist"); - let instance = self.instance_mut(instance_id).expect("validated"); - + let staged_bounds = &mut self.staged_compaction_bounds; + let instance = self.instances.get_mut(&instance_id).expect("validated"); let mut shared_collection_state = BTreeMap::new(); for id in dataflow.export_ids() { - let shared = SharedCollectionState::new(as_of.clone()); + let bound = staged_bounds.remove(&id); + let write_only = dataflow.sink_exports.contains_key(&id); + let permission = if write_only { + None + } else { + bound.or_else(|| { + (self.catalog_read_protection_enabled && (id.is_user() || id.is_system())) + .then(|| as_of.clone()) + }) + }; + let shared = SharedCollectionState::new(as_of.clone(), permission); + shared.track_compaction_proposals(id, Arc::clone(&self.dirty_compaction_proposals)); let collection = Collection { - write_only: dataflow.sink_exports.contains_key(&id), + write_only, compute_dependencies: dataflow.imported_index_ids().collect(), shared: shared.clone(), time_dependence: time_dependence.clone(), @@ -941,6 +993,8 @@ impl ComputeController { /// Drop the read capability for the given collections and allow their resources to be /// reclaimed. + /// + /// For governed indexes, the caller must commit the drop before calling this method. pub fn drop_collections( &mut self, instance_id: ComputeInstanceId, @@ -1041,6 +1095,79 @@ impl ComputeController { Ok(()) } + /// Apply a committed catalog compaction permission for a globally unique collection ID. + /// + /// An absent collection's bound is staged for creation. Live indexes must already be + /// governed, and write-only exports reject bounds. Repeated or stale deliveries never + /// regress permission. Live updates take effect asynchronously on the instance task. + /// The caller must deliver only committed bounds and must not deliver a dropped ID again. + pub fn apply_compaction_bound( + &mut self, + id: GlobalId, + bound: Antichain, + ) -> Result<(), CompactionBoundError> { + if !(id.is_user() || id.is_system()) { + return Err(CompactionBoundError::UngovernedCollection(id)); + } + for instance in self.instances.values() { + if let Some(collection) = instance.collections.get(&id) { + if collection.write_only { + return Err(CompactionBoundError::WriteOnlyCollection(id)); + } + if collection.shared.compaction_bound().is_none() { + return Err(CompactionBoundError::UngovernedCollection(id)); + } + instance.call(move |i| i.apply_compaction_bound(id, bound)); + return Ok(()); + } + } + let staged = self + .staged_compaction_bounds + .entry(id) + .or_insert_with(|| bound.clone()); + if PartialOrder::less_than(staged, &bound) { + *staged = bound; + } + Ok(()) + } + + /// Take current compaction candidates for changed or explicitly requested governed indexes. + /// + /// Each candidate excludes only the catalog permission contribution. Reader, policy, + /// dependency, and warmup holds still constrain it. These are snapshots, not permissions. + /// They may precede committed permission or regress when new readers arrive. + /// Missing, dropped, write-only, transient, and ungoverned collections are omitted. + /// Changes concurrent with sampling are included or retained for the next call. + /// After failed publication, pass the affected IDs in `additional_ids` to resample them. + pub fn take_compaction_bound_proposals( + &mut self, + additional_ids: &BTreeSet, + ) -> BTreeMap> { + // Never hold the dirty-set lock while acquiring a collection lock. Writers and + // sampling both lock capabilities before clearing or setting their notification. + let mut ids = self + .dirty_compaction_proposals + .lock() + .expect("poisoned") + .clone(); + ids.extend(additional_ids); + ids.into_iter() + .filter_map(|id| { + let collection = self.instances.values().find_map(|i| i.collections.get(&id)); + let proposal = collection + .filter(|c| !c.write_only && (id.is_user() || id.is_system())) + .and_then(|c| c.shared.take_compaction_bound_proposal()); + if collection.is_none() { + self.dirty_compaction_proposals + .lock() + .expect("poisoned") + .remove(&id); + } + proposal.map(|bound| (id, bound)) + }) + .collect() + } + /// Assign a read policy to specific identifiers. /// /// The policies are assigned in the order presented, and repeated identifiers should @@ -1304,7 +1431,7 @@ impl InstanceState { // at the implied capability. let collection = self.collection(id)?; - let since = collection.shared.lock_read_capabilities(|caps| { + let since = collection.shared.mutate_read_capabilities(|caps| { let since = caps.frontier().to_owned(); caps.update_iter(since.iter().map(|t| (t.clone(), 1))); since @@ -1358,12 +1485,12 @@ struct Collection { } impl Collection { - fn new_log() -> Self { + fn new_log(bound: Option>) -> Self { let as_of = Antichain::from_elem(Timestamp::MIN); Self { write_only: false, compute_dependencies: Default::default(), - shared: SharedCollectionState::new(as_of), + shared: SharedCollectionState::new(as_of, bound), time_dependence: Some(TimeDependence::default()), } } diff --git a/src/compute-client/src/controller/compaction_tests.rs b/src/compute-client/src/controller/compaction_tests.rs new file mode 100644 index 0000000000000..789a1406593d5 --- /dev/null +++ b/src/compute-client/src/controller/compaction_tests.rs @@ -0,0 +1,866 @@ +// Copyright Materialize, Inc. and contributors. All rights reserved. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0. + +use std::collections::{BTreeMap, BTreeSet}; +use std::sync::Arc; + +use async_trait::async_trait; +use futures::future::BoxFuture; +use futures::stream::BoxStream; +use mz_build_info::DUMMY_BUILD_INFO; +use mz_cluster_client::WallclockLagFn; +use mz_cluster_client::metrics::ControllerMetrics; +use mz_compute_types::ComputeInstanceId; +use mz_compute_types::dataflows::{DataflowDescription, IndexDesc, IndexImport}; +use mz_compute_types::plan::LirRelationExpr; +use mz_compute_types::sinks::{ComputeSinkConnection, ComputeSinkDesc, MetricSinkConnection}; +use mz_ore::metrics::MetricsRegistry; +use mz_ore::now::SYSTEM_TIME; +use mz_persist_client::stats::{SnapshotPartsStats, SnapshotStats}; +use mz_persist_types::{PersistLocation, ShardId}; +use mz_repr::{GlobalId, RelationDesc, RelationVersion, ReprRelationType, Row, Timestamp}; +use mz_storage_client::client::TimestamplessUpdateBuilder; +use mz_storage_client::controller::{CollectionDescription, StorageMetadata, StorageTxn}; +use mz_storage_client::storage_collections::{ + CollectionFrontiers, SnapshotCursor, StorageCollections, +}; +use mz_storage_types::StorageDiff; +use mz_storage_types::controller::{CollectionMetadata, StorageError}; +use mz_storage_types::errors::CollectionMissing; +use mz_storage_types::parameters::StorageParameters; +use mz_storage_types::read_holds::ReadHold; +use mz_storage_types::read_policy::ReadPolicy; +use mz_storage_types::sources::SourceData; +use mz_storage_types::time_dependence::{TimeDependence, TimeDependenceError}; +use timely::progress::Antichain; +use tokio::sync::mpsc; + +use super::Instance; +use crate::controller::ComputeController; +use crate::controller::error::{CompactionBoundError, DataflowCreationError}; +use crate::logging::{LogVariant, TimelyLog}; +use crate::protocol::command::ComputeCommand; + +const INSTANCE: ComputeInstanceId = ComputeInstanceId::User(1); +const INPUT: GlobalId = GlobalId::System(1); +const OUTPUT: GlobalId = GlobalId::User(1); +const BASE: GlobalId = GlobalId::User(100); + +fn frontier(time: u64) -> Antichain { + Antichain::from_elem(time.into()) +} + +fn controller() -> ComputeController { + let registry = MetricsRegistry::new(); + ComputeController::new( + &DUMMY_BUILD_INFO, + Arc::new(EmptyStorage), + false, + false, + ®istry, + PersistLocation::new_in_mem(), + ControllerMetrics::new(®istry), + SYSTEM_TIME.clone(), + WallclockLagFn::new(SYSTEM_TIME.clone()), + ) +} + +fn create_instance(controller: &mut ComputeController) { + controller + .create_instance( + INSTANCE, + BTreeMap::from([(LogVariant::Timely(TimelyLog::Operates), INPUT)]), + None, + ) + .unwrap(); +} + +fn dataflow(as_of: u64, write_only: bool) -> DataflowDescription { + let mut dataflow = DataflowDescription::new("compaction bound test".into()); + dataflow.as_of = Some(frontier(as_of)); + let desc = IndexDesc { + on_id: BASE, + key: Vec::new(), + }; + dataflow.index_imports.insert( + INPUT, + IndexImport { + desc: desc.clone(), + typ: ReprRelationType::empty(), + monotonic: false, + with_snapshot: true, + }, + ); + if write_only { + dataflow.sink_exports.insert( + OUTPUT, + ComputeSinkDesc { + from: BASE, + from_desc: RelationDesc::empty(), + connection: ComputeSinkConnection::MetricSink(MetricSinkConnection { + label: OUTPUT.to_string(), + }), + with_snapshot: true, + up_to: Antichain::new(), + non_null_assertions: Vec::new(), + refresh_schedule: None, + }, + ); + } else { + dataflow + .index_exports + .insert(OUTPUT, (desc, ReprRelationType::empty())); + } + dataflow +} + +// Read hold changes enqueue more instance commands. Drain them before observing the contract, +// without relying on which task wins a scheduling race against a barrier command. +fn drain(instance: &mut Instance) { + while let Ok(command) = instance.command_rx.try_recv() { + command(instance); + } +} + +async fn observe(controller: &ComputeController, expected: Antichain) { + controller + .instance(INSTANCE) + .unwrap() + .call_sync(move |instance| { + drain(instance); + assert_eq!( + instance.collection(INPUT).unwrap().read_frontier(), + expected + ); + assert_eq!( + instance.collection(OUTPUT).unwrap().read_frontier(), + expected + ); + for id in [INPUT, OUTPUT] { + // Replay folds compaction into CreateDataflow's as_of and removes completed + // dataflows. Check the commanded frontier independently of that representation. + let commanded = instance + .history + .iter() + .filter_map(|cmd| match cmd { + ComputeCommand::AllowCompaction { id: cid, frontier } if *cid == id => { + Some(frontier) + } + ComputeCommand::CreateDataflow(dataflow) + if dataflow.export_ids().any(|export| export == id) => + { + dataflow.as_of.as_ref() + } + _ => None, + }) + .last() + .cloned() + .unwrap_or_else(Antichain::new); + assert_eq!(commanded, expected); + } + }) + .await; +} + +#[mz_ore::test(tokio::test)] +async fn compaction_permission_caps_inputs_and_commands_without_overriding_holds() { + let mut controller = controller(); + controller + .apply_compaction_bound(OUTPUT, frontier(5)) + .unwrap(); + create_instance(&mut controller); + controller + .create_dataflow(INSTANCE, dataflow(0, false), None) + .unwrap(); + + // The storage dependency observes the same cap as the actual compute import. + let (storage_tx, mut storage_rx) = mpsc::unbounded_channel(); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(move |instance| { + let output = instance.collection_mut(OUTPUT).unwrap(); + output.storage_dependencies.insert( + GlobalId::User(200), + ReadHold::with_channel(GlobalId::User(200), frontier(0), storage_tx), + ); + output.warmup_read_hold.try_downgrade(frontier(15)).unwrap(); + instance + .collection_mut(INPUT) + .unwrap() + .warmup_read_hold + .release(); + instance + .set_read_policy(vec![ + (INPUT, ReadPolicy::ValidFrom(Antichain::new())), + (OUTPUT, ReadPolicy::ValidFrom(frontier(20))), + ]) + .unwrap(); + drain(instance); + }) + .await; + observe(&controller, frontier(5)).await; + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new())[&OUTPUT], + frontier(15) + ); + let (_, mut changes) = storage_rx.try_recv().unwrap(); + assert_eq!( + changes.drain().collect::>(), + vec![(0.into(), -1), (5.into(), 1)] + ); + assert!(storage_rx.try_recv().is_err()); + + controller + .instance(INSTANCE) + .unwrap() + .call_sync(|instance| { + instance + .collection_mut(OUTPUT) + .unwrap() + .warmup_read_hold + .try_downgrade(frontier(30)) + .unwrap(); + drain(instance); + }) + .await; + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new())[&OUTPUT], + frontier(20) + ); + let mut reader = controller.acquire_read_hold(INSTANCE, OUTPUT).unwrap(); + reader.try_downgrade(frontier(7)).unwrap(); + for time in [10, 10, 6] { + controller + .apply_compaction_bound(OUTPUT, frontier(time)) + .unwrap(); + } + observe(&controller, frontier(7)).await; + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new())[&OUTPUT], + frontier(7) + ); + reader.release(); + observe(&controller, frontier(10)).await; + controller + .apply_compaction_bound(OUTPUT, frontier(25)) + .unwrap(); + observe(&controller, frontier(20)).await; + + let reader = controller.acquire_read_hold(INSTANCE, OUTPUT).unwrap(); + controller.drop_collections(INSTANCE, vec![OUTPUT]).unwrap(); + observe(&controller, frontier(20)).await; + assert!( + !controller + .take_compaction_bound_proposals(&BTreeSet::from([OUTPUT])) + .contains_key(&OUTPUT) + ); + drop(reader); + observe(&controller, Antichain::new()).await; + controller.drop_instance(INSTANCE); + assert!(controller.staged_compaction_bounds.is_empty()); +} + +#[mz_ore::test(tokio::test)] +async fn compaction_bounds_are_staged_before_creation_and_never_adopt_live_exports() { + let mut controller = controller(); + for time in [5, 5, 3] { + controller + .apply_compaction_bound(INPUT, frontier(time)) + .unwrap(); + controller + .apply_compaction_bound(OUTPUT, frontier(time)) + .unwrap(); + } + create_instance(&mut controller); + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new())[&INPUT], + frontier(0) + ); + let reader = controller.acquire_read_hold(INSTANCE, INPUT).unwrap(); + assert_eq!(reader.since(), &frontier(0)); + assert!(matches!( + controller.create_dataflow(INSTANCE, dataflow(6, false), None), + Err(DataflowCreationError::CompactionBoundViolation(OUTPUT)) + )); + assert_eq!(controller.staged_compaction_bounds[&OUTPUT], frontier(5)); + assert!(controller.collection_frontiers(OUTPUT, None).is_err()); + controller + .apply_compaction_bound(OUTPUT, frontier(6)) + .unwrap(); + controller + .create_dataflow(INSTANCE, dataflow(6, false), None) + .unwrap(); + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new())[&OUTPUT], + frontier(6) + ); + drop(reader); + controller.drop_instance(INSTANCE); + assert!( + controller + .take_compaction_bound_proposals(&BTreeSet::new()) + .is_empty() + ); + assert!(controller.staged_compaction_bounds.is_empty()); + + create_instance(&mut controller); + assert!(matches!( + controller.apply_compaction_bound(INPUT, frontier(8)), + Err(CompactionBoundError::UngovernedCollection(INPUT)) + )); + controller + .apply_compaction_bound(OUTPUT, frontier(1)) + .unwrap(); + controller + .create_dataflow(INSTANCE, dataflow(9, true), None) + .unwrap(); + assert!( + controller + .take_compaction_bound_proposals(&BTreeSet::from([INPUT, OUTPUT])) + .is_empty() + ); + assert!(controller.staged_compaction_bounds.is_empty()); + assert!(matches!( + controller.apply_compaction_bound(OUTPUT, frontier(10)), + Err(CompactionBoundError::WriteOnlyCollection(OUTPUT)) + )); + controller.drop_collections(INSTANCE, vec![OUTPUT]).unwrap(); + assert!(controller.staged_compaction_bounds.is_empty()); + controller.drop_instance(INSTANCE); +} + +#[mz_ore::test(tokio::test(flavor = "multi_thread", worker_threads = 2))] +async fn compaction_proposals_observe_permission_counts_atomically() { + let mut controller = controller(); + controller + .apply_compaction_bound(INPUT, frontier(0)) + .unwrap(); + create_instance(&mut controller); + let mut reader = controller.acquire_read_hold(INSTANCE, INPUT).unwrap(); + reader.try_downgrade(frontier(1000)).unwrap(); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(|instance| { + instance + .collection_mut(INPUT) + .unwrap() + .warmup_read_hold + .try_downgrade(frontier(3000)) + .unwrap(); + instance + .set_read_policy(vec![(INPUT, ReadPolicy::ValidFrom(frontier(2000)))]) + .unwrap(); + drain(instance); + }) + .await; + + for time in 1..=1500 { + controller + .apply_compaction_bound(INPUT, frontier(time)) + .unwrap(); + drop(reader.clone()); + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::from([INPUT]))[&INPUT], + frontier(1000) + ); + } + controller + .apply_compaction_bound(INPUT, Antichain::new()) + .unwrap(); + controller + .apply_compaction_bound(INPUT, frontier(1)) + .unwrap(); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(|instance| { + drain(instance); + let collection = instance.collection(INPUT).unwrap(); + assert_eq!(collection.read_frontier(), frontier(1000)); + assert_eq!(collection.shared.compaction_bound(), Some(Antichain::new())); + }) + .await; + reader.release(); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(drain) + .await; + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new())[&INPUT], + frontier(2000) + ); + controller.drop_instance(INSTANCE); +} + +#[mz_ore::test(tokio::test)] +async fn permanent_exports_are_governed_without_saved_bounds() { + let mut controller = controller(); + controller.catalog_read_protection_enabled = true; + create_instance(&mut controller); + controller + .create_dataflow(INSTANCE, dataflow(7, false), None) + .unwrap(); + let proposals = controller.take_compaction_bound_proposals(&BTreeSet::new()); + assert_eq!(proposals[&INPUT], frontier(0)); + assert_eq!(proposals[&OUTPUT], frontier(7)); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(|instance| { + for (id, time) in [(INPUT, 0), (OUTPUT, 7)] { + let collection = instance.collection_mut(id).unwrap(); + collection.warmup_read_hold.release(); + assert_eq!(collection.shared.compaction_bound(), Some(frontier(time))); + } + instance + .set_read_policy(vec![ + (INPUT, ReadPolicy::ValidFrom(frontier(30))), + (OUTPUT, ReadPolicy::ValidFrom(frontier(30))), + ]) + .unwrap(); + drain(instance); + assert_eq!( + instance.collection(OUTPUT).unwrap().read_frontier(), + frontier(7) + ); + }) + .await; + controller + .apply_compaction_bound(OUTPUT, frontier(3)) + .unwrap(); + let reader = controller.acquire_read_hold(INSTANCE, OUTPUT).unwrap(); + controller + .apply_compaction_bound(OUTPUT, frontier(20)) + .unwrap(); + assert_eq!(reader.since(), &frontier(7)); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(|instance| { + drain(instance); + let collection = instance.collection(OUTPUT).unwrap(); + assert_eq!(collection.shared.compaction_bound(), Some(frontier(20))); + assert_eq!(collection.read_frontier(), frontier(7)); + }) + .await; + drop(reader); + controller.drop_instance(INSTANCE); + + create_instance(&mut controller); + let mut transient = dataflow(7, false); + let export = transient.index_exports.remove(&OUTPUT).unwrap(); + transient + .index_exports + .insert(GlobalId::Transient(1), export); + assert!(matches!( + controller.apply_compaction_bound(GlobalId::Transient(1), frontier(10)), + Err(CompactionBoundError::UngovernedCollection( + GlobalId::Transient(1) + )) + )); + controller + .create_dataflow(INSTANCE, transient, None) + .unwrap(); + controller + .create_dataflow(INSTANCE, dataflow(7, true), None) + .unwrap(); + assert_eq!( + controller + .take_compaction_bound_proposals(&BTreeSet::from([ + INPUT, + OUTPUT, + GlobalId::Transient(1), + ])) + .keys() + .copied() + .collect::>(), + vec![INPUT] + ); + controller.drop_instance(INSTANCE); +} + +#[mz_ore::test(tokio::test)] +async fn replacement_installs_only_at_actual_readability() { + let mut controller = controller(); + controller.catalog_read_protection_enabled = true; + create_instance(&mut controller); + controller + .apply_compaction_bound(INPUT, frontier(10)) + .unwrap(); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(|instance| { + instance + .collection_mut(INPUT) + .unwrap() + .warmup_read_hold + .release(); + instance + .set_read_policy(vec![(INPUT, ReadPolicy::ValidFrom(frontier(10)))]) + .unwrap(); + drain(instance); + assert_eq!( + instance.collection(INPUT).unwrap().read_frontier(), + frontier(10) + ); + }) + .await; + controller + .apply_compaction_bound(OUTPUT, frontier(5)) + .unwrap(); + assert!(matches!( + controller.create_dataflow(INSTANCE, dataflow(11, false), None), + Err(DataflowCreationError::CompactionBoundViolation(OUTPUT)) + )); + assert_eq!(controller.staged_compaction_bounds[&OUTPUT], frontier(5)); + controller + .create_dataflow(INSTANCE, dataflow(10, false), None) + .unwrap(); + controller + .apply_compaction_bound(OUTPUT, frontier(5)) + .unwrap(); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(|instance| { + drain(instance); + let collection = instance.collection(OUTPUT).unwrap(); + assert_eq!(collection.shared.compaction_bound(), Some(frontier(10))); + assert_eq!(collection.read_frontier(), frontier(10)); + }) + .await; + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new())[&OUTPUT], + frontier(10) + ); + controller.drop_instance(INSTANCE); +} + +#[mz_ore::test(tokio::test)] +async fn compaction_proposals_coalesce_and_resample_without_unchanged_redelivery() { + let mut controller = controller(); + controller.catalog_read_protection_enabled = true; + create_instance(&mut controller); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(|instance| { + instance + .collection_mut(INPUT) + .unwrap() + .warmup_read_hold + .release(); + for time in [10, 20, 30] { + instance + .set_read_policy(vec![(INPUT, ReadPolicy::ValidFrom(frontier(time)))]) + .unwrap(); + } + drain(instance); + assert_eq!( + instance.collection(INPUT).unwrap().read_frontier(), + frontier(0) + ); + }) + .await; + let expected = BTreeMap::from([(INPUT, frontier(30))]); + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new()), + expected + ); + assert!( + controller + .take_compaction_bound_proposals(&BTreeSet::new()) + .is_empty() + ); + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::from([INPUT, OUTPUT])), + expected + ); + assert!( + controller + .take_compaction_bound_proposals(&BTreeSet::new()) + .is_empty() + ); + + let mut reader = controller.acquire_read_hold(INSTANCE, INPUT).unwrap(); + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new()), + BTreeMap::from([(INPUT, frontier(0))]) + ); + reader.try_downgrade(frontier(5)).unwrap(); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(drain) + .await; + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new()), + BTreeMap::from([(INPUT, frontier(5))]) + ); + drop(reader); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(drain) + .await; + assert_eq!( + controller.take_compaction_bound_proposals(&BTreeSet::new()), + expected + ); + assert!( + controller + .take_compaction_bound_proposals(&BTreeSet::new()) + .is_empty() + ); + controller.drop_instance(INSTANCE); + assert!( + controller + .take_compaction_bound_proposals(&BTreeSet::from([INPUT])) + .is_empty() + ); +} + +#[mz_ore::test(tokio::test(flavor = "multi_thread", worker_threads = 2))] +async fn compaction_proposals_do_not_lose_concurrent_mutations() { + let mut controller = controller(); + controller.catalog_read_protection_enabled = true; + create_instance(&mut controller); + controller + .instance(INSTANCE) + .unwrap() + .call_sync(|instance| { + instance + .collection_mut(INPUT) + .unwrap() + .warmup_read_hold + .release(); + drain(instance); + }) + .await; + controller.take_compaction_bound_proposals(&BTreeSet::new()); + + let barrier = Arc::new(std::sync::Barrier::new(2)); + let updater_barrier = Arc::clone(&barrier); + controller + .instance(INSTANCE) + .unwrap() + .call(move |instance| { + for time in 1..=100 { + updater_barrier.wait(); + instance + .set_read_policy(vec![(INPUT, ReadPolicy::ValidFrom(frontier(time)))]) + .unwrap(); + drain(instance); + updater_barrier.wait(); + } + }); + let mut observed = Vec::new(); + for time in 1..=100 { + barrier.wait(); + let mut proposals = controller.take_compaction_bound_proposals(&BTreeSet::new()); + barrier.wait(); + // A racing change must appear in the racing sample or remain dirty afterward. + proposals.extend(controller.take_compaction_bound_proposals(&BTreeSet::new())); + observed.push((time, proposals)); + } + for (time, proposals) in observed { + assert_eq!(proposals, BTreeMap::from([(INPUT, frontier(time))])); + } + assert!( + controller + .take_compaction_bound_proposals(&BTreeSet::new()) + .is_empty() + ); + controller.drop_instance(INSTANCE); +} + +#[derive(Debug)] +struct EmptyStorage; + +#[async_trait] +impl StorageCollections for EmptyStorage { + async fn initialize_state( + &self, + _txn: &mut (dyn StorageTxn + Send), + _init_ids: BTreeSet, + ) -> Result<(), StorageError> { + unimplemented!("storage is not used by these compute tests") + } + + fn update_parameters(&self, _config_params: StorageParameters) { + unimplemented!("storage is not used by these compute tests") + } + + fn collection_metadata(&self, _id: GlobalId) -> Result { + unimplemented!("storage is not used by these compute tests") + } + + fn active_collection_metadatas(&self) -> Vec<(GlobalId, CollectionMetadata)> { + unimplemented!("storage is not used by these compute tests") + } + + fn collections_frontiers( + &self, + ids: Vec, + ) -> Result, CollectionMissing> { + match ids.first() { + Some(id) => Err(CollectionMissing(*id)), + None => Ok(Vec::new()), + } + } + + fn active_collection_frontiers(&self) -> Vec { + unimplemented!("storage is not used by these compute tests") + } + + fn take_read_protection_frontiers( + &self, + _additional_ids: &BTreeSet, + ) -> BTreeMap)> { + unimplemented!("storage is not used by these compute tests") + } + + fn check_exists(&self, _id: GlobalId) -> Result<(), StorageError> { + unimplemented!("storage is not used by these compute tests") + } + + async fn snapshot_stats( + &self, + _id: GlobalId, + _as_of: Antichain, + ) -> Result { + unimplemented!("storage is not used by these compute tests") + } + + async fn snapshot_parts_stats( + &self, + _id: GlobalId, + _as_of: Antichain, + ) -> BoxFuture<'static, Result> { + unimplemented!("storage is not used by these compute tests") + } + + fn snapshot( + &self, + _id: GlobalId, + _as_of: Timestamp, + ) -> BoxFuture<'static, Result, StorageError>> { + unimplemented!("storage is not used by these compute tests") + } + + async fn snapshot_latest(&self, _id: GlobalId) -> Result, StorageError> { + unimplemented!("storage is not used by these compute tests") + } + + fn snapshot_cursor( + &self, + _id: GlobalId, + _as_of: Timestamp, + ) -> BoxFuture<'static, Result> { + unimplemented!("storage is not used by these compute tests") + } + + fn snapshot_and_stream( + &self, + _id: GlobalId, + _as_of: Timestamp, + ) -> BoxFuture< + 'static, + Result, StorageError>, + > { + unimplemented!("storage is not used by these compute tests") + } + + fn create_update_builder( + &self, + _id: GlobalId, + ) -> BoxFuture< + 'static, + Result, StorageError>, + > { + unimplemented!("storage is not used by these compute tests") + } + + async fn prepare_state( + &self, + _txn: &mut (dyn StorageTxn + Send), + _ids_to_add: BTreeSet, + _ids_to_drop: BTreeSet, + _ids_to_register: BTreeMap, + ) -> Result<(), StorageError> { + unimplemented!("storage is not used by these compute tests") + } + + async fn create_collections_for_bootstrap( + &self, + _storage_metadata: &StorageMetadata, + _register_ts: Option, + _collections: Vec<(GlobalId, CollectionDescription)>, + _migrated_storage_collections: &BTreeSet, + ) -> Result<(), StorageError> { + unimplemented!("storage is not used by these compute tests") + } + + async fn alter_table_desc( + &self, + _storage_metadata: &StorageMetadata, + _existing_collection: GlobalId, + _new_collection: GlobalId, + _new_desc: RelationDesc, + _expected_version: RelationVersion, + ) -> Result<(), StorageError> { + unimplemented!("storage is not used by these compute tests") + } + + fn drop_collections_unvalidated( + &self, + _storage_metadata: &StorageMetadata, + _identifiers: Vec, + ) { + unimplemented!("storage is not used by these compute tests") + } + + fn set_read_policies(&self, _policies: Vec<(GlobalId, ReadPolicy)>) { + unimplemented!("storage is not used by these compute tests") + } + + fn apply_compaction_bounds( + &self, + _bounds: BTreeMap>, + ) -> Result<(), StorageError> { + unimplemented!("storage is not used by these compute tests") + } + + fn compaction_bound(&self, id: GlobalId) -> Result>, StorageError> { + Err(StorageError::IdentifierMissing(id)) + } + + fn acquire_read_holds( + &self, + desired_holds: Vec, + ) -> Result, CollectionMissing> { + assert!(desired_holds.is_empty()); + Ok(Vec::new()) + } + + fn determine_time_dependence( + &self, + _id: GlobalId, + ) -> Result, TimeDependenceError> { + unimplemented!("storage is not used by these compute tests") + } + + fn dump(&self) -> Result { + unimplemented!("storage is not used by these compute tests") + } +} diff --git a/src/compute-client/src/controller/error.rs b/src/compute-client/src/controller/error.rs index b77da16abac14..cdcd0e1487396 100644 --- a/src/compute-client/src/controller/error.rs +++ b/src/compute-client/src/controller/error.rs @@ -119,6 +119,9 @@ pub enum DataflowCreationError { /// One of the imported collections has a read frontier greater than the dataflow `as_of`. #[error("dataflow has an as_of not beyond the since of collection: {0}")] SinceViolation(GlobalId), + /// An index's initial since exceeds its committed compaction permission. + #[error("dataflow as_of exceeds the compaction bound of index: {0}")] + CompactionBoundViolation(GlobalId), /// We skip dataflow creation for empty `as_of`s, which would be a problem for a SUBSCRIBE, /// because an initial response is expected. #[error("subscribe dataflow has an empty as_of")] @@ -196,6 +199,17 @@ impl From for CollectionUpdateError { } } +/// Errors arising during committed compaction bound application. +#[derive(Error, Debug)] +pub enum CompactionBoundError { + /// Bounds must be installed before exposing a readable collection. + #[error("live collection has no compaction bound: {0}")] + UngovernedCollection(GlobalId), + /// Write-only exports have no readable compute trace to govern. + #[error("collection is write-only: {0}")] + WriteOnlyCollection(GlobalId), +} + /// Errors arising during collection read policy assignment. #[derive(Error, Debug)] pub enum ReadPolicyError { diff --git a/src/compute-client/src/controller/instance.rs b/src/compute-client/src/controller/instance.rs index 2f444d89f4755..9e8ada38e4a2b 100644 --- a/src/compute-client/src/controller/instance.rs +++ b/src/compute-client/src/controller/instance.rs @@ -15,6 +15,7 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use chrono::{DateTime, DurationRound, TimeDelta, Utc}; +use differential_dataflow::lattice::Lattice; use mz_build_info::BuildInfo; use mz_cluster_client::WallclockLagFn; use mz_compute_types::dataflows::{BuildDesc, DataflowDescription}; @@ -1477,7 +1478,7 @@ impl Instance { for export_id in dataflow.export_ids() { let shared = shared_collection_state .remove(&export_id) - .unwrap_or_else(|| SharedCollectionState::new(as_of.clone())); + .unwrap_or_else(|| SharedCollectionState::new(as_of.clone(), None)); let write_only = dataflow.sink_exports.contains_key(&export_id); let storage_sink = dataflow.persist_sink_ids().any(|id| id == export_id); @@ -1742,6 +1743,8 @@ impl Instance { // If the collection is a copy to, stop tracking it. This ensures that the controller // ceases to produce `CopyToResponse`s` for this copy to. self.copy_tos.remove(id); + + self.update_read_capabilities(*id, |caps| caps.replace_permission(None)); } Ok(()) @@ -1872,6 +1875,12 @@ impl Instance { let new_since = new_policy.frontier(collection.write_frontier().borrow()); let _ = collection.implied_read_hold.try_downgrade(new_since); collection.read_policy = Some(new_policy); + collection + .shared + .read_capabilities + .lock() + .expect("poisoned") + .mark_compaction_proposal_dirty(); } Ok(()) @@ -1937,19 +1946,44 @@ impl Instance { } /// Apply a collection read hold change. - pub(super) fn apply_read_hold_change( + pub(super) fn apply_read_hold_change(&mut self, id: GlobalId, update: ChangeBatch) { + self.update_read_capabilities(id, |_| update); + } + + pub(super) fn apply_compaction_bound(&mut self, id: GlobalId, bound: Antichain) { + self.update_read_capabilities(id, |caps| { + let permission = caps.permission.as_ref().expect("installed governed index"); + if PartialOrder::less_than(permission, &bound) { + caps.replace_permission(Some(bound)) + } else { + ChangeBatch::new() + } + }); + } + + fn update_read_capabilities( &mut self, id: GlobalId, - mut update: ChangeBatch, + update: impl FnOnce(&mut ReadCapabilities) -> ChangeBatch, ) { let Some(collection) = self.collections.get_mut(&id) else { - soft_panic_or_log!( - "read hold change for absent collection (id={id}, changes={update:?})" - ); + soft_panic_or_log!("read capability change for absent collection (id={id})"); return; }; - let new_since = collection.shared.lock_read_capabilities(|caps| { + let new_since = { + let mut state = collection + .shared + .read_capabilities + .lock() + .expect("poisoned"); + // Permission metadata and its count must change under the same lock. Otherwise a + // concurrent proposal could subtract a contribution that is not in the counts yet. + let mut update = update(&mut state); + if !update.is_empty() { + state.mark_compaction_proposal_dirty(); + } + let caps = &mut state.holds; // Sanity check to prevent corrupted `read_capabilities`, which can cause hard-to-debug // issues (usually stuck read frontiers). let read_frontier = caps.frontier(); @@ -1973,13 +2007,14 @@ impl Instance { let changed = changes.count() > 0; changed.then(|| caps.frontier().to_owned()) - }); + }; let Some(new_since) = new_since else { return; // read frontier did not change }; - // Propagate read frontier update to dependencies. + // Catalog permission must constrain this frontier before releasing dependencies, not + // merely clip the command. Replicas need these inputs to recover the readable trace. for read_hold in collection.compute_dependencies.values_mut() { read_hold .try_downgrade(new_since.clone()) @@ -2466,7 +2501,7 @@ impl Instance { // compute dependencies at frontiers that are held back by other read holds the caller // has previously taken. let collection = self.collection(id)?; - let since = collection.shared.lock_read_capabilities(|caps| { + let since = collection.shared.mutate_read_capabilities(|caps| { let since = caps.frontier().to_owned(); caps.update_iter(since.iter().map(|t| (t.clone(), 1))); since @@ -2601,7 +2636,7 @@ impl CollectionState { let warmup_read_hold = ReadHold::new(collection_id, since.clone(), read_hold_tx); let updates = warmup_read_hold.since().iter().map(|t| (t.clone(), 1)); - shared.lock_read_capabilities(|c| { + shared.mutate_read_capabilities(|c| { c.update_iter(updates); }); @@ -2696,23 +2731,24 @@ impl CollectionState { pub(super) struct SharedCollectionState { /// Accumulation of read capabilities for the collection. /// - /// This accumulation contains the capabilities held by all [`ReadHold`]s given out for the - /// collection, including `implied_read_hold` and `warmup_read_hold`. + /// This accumulation contains all [`ReadHold`] capabilities, including `implied_read_hold` + /// and `warmup_read_hold`, plus the catalog permission contribution. /// - /// NOTE: This field may only be modified by [`Instance::apply_read_hold_change`], + /// NOTE: This field may only be modified by [`Instance::update_read_capabilities`], /// [`Instance::acquire_read_hold`], and `ComputeController::acquire_read_hold`. /// Nobody else should modify read capabilities directly. Instead, collection users should /// manage read holds through [`ReadHold`] objects acquired through /// `ComputeController::acquire_read_hold`. /// /// TODO(teskje): Restructure the code to enforce the above in the type system. - read_capabilities: Arc>>, + read_capabilities: Arc>, /// The write frontier of this collection. write_frontier: Arc>>, } impl SharedCollectionState { - pub fn new(as_of: Antichain) -> Self { + pub fn new(as_of: Antichain, permission: Option>) -> Self { + let permission = permission.map(|bound| bound.join(&as_of)); // A collection is not readable before the `as_of`. let since = as_of.clone(); // A collection won't produce updates for times before the `as_of`. @@ -2723,19 +2759,65 @@ impl SharedCollectionState { // [`CollectionState::new`]. let mut read_capabilities = MutableAntichain::new(); read_capabilities.update_iter(since.iter().map(|time| (time.clone(), 1))); + if let Some(bound) = &permission { + read_capabilities.update_iter(bound.iter().map(|time| (*time, 1))); + } Self { - read_capabilities: Arc::new(Mutex::new(read_capabilities)), + read_capabilities: Arc::new(Mutex::new(ReadCapabilities { + holds: read_capabilities, + permission, + dirty_compaction_proposals: None, + })), write_frontier: Arc::new(Mutex::new(upper)), } } pub fn lock_read_capabilities(&self, f: F) -> R + where + F: FnOnce(&MutableAntichain) -> R, + { + let caps = self.read_capabilities.lock().expect("poisoned"); + f(&caps.holds) + } + + pub fn mutate_read_capabilities(&self, f: F) -> R where F: FnOnce(&mut MutableAntichain) -> R, { let mut caps = self.read_capabilities.lock().expect("poisoned"); - f(&mut *caps) + let result = f(&mut caps.holds); + caps.mark_compaction_proposal_dirty(); + result + } + + pub fn track_compaction_proposals(&self, id: GlobalId, dirty: Arc>>) { + let mut caps = self.read_capabilities.lock().expect("poisoned"); + if caps.permission.is_some() { + caps.dirty_compaction_proposals = Some((id, dirty)); + caps.mark_compaction_proposal_dirty(); + } + } + + pub fn compaction_bound(&self) -> Option> { + self.read_capabilities + .lock() + .expect("poisoned") + .permission + .clone() + } + + pub fn take_compaction_bound_proposal(&self) -> Option> { + let caps = self.read_capabilities.lock().expect("poisoned"); + // Clear under the snapshot lock so a mutation cannot be acknowledged without + // being sampled. + if let Some((id, dirty)) = &caps.dirty_compaction_proposals { + dirty.lock().expect("poisoned").remove(id); + } + let permission = caps.permission.as_ref()?; + let mut holds = caps.holds.clone(); + holds.update_iter(permission.iter().map(|time| (*time, -1))); + Some(holds.frontier().to_owned()) } pub fn lock_write_frontier(&self, f: F) -> R @@ -2747,6 +2829,38 @@ impl SharedCollectionState { } } +#[derive(Debug)] +struct ReadCapabilities { + holds: MutableAntichain, + permission: Option>, + dirty_compaction_proposals: Option<(GlobalId, Arc>>)>, +} + +impl ReadCapabilities { + fn mark_compaction_proposal_dirty(&self) { + if let Some((id, dirty)) = &self.dirty_compaction_proposals { + dirty.lock().expect("poisoned").insert(*id); + } + } + + /// Replace permission metadata and return the corresponding count changes. + /// The caller must apply these changes before releasing the capability lock. + fn replace_permission( + &mut self, + permission: Option>, + ) -> ChangeBatch { + let mut changes = ChangeBatch::new(); + if let Some(old) = self.permission.take() { + changes.extend(old.into_iter().map(|time| (time, -1))); + } + if let Some(new) = &permission { + changes.extend(new.iter().map(|time| (*time, 1))); + } + self.permission = permission; + changes + } +} + /// Manages certain introspection relations associated with a collection. Upon creation, it adds /// rows to introspection relations. When dropped, it retracts its managed rows. #[derive(Debug)] @@ -3429,6 +3543,10 @@ impl Drop for ReplicaCollectionIntrospection { } } +#[cfg(test)] +#[path = "compaction_tests.rs"] +mod compaction_tests; + #[cfg(test)] mod tests { use std::collections::BTreeMap; diff --git a/src/controller/src/lib.rs b/src/controller/src/lib.rs index 6e675eb3001d1..3584aec39f2b7 100644 --- a/src/controller/src/lib.rs +++ b/src/controller/src/lib.rs @@ -677,6 +677,7 @@ impl Controller { config: ControllerConfig, envd_epoch: NonZeroI64, read_only: bool, + catalog_read_protection_enabled: bool, storage_txn: &dyn StorageTxn, ) -> Self { if read_only { @@ -725,6 +726,7 @@ impl Controller { config.build_info, storage_collections, read_only, + catalog_read_protection_enabled, &config.metrics_registry, config.persist_location, controller_metrics, diff --git a/src/environmentd/src/http/catalog.rs b/src/environmentd/src/http/catalog.rs index b346a1cecb713..aa7165ee4673c 100644 --- a/src/environmentd/src/http/catalog.rs +++ b/src/environmentd/src/http/catalog.rs @@ -10,6 +10,7 @@ //! Catalog introspection HTTP endpoints. use axum::Json; +use axum::extract::Query; use axum::response::IntoResponse; use axum_extra::TypedHeader; use headers::ContentType; @@ -19,17 +20,31 @@ use mz_adapter::catalog::InjectedAuditEvent; use crate::http::AuthedClient; pub async fn handle_catalog_dump(mut client: AuthedClient) -> impl IntoResponse { - match client.client.dump_catalog().await.map(|c| c.into_string()) { - Ok(res) => Ok((TypedHeader(ContentType::json()), res)), + match client.client.dump_catalog().await { + Ok(dump) => Ok((TypedHeader(ContentType::json()), dump.into_string())), Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), } } -pub async fn handle_catalog_check(mut client: AuthedClient) -> impl IntoResponse { - let response = match client.client.check_catalog().await { +#[derive(Default, serde::Deserialize)] +pub struct CatalogCheckParams { + #[serde(default)] + durable: bool, +} + +pub async fn handle_catalog_check( + mut client: AuthedClient, + Query(params): Query, +) -> impl IntoResponse { + let response = match client.client.check_catalog(params.durable).await { Ok(_) => serde_json::Value::String("".to_string()), Err(inconsistencies) => serde_json::json!({ "err": inconsistencies }), }; + let response = if params.durable { + serde_json::json!({ "durable": true, "inconsistencies": response }) + } else { + response + }; (TypedHeader(ContentType::json()), response.to_string()) } diff --git a/src/environmentd/src/lib.rs b/src/environmentd/src/lib.rs index 1f0ab1fa41eea..a11d88e10c6fa 100644 --- a/src/environmentd/src/lib.rs +++ b/src/environmentd/src/lib.rs @@ -672,7 +672,7 @@ impl Listeners { let preflight_config = PreflightInput { boot_ts, environment_id: config.environment_id.clone(), - persist_client, + persist_client: persist_client.clone(), deploy_generation: config.controller.deploy_generation, deployment_state: deployment_state.clone(), openable_adapter_storage, @@ -704,7 +704,7 @@ impl Listeners { }; // Load the adapter durable storage. - let adapter_storage = if read_only { + let mut adapter_storage = if read_only { // TODO: behavior of migrations when booting in savepoint mode is // not well defined. let adapter_storage = openable_adapter_storage @@ -728,6 +728,36 @@ impl Listeners { adapter_storage }; + let compaction_bound_subscriber = if read_only + && adapter_storage + .snapshot() + .await? + .configs + .iter() + .any(|(key, value)| { + key.key == "catalog_read_protection_enabled" && value.value != 0 + }) { + // The savepoint reconstructs SQL but cannot follow durable updates. Only + // environments born with read protection can use an independent reader. + let subscriber = mz_catalog::durable::persist_backed_catalog_state( + persist_client, + config.environment_id.organization_id(), + BUILD_INFO.semver_version(), + // Adopt the writer's generation and epoch so promotion fences this reader. + None, + Arc::clone(&config.catalog_config.metrics), + ) + .await? + .open_read_only(&bootstrap_args) + .await + .context( + "opening protected catalog subscriber, which requires no catalog migrations", + )?; + Some(subscriber) + } else { + None + }; + // Enable Persist compaction if we're not in read only. if !read_only { config.controller.persist_clients.cfg().enable_compaction(); @@ -780,6 +810,7 @@ impl Listeners { controller_config: config.controller, controller_envd_epoch: envd_epoch, storage: adapter_storage, + compaction_bound_subscriber, timestamp_oracle_url: config.timestamp_oracle_url, unsafe_mode: config.unsafe_mode, all_features: config.all_features, diff --git a/src/sql/src/session/vars/definitions.rs b/src/sql/src/session/vars/definitions.rs index 407fe531dc17b..68a19cfa3819c 100644 --- a/src/sql/src/session/vars/definitions.rs +++ b/src/sql/src/session/vars/definitions.rs @@ -960,11 +960,10 @@ pub static CATALOG_READ_PROTECTION_PUBLISH_INTERVAL: VarDefinition = VarDefiniti "catalog_read_protection_publish_interval", value!(Duration; Duration::from_secs(1)), "Sets the minimum delay after a catalog read protection publication completes before \ - starting the next publication. Zero suspends publication and retains history without \ - releasing protection. Used to measure the publication cadence and history retention \ - tradeoff and to quiesce periodic metadata for consistency checks.", + starting the next publication. Longer intervals retain additional history.", false, -); +) +.with_constraint(&NON_ZERO_DURATION); /// Controls the connect_timeout setting when connecting to PG via `mz_postgres_util`. pub static PG_SOURCE_CONNECT_TIMEOUT: VarDefinition = VarDefinition::new( diff --git a/src/storage-client/src/client.rs b/src/storage-client/src/client.rs index 65fbdddd3765e..873fea593c149 100644 --- a/src/storage-client/src/client.rs +++ b/src/storage-client/src/client.rs @@ -131,6 +131,11 @@ pub struct RunIngestionCommand { /// The description of what source type should be ingested and what post-processing steps must /// be applied to the data before writing them down into the storage collection pub description: IngestionDescription, + /// Committed compaction permission for the remap collection, used as a lower bound on + /// ingestion as-of even when physical compaction lags. `None` denotes a legacy collection. + /// Must not exceed any unfinished ordinary export's recovery requirement. + #[serde(default)] + pub remap_compaction_bound: Option>, } /// A command that starts ingesting the given ingestion description diff --git a/src/storage-client/src/storage_collections.rs b/src/storage-client/src/storage_collections.rs index 25a32cfe7a322..feeb3d198259b 100644 --- a/src/storage-client/src/storage_collections.rs +++ b/src/storage-client/src/storage_collections.rs @@ -130,11 +130,18 @@ pub trait StorageCollections: Debug + Sync { /// capabilities. fn active_collection_frontiers(&self) -> Vec; - /// Snapshots local read requirements, excluding each collection's own writable permission cap. - /// Dependency-propagated capabilities remain included. This reserves no protection. - /// Callers must serialize new local read requirements with publication or reconcile them - /// before committing, and must also constrain proposals by durable read requirements. - fn compaction_frontiers(&self) -> BTreeMap>; + /// Atomically drains changed governed collections and samples `additional_ids`. + /// Each entry contains current frontiers and a proposal excluding only the collection's + /// own writable permission cap. Dependency and execution holds remain included. + /// This reserves no protection. Callers must serialize new local requirements with + /// publication and constrain proposals by durable requirements. + /// + /// Missing, dropped, and ungoverned IDs are omitted. Changes are coalesced across + /// clones. After failed publication, request the affected IDs again in `additional_ids`. + fn take_read_protection_frontiers( + &self, + additional_ids: &BTreeSet, + ) -> BTreeMap)>; /// Checks whether a collection exists under the given `GlobalId`. Returns /// an error if the collection does not exist. @@ -319,6 +326,10 @@ pub trait StorageCollections: Debug + Sync { bounds: BTreeMap>, ) -> Result<(), StorageError>; + /// Returns the installed committed compaction permission, or `None` for a legacy collection. + /// This is not the physical since or a read hold. + fn compaction_bound(&self, id: GlobalId) -> Result>, StorageError>; + /// Acquires and returns the earliest possible read holds for the specified /// collections. fn acquire_read_holds( @@ -356,7 +367,7 @@ impl SnapshotCursor { } /// Frontiers of the collection identified by `id`. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CollectionFrontiers { /// The [GlobalId] of the collection that these frontiers belong to. pub id: GlobalId, @@ -405,6 +416,10 @@ pub struct StorageCollectionsImpl { /// Collections maintained by this [StorageCollections]. collections: Arc>>, + /// Access only while holding `collections`, before locking this set. This makes + /// draining changes atomic with sampling and prevents lost concurrent updates. + dirty_read_protection: Arc>>, + /// A shared TxnsCache running in a task and communicated with over a channel. txns_read: TxnsRead, @@ -514,6 +529,7 @@ impl StorageCollectionsImpl { let txns_read = TxnsRead::start::(txns_client.clone(), txns_id).await; let collections = Arc::new(std::sync::Mutex::new(BTreeMap::default())); + let dirty_read_protection = Arc::new(Mutex::new(BTreeSet::new())); let finalizable_shards = Arc::new(ShardIdSet::new(metrics.finalization_outstanding.clone())); let finalized_shards = @@ -533,6 +549,7 @@ impl StorageCollectionsImpl { cmds_rx: cmd_rx, holds_rx, collections: Arc::clone(&collections), + dirty_read_protection: Arc::clone(&dirty_read_protection), finalizable_shards: Arc::clone(&finalizable_shards), shard_by_id: BTreeMap::new(), since_handles: BTreeMap::new(), @@ -564,6 +581,7 @@ impl StorageCollectionsImpl { finalizable_shards, finalized_shards, collections, + dirty_read_protection, txns_read, envd_epoch, read_only, @@ -956,6 +974,7 @@ impl StorageCollectionsImpl { StorageCollectionsImpl::update_read_capabilities_inner( &self.cmd_tx, + &self.dirty_read_protection, self_collections, &mut storage_read_updates, ); @@ -1177,6 +1196,12 @@ impl StorageCollectionsImpl { } collection.read_policy = policy; + if collection.compaction_bound.is_some() { + self.dirty_read_protection + .lock() + .expect("lock poisoned") + .insert(id); + } } for (id, changes) in read_capability_changes.iter() { @@ -1188,6 +1213,7 @@ impl StorageCollectionsImpl { if !read_capability_changes.is_empty() { StorageCollectionsImpl::update_read_capabilities_inner( &self.cmd_tx, + &self.dirty_read_protection, collections, &mut read_capability_changes, ); @@ -1199,6 +1225,7 @@ impl StorageCollectionsImpl { // collections state. fn update_read_capabilities_inner( cmd_tx: &mpsc::UnboundedSender, + dirty_read_protection: &Mutex>, collections: &mut BTreeMap, updates: &mut BTreeMap>, ) { @@ -1254,6 +1281,14 @@ impl StorageCollectionsImpl { } } + // NOTE: A capability change can advance the proposal even when the + // catalog bound keeps the effective read frontier unchanged. + if !update.is_empty() && collection.compaction_bound.is_some() { + dirty_read_protection + .lock() + .expect("lock poisoned") + .insert(id); + } let changes = collection.read_capabilities.update_iter(update.drain()); update.extend(changes); @@ -1465,19 +1500,32 @@ impl StorageCollections for StorageCollectionsImpl { res } - fn compaction_frontiers(&self) -> BTreeMap> { + fn take_read_protection_frontiers( + &self, + additional_ids: &BTreeSet, + ) -> BTreeMap)> { let collections = self.collections.lock().expect("lock poisoned"); - collections - .iter() - .filter(|(_, collection)| !collection.is_dropped()) - .map(|(id, collection)| { + let mut ids = + std::mem::take(&mut *self.dirty_read_protection.lock().expect("lock poisoned")); + ids.extend(additional_ids); + ids.into_iter() + .filter_map(|id| { + let collection = collections.get(&id)?; + let bound = collection.compaction_bound.as_ref()?; + if collection.is_dropped() { + return None; + } + let frontiers = CollectionFrontiers { + id, + write_frontier: collection.write_frontier.clone(), + implied_capability: collection.implied_capability.clone(), + read_capabilities: collection.read_capabilities.frontier().to_owned(), + }; let mut capabilities = collection.read_capabilities.clone(); - if !self.read_only - && let Some(bound) = &collection.compaction_bound - { + if !self.read_only { capabilities.update_iter(bound.iter().map(|time| (*time, -1))); } - (*id, capabilities.frontier().to_owned()) + Some((id, (frontiers, capabilities.frontier().to_owned()))) }) .collect() } @@ -2175,6 +2223,13 @@ impl StorageCollections for StorageCollectionsImpl { collection_state.compaction_bound = Some(bound.clone()); } + if collection_state.compaction_bound.is_some() { + self.dirty_read_protection + .lock() + .expect("lock poisoned") + .insert(id); + } + // Install the collection state in the appropriate spot. match &description.data_source { DataSource::Introspection(_) => { @@ -2412,12 +2467,20 @@ impl StorageCollections for StorageCollectionsImpl { collection_state.compaction_bound = Some(bound.clone()); } + if collection_state.compaction_bound.is_some() { + self.dirty_read_protection + .lock() + .expect("lock poisoned") + .insert(new_collection); + } + // Add a record of the new collection. self_collections.insert(new_collection, collection_state); let mut updates = BTreeMap::from([(new_collection, changes)]); StorageCollectionsImpl::update_read_capabilities_inner( &self.cmd_tx, + &self.dirty_read_protection, &mut *self_collections, &mut updates, ); @@ -2495,6 +2558,10 @@ impl StorageCollections for StorageCollectionsImpl { finalized_policies.push((id, ReadPolicy::ValidFrom(Antichain::new()))); let collection = self_collections.get_mut(&id).expect("checked above"); + self.dirty_read_protection + .lock() + .expect("lock poisoned") + .remove(&id); if let Some(bound) = collection.compaction_bound.take() && !self.read_only { @@ -2507,6 +2574,7 @@ impl StorageCollections for StorageCollectionsImpl { self.set_read_policies_inner(&mut self_collections, finalized_policies); Self::update_read_capabilities_inner( &self.cmd_tx, + &self.dirty_read_protection, &mut self_collections, &mut bound_releases, ); @@ -2583,10 +2651,23 @@ impl StorageCollections for StorageCollectionsImpl { changes.extend(bound.iter().map(|t| (*t, 1))); updates.insert(id, changes); } - Self::update_read_capabilities_inner(&self.cmd_tx, &mut collections, &mut updates); + Self::update_read_capabilities_inner( + &self.cmd_tx, + &self.dirty_read_protection, + &mut collections, + &mut updates, + ); Ok(()) } + fn compaction_bound(&self, id: GlobalId) -> Result>, StorageError> { + let collections = self.collections.lock().expect("lock poisoned"); + let collection = collections + .get(&id) + .ok_or(StorageError::IdentifierMissing(id))?; + Ok(collection.compaction_bound.clone()) + } + fn acquire_read_holds( &self, desired_holds: Vec, @@ -2625,6 +2706,7 @@ impl StorageCollections for StorageCollectionsImpl { StorageCollectionsImpl::update_read_capabilities_inner( &self.cmd_tx, + &self.dirty_read_protection, &mut collections, &mut updates, ); @@ -2658,6 +2740,7 @@ impl StorageCollections for StorageCollectionsImpl { finalizable_shards, finalized_shards, collections, + dirty_read_protection: _, txns_read: _, config, initial_txn_upper, @@ -2912,6 +2995,7 @@ struct BackgroundTask { holds_rx: mpsc::UnboundedReceiver<(GlobalId, ChangeBatch)>, finalizable_shards: Arc, collections: Arc>>, + dirty_read_protection: Arc>>, // So we know what shard ID corresponds to what global ID, which we need // when re-enqueing futures for determining the next upper update. shard_by_id: BTreeMap, @@ -3165,6 +3249,7 @@ impl BackgroundTask { StorageCollectionsImpl::update_read_capabilities_inner( &self.cmds_tx, + &self.dirty_read_protection, &mut collections, &mut batched_changes, ); @@ -3193,6 +3278,12 @@ impl BackgroundTask { if PartialOrder::less_than(&collection.write_frontier, *new_upper) { collection.write_frontier.clone_from(new_upper); + if collection.compaction_bound.is_some() { + self.dirty_read_protection + .lock() + .expect("lock poisoned") + .insert(*id); + } } let mut new_read_capability = collection @@ -3224,6 +3315,7 @@ impl BackgroundTask { if !read_capability_changes.is_empty() { StorageCollectionsImpl::update_read_capabilities_inner( &self.cmds_tx, + &self.dirty_read_protection, &mut self_collections, &mut read_capability_changes, ); @@ -3687,6 +3779,321 @@ mod tests { .expect("read frontier did not converge"); } + #[mz_ore::test(tokio::test)] + async fn test_dependency_creation_uses_held_readability() { + for governed in [false, true] { + check_creation_uses_held_readability(governed, false).await; + } + } + + #[mz_ore::test(tokio::test)] + async fn test_sink_creation_uses_held_readability() { + for governed in [false, true] { + check_creation_uses_held_readability(governed, true).await; + } + } + + async fn check_creation_uses_held_readability(governed: bool, sink: bool) { + let (controller, _persist) = bound_test_controller().await; + let input = GlobalId::User(1); + let dependent = GlobalId::User(2); + let shard = ShardId::new(); + let mut metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(input, shard)]), + compaction_bounds: if governed { + BTreeMap::from([(input, frontier(20))]) + } else { + BTreeMap::new() + }, + ..Default::default() + }; + controller + .create_collections_for_bootstrap( + &metadata, + None, + vec![( + input, + CollectionDescription::for_other(RelationDesc::empty(), Some(frontier(5))), + )], + &BTreeSet::new(), + ) + .await + .unwrap(); + let holds = controller.acquire_read_holds(vec![input]).unwrap(); + controller.set_read_policies(vec![(input, ReadPolicy::ValidFrom(frontier(20)))]); + let frontiers = controller.collection_frontiers(input).unwrap(); + assert_eq!(frontiers.implied_capability, frontier(20)); + assert_eq!(frontiers.read_capabilities, frontier(5)); + assert_eq!( + controller.compaction_bound(input).unwrap(), + governed.then(|| frontier(20)) + ); + + metadata + .collection_metadata + .insert(dependent, if sink { ShardId::new() } else { shard }); + if governed { + metadata.compaction_bounds.insert(dependent, frontier(20)); + } + let mut description = CollectionDescription::for_other(RelationDesc::empty(), None); + if sink { + description.data_source = held_readability_sink(input); + } else { + description.primary = Some(input); + } + controller + .create_collections_for_bootstrap( + &metadata, + None, + vec![(dependent, description)], + &BTreeSet::new(), + ) + .await + .unwrap(); + assert_eq!( + controller + .collection_frontiers(dependent) + .unwrap() + .read_capabilities, + frontier(5), + "creation must use held readability, governed={governed}, sink={sink}" + ); + drop(holds); + } + + fn held_readability_sink(from: GlobalId) -> DataSource { + use mz_storage_types::connections::{KafkaConnection, Tunnel}; + use mz_storage_types::sinks::{ + KafkaIdStyle, KafkaSinkCompressionType, KafkaSinkConnection, KafkaSinkFormat, + KafkaSinkFormatType, SinkEnvelope, StorageSinkConnection, StorageSinkDesc, + }; + + DataSource::Sink { + desc: crate::controller::ExportDescription { + sink: StorageSinkDesc { + from, + from_desc: RelationDesc::empty(), + connection: StorageSinkConnection::Kafka(KafkaSinkConnection { + connection_id: mz_repr::CatalogItemId::System(1), + connection: KafkaConnection { + brokers: Default::default(), + default_tunnel: Tunnel::Direct, + progress_topic: Default::default(), + progress_topic_options: Default::default(), + options: Default::default(), + tls: Default::default(), + sasl: Default::default(), + }, + format: KafkaSinkFormat { + key_format: None, + value_format: KafkaSinkFormatType::Text, + }, + relation_key_indices: None, + key_desc_and_indices: None, + headers_index: None, + value_desc: RelationDesc::empty(), + partition_by: Default::default(), + topic: Default::default(), + topic_options: Default::default(), + compression_type: KafkaSinkCompressionType::None, + progress_group_id: KafkaIdStyle::Legacy, + transactional_id: KafkaIdStyle::Legacy, + topic_metadata_refresh_interval: Default::default(), + }), + with_snapshot: true, + version: 0, + envelope: SinkEnvelope::Upsert, + as_of: frontier(5), + from_storage_metadata: (), + to_storage_metadata: (), + commit_interval: Default::default(), + }, + instance_id: mz_storage_types::instances::StorageInstanceId::system(1).unwrap(), + }, + } + } + + #[mz_ore::test(tokio::test)] + async fn test_take_read_protection_frontiers() { + let (controller, persist) = bound_test_controller().await; + let id = GlobalId::User(1); + let unchanged = GlobalId::User(2); + let ungoverned = GlobalId::User(3); + let missing = GlobalId::User(4); + let shard = ShardId::new(); + let metadata = StorageMetadata { + collection_metadata: BTreeMap::from([ + (id, shard), + (unchanged, ShardId::new()), + (ungoverned, ShardId::new()), + ]), + compaction_bounds: BTreeMap::from([(id, frontier(5)), (unchanged, frontier(5))]), + ..Default::default() + }; + controller + .create_collections_for_bootstrap( + &metadata, + None, + [id, unchanged, ungoverned] + .into_iter() + .map(|id| { + ( + id, + CollectionDescription::for_other(RelationDesc::empty(), None), + ) + }) + .collect(), + &BTreeSet::new(), + ) + .await + .unwrap(); + let initial = controller + .clone() + .take_read_protection_frontiers(&BTreeSet::new()); + assert_eq!(initial.keys().copied().collect_vec(), vec![id, unchanged]); + assert_eq!(initial[&id].0.write_frontier, frontier(0)); + assert_eq!(initial[&id].0.read_capabilities, frontier(5)); + assert_eq!(initial[&id].1, frontier(5)); + assert!( + controller + .take_read_protection_frontiers(&BTreeSet::new()) + .is_empty() + ); + + controller.set_read_policies(vec![(id, ReadPolicy::ValidFrom(frontier(10)))]); + controller.set_read_policies(vec![ + (id, ReadPolicy::ValidFrom(frontier(20))), + (ungoverned, ReadPolicy::ValidFrom(frontier(20))), + ]); + let changed = controller.take_read_protection_frontiers(&BTreeSet::new()); + assert_eq!(changed.keys().copied().collect_vec(), vec![id]); + assert_eq!(changed[&id].0.read_capabilities, frontier(5)); + assert_eq!(changed[&id].0.implied_capability, frontier(20)); + assert_eq!(changed[&id].1, frontier(20)); + assert!( + controller + .take_read_protection_frontiers(&BTreeSet::new()) + .is_empty() + ); + let sampled = + controller.take_read_protection_frontiers(&BTreeSet::from([id, ungoverned, missing])); + assert_eq!(sampled.keys().copied().collect_vec(), vec![id]); + assert_eq!(sampled[&id].1, frontier(20)); + + let holds = controller.acquire_read_holds(vec![id]).unwrap(); + let held = controller.take_read_protection_frontiers(&BTreeSet::new()); + assert_eq!(held[&id].1, frontier(5)); + drop(holds); + tokio::time::timeout(Duration::from_secs(10), async { + loop { + let changed = controller.take_read_protection_frontiers(&BTreeSet::new()); + if let Some((frontiers, proposal)) = changed.get(&id) { + assert_eq!(frontiers.read_capabilities, frontier(5)); + assert_eq!(*proposal, frontier(20)); + break; + } + tokio::task::yield_now().await; + } + }) + .await + .expect("read hold release must emit even behind the catalog cap"); + + let mut writer = persist + .open_writer::( + shard, + Arc::new(RelationDesc::empty()), + Arc::new(UnitSchema), + Diagnostics::for_tests(), + ) + .await + .unwrap(); + let updates: Vec<((SourceData, ()), Timestamp, StorageDiff)> = Vec::new(); + writer + .compare_and_append(&updates, frontier(0), frontier(30)) + .await + .unwrap() + .unwrap(); + tokio::time::timeout(Duration::from_secs(10), async { + loop { + let changed = controller.take_read_protection_frontiers(&BTreeSet::new()); + if let Some((frontiers, proposal)) = changed.get(&id) { + assert_eq!(frontiers.write_frontier, frontier(30)); + assert_eq!(*proposal, frontier(20)); + break; + } + tokio::task::yield_now().await; + } + }) + .await + .expect("durable progress must emit without a read policy change"); + writer.expire().await; + + let holds = controller.acquire_read_holds(vec![id]).unwrap(); + let mut dropped_metadata = metadata; + dropped_metadata.collection_metadata.remove(&id); + dropped_metadata.compaction_bounds.remove(&id); + controller.drop_collections_unvalidated(&dropped_metadata, vec![id]); + assert!( + controller + .take_read_protection_frontiers(&BTreeSet::from([id])) + .is_empty() + ); + drop(holds); + } + + #[mz_ore::test(tokio::test)] + async fn test_take_read_protection_frontiers_dependency_capabilities() { + let (controller, _persist) = bound_test_controller().await; + let input = GlobalId::User(1); + let dependent = GlobalId::User(2); + let shard = ShardId::new(); + let metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(input, shard), (dependent, shard)]), + compaction_bounds: BTreeMap::from([(input, frontier(5)), (dependent, frontier(5))]), + ..Default::default() + }; + let mut description = CollectionDescription::for_other(RelationDesc::empty(), None); + description.primary = Some(input); + controller + .create_collections_for_bootstrap( + &metadata, + None, + vec![ + ( + input, + CollectionDescription::for_other(RelationDesc::empty(), None), + ), + (dependent, description), + ], + &BTreeSet::new(), + ) + .await + .unwrap(); + controller.set_read_policies(vec![ + (input, ReadPolicy::ValidFrom(frontier(20))), + (dependent, ReadPolicy::ValidFrom(frontier(20))), + ]); + let initial = controller.take_read_protection_frontiers(&BTreeSet::new()); + assert_eq!(initial[&input].1, frontier(5)); + assert_eq!(initial[&dependent].1, frontier(20)); + + controller + .apply_compaction_bounds(BTreeMap::from([(dependent, frontier(20))])) + .unwrap(); + let changed = controller.take_read_protection_frontiers(&BTreeSet::new()); + assert_eq!( + changed.keys().copied().collect_vec(), + vec![input, dependent] + ); + assert_eq!(changed[&input].0.read_capabilities, frontier(5)); + assert_eq!(changed[&input].1, frontier(20)); + assert!( + controller + .take_read_protection_frontiers(&BTreeSet::new()) + .is_empty() + ); + } + #[mz_ore::test(tokio::test)] async fn test_committed_compaction_bound_caps_progress() { let (controller, persist) = bound_test_controller().await; @@ -3734,7 +4141,11 @@ mod tests { controller .apply_compaction_bounds(BTreeMap::from([(id, frontier(10))])) .unwrap(); - assert_eq!(controller.compaction_frontiers()[&id], frontier(5)); + assert_eq!(controller.compaction_bound(id).unwrap(), Some(frontier(10))); + assert_eq!( + controller.take_read_protection_frontiers(&BTreeSet::from([id]))[&id].1, + frontier(5) + ); assert_eq!( controller .collection_frontiers(id) @@ -3745,7 +4156,10 @@ mod tests { drop(holds); await_frontier(&controller, id, frontier(10)).await; await_persist_since(&persist, shard, frontier(10)).await; - assert_eq!(controller.compaction_frontiers()[&id], frontier(30)); + assert_eq!( + controller.take_read_protection_frontiers(&BTreeSet::from([id]))[&id].1, + frontier(30) + ); assert_err!(controller.apply_compaction_bounds(BTreeMap::from([(id, frontier(9))]))); assert_err!(controller.apply_compaction_bounds(BTreeMap::from([ (id, frontier(15)), @@ -4266,6 +4680,7 @@ mod tests { ) .await .unwrap(); + assert_eq!(controller.compaction_bound(id).unwrap(), bound); let expected = bound.unwrap_or_else(|| frontier(100)); assert_eq!( controller @@ -4780,6 +5195,7 @@ mod tests { UIntGauge::new("finalizable_shards", "dummy gauge for tests").unwrap(), )), collections: Arc::new(Mutex::new(BTreeMap::new())), + dirty_read_protection: Arc::new(Mutex::new(BTreeSet::new())), shard_by_id: BTreeMap::new(), since_handles: BTreeMap::new(), pending_since_downgrades: BTreeMap::new(), diff --git a/src/storage-controller/src/history.rs b/src/storage-controller/src/history.rs index 1c4ab69b2f231..ea39ac10077cd 100644 --- a/src/storage-controller/src/history.rs +++ b/src/storage-controller/src/history.rs @@ -412,6 +412,7 @@ mod tests { StorageCommand::RunIngestion(Box::new(RunIngestionCommand { id: GlobalId::User(1), description: ingestion_description(1, [2], 3), + remap_compaction_bound: None, })), StorageCommand::AllowCompaction(GlobalId::User(1), Antichain::new()), StorageCommand::AllowCompaction(GlobalId::User(2), Antichain::new()), @@ -436,6 +437,7 @@ mod tests { StorageCommand::RunIngestion(Box::new(RunIngestionCommand { id: GlobalId::User(1), description: ingestion_description(1, [2], 3), + remap_compaction_bound: Some(Antichain::from_elem(100.into())), })), StorageCommand::AllowCompaction(GlobalId::User(1), Antichain::from_elem(1.into())), StorageCommand::AllowCompaction(GlobalId::User(2), Antichain::from_elem(2.into())), @@ -460,6 +462,7 @@ mod tests { StorageCommand::RunIngestion(Box::new(RunIngestionCommand { id: GlobalId::User(1), description: ingestion_description(1, [2], 3), + remap_compaction_bound: Some(Antichain::from_elem(100.into())), })), StorageCommand::AllowCompaction(GlobalId::User(2), Antichain::new()), ]; @@ -474,6 +477,22 @@ mod tests { assert_eq!(commands_after, commands); } + #[mz_ore::test] + fn ingestion_replay_preserves_latest_permission() { + let mut history = history(); + let mut ingestion = RunIngestionCommand { + id: GlobalId::User(1), + description: ingestion_description(1, [2], 3), + remap_compaction_bound: None, + }; + history.push(StorageCommand::RunIngestion(Box::new(ingestion.clone()))); + ingestion.remap_compaction_bound = Some(Antichain::from_elem(100.into())); + let latest = StorageCommand::RunIngestion(Box::new(ingestion)); + history.push(latest.clone()); + history.reduce(); + assert_eq!(history.iter().cloned().collect::>(), [latest]); + } + #[mz_ore::test] fn reduce_drops_dropped_sink() { let mut history = history(); diff --git a/src/storage-controller/src/instance.rs b/src/storage-controller/src/instance.rs index e36c315801f77..1f66fa1092519 100644 --- a/src/storage-controller/src/instance.rs +++ b/src/storage-controller/src/instance.rs @@ -34,7 +34,7 @@ use mz_storage_client::client::{ }; use mz_storage_client::metrics::{InstanceMetrics, ReplicaMetrics}; use mz_storage_types::sinks::StorageSinkDesc; -use mz_storage_types::sources::{IngestionDescription, SourceConnection}; +use mz_storage_types::sources::SourceConnection; use timely::progress::Antichain; use tokio::select; use tokio::sync::mpsc; @@ -527,11 +527,12 @@ impl Instance { let mut scheduling_preferences: Vec<(ObjectId, bool)> = Vec::new(); for ingestion_id in self.active_ingestions.keys() { - let ingestion_description = self - .get_ingestion_description(ingestion_id) - .expect("missing ingestion description"); + let ingestion = self + .get_ingestion_command(ingestion_id) + .expect("missing ingestion command"); - let prefers_single_replica = ingestion_description + let prefers_single_replica = ingestion + .description .desc .connection .prefers_single_replica(); @@ -613,13 +614,11 @@ impl Instance { for object_id in object_ids { match object_id { ObjectId::Ingestion(id) => { - ingestion_commands.push(RunIngestionCommand { - id, - description: self - .get_ingestion_description(&id) - .expect("missing ingestion description") + ingestion_commands.push( + self.get_ingestion_command(&id) + .expect("missing ingestion command") .clone(), - }); + ); } ObjectId::Export(id) => { export_commands.push(RunSinkCommand { @@ -646,17 +645,8 @@ impl Instance { } } - /// Returns the ingestion description for the given ingestion ID, if it - /// exists. - /// - /// This function searches through the command history to find the most - /// recent RunIngestionCommand for the specified ingestion ID and returns - /// its description. Returns None if no ingestion with the given ID is - /// found. - pub fn get_ingestion_description( - &self, - id: &GlobalId, - ) -> Option> { + /// Returns the latest command for an active ingestion, including its remap permission. + fn get_ingestion_command(&self, id: &GlobalId) -> Option<&RunIngestionCommand> { if !self.active_ingestions.contains_key(id) { return None; } @@ -664,7 +654,7 @@ impl Instance { self.history.iter().rev().find_map(|command| { if let StorageCommand::RunIngestion(ingestion) = command { if &ingestion.id == id { - Some(ingestion.description.clone()) + Some(ingestion.as_ref()) } else { None } diff --git a/src/storage-controller/src/lib.rs b/src/storage-controller/src/lib.rs index 846a3e932635b..a784d826fac84 100644 --- a/src/storage-controller/src/lib.rs +++ b/src/storage-controller/src/lib.rs @@ -3482,6 +3482,9 @@ where }; let storage_instance_id = description.instance_id; + let remap_compaction_bound = self + .storage_collections + .compaction_bound(description.remap_collection_id)?; // Fetch the client for this ingestion's instance. let instance = self .instances @@ -3491,7 +3494,11 @@ where ingestion_id: id, })?; - let augmented_ingestion = Box::new(RunIngestionCommand { id, description }); + let augmented_ingestion = Box::new(RunIngestionCommand { + id, + description, + remap_compaction_bound, + }); instance.send(StorageCommand::RunIngestion(augmented_ingestion)); Ok(()) @@ -3514,7 +3521,14 @@ where // reading it; otherwise assume we may have to replay from the beginning. let export_state = self.storage_collections.collection_frontiers(id)?; let mut as_of = description.sink.as_of.clone(); - as_of.join_assign(&export_state.implied_capability); + // Policy permission alone does not prove recovery can skip history. + // The descriptor carries the recovery frontier, while actual readability + // also accounts for leases opened against a stale read-only snapshot. + as_of.join_assign(&export_state.read_capabilities); + let input_state = self + .storage_collections + .collection_frontiers(description.sink.from)?; + as_of.join_assign(&input_state.read_capabilities); let with_snapshot = description.sink.with_snapshot && !PartialOrder::less_than(&as_of, &export_state.write_frontier); @@ -4144,3 +4158,296 @@ fn swap_updates( } update } + +#[cfg(test)] +mod tests { + use std::num::NonZeroI64; + + use mz_build_info::DUMMY_BUILD_INFO; + use mz_ore::now::SYSTEM_TIME; + use mz_persist_client::cfg::PersistConfig; + use mz_persist_client::rpc::PubSubClientConnection; + use mz_service::secrets::{SecretsControllerKind, SecretsReaderCliArgs}; + use mz_storage_client::storage_collections::StorageCollectionsImpl; + use mz_storage_types::connections::{KafkaConnection, Tunnel}; + use mz_storage_types::sinks::{ + KafkaIdStyle, KafkaSinkCompressionType, KafkaSinkConnection, KafkaSinkFormat, + KafkaSinkFormatType, SinkEnvelope, + }; + + use super::*; + + struct TestTxn(ShardId); + + #[async_trait] + impl StorageTxn for TestTxn { + fn get_collection_metadata(&self) -> BTreeMap { + BTreeMap::new() + } + fn insert_collection_metadata( + &mut self, + _: BTreeMap, + ) -> Result<(), StorageError> { + unimplemented!() + } + fn delete_collection_metadata( + &mut self, + _: BTreeSet, + ) -> Vec<(GlobalId, ShardId)> { + unimplemented!() + } + fn get_unfinalized_shards(&self) -> BTreeSet { + BTreeSet::new() + } + fn insert_unfinalized_shards(&mut self, _: BTreeSet) -> Result<(), StorageError> { + unimplemented!() + } + fn remove_unfinalized_shards(&mut self, _: BTreeSet) { + unimplemented!() + } + fn get_txn_wal_shard(&self) -> Option { + Some(self.0) + } + fn write_txn_wal_shard(&mut self, _: ShardId) -> Result<(), StorageError> { + unimplemented!() + } + } + + fn frontier(ts: u64) -> Antichain { + Antichain::from_elem(ts.into()) + } + + async fn export_test_controller() -> (Controller, Arc, PersistClient) { + let registry = MetricsRegistry::new(); + let location = PersistLocation { + blob_uri: "mem://".parse().unwrap(), + consensus_uri: "mem://".parse().unwrap(), + }; + let mut config = PersistConfig::new_default_configs(&DUMMY_BUILD_INFO, SYSTEM_TIME.clone()); + config.critical_downgrade_interval = Duration::ZERO; + let cache = Arc::new(PersistClientCache::new(config, ®istry, |_, _| { + PubSubClientConnection::noop() + })); + let persist = cache.open(location.clone()).await.unwrap(); + // No replica connects and the sink has no secrets, so this reader performs no file I/O. + let secrets_reader = SecretsReaderCliArgs { + secrets_reader: SecretsControllerKind::LocalFile, + secrets_reader_local_file_dir: Some("/dev/null".into()), + secrets_reader_kubernetes_context: None, + secrets_reader_aws_prefix: None, + secrets_reader_name_prefix: None, + } + .load() + .await + .unwrap(); + let context = ConnectionContext::for_tests(secrets_reader); + let txns_metrics = Arc::new(TxnMetrics::new(®istry)); + let txn = TestTxn(ShardId::new()); + let collections = Arc::new( + StorageCollectionsImpl::new( + location.clone(), + Arc::clone(&cache), + ®istry, + SYSTEM_TIME.clone(), + Arc::clone(&txns_metrics), + NonZeroI64::new(1).unwrap(), + false, + context.clone(), + &txn, + ) + .await, + ); + let controller = Controller::new( + &DUMMY_BUILD_INFO, + location, + cache, + SYSTEM_TIME.clone(), + WallclockLagFn::new(SYSTEM_TIME.clone()), + txns_metrics, + false, + ®istry, + ControllerMetrics::new(®istry), + context, + &txn, + Arc::::clone(&collections), + ) + .await; + (controller, collections, persist) + } + + fn export_description(from: GlobalId) -> ExportDescription { + ExportDescription { + sink: StorageSinkDesc { + from, + from_desc: RelationDesc::empty(), + connection: StorageSinkConnection::Kafka(KafkaSinkConnection { + connection_id: mz_repr::CatalogItemId::System(1), + connection: KafkaConnection { + brokers: Default::default(), + default_tunnel: Tunnel::Direct, + progress_topic: Default::default(), + progress_topic_options: Default::default(), + options: Default::default(), + tls: Default::default(), + sasl: Default::default(), + }, + format: KafkaSinkFormat { + key_format: None, + value_format: KafkaSinkFormatType::Text, + }, + relation_key_indices: None, + key_desc_and_indices: None, + headers_index: None, + value_desc: RelationDesc::empty(), + partition_by: Default::default(), + topic: Default::default(), + topic_options: Default::default(), + compression_type: KafkaSinkCompressionType::None, + progress_group_id: KafkaIdStyle::Legacy, + transactional_id: KafkaIdStyle::Legacy, + topic_metadata_refresh_interval: Default::default(), + }), + with_snapshot: true, + version: 0, + envelope: SinkEnvelope::Upsert, + as_of: frontier(5), + from_storage_metadata: (), + to_storage_metadata: (), + commit_interval: Default::default(), + }, + instance_id: StorageInstanceId::system(1).unwrap(), + } + } + + #[mz_ore::test(tokio::test)] + async fn run_export_uses_held_readability() { + for input_governed in [false, true] { + for sink_governed in [false, true] { + check_export_readability(input_governed, sink_governed).await; + } + } + } + + async fn check_export_readability(input_governed: bool, sink_governed: bool) { + let (mut controller, collections, persist) = export_test_controller().await; + let input = GlobalId::User(1); + let sink = GlobalId::User(2); + let description = export_description(input); + let instance = description.instance_id; + let data_source = DataSource::Sink { desc: description }; + let metadata = StorageMetadata { + collection_metadata: BTreeMap::from([(input, ShardId::new()), (sink, ShardId::new())]), + compaction_bounds: [(input, input_governed), (sink, sink_governed)] + .into_iter() + .filter_map(|(id, governed)| governed.then(|| (id, frontier(20)))) + .collect(), + ..Default::default() + }; + let mut sink_collection = CollectionDescription::for_other(RelationDesc::empty(), None); + sink_collection.data_source = data_source.clone(); + collections + .create_collections_for_bootstrap( + &metadata, + None, + vec![ + ( + input, + CollectionDescription::for_other(RelationDesc::empty(), Some(frontier(5))), + ), + (sink, sink_collection), + ], + &BTreeSet::new(), + ) + .await + .unwrap(); + let [read_hold, self_hold] = collections + .acquire_read_holds(vec![input, sink]) + .unwrap() + .try_into() + .expect("two holds"); + collections.set_read_policies(vec![ + (input, ReadPolicy::ValidFrom(frontier(20))), + (sink, ReadPolicy::ValidFrom(frontier(20))), + ]); + for (id, governed) in [(input, input_governed), (sink, sink_governed)] { + let state = collections.collection_frontiers(id).unwrap(); + assert_eq!(state.implied_capability, frontier(20)); + assert_eq!(state.read_capabilities, frontier(5)); + assert_eq!( + collections.compaction_bound(id).unwrap(), + governed.then(|| frontier(20)) + ); + } + controller.create_instance(instance, None); + controller.collections.insert( + sink, + CollectionState::new( + data_source, + collections.collection_metadata(sink).unwrap(), + CollectionStateExtra::Export(ExportState::new( + instance, + read_hold, + self_hold, + frontier(0), + ReadPolicy::step_back(), + )), + controller.metrics.wallclock_lag_metrics(sink, None), + ), + ); + let mut writer = persist + .open_writer::( + metadata.collection_metadata[&sink], + Arc::new(RelationDesc::empty()), + Arc::new(UnitSchema), + Diagnostics::for_tests(), + ) + .await + .unwrap(); + for (upper, since, snapshot) in [(0, 5, true), (5, 5, true), (6, 5, false), (21, 20, false)] + { + if writer.upper() != &frontier(upper) { + writer + .compare_and_append( + Vec::<((SourceData, ()), Timestamp, StorageDiff)>::new(), + writer.upper().clone(), + frontier(upper), + ) + .await + .unwrap() + .unwrap(); + } + controller.update_write_frontier(sink, &frontier(upper)); + tokio::time::timeout(Duration::from_secs(10), async { + loop { + let state = collections.collection_frontiers(sink).unwrap(); + if state.write_frontier == frontier(upper) + && state.read_capabilities == frontier(since) + { + break; + } + tokio::task::yield_now().await; + } + }) + .await + .unwrap(); + controller.run_export(sink).unwrap(); + let command = controller.instances[&instance] + .get_export_description(&sink) + .unwrap(); + assert_eq!( + command.as_of, + frontier(since), + "input_governed={input_governed}, sink_governed={sink_governed}, upper={upper}" + ); + assert_eq!(command.with_snapshot, snapshot); + assert_eq!( + command.from_storage_metadata, + collections.collection_metadata(input).unwrap() + ); + assert_eq!( + command.to_storage_metadata, + collections.collection_metadata(sink).unwrap() + ); + } + } +} diff --git a/src/storage/src/storage_state.rs b/src/storage/src/storage_state.rs index 44922849a9f45..5c999229e0405 100644 --- a/src/storage/src/storage_state.rs +++ b/src/storage/src/storage_state.rs @@ -100,7 +100,6 @@ use mz_storage_types::controller::CollectionMetadata; use mz_storage_types::dyncfgs::STORAGE_SERVER_MAINTENANCE_INTERVAL; use mz_storage_types::oneshot_sources::OneshotIngestionDescription; use mz_storage_types::sinks::StorageSinkDesc; -use mz_storage_types::sources::IngestionDescription; use mz_timely_util::builder_async::PressOnDropButton; use mz_txn_wal::operator::TxnsContext; use timely::order::PartialOrder; @@ -272,8 +271,8 @@ pub struct StorageState { pub metrics: StorageMetrics, /// Tracks the conditional write frontiers we have reported. pub reported_frontiers: BTreeMap>, - /// Descriptions of each installed ingestion. - pub ingestions: BTreeMap>, + /// Commands for each installed ingestion, retained for reconciliation and restart. + pub ingestions: BTreeMap, /// Descriptions of each installed export. pub exports: BTreeMap>, /// Descriptions of oneshot ingestions that are currently running. @@ -578,7 +577,7 @@ impl<'w> Worker<'w> { ); let maybe_ingestion = self.storage_state.ingestions.get(&id).cloned(); - if let Some(ingestion_description) = maybe_ingestion { + if let Some(ingestion) = maybe_ingestion { // Yank the token of the previously existing source dataflow.Note that this // token also includes any source exports/subsources. let maybe_token = self.storage_state.source_tokens.remove(&id); @@ -601,14 +600,14 @@ impl<'w> Worker<'w> { // putting undue pressure on worker 0 we can pick the // designated worker for a source/sink based on `id.hash()`. if self.timely_worker.index() == 0 { - for (id, _) in ingestion_description.source_exports.iter() { + for (id, _) in ingestion.description.source_exports.iter() { self.storage_state .aggregated_statistics .advance_global_epoch(*id); } self.storage_state .async_worker - .update_ingestion_frontiers(id, ingestion_description); + .update_ingestion_frontiers(ingestion); } // Continue with other commands. @@ -647,7 +646,7 @@ impl<'w> Worker<'w> { .storage_state .ingestions .values() - .any(|v| v.source_exports.contains_key(&id)) + .any(|v| v.description.source_exports.contains_key(&id)) { // Our current approach to dropping a source results in a race between shard // finalization (which happens in the controller) and dataflow shutdown (which @@ -1031,7 +1030,7 @@ impl<'w> Worker<'w> { let mut expected_objects = BTreeSet::new(); let mut drop_commands = BTreeSet::new(); - let mut running_ingestion_descriptions = self.storage_state.ingestions.clone(); + let mut running_ingestions = self.storage_state.ingestions.clone(); let mut running_exports_descriptions = self.storage_state.exports.clone(); let mut create_oneshot_ingestions: BTreeSet = BTreeSet::new(); @@ -1059,14 +1058,14 @@ impl<'w> Worker<'w> { info!(%worker_id, ?ingestion, "reconcile: received RunIngestion command"); // Ensure that ingestions are forward-rolling alter compatible. - let prev = running_ingestion_descriptions - .insert(ingestion.id, ingestion.description.clone()); + let prev = running_ingestions.insert(ingestion.id, ingestion.as_ref().clone()); if let Some(prev_ingest) = prev { // If the new ingestion is not exactly equal to the currently running // ingestion, we must either track that we need to synthesize an update // command to change the ingestion, or panic. prev_ingest + .description .alter_compatible(ingestion.id, &ingestion.description) .expect("only alter compatible ingestions permitted"); } @@ -1166,10 +1165,10 @@ impl<'w> Worker<'w> { // We keep only: // - The most recent version of the ingestion, which // is why these commands are run in reverse. - // - Ingestions whose descriptions are not exactly + // - Ingestions whose commands are not exactly // those that are currently running. - should_keep = most_recent_defintion - && running_ingestion != Some(&ingestion.description) + should_keep = + most_recent_defintion && running_ingestion != Some(ingestion.as_ref()) } } StorageCommand::RunSink(export) => { @@ -1242,7 +1241,7 @@ impl<'w> Worker<'w> { .storage_state .ingestions .values() - .map(|i| i.collection_ids()) + .map(|i| i.description.collection_ids()) .flatten() .chain(self.storage_state.exports.keys().copied()) // Objects are considered stale if we did not see them re-created. @@ -1353,14 +1352,11 @@ impl StorageState { } } StorageCommand::RunIngestion(ingestion) => { - let RunIngestionCommand { id, description } = *ingestion; - - // Remember the ingestion description to facilitate possible - // reconciliation later. - self.ingestions.insert(id, description.clone()); + self.ingestions + .insert(ingestion.id, ingestion.as_ref().clone()); // Initialize shared frontier reporting. - for id in description.collection_ids() { + for id in ingestion.description.collection_ids() { self.reported_frontiers .entry(id) .or_insert_with(|| Antichain::from_elem(mz_repr::Timestamp::minimum())); @@ -1377,8 +1373,7 @@ impl StorageState { // ingestion in the local storage state. This is something we might have // interest in fixing in the future, e.g. materialize#19907 if self.timely_worker_index == 0 { - self.async_worker - .update_ingestion_frontiers(id, description); + self.async_worker.update_ingestion_frontiers(*ingestion); } } StorageCommand::RunOneshotIngestion(oneshot) => { diff --git a/src/storage/src/storage_state/async_storage_worker.rs b/src/storage/src/storage_state/async_storage_worker.rs index f50c130834f4d..06c8ccf4c97a8 100644 --- a/src/storage/src/storage_state/async_storage_worker.rs +++ b/src/storage/src/storage_state/async_storage_worker.rs @@ -25,6 +25,7 @@ use mz_persist_client::read::ListenEvent; use mz_persist_types::Codec64; use mz_persist_types::codec_impls::UnitSchema; use mz_repr::{GlobalId, Row, TimestampManipulation}; +use mz_storage_client::client::RunIngestionCommand; use mz_storage_types::StorageDiff; use mz_storage_types::controller::CollectionMetadata; use mz_storage_types::sinks::StorageSinkDesc; @@ -53,7 +54,11 @@ pub struct AsyncStorageWorker { #[derive(Debug)] pub enum AsyncStorageWorkerCommand { /// Calculate a recent resumption frontier for the ingestion. - UpdateIngestionFrontiers(GlobalId, IngestionDescription), + UpdateIngestionFrontiers( + GlobalId, + IngestionDescription, + Option>, + ), /// Calculate a recent resumption frontier for the Sink. UpdateSinkFrontiers(GlobalId, StorageSinkDesc), @@ -219,6 +224,7 @@ impl AsyncStorageWorkerCommand::UpdateIngestionFrontiers( id, ingestion_description, + remap_compaction_bound, ) => { let mut resume_uppers = BTreeMap::new(); @@ -254,7 +260,9 @@ impl .unwrap(); let upper = write_handle.fetch_recent_upper().await; let upper = match export.data_config.envelope { - // The CdcV2 envelope must re-ingest everything since the Mz frontier does not have a relation to upstream timestamps. + // CDCv2 replays all upstream data. Its decoder discards transport + // timestamps and reconstructs embedded output timestamps, so neither + // output progress nor the remap as-of can shorten upstream replay. // TODO(petrosagg): move this reasoning to the controller SourceEnvelope::CdcV2 if upper.is_empty() => Antichain::new(), SourceEnvelope::CdcV2 => Antichain::from_elem(Timestamp::minimum()), @@ -264,21 +272,6 @@ impl write_handle.expire().await; } - // Here we update the as-of frontier of the ingestion. - // - // The as-of frontier controls the frontier with which all inputs of the - // ingestion dataflow will be advanced by. It is in our interest to set the - // as-of froniter to the largest possible value, which will result in the - // maximum amount of consolidation, which in turn results in the minimum - // amount of memory required to hydrate. - // - // For each output `o` and for each input `i` of the ingestion the - // controller guarantees that i.since < o.upper except when o.upper is - // [T::minimum()]. Therefore the largest as-of for a particular output `o` - // is `{ (t - 1).advance_by(i.since) | t in o.upper }`. - // - // To calculate the global as_of frontier we take the minimum of all those - // per-output as-of frontiers. let client = persist_clients .open( ingestion_description @@ -310,16 +303,11 @@ impl tokio::time::sleep(std::time::Duration::from_secs(300)).await; read_handle.expire().await; }); - let mut as_of = Antichain::new(); - for upper in resume_uppers.values() { - for t in upper.elements() { - let mut t_prime = t.step_back().unwrap_or_else(T::minimum); - if !remap_since.is_empty() { - t_prime.advance_by(remap_since.borrow()); - as_of.insert(t_prime); - } - } - } + let as_of = ingestion_as_of( + &remap_since, + remap_compaction_bound.as_ref(), + resume_uppers.values(), + ); /// Convenience function to convert `BTreeMap>` to /// `BTreeMap>`. @@ -472,16 +460,23 @@ impl } } - /// Updates the frontiers associated with the provided `IngestionDescription` to recent values. - /// Currently this will calculate a fresh as-of for the ingestion and a fresh resumption - /// frontier for each of the exports. - pub fn update_ingestion_frontiers( - &self, - id: GlobalId, - ingestion: IngestionDescription, - ) { + /// Calculates a readable ingestion as-of respecting the command's remap permission, + /// and recent resumption frontiers for its exports. + pub fn update_ingestion_frontiers(&self, ingestion: RunIngestionCommand) + where + T: From, + { + let RunIngestionCommand { + id, + description, + remap_compaction_bound, + } = ingestion; + let remap_compaction_bound = + remap_compaction_bound.map(|bound| bound.into_iter().map(T::from).collect()); self.send(AsyncStorageWorkerCommand::UpdateIngestionFrontiers( - id, ingestion, + id, + description, + remap_compaction_bound, )) } @@ -522,6 +517,30 @@ impl } } +/// Chooses the ingestion input as-of without advancing physical compaction. +fn ingestion_as_of<'a, T: TimestampManipulation>( + remap_since: &Antichain, + remap_compaction_bound: Option<&Antichain>, + resume_uppers: impl IntoIterator>, +) -> Antichain { + let mut as_of: Antichain = resume_uppers + .into_iter() + .flat_map(|upper| { + upper + .iter() + .map(|t| t.step_back().unwrap_or_else(T::minimum)) + }) + .collect(); + as_of.join_assign(remap_since); + // New exports at MIN must start at the committed floor even if a lease or + // physical compaction still holds remap since behind it. The command's recovery + // constraint makes this safe for existing exports too. + if let Some(bound) = remap_compaction_bound { + as_of.join_assign(bound); + } + as_of +} + /// Helper that makes sure that we always unpark the target thread when we send a /// message. struct ActivatingSender { @@ -540,3 +559,230 @@ impl ActivatingSender { res } } + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use mz_persist_client::ShardId; + use mz_persist_types::PersistLocation; + use mz_repr::{RelationDesc, Timestamp}; + use mz_storage_types::instances::StorageInstanceId; + use mz_storage_types::sources::envelope::{KeyEnvelope, NoneEnvelope}; + use mz_storage_types::sources::load_generator::{ + LoadGenerator, LoadGeneratorOutput, LoadGeneratorSourceExportDetails, + }; + use mz_storage_types::sources::{ + MzOffset, SourceDesc, SourceExport, SourceExportDataConfig, SourceExportDetails, + }; + + use super::*; + + fn frontier(t: u64) -> Antichain { + Antichain::from_elem(t.into()) + } + + #[mz_ore::test] + fn ingestion_as_of_respects_readability_and_completion() { + assert_eq!( + ingestion_as_of(&frontier(200), Some(&frontier(100)), &[frontier(0)]), + frontier(200), + ); + for (since, permission, uppers) in [ + (Antichain::new(), frontier(100), vec![frontier(0)]), + (frontier(10), Antichain::new(), vec![frontier(0)]), + (frontier(10), frontier(100), vec![Antichain::new()]), + ] { + assert!(ingestion_as_of(&since, Some(&permission), &uppers).is_empty()); + } + } + + #[mz_ore::test(tokio::test)] + async fn ingestion_birth_floor_and_recovery() { + let clients = Arc::new(PersistClientCache::new_no_metrics()); + let location = PersistLocation { + blob_uri: "mem://".parse().unwrap(), + consensus_uri: "mem://".parse().unwrap(), + }; + let client = clients.open(location.clone()).await.unwrap(); + let connection = LoadGeneratorSourceConnection { + load_generator: LoadGenerator::Counter { + max_cardinality: None, + }, + tick_micros: None, + as_of: 0, + up_to: u64::MAX, + }; + let remap_metadata = CollectionMetadata { + persist_location: location.clone(), + data_shard: ShardId::new(), + relation_desc: connection.timestamp_desc(), + txns_shard: None, + }; + let (mut remap_writer, mut remap_reader) = client + .open::( + remap_metadata.data_shard, + Arc::new(remap_metadata.relation_desc.clone()), + Arc::new(UnitSchema), + Diagnostics::for_tests(), + false, + ) + .await + .unwrap(); + // Offset 7 is needed by existing exports, while new exports must replay from MIN. + let bindings = [(0, 0, 1), (0, 50, -1), (7, 50, 1)].map(|(offset, time, diff)| { + ( + (SourceData(Ok(MzOffset::from(offset).encode_row())), ()), + Timestamp::from(time), + StorageDiff::from(diff), + ) + }); + remap_writer + .compare_and_append(bindings, frontier(0), frontier(300)) + .await + .unwrap() + .unwrap(); + remap_reader.downgrade_since(&frontier(10)).await; + + let worker = AsyncStorageWorker::::new(std::thread::current(), clients); + let ordinary = SourceEnvelope::None(NoneEnvelope { + key_envelope: KeyEnvelope::None, + key_arity: 0, + }); + for (name, permission, uppers, envelope, expected_as_of) in [ + ( + "new alongside older exports", + Some(100), + vec![0, 151, 201], + ordinary.clone(), + 100, + ), + ( + "restart at permission", + Some(100), + vec![101, 151], + ordinary.clone(), + 100, + ), + ( + "partial progress", + Some(100), + vec![151, 201], + ordinary.clone(), + 150, + ), + ( + "legacy new export", + None, + vec![0, 151], + ordinary.clone(), + 10, + ), + ("legacy restart", None, vec![151, 201], ordinary, 150), + ( + "CDCv2 full replay", + Some(100), + vec![151], + SourceEnvelope::CdcV2, + 100, + ), + ] { + let mut source_exports = BTreeMap::new(); + for (index, upper) in uppers.iter().enumerate() { + let id = GlobalId::User(u64::try_from(index).unwrap() + 1); + let metadata = CollectionMetadata { + persist_location: location.clone(), + data_shard: ShardId::new(), + relation_desc: RelationDesc::empty(), + txns_shard: None, + }; + let mut writer = client + .open_writer::( + metadata.data_shard, + Arc::new(metadata.relation_desc.clone()), + Arc::new(UnitSchema), + Diagnostics::for_tests(), + ) + .await + .unwrap(); + let updates: Vec<((SourceData, ()), Timestamp, StorageDiff)> = Vec::new(); + writer + .compare_and_append(updates, frontier(0), frontier(*upper)) + .await + .unwrap() + .unwrap(); + writer.expire().await; + source_exports.insert( + id, + SourceExport { + storage_metadata: metadata, + details: SourceExportDetails::LoadGenerator( + LoadGeneratorSourceExportDetails { + output: LoadGeneratorOutput::Default, + }, + ), + data_config: SourceExportDataConfig { + encoding: None, + envelope: envelope.clone(), + }, + }, + ); + } + worker.update_ingestion_frontiers(RunIngestionCommand { + id: GlobalId::User(1), + description: IngestionDescription { + desc: SourceDesc { + connection: GenericSourceConnection::LoadGenerator(connection.clone()), + timestamp_interval: Duration::from_secs(1), + }, + source_exports, + instance_id: StorageInstanceId::system(0).unwrap(), + remap_collection_id: GlobalId::User(10), + remap_metadata: remap_metadata.clone(), + }, + remap_compaction_bound: permission.map(frontier), + }); + let response = tokio::time::timeout(Duration::from_secs(30), async { + loop { + match worker.try_recv() { + Ok(response) => break response, + Err(crossbeam_channel::TryRecvError::Empty) => { + tokio::task::yield_now().await + } + Err(err) => panic!("{err}"), + } + } + }) + .await + .expect(name); + let AsyncStorageWorkerResponse::IngestionFrontiersUpdated { + as_of, + resume_uppers, + source_resume_uppers, + .. + } = response + else { + panic!("unexpected response: {response:?}") + }; + assert_eq!(as_of, frontier(expected_as_of), "{name}"); + for (index, upper) in uppers.into_iter().enumerate() { + let id = GlobalId::User(u64::try_from(index).unwrap() + 1); + let upper = if envelope == SourceEnvelope::CdcV2 { + 0 + } else { + upper + }; + assert_eq!(resume_uppers[&id], frontier(upper), "{name}"); + let offset = if upper == 0 { 0 } else { 7 }; + assert_eq!( + source_resume_uppers[&id], + [MzOffset::from(offset).encode_row()], + "{name}" + ); + } + } + assert_eq!(remap_reader.since(), &frontier(10)); + remap_reader.expire().await; + remap_writer.expire().await; + } +} diff --git a/src/testdrive/src/action.rs b/src/testdrive/src/action.rs index f113b7d994b8c..5fd0355985b5f 100644 --- a/src/testdrive/src/action.rs +++ b/src/testdrive/src/action.rs @@ -22,11 +22,9 @@ use aws_credential_types::provider::ProvideCredentials; use aws_types::SdkConfig; use futures::future::FutureExt; use itertools::Itertools; -use mz_adapter::catalog::{Catalog, ConnCatalog, DebugAwsContext}; -use mz_adapter::session::Session; use mz_build_info::BuildInfo; use mz_catalog::config::ClusterReplicaSizeMap; -use mz_catalog::durable::BootstrapArgs; +use mz_catalog::durable::{BootstrapArgs, DurableCatalogState, TestCatalogStateBuilder}; use mz_ccsr::SubjectVersion; use mz_kafka_util::client::{MzClientContext, create_new_client_config_simple}; use mz_ore::error::ErrorExt; @@ -441,64 +439,33 @@ impl State { Ok(()) } - /// Makes of copy of the durable catalog and runs a function on its - /// state. Returns `None` if there's no catalog information in the State. - pub async fn with_catalog_copy( + /// Opens a live read-only durable handle for diagnostic snapshot acquisition. + /// Returns `None` if no durable catalog location is configured. + pub async fn open_catalog_copy( &self, - system_parameter_defaults: BTreeMap, - build_info: &'static BuildInfo, - bootstrap_args: &BootstrapArgs, - enable_expression_cache_override: Option, - f: F, - ) -> Result, anyhow::Error> - where - F: FnOnce(ConnCatalog) -> T, - { - async fn persist_client( - persist_consensus_url: SensitiveUrl, - persist_blob_url: SensitiveUrl, - persist_clients: &PersistClientCache, - ) -> Result { - let persist_location = PersistLocation { - blob_uri: persist_blob_url, - consensus_uri: persist_consensus_url, - }; - Ok(persist_clients.open(persist_location).await?) - } - - if let Some(CatalogConfig { + ) -> Result)>, anyhow::Error> { + let Some(CatalogConfig { persist_consensus_url, persist_blob_url, }) = &self.materialize.catalog_config - { - let persist_client = persist_client( - persist_consensus_url.clone(), - persist_blob_url.clone(), - &self.persist_clients, - ) + else { + return Ok(None); + }; + let persist_client = self + .persist_clients + .open(PersistLocation { + blob_uri: persist_blob_url.clone(), + consensus_uri: persist_consensus_url.clone(), + }) .await?; - let aws_context = DebugAwsContext { - aws_account_id: self.materialize.aws_account_id.clone(), - aws_external_id_prefix: self.materialize.aws_external_id_prefix.clone(), - aws_connection_role_arn: self.materialize.aws_connection_role_arn.clone(), - }; - let catalog = Catalog::open_debug_read_only_persist_catalog_config( - persist_client, - SYSTEM_TIME.clone(), - self.materialize.environment_id.clone(), - system_parameter_defaults, - build_info, - bootstrap_args, - enable_expression_cache_override, - Some(aws_context), - ) + let storage = TestCatalogStateBuilder::new(persist_client.clone()) + .with_organization_id(self.materialize.environment_id.organization_id()) + .with_version(self.build_info.version.parse().expect("invalid version")) + .build() + .await? + .open_read_only(&self.materialize.bootstrap_args) .await?; - let res = f(catalog.for_session(&Session::dummy())); - catalog.expire().await; - Ok(Some(res)) - } else { - Ok(None) - } + Ok(Some((persist_client, storage))) } pub fn aws_endpoint(&self) -> &str { diff --git a/src/testdrive/src/action/consistency.rs b/src/testdrive/src/action/consistency.rs index 83fbf7aa0fe97..8f72863cbf024 100644 --- a/src/testdrive/src/action/consistency.rs +++ b/src/testdrive/src/action/consistency.rs @@ -18,6 +18,9 @@ use crate::action; use crate::action::{ControlFlow, Run, State}; use crate::parser::{BuiltinCommand, LineReader, parse}; use anyhow::{Context, anyhow, bail}; +use mz_adapter::catalog::{Catalog, DebugAwsContext}; +use mz_adapter::session::Session; +use mz_ore::now::SYSTEM_TIME; use mz_ore::retry::{Retry, RetryResult}; use mz_persist_client::{PersistLocation, ShardId}; use mz_postgres_util::{query_one, sql}; @@ -99,7 +102,7 @@ pub async fn run_consistency_checks(state: &State) -> Result Result<(), anyhow::Error> { } } -/// Quiesces periodic protection publication while comparing independently acquired snapshots. -async fn check_catalog_state_quiesced(state: &State) -> Result<(), anyhow::Error> { - use crate::util::postgres::postgres_client; - use mz_postgres_util::{PostgresError, batch_execute, query_opt}; - use tokio_postgres::error::SqlState; - +/// Checks the full in-memory catalog against independently reconstructed durable state. +async fn check_catalog_state(state: &State) -> Result<(), anyhow::Error> { if state.materialize.catalog_config.is_none() { - return with_deadline(state, check_catalog_state(state)).await; - } - - let url = format!( - "postgres://mz_system:materialize@{}", - state.materialize.internal_sql_addr - ); - let (client, _handle) = with_deadline(state, async { - postgres_client(&url, state.default_timeout).await - }) - .await?; - let supported = with_deadline(state, async { - Ok(query_one( - &client, - sql!("SHOW catalog_read_protection_publish_interval"), - &[], - ) - .await) - }) - .await?; - match supported { - Err(PostgresError::Postgres(error)) - if error.code() == Some(&SqlState::UNDEFINED_OBJECT) => - { - return with_deadline(state, check_catalog_state(state)).await; - } - Err(error) => return Err(error.into()), - Ok(_) => {} + return Ok(()); } - let read_only = with_deadline(state, async { - let dump: serde_json::Value = reqwest::get(format!( - "http://{}/api/coordinator/dump", - state.materialize.internal_http_addr - )) - .await? - .error_for_status()? - .json() - .await?; - dump.pointer("/controller/read_only") - .and_then(serde_json::Value::as_bool) - .ok_or_else(|| anyhow!("coordinator dump is missing controller read-only mode")) - }) - .await?; - if read_only { - return with_deadline(state, check_catalog_state(state)).await; + let response = reqwest::get(format!( + "http://{}/api/catalog/check?durable=true", + state.materialize.internal_http_addr, + )) + .await + .context("GET catalog check")? + .error_for_status()?; + let result: serde_json::Value = response.json().await?; + if result.get("durable") == Some(&serde_json::Value::Bool(true)) { + return match result.get("inconsistencies") { + Some(serde_json::Value::String(message)) if message.is_empty() => Ok(()), + _ => bail!("catalog inconsistencies: {result}"), + }; } - let original = with_deadline(state, async { - Ok(query_opt( - &client, - sql!( - "SELECT data->'value'->>'value' FROM mz_internal.mz_catalog_raw - WHERE data->>'kind' = 'SystemConfiguration' - AND data->'key'->>'name' = 'catalog_read_protection_publish_interval'" - ), - &[], - ) - .await? - .map(|row| row.get::<_, String>(0))) - }) - .await?; - - // Like the schema comparison itself, this assumes no concurrent DDL or - // consistency checker. SET completes on the coordinator after any in-flight - // publication. Existing bounds continue protecting history while paused. - let paused = with_deadline(state, async { - batch_execute( - &client, - sql!("ALTER SYSTEM SET catalog_read_protection_publish_interval = '0s'"), - ) - .await - .map_err(Into::into) - }) - .await; - let result = match paused { - Ok(()) => with_deadline(state, check_catalog_state(state)).await, - Err(error) => Err(error), - }; - // Restore outside the timed check, including on timeout or comparison failure. - let restored = with_deadline(state, async { - match original { - Some(value) => { - batch_execute( - &client, - sql!( - "ALTER SYSTEM SET catalog_read_protection_publish_interval = {}", - mz_postgres_util::Sql::literal(&value) - ), - ) - .await? - } - None => { - batch_execute( - &client, - sql!("ALTER SYSTEM RESET catalog_read_protection_publish_interval"), - ) - .await? - } + match result { + serde_json::Value::String(message) if message.is_empty() => { + // Old servers ignore the opt-in parameter. Their invariant check alone + // cannot certify agreement with durable state. + Retry::default() + .max_duration(state.consistency_check_timeout) + .retry_async(|_| check_legacy_catalog_state(state)) + .await } - Ok(()) - }) - .await; - match (result, restored) { - (result, Ok(())) => result, - (Ok(()), Err(error)) => Err(error.context("restoring publication interval")), - (Err(check), Err(restore)) => Err(anyhow!( - "{check:#}\nrestoring publication interval: {restore:#}" - )), + other => bail!("catalog inconsistencies: {other}"), } } +async fn catalog_memory_dump(state: &State) -> Result { + reqwest::get(format!( + "http://{}/api/catalog/dump", + state.materialize.internal_http_addr, + )) + .await + .context("GET catalog dump")? + .error_for_status()? + .text() + .await + .context("read catalog dump") +} + /// Checks that the in-memory catalog matches what we have persisted on disk. -async fn check_catalog_state(state: &State) -> Result<(), anyhow::Error> { +async fn check_legacy_catalog_state(state: &State) -> Result<(), anyhow::Error> { #[derive(Debug, Deserialize)] struct StorageMetadata { unfinalized_shards: Option>, @@ -313,77 +244,62 @@ async fn check_catalog_state(state: &State) -> Result<(), anyhow::Error> { storage_metadata: Option, } - // The comparison below needs the on-disk catalog, which `with_catalog_copy` - // can only open when a catalog config was supplied - // (`--validate-catalog-store`). Without one it returns `None` and the - // check is skipped, so do not fetch and parse the dump (100+ MB) for - // nothing. - if state.materialize.catalog_config.is_none() { - return Ok(()); - } - - // Dump the in-memory catalog state of the Materialize environment that we're - // connected to. - let memory_catalog = reqwest::get(&format!( - "http://{}/api/catalog/dump", - state.materialize.internal_http_addr, - )) - .await - .context("GET catalog")? - .text() - .await - .context("deserialize catalog")?; + let memory_catalog = catalog_memory_dump(state).await?; + let (persist_client, storage) = state + .open_catalog_copy() + .await? + .context("catalog validation requires durable storage")?; // Pull out the system parameter defaults from the in-memory catalog, as we // need to load the disk catalog with the same defaults. - let dump: CatalogDump = serde_json::from_str(&memory_catalog).context("decoding catalog")?; + let dump: CatalogDump = match serde_json::from_str(&memory_catalog) { + Ok(dump) => dump, + Err(error) => { + storage.expire().await; + return Err(error).context("decoding catalog"); + } + }; let Some(system_parameter_defaults) = dump.system_parameter_defaults else { - // TODO(parkmycar, def-): Ideally this could be an error, but a lot of test suites fail. We - // should explicitly disable consistency check in these test suites. - tracing::warn!( - "Missing system_parameter_defaults in memory catalog state, skipping consistency check" - ); - return Ok(()); + storage.expire().await; + bail!("missing system_parameter_defaults in memory catalog state"); }; let unfinalized_shards = dump .storage_metadata .and_then(|storage_metadata| storage_metadata.unfinalized_shards); - // Load the on-disk catalog and dump its state. - - // Make sure the version is parseable. - let _: semver::Version = state.build_info.version.parse().expect("invalid version"); - - let maybe_disk_catalog = state - .with_catalog_copy( - system_parameter_defaults, - state.build_info, - &state.materialize.bootstrap_args, - // The expression cache can be taxing on the CPU and is unnecessary for consistency checks. - Some(false), - |catalog| catalog.state().clone(), - ) - .await - .map_err(|e| anyhow!("failed to read on-disk catalog state: {e}"))? - .map(|catalog| { - catalog - // The set of unfinalized shards in the catalog are updated asynchronously by - // background processes. As a result, the value may legitimately change after - // fetching the memory catalog but before fetching the disk catalog, causing the - // comparison to fail. This is a gross hack that always sets the disk catalog's - // unfinalized shards equal to the memory catalog's unfinalized shards to ignore - // false negatives. Unfortunately, we also end up ignoring true negatives. - .dump(unfinalized_shards) - .expect("state must be dumpable") - }); - let Some(disk_catalog) = maybe_disk_catalog else { - // TODO(parkmycar, def-): Ideally this could be an error, but a lot of test suites fail. We - // should explicitly disable consistency check in these test suites. - tracing::warn!("No Catalog state on disk, skipping consistency check"); - return Ok(()); - }; + let catalog = Catalog::open_debug_catalog_inner( + persist_client, + storage, + SYSTEM_TIME.clone(), + Some(state.materialize.environment_id.clone()), + state.build_info, + system_parameter_defaults, + &state.materialize.bootstrap_args, + // The expression cache can be taxing on the CPU and is unnecessary for consistency checks. + Some(false), + Some(DebugAwsContext { + aws_account_id: state.materialize.aws_account_id.clone(), + aws_external_id_prefix: state.materialize.aws_external_id_prefix.clone(), + aws_connection_role_arn: state.materialize.aws_connection_role_arn.clone(), + }), + ) + .await + .context("failed to reconstruct on-disk catalog state")?; + let disk_catalog = catalog + .for_session(&Session::dummy()) + .state() + // Shard finalization is asynchronous and is not part of this comparison. + .dump(unfinalized_shards) + .expect("state must be dumpable"); + catalog.expire().await; + + // Only the compatibility path needs a quiet window. Bracket the entire + // readonly open and reconstruction, since it may sync during initialization. + if catalog_memory_dump(state).await? != memory_catalog { + bail!("catalog changed during legacy reconstruction, retrying"); + } if disk_catalog != memory_catalog { // The state objects here are around 100k lines pretty printed, so find the diff --git a/test/0dt/mzcompose.py b/test/0dt/mzcompose.py index f20f6f1957bcc..4434287b27f90 100644 --- a/test/0dt/mzcompose.py +++ b/test/0dt/mzcompose.py @@ -20,12 +20,17 @@ import psycopg import pymysql +import requests from psycopg.errors import OperationalError from psycopg.sql import SQL, Identifier from materialize import buildkite from materialize.mzcompose import get_default_system_parameters, sanitizer_enabled -from materialize.mzcompose.composition import Composition, Service +from materialize.mzcompose.composition import ( + Composition, + Service, + WorkflowArgumentParser, +) from materialize.mzcompose.services.kafka import Kafka from materialize.mzcompose.services.materialized import ( LEADER_STATUS_HEALTHCHECK, @@ -1030,6 +1035,285 @@ def workflow_basic(c: Composition) -> None: """)) +def workflow_index_read_protection( + c: Composition, parser: WorkflowArgumentParser +) -> None: + """Follow committed index permissions beyond a readonly catalog savepoint.""" + parser.add_argument( + "--index-count", + type=int, + default=1, + help="Stable live cohort including keep (default: 1), plus one live drop index", + ) + parser.add_argument("--publication-interval-ms", type=int, default=1000) + args = parser.parse_args() + if args.index_count < 1 or args.publication_interval_ms < 1: + parser.error("index count and publication interval must be positive") + c.down(destroy_volumes=True) + c.up("mz_old", Service("testdrive", idle=True)) + setup(c) + + def query(sql: str, service: str = "mz_old") -> list[tuple]: + with c.sql_connection( + service=service, + port=6877, + user="mz_system", + startup_params={"statement_timeout": "5s", "cluster": "cluster"}, + ) as conn: + cursor = conn.execute(sql.encode()) + return cursor.fetchall() if cursor.description is not None else [] + + query("ALTER SYSTEM SET enable_logical_compaction_window = true") + query("ALTER SYSTEM SET enable_index_options = true") + query(f"ALTER SYSTEM SET max_objects_per_schema = {args.index_count + 100}") + query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '1h'") + unpublished_start = time.monotonic() + c.testdrive(dedent(""" + > CREATE CLUSTER permission_suspended SIZE 'scale=1,workers=1', REPLICATION FACTOR 0; + > CREATE TABLE permission_suspended_input (a int); + > INSERT INTO permission_suspended_input VALUES (1); + > CREATE TABLE permission_input (a int); + > INSERT INTO permission_input VALUES (1); + > CREATE VIEW permission_keep AS SELECT a + 1 AS a FROM permission_input; + > CREATE VIEW permission_drop AS SELECT a + 2 AS a FROM permission_input; + > CREATE INDEX permission_keep_idx ON permission_keep (a) + WITH (RETAIN HISTORY = FOR '1s'); + > CREATE INDEX permission_drop_idx ON permission_drop (a) + WITH (RETAIN HISTORY = FOR '1s'); + > CREATE INDEX permission_suspended_idx IN CLUSTER permission_suspended + ON permission_suspended_input (a) WITH (RETAIN HISTORY = FOR '1s'); + > CREATE INDEX permission_bootstrap_drop_idx IN CLUSTER permission_suspended + ON permission_suspended_input (a) WITH (RETAIN HISTORY = FOR '1s'); + """)) + extra_names = [f"permission_cohort_{i}_idx" for i in range(1, args.index_count)] + if extra_names: + c.testdrive( + "\n".join( + f"> CREATE INDEX {name} IN CLUSTER cluster ON permission_input (a) " + "WITH (RETAIN HISTORY = FOR '1s');" + for name in extra_names + ) + ) + names = [ + "permission_keep_idx", + "permission_drop_idx", + "permission_suspended_idx", + "permission_bootstrap_drop_idx", + *extra_names, + ] + names_sql = ", ".join(f"'{name}'" for name in names) + ids = dict(query(f""" + SELECT o.name, g.global_id FROM mz_objects o + JOIN mz_internal.mz_object_global_ids g ON g.id = o.id + WHERE o.name IN ({names_sql}) + """)) + assert set(ids) == set(names), ids + assert len(set(ids.values())) == len(names), ids + stable_ids = [ids[name] for name in ["permission_keep_idx", *extra_names]] + + def catalog_bounds(service: str) -> dict: + # mz_catalog_raw tails the writer even in readonly mode. The memory dump, + # not a SQL query over that live collection, exposes the frozen savepoint. + response = requests.get( + f"http://localhost:{c.port(service, 6878)}/api/catalog/dump", timeout=30 + ) + response.raise_for_status() + return response.json()["collection_compaction_bounds"] + + def frozen_bounds() -> dict: + bounds = catalog_bounds("mz_new") + return {name: bounds.get(gid) for name, gid in ids.items()} + + bounds = catalog_bounds("mz_old") + assert all(gid not in bounds for gid in ids.values()) + # Starting without the health wait overlaps DDL with boot, but does not prove + # whether the savepoint includes this index. Both schedules must boot. + c.up("mz_new", wait=False) + query("DROP INDEX permission_bootstrap_drop_idx") + c.await_mz_deployment_status(DeploymentStatus.READY_TO_PROMOTE, "mz_new") + bounds = catalog_bounds("mz_old") + assert all(gid not in bounds for gid in ids.values()) + assert time.monotonic() - unpublished_start < 300 + frozen = frozen_bounds() + assert all(bound is None for bound in frozen.values()), frozen + assert ( + query( + """ + SELECT count(*) FROM mz_cluster_replicas r + JOIN mz_clusters c ON c.id = r.cluster_id + WHERE c.name = 'permission_suspended' + """, + "mz_new", + ) + == [(0,)] + ) + with c.override( + Testdrive( + materialize_url="postgres://materialize@mz_new:6875", + materialize_url_internal="postgres://materialize@mz_new:6877", + mz_service="mz_new", + materialize_params={"cluster": "cluster"}, + no_reset=True, + seed=1, + default_timeout=DEFAULT_TIMEOUT, + ) + ): + c.up(Service("testdrive", idle=True)) + c.testdrive(dedent(""" + > SET statement_timeout = '5s'; + > SET TRANSACTION_ISOLATION = 'SERIALIZABLE'; + ! INSERT INTO permission_input VALUES (99); + contains: cannot write in read-only mode + > SELECT DISTINCT name, hydrated + FROM mz_internal.mz_hydration_statuses + JOIN mz_indexes ON (id = object_id) + WHERE name IN ('permission_keep_idx', 'permission_drop_idx'); + permission_drop_idx true + permission_keep_idx true + > SELECT * FROM permission_keep; + 2 + > SELECT * FROM permission_drop; + 3 + """)) + assert frozen_bounds() == frozen + bounds = catalog_bounds("mz_old") + assert all(gid not in bounds for gid in ids.values()) + assert time.monotonic() - unpublished_start < 300 + query( + "ALTER SYSTEM SET catalog_read_protection_publish_interval = " + f"'{args.publication_interval_ms}ms'" + ) + + # The subscriber follows permissions, not SQL schema changes. A removed + # writer index must not prevent delivery for another stable global ID. + for value in (2, 3): + if value == 3: + query("DROP INDEX permission_drop_idx") + query(f"INSERT INTO permission_input VALUES ({value})") + keep = ids["permission_keep_idx"] + cohort = stable_ids + ([ids["permission_drop_idx"]] if value == 2 else []) + bounds = catalog_bounds("mz_old") + baseline = { + gid: bounds.get(gid, {"elements": [0]})["elements"][0] for gid in cohort + } + deadline = time.monotonic() + 300 + publication_start = time.monotonic() + while True: + writer_request_start = time.monotonic() + bounds = catalog_bounds("mz_old") + committed_observed = time.monotonic() + targets = { + gid: bounds.get(gid, {"elements": [0]})["elements"][0] + for gid in cohort + } + if all(targets[gid] > baseline[gid] for gid in cohort): + break + assert time.monotonic() < deadline, (baseline, targets) + time.sleep(0.5) + committed = targets[keep] + assert committed > 0 + # Freeze the writer snapshot. One aggregate query checks every target + # without installing execution read holds on the governed indexes. + targets_sql = ", ".join( + f"('{gid}', {permission})" for gid, permission in targets.items() + ) + follower_requests = 0 + follower_request_seconds = 0.0 + while True: + follower_request_start = time.monotonic() + [(observed_count,)] = query( + f""" + SELECT count(*) FROM (VALUES {targets_sql}) AS targets(gid, permission) + JOIN mz_internal.mz_frontiers f ON f.object_id = targets.gid + WHERE f.read_frontier::text::numeric >= targets.permission + """, + "mz_new", + ) + follower_observed = time.monotonic() + follower_requests += 1 + follower_request_seconds += follower_observed - follower_request_start + if observed_count == len(cohort): + break + assert time.monotonic() < deadline, ( + len(cohort), + observed_count, + targets, + ) + time.sleep(0.5) + c.testdrive(dedent(f""" + > SELECT read_frontier::text::numeric >= {committed} + FROM mz_internal.mz_frontiers WHERE object_id = '{keep}'; + true + > SET TRANSACTION_ISOLATION = 'SERIALIZABLE'; + > SELECT count(*), sum(a) FROM permission_keep; + {value} {sum(range(2, value + 2))} + """)) + print( + json.dumps( + { + "workflow": "index-read-protection", + "publication_interval_ms": args.publication_interval_ms, + "index_count": args.index_count, + "initial_live_index_count": len(stable_ids) + 1, + "cohort_index_count": len(cohort), + "follower_observed_index_count": observed_count, + "observer": "writer catalog dump and follower SQL frontier polling at 500ms", + "measurement_scope": "snapshot observation to all-complete read frontier observation, not isolated subscriber CPU or unobserved commit-to-poll latency", + "committed_permission": committed, + "writer_permission_observation_seconds": committed_observed + - publication_start, + "writer_observed_to_follower_frontier_seconds": follower_observed + - committed_observed, + "max_all_complete_observation_lag_seconds": follower_observed + - committed_observed, + "writer_snapshot_request_seconds": committed_observed + - writer_request_start, + "follower_frontier_request_count": follower_requests, + "follower_frontier_request_seconds": follower_request_seconds, + "follower_final_frontier_request_seconds": follower_observed + - follower_request_start, + "follower_read_check_seconds": time.monotonic() + - follower_observed, + "writer_index_dropped": value == 3, + }, + sort_keys=True, + ) + ) + assert frozen_bounds() == frozen + plan = query("EXPLAIN SELECT * FROM permission_keep", "mz_new")[0][0] + assert "permission_keep_idx" in plan, plan + + query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '1h'") + capped_start = time.monotonic() + cap = catalog_bounds("mz_old")[keep]["elements"][0] + for value in (4, 5, 6): + query(f"INSERT INTO permission_input VALUES ({value})") + [(upper,)] = query( + "SELECT write_frontier::text::numeric FROM mz_internal.mz_frontiers " + f"WHERE object_id = '{keep}'", + "mz_new", + ) + c.testdrive(dedent(f""" + > SELECT write_frontier::text::numeric > {max(upper, cap + 1000)}, + read_frontier::text::numeric <= {cap} + FROM mz_internal.mz_frontiers WHERE object_id = '{keep}'; + true true + """)) + assert catalog_bounds("mz_old")[keep]["elements"] == [cap] + assert time.monotonic() - capped_start < 300 + + query( + "ALTER SYSTEM SET catalog_read_protection_publish_interval = " + f"'{args.publication_interval_ms}ms'" + ) + c.testdrive(dedent(f""" + > SELECT read_frontier::text::numeric > {cap} + FROM mz_internal.mz_frontiers WHERE object_id = '{keep}'; + true + """)) + assert frozen_bounds() == frozen + + def workflow_kafka_source_rehydration(c: Composition) -> None: """Verify Kafka source rehydration in 0dt deployment""" c.down(destroy_volumes=True) diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index 7f0e8a3f6c4fb..972a9a956835e 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -15,20 +15,29 @@ import copy import json +import math +import re import time from collections.abc import Callable +from concurrent.futures import ThreadPoolExecutor from datetime import datetime from textwrap import dedent import requests +from psycopg import Error as PsycopgError from psycopg.errors import ( InternalError_, OperationalError, ) +from urllib3.exceptions import ReadTimeoutError from materialize import MZ_ROOT, buildkite from materialize.mzcompose import cluster_replica_size_map -from materialize.mzcompose.composition import Composition, Service +from materialize.mzcompose.composition import ( + Composition, + Service, + WorkflowArgumentParser, +) from materialize.mzcompose.services.kafka import Kafka from materialize.mzcompose.services.materialized import Materialized from materialize.mzcompose.services.metadata_store import CockroachOrPostgresMetadata @@ -974,6 +983,59 @@ def verify() -> None: c.sql("DROP CLUSTER uncached_refresh", reuse_connection=False) +def _catalog_protection_metrics(text: str, shard: str) -> dict: + names = ( + "mz_persist_shard_diff_size_bytes", + "mz_persist_shard_cmd_succeeded", + "mz_persist_shard_usage_current_state_batches_bytes", + "mz_persist_shard_compaction_applied", + "mz_persist_shard_batch_part_count", + ) + series = {} + for line in text.splitlines(): + if line.split("{", 1)[0] in names and f'shard="{shard}"' in line: + labels, value = line.rsplit(" ", 1) + assert labels not in series, labels + series[labels] = float(value) + counts = { + name: sum(key.split("{", 1)[0] == name for key in series) for name in names + } + assert all(counts.values()), (shard, counts) + assert all(math.isfinite(value) for value in series.values()), (shard, series) + return { + "series": series, + "series_counts": counts, + "totals": { + name: sum( + value for key, value in series.items() if key.split("{", 1)[0] == name + ) + for name in names + }, + } + + +def _catalog_committed_update_metrics(text: str) -> dict: + names = ("mz_catalog_committed_updates", "mz_catalog_committed_update_bytes") + kinds = ("compaction_bound", "maintained_read_requirement", "other") + series = {} + by_kind = {kind: {} for kind in kinds} + for line in text.splitlines(): + name = line.split("{", 1)[0] + if name not in names: + continue + labels, value = line.rsplit(" ", 1) + kind_match = re.search(r'(?:\{|,)kind="([^"]+)"(?:,|\})', labels) + assert kind_match is not None, labels + kind = kind_match.group(1) + assert kind in by_kind and name not in by_kind[kind], labels + number = float(value) + assert math.isfinite(number), (labels, number) + series[labels] = number + by_kind[kind][name] = number + assert all(set(values) == set(names) for values in by_kind.values()), by_kind + return {"series": series, "by_kind": by_kind} + + def workflow_catalog_read_protection(c: Composition) -> None: """Exercise logical recovery protection and persist compaction without cached plans.""" c.down(destroy_volumes=True) @@ -1030,6 +1092,9 @@ def inspect(global_id: str) -> dict: "enable_catalog_read_protection": "true", "enable_expression_cache": "false", "enable_logical_compaction_window": "true", + "enable_index_options": "true", + # Crash-surviving readers must expire before physical reclamation is observable. + "persist_reader_lease_duration": "60s", # Small batches must exercise real blob compaction, not inline writes. "persist_inline_writes_single_max_bytes": "0", "persist_compaction_heuristic_min_inputs": "2", @@ -1038,6 +1103,7 @@ def inspect(global_id: str) -> dict: Testdrive( name="testdrive_no_reset", no_reset=True, + consistent_seed=True, materialize_url="postgres://mz_system@materialized:6877", ), ): @@ -1064,6 +1130,118 @@ def inspect(global_id: str) -> dict: > INSERT INTO protected_control VALUES (1); > CREATE TABLE protected_catalog_probe (a int); """) + td(""" + > CREATE TABLE protected_index_input (a int) WITH (RETAIN HISTORY = FOR '1s'); + > INSERT INTO protected_index_input VALUES (1); + """) + index_input = gid("protected_index_input") + td(f""" + > SELECT read_frontier::text::numeric > 0 + FROM mz_internal.mz_frontiers WHERE object_id = '{index_input}'; + true + """) + physical_deadline = time.monotonic() + 120 + while True: + physical_since = inspect(index_input)["since"] + if physical_since and physical_since[0] > 0: + break + if time.monotonic() >= physical_deadline: + raise UIError( + f"index input physical since did not advance: {physical_since}" + ) + time.sleep(0.5) + assert inspect(index_input)["since"][0] > 0 + query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '1h'") + unpublished_start = time.monotonic() + td(""" + > CREATE INDEX protected_index ON protected_index_input (a) + WITH (RETAIN HISTORY = FOR '1s'); + """) + index = gid("protected_index") + index_bound_sql = record_sql("CollectionCompactionBound", index) + index_frontier_sql = ( + "SELECT read_frontier::text::numeric, write_frontier::text::numeric " + f"FROM mz_internal.mz_frontiers WHERE object_id = '{index}'" + ) + + def index_permission() -> dict | None: + response = requests.get( + f"http://localhost:{c.port('materialized', 6878)}/api/catalog/dump", + timeout=10, + ) + response.raise_for_status() + return response.json()["collection_compaction_bounds"].get(index) + + assert index_permission() is None + td(f""" + > SELECT read_frontier > 0 FROM ({index_frontier_sql}) f; + true + > SELECT a FROM protected_index_input; + 1 + """) + assert index_permission() is None + c.kill("materialized") + c.up("materialized") + assert gid("protected_index") == index + td(f""" + > SELECT read_frontier > 0 FROM ({index_frontier_sql}) f; + true + > SELECT a FROM protected_index_input; + 1 + """) + assert index_permission() is None + assert time.monotonic() - unpublished_start < 300 + query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '1s'") + td(f""" + > SELECT (v->>'frontier')::numeric > 0 + FROM ({index_bound_sql}) r(v); + true + > SELECT read_frontier > 0 FROM ({index_frontier_sql}) f; + true + """) + query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '1h'") + # Stay well inside the positive cadence while driving progress past the cap. + # No peek is kept open to retain index history. + capped_start = time.monotonic() + published_permission = index_permission() + assert published_permission is not None + cap = published_permission["elements"][0] + for value in range(2, 5): + query(f"INSERT INTO protected_index_input VALUES ({value})") + [(read, upper)] = query(index_frontier_sql) + td(f""" + > SELECT write_frontier > {max(upper, cap + 1000)}, + read_frontier <= {cap} + FROM ({index_frontier_sql}) f; + true true + """) + assert record("CollectionCompactionBound", index)["frontier"] == cap + assert index_permission() == {"elements": [cap]} + assert time.monotonic() - capped_start < 300 + query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '1s'") + td(f""" + > SELECT (v->>'frontier')::numeric > {cap} + FROM ({index_bound_sql}) r(v); + true + > SELECT read_frontier > {cap} FROM ({index_frontier_sql}) f; + true + """) + permission = record("CollectionCompactionBound", index)["frontier"] + c.kill("materialized") + c.up("materialized") + assert gid("protected_index") == index + assert record("CollectionCompactionBound", index)["frontier"] >= permission + td(f""" + > SELECT read_frontier >= {permission} FROM ({index_frontier_sql}) f; + true + > SELECT a FROM protected_index_input ORDER BY a; + 1 + 2 + 3 + 4 + """) + plan = query("EXPLAIN SELECT a FROM protected_index_input")[0][0] + assert "protected_index" in plan, plan eliminated_birth = record( "CollectionCompactionBound", gid("protected_eliminated") )["frontier"] @@ -1126,10 +1304,15 @@ def verify_plan() -> None: control_sql = record_sql(bound_kind, ids["protected_control"]) - def await_state(description: str, ready: Callable[[], bool]) -> None: + def await_state( + description: str, + ready: Callable[[], bool], + timeout: float = 120, + advance: Callable[[], None] | None = None, + ) -> None: # INSPECT is not composable SQL. Native testdrive Retry waits for each # publication advance, then we inspect without acquiring input read holds. - deadline = time.monotonic() + 120 + deadline = time.monotonic() + timeout while True: control_bound = record(bound_kind, ids["protected_control"])["frontier"] if ready(): @@ -1137,6 +1320,8 @@ def await_state(description: str, ready: Callable[[], bool]) -> None: if time.monotonic() >= deadline: sample(f"timeout: {description}") raise UIError(f"timed out waiting for {description}") + if advance is not None: + advance() # Persist schedules merges in response to writes. Keep producing real # batches while waiting, including after a requirement has completed. for _ in range(8): @@ -1170,51 +1355,45 @@ def updates(state: dict) -> int: return sum(batch["len"] for batch in batches(state)) def sample(label: str) -> dict: + history_start = time.monotonic() catalog_state = inspect(ids["mz_catalog_raw"]) + catalog_inspection_end = time.monotonic() + metrics_start = time.monotonic() response = requests.get( f"http://localhost:{c.port('materialized', 6878)}/metrics", timeout=10 ) + metrics_end = time.monotonic() response.raise_for_status() - metric_lines = [ - line - for line in response.text.splitlines() - if line.split("{", 1)[0] - in ( - "mz_persist_shard_diff_size_bytes", - "mz_persist_shard_cmd_succeeded", - "mz_persist_shard_compaction_applied", - "mz_persist_shard_batch_part_count", - "mz_persist_shard_usage_current_state_batches_bytes", - ) - ] - def metrics_for(shard_id: str) -> dict[str, float]: - return { - line.split("{", 1)[0]: float(line.rsplit(" ", 1)[1]) - for line in metric_lines - if f'shard="{shard_id}"' in line - } + def metrics_for(shard_id: str) -> dict: + return _catalog_protection_metrics(response.text, shard_id) + catalog_metrics = metrics_for(catalog_state["shard_id"]) counters = { - metric: value - for metric, value in metrics_for(catalog_state["shard_id"]).items() - if metric - in ( + metric: catalog_metrics["totals"][metric] + for metric in ( "mz_persist_shard_diff_size_bytes", "mz_persist_shard_cmd_succeeded", ) } - assert len(counters) == 2, counters state = { "label": label, - "monotonic_seconds": time.monotonic(), + "metrics_start": metrics_start, + "metrics_end": metrics_end, + "catalog_inspection_start": history_start, + "catalog_inspection_end": catalog_inspection_end, + "catalog_metrics": catalog_metrics, "catalog_seqno": catalog_state["seqno"], "catalog_counters": counters, "collections": {}, } for name, global_id in ids.items(): + inspection_start = time.monotonic() shard = inspect(global_id) + inspection_end = time.monotonic() state["collections"][name] = { + "inspection_start": inspection_start, + "inspection_end": inspection_end, "id": global_id, "bound": record(bound_kind, global_id), "shard_id": shard["shard_id"], @@ -1240,11 +1419,11 @@ def metrics_for(shard_id: str) -> dict[str, float]: byte_metric = "mz_persist_shard_usage_current_state_batches_bytes" state["extra_retained_input_bytes"] = ( state["collections"]["protected_eliminated"]["environmentd_metrics"][ - byte_metric - ] + "totals" + ][byte_metric] - state["collections"]["protected_control"]["environmentd_metrics"][ - byte_metric - ] + "totals" + ][byte_metric] ) print(json.dumps(state, sort_keys=True)) return state @@ -1310,18 +1489,29 @@ def verify_pending() -> bool: """) await_state("control physically compacted past first refresh", verify_pending) after = sample("pending-after-compaction") - elapsed = after["monotonic_seconds"] - before["monotonic_seconds"] + assert ( + before["catalog_metrics"]["series"].keys() + == after["catalog_metrics"]["series"].keys() + ) + assert all( + after["catalog_counters"][metric] >= value + for metric, value in before["catalog_counters"].items() + ) + elapsed = after["metrics_end"] - before["metrics_start"] # These are measured catalog-shard totals, including builtin maintenance, # not an attribution of every persist command to bound publication. print( json.dumps( { - "publication_sample_seconds": elapsed, + "cost_scope": "combined catalog traffic during variable-duration correctness proof, not a cost comparison", + "counter_sample_outer_seconds": elapsed, + "counter_sample_inner_seconds": after["metrics_start"] + - before["metrics_end"], "catalog_counter_deltas": { metric: value - before["catalog_counters"][metric] for metric, value in after["catalog_counters"].items() }, - "catalog_diff_bytes_per_second": ( + "catalog_diff_bytes_per_second_lower_bound": ( after["catalog_counters"]["mz_persist_shard_diff_size_bytes"] - before["catalog_counters"]["mz_persist_shard_diff_size_bytes"] ) @@ -1461,6 +1651,931 @@ def ongoing_advanced() -> bool: > DROP MATERIALIZED VIEW protected_ar; > DROP MATERIALIZED VIEW protected_br; """) + + c.up("kafka") + td(""" + > CREATE CONNECTION protected_kafka TO KAFKA + (BROKER '${testdrive.kafka-addr}', SECURITY PROTOCOL PLAINTEXT); + > CREATE CLUSTER protected_ingest SIZE 'scale=1,workers=1'; + > CREATE CLUSTER protected_sink_cluster SIZE 'scale=1,workers=1', REPLICATION FACTOR 0; + $ kafka-create-topic topic=protected-source partitions=1 + $ kafka-ingest format=bytes topic=protected-source + one + two + > CREATE SOURCE protected_source IN CLUSTER protected_ingest + FROM KAFKA CONNECTION protected_kafka + (TOPIC 'testdrive-protected-source-${testdrive.seed}'); + > ALTER SOURCE protected_source SET (RETAIN HISTORY = FOR '1s'); + > CREATE TABLE protected_export FROM SOURCE protected_source + (REFERENCE "testdrive-protected-source-${testdrive.seed}") + FORMAT TEXT WITH (RETAIN HISTORY = FOR '1s'); + > SELECT * FROM protected_export; + one + two + + > CREATE TABLE protected_sink_input (a int) WITH (RETAIN HISTORY = FOR '1s'); + > INSERT INTO protected_sink_input VALUES (1); + > CREATE SINK protected_sink IN CLUSTER protected_sink_cluster + FROM protected_sink_input INTO KAFKA CONNECTION protected_kafka + (TOPIC 'protected-sink-${testdrive.seed}') FORMAT JSON ENVELOPE DEBEZIUM; + """) + source = gid("protected_source") + export = gid("protected_export") + sink = gid("protected_sink") + sink_input = gid("protected_sink_input") + + def storage_requirement(owner: str, inputs: list[str]) -> dict: + # Sample input limits before the requirement, and durable output after it. + # Monotonicity makes these inequalities safe across publication races. + limits = [ + (record(bound_kind, input_id)["frontier"], inspect(input_id)["since"]) + for input_id in inputs + ] + requirement = record(requirement_kind, owner) + assert sorted(requirement["inputs"], key=str) == sorted( + [encoded_id(input_id) for input_id in inputs], key=str + ), requirement + frontier = requirement["frontier"] + assert isinstance(frontier, int), requirement + assert all( + bound is not None + and bound <= frontier + and since + and since[0] <= frontier + for bound, since in limits + ), (requirement, limits) + return requirement + + def storage_advanced(owner: str, inputs: list[str], start: int) -> bool: + requirement = storage_requirement(owner, inputs) + upper = inspect(owner)["upper"] + print( + json.dumps({"owner": owner, "requirement": requirement, "upper": upper}) + ) + if requirement["frontier"] <= start: + return False + assert upper and requirement["frontier"] <= upper[0] - 1, ( + requirement, + upper, + ) + return True + + pending_sink = storage_requirement(sink, [sink, sink_input]) + sink_start = pending_sink["frontier"] + assert inspect(sink)["upper"] == [0] + td(""" + > DELETE FROM protected_sink_input; + > INSERT INTO protected_sink_input VALUES (2); + """) + + def sink_pending() -> bool: + assert storage_requirement(sink, [sink, sink_input]) == pending_sink + assert inspect(sink)["upper"] == [0] + assert query(""" + SELECT count(*) FROM mz_cluster_replicas r + JOIN mz_clusters c ON c.id = r.cluster_id + WHERE c.name = 'protected_sink_cluster' + """) == [(0,)] + return record(bound_kind, ids["protected_control"])["frontier"] > sink_start + + await_state( + "short retention elapsing with initial sink output pending", sink_pending + ) + await_state( + "remap permission advancing before export birth", + lambda: storage_advanced(source, [source], 0) + and record(bound_kind, source)["frontier"] > 0, + ) + td(""" + > ALTER CLUSTER protected_ingest SET (REPLICATION FACTOR 0); + """) + remap_permission = record(bound_kind, source)["frontier"] + remap_since = inspect(source)["since"] + td(""" + > CREATE TABLE protected_late_export FROM SOURCE protected_source + (REFERENCE "testdrive-protected-source-${testdrive.seed}") + FORMAT TEXT WITH (RETAIN HISTORY = FOR '1s'); + """) + late_export = gid("protected_late_export") + late_birth = storage_requirement(late_export, [source, late_export]) + birth = late_birth["frontier"] + assert birth >= remap_permission > 0, (late_birth, remap_permission) + assert record(bound_kind, late_export)["frontier"] == birth + assert inspect(late_export)["upper"] == [0] + # This records physical lag but does not require it. Native SQL read holds + # constrain catalog permission too, so they cannot pin only physical since. + print( + json.dumps( + { + "export_birth": birth, + "remap_permission_before_birth": remap_permission, + "remap_since_before_birth": remap_since, + "remap_since_after_birth": inspect(source)["since"], + } + ) + ) + + c.kill("materialized") + c.up("materialized") + assert sink_pending() + assert storage_requirement(late_export, [source, late_export]) == late_birth + assert inspect(late_export)["upper"] == [0] + td(""" + > ALTER CLUSTER protected_ingest SET (REPLICATION FACTOR 1); + > ALTER CLUSTER protected_sink_cluster SET (REPLICATION FACTOR 1); + > SELECT * FROM protected_export; + one + two + > SELECT * FROM protected_late_export; + one + two + $ kafka-verify-data format=json sink=materialize.public.protected_sink key=false sort-messages=true + {"before": null, "after": {"a": 1}} + {"before": null, "after": {"a": 2}} + {"before": {"a": 1}, "after": null} + $ kafka-ingest format=bytes topic=protected-source + three + > SELECT * FROM protected_late_export; + one + three + two + """) + await_state( + "source recovery requirements advancing and remap readers releasing history", + lambda: storage_advanced(source, [source], birth) + and storage_advanced(export, [source, export], birth) + and storage_advanced(late_export, [source, late_export], birth) + and inspect(late_export)["since"][0] > birth + and inspect(source)["since"][0] > birth, + # Ingestion resumption retains its leased reader for 300 seconds. + timeout=360, + ) + source_rows = ["one", "two", "three"] + + def drive_remap_compaction() -> None: + # Empty progress batches can fuse without merging the old nonempty + # remap batch. New offsets drive merges after the readers release history. + row = f"compaction-probe-{len(source_rows)}" + source_rows.append(row) + td(f""" + $ kafka-ingest format=bytes topic=protected-source + {row} + > SELECT count(*) FROM protected_late_export; + {len(source_rows)} + """) + + def verify_source_rows() -> None: + expected = "\n".join(sorted(source_rows)) + td( + f"> SELECT * FROM protected_export;\n{expected}\n" + f"> SELECT * FROM protected_late_export;\n{expected}\n" + ) + + await_state( + "remap batches physically compacting past export birth", + lambda: compacted_past(inspect(source), birth), + advance=drive_remap_compaction, + ) + verify_source_rows() + await_state( + "sink durable progress releasing initial input history", + lambda: storage_advanced(sink, [sink, sink_input], sink_start) + and inspect(sink_input)["since"][0] > sink_start, + ) + + td(""" + > CREATE TABLE protected_sink_next (a int) WITH (RETAIN HISTORY = FOR '1s'); + > INSERT INTO protected_sink_next VALUES (99); + """) + sink_next = gid("protected_sink_next") + # A table's physical upper can lag its committed txn-WAL writes. Use the + # SQL oracle after INSERT to put the ignored row behind the sink's progress. + [(timestamp,)] = query( + "EXPLAIN TIMESTAMP AS JSON FOR SELECT * FROM protected_sink_next" + ) + next_snapshot = int(json.loads(timestamp)["determination"]["oracle_read_ts"]) + await_state( + "sink progress passing the replacement input snapshot", + lambda: storage_advanced(sink, [sink, sink_input], next_snapshot), + ) + td(""" + > ALTER SINK protected_sink SET FROM protected_sink_next; + > ALTER CLUSTER protected_sink_cluster SET (REPLICATION FACTOR 0); + """) + before_restart = storage_requirement(sink, [sink, sink_next]) + assert before_restart["frontier"] > next_snapshot + c.kill("materialized") + c.up("materialized") + # Restart fences the old worker before more input arrives. At a stationary + # upper, publication must reach exactly its predecessor, not just stay below it. + await_state( + "recovered paused sink publishing its durable upper predecessor", + lambda: storage_requirement(sink, [sink, sink_next])["frontier"] + == inspect(sink)["upper"][0] - 1, + ) + altered = storage_requirement(sink, [sink, sink_next]) + assert altered["frontier"] >= before_restart["frontier"] + td(f""" + > SELECT read_frontier <= {altered['frontier']}::mz_timestamp, + write_frontier = {altered['frontier'] + 1}::mz_timestamp + FROM mz_internal.mz_frontiers WHERE object_id = '{sink}'; + true true + """) + td(""" + > INSERT INTO protected_sink_next VALUES (3); + > INSERT INTO protected_sink_input VALUES (100); + """) + await_state( + "ALTER releasing the old input while protecting the new one", + lambda: record(bound_kind, sink_input)["frontier"] > altered["frontier"], + ) + assert storage_requirement(sink, [sink, sink_next]) == altered + verify_source_rows() + td(""" + > ALTER CLUSTER protected_sink_cluster SET (REPLICATION FACTOR 1); + $ kafka-verify-data format=json sink=materialize.public.protected_sink key=false + {"before": null, "after": {"a": 3}} + $ kafka-ingest format=bytes topic=protected-source + four + """) + source_rows.append("four") + verify_source_rows() + await_state( + "altered sink advancing after recovery", + lambda: storage_advanced(sink, [sink, sink_next], altered["frontier"]) + and inspect(sink_next)["since"][0] > altered["frontier"], + ) + + td(""" + > ALTER CLUSTER protected_sink_cluster SET (REPLICATION FACTOR 0); + > CREATE TABLE protected_no_snapshot_input (a int) + WITH (RETAIN HISTORY = FOR '1s'); + > INSERT INTO protected_no_snapshot_input VALUES (10); + """) + no_snapshot_input = gid("protected_no_snapshot_input") + [(timestamp,)] = query( + "EXPLAIN TIMESTAMP AS JSON FOR SELECT * FROM protected_no_snapshot_input" + ) + old_row = int(json.loads(timestamp)["determination"]["oracle_read_ts"]) + fresh_sink = "" + fresh_requirement: dict = {} + attempt = 0 + + def fresh_sink_during_lag() -> bool: + nonlocal fresh_sink, fresh_requirement, attempt + permission = record(bound_kind, no_snapshot_input)["frontier"] + since_before = inspect(no_snapshot_input)["since"][0] + if permission <= old_row or since_before >= permission: + return False + attempt += 1 + td(f""" + > CREATE SINK protected_no_snapshot IN CLUSTER protected_sink_cluster + FROM protected_no_snapshot_input INTO KAFKA CONNECTION protected_kafka + (TOPIC 'protected-no-snapshot-{attempt}-${{testdrive.seed}}') + FORMAT JSON ENVELOPE DEBEZIUM WITH (SNAPSHOT = false); + """) + fresh_sink = gid("protected_no_snapshot") + fresh_requirement = storage_requirement( + fresh_sink, [fresh_sink, no_snapshot_input] + ) + since_after = inspect(no_snapshot_input)["since"][0] + permission_after = record(bound_kind, no_snapshot_input)["frontier"] + assert permission <= fresh_requirement["frontier"] <= permission_after, ( + fresh_requirement, + permission, + permission_after, + ) + assert inspect(fresh_sink)["upper"] == [0] + print( + json.dumps( + { + "fresh_sink_snapshot": False, + "permission_before_birth": permission, + "permission_after_birth": permission_after, + "requirement": fresh_requirement, + "since_before_birth": since_before, + "since_after_birth": since_after, + }, + sort_keys=True, + ) + ) + if since_after >= permission: + # A creation only counts if physical lag brackets admission. + query("DROP SINK protected_no_snapshot") + return False + return True + + await_state( + "fresh SNAPSHOT=false sink admitted during physical lag", + fresh_sink_during_lag, + ) + td(""" + > DELETE FROM protected_no_snapshot_input; + > INSERT INTO protected_no_snapshot_input VALUES (20); + """) + fresh_start = fresh_requirement["frontier"] + await_state( + "fresh sink changes pending beyond the retention window", + lambda: record(bound_kind, ids["protected_control"])["frontier"] + > fresh_start, + ) + assert ( + storage_requirement(fresh_sink, [fresh_sink, no_snapshot_input]) + == fresh_requirement + ) + assert inspect(fresh_sink)["upper"] == [0] + c.kill("materialized") + c.up("materialized") + assert ( + storage_requirement(fresh_sink, [fresh_sink, no_snapshot_input]) + == fresh_requirement + ) + assert inspect(fresh_sink)["upper"] == [0] + td(""" + > ALTER CLUSTER protected_sink_cluster SET (REPLICATION FACTOR 1); + $ kafka-verify-data format=json sink=materialize.public.protected_no_snapshot key=false sort-messages=true + {"before": null, "after": {"a": 20}} + {"before": {"a": 10}, "after": null} + """) + await_state( + "fresh sink requirement advancing only after durable output", + lambda: storage_advanced( + fresh_sink, [fresh_sink, no_snapshot_input], fresh_start + ) + and inspect(no_snapshot_input)["since"][0] > fresh_start, + ) + c.down(destroy_volumes=True) + + +def workflow_catalog_publication_measurement( + c: Composition, parser: WorkflowArgumentParser +) -> None: + """Measure the production catalog path. Requires disposable composition volumes.""" + parser.add_argument( + "--collection-counts", + default="100,1000,10000", + help="Total generated objects per step, tables plus filler views or indexes", + ) + parser.add_argument("--publication-rounds", type=int, default=3) + parser.add_argument("--publication-interval-ms", type=int, default=1000) + parser.add_argument("--timeout-seconds", type=int, default=120) + parser.add_argument( + "--active-collections", + type=int, + help="Use this many advancing tables and fill the remaining count with --filler-kind", + ) + parser.add_argument( + "--filler-kind", choices=("views", "indexes"), default="indexes" + ) + parser.add_argument( + "--ddl-rate-hz", + type=float, + default=2, + help="Offered CREATE/DROP pairs per second, zero disables benchmark DDL", + ) + parser.add_argument("--observer", choices=("poll", "none"), default="poll") + parser.add_argument("--observation-seconds", type=float, default=10) + args = parser.parse_args() + try: + counts = sorted(set(int(value) for value in args.collection_counts.split(","))) + except ValueError: + parser.error("--collection-counts must be comma-separated integers") + if ( + not counts + or counts[0] <= 0 + or args.publication_rounds <= 0 + or args.publication_interval_ms <= 0 + or args.timeout_seconds <= 0 + or not math.isfinite(args.observation_seconds) + or args.observation_seconds <= 0 + or not math.isfinite(args.ddl_rate_hz) + or args.ddl_rate_hz < 0 + or ( + args.active_collections is not None + and not 0 < args.active_collections <= counts[0] + ) + ): + parser.error( + "counts, rounds, durations must be positive, DDL rate nonnegative, active collections in 1..min(counts)" + ) + + def query(sql: str) -> list[tuple]: + with c.sql_connection( + port=6877, + user="mz_system", + startup_params={"statement_timeout": f"{args.timeout_seconds}s"}, + ) as conn: + cursor = conn.execute(sql.encode()) + return cursor.fetchall() if cursor.description is not None else [] + + def execute_each(statements: list[str]) -> None: + # One connection per batch keeps large sweeps from being dominated by + # connection setup rather than the catalog path under measurement. + with c.sql_connection( + port=6877, + user="mz_system", + startup_params={"statement_timeout": f"{args.timeout_seconds}s"}, + ) as conn: + conn.autocommit = True + for statement in statements: + conn.execute(statement.encode()) + + def inspect(item_id: str) -> dict: + start = time.monotonic() + state = query(f"INSPECT SHARD '{item_id}'")[0][0] + end = time.monotonic() + logical_start = time.monotonic() + logical = query(f""" + SELECT g.global_id, f.read_frontier::text, f.write_frontier::text + FROM mz_internal.mz_object_global_ids g + JOIN mz_internal.mz_frontiers f ON f.object_id = g.global_id + WHERE g.id = '{item_id}' + ORDER BY g.global_id + """) + logical_end = time.monotonic() + assert logical, (item_id, "missing logical collection frontiers") + batches = [*state["batches"], *state["hollow_batches"].values()] + return { + "start": start, + "end": end, + "logical_frontiers_start": logical_start, + "logical_frontiers_end": logical_end, + "logical_frontiers": [ + { + "global_id": gid, + "read": read, + "write": write, + "upper_minus_read_ms": ( + int(write) - int(read) + if read is not None and write is not None and int(read) > 0 + else None + ), + } + for gid, read, write in logical + ], + "state": { + key: state[key] for key in ("shard_id", "seqno", "since", "upper") + }, + "updates": sum(batch["len"] for batch in batches), + "batch_count": len(batches), + # Transactional tables can have physical uppers behind their logical progress. + # This signed physical difference is not a table publication-lag measurement. + "persist_upper_minus_since_ms": ( + state["upper"][0] - state["since"][0] + if state["upper"] and state["since"] + else None + ), + } + + def metrics_snapshot(shards: dict[str, str]) -> dict: + start = time.monotonic() + response = requests.get( + f"http://localhost:{c.port('materialized', 6878)}/metrics", timeout=10 + ) + end = time.monotonic() + response.raise_for_status() + return { + "start": start, + "end": end, + "catalog_committed_updates": _catalog_committed_update_metrics( + response.text + ), + "shards": { + name: _catalog_protection_metrics(response.text, shard) + for name, shard in shards.items() + }, + } + + def catalog_bounds(timeout: float | tuple[float, float] | None = None) -> dict: + response = requests.get( + f"http://localhost:{c.port('materialized', 6878)}/api/catalog/dump", + timeout=timeout if timeout is not None else args.timeout_seconds, + ) + response.raise_for_status() + return response.json()["collection_compaction_bounds"] + + def concurrent_ddl(conn, start: float, end: float) -> dict: + samples = [] + slot = 0 + offered = math.ceil(args.observation_seconds * args.ddl_rate_hz) + while slot < offered: + scheduled = start + slot / args.ddl_rate_hz + time.sleep(max(0, scheduled - time.monotonic())) + if time.monotonic() >= end: + break + sample = {"slot": slot, "scheduled": scheduled, "start": time.monotonic()} + try: + conn.execute(b"CREATE VIEW publication_ddl_probe AS SELECT 1") + sample["create_end"] = time.monotonic() + sample["drop_start"] = time.monotonic() + conn.execute(b"DROP VIEW publication_ddl_probe") + except PsycopgError as error: + sample["error"] = str(error) + sample["sqlstate"] = error.sqlstate + sample["end"] = time.monotonic() + sample["create_seconds"] = ( + sample.get("create_end", sample["end"]) - sample["start"] + ) + sample["drop_seconds"] = ( + sample["end"] - sample["drop_start"] if "drop_start" in sample else None + ) + sample["straddles_observation_end"] = sample["end"] > end + samples.append(sample) + if "error" in sample: + break + # Drop overdue slots instead of replaying backlog at saturation. + slot = max( + slot + 1, math.ceil((time.monotonic() - start) * args.ddl_rate_hz) + ) + latencies = sorted( + sample["end"] - sample["start"] + for sample in samples + if sample["end"] <= end and "error" not in sample + ) + return { + "samples": samples, + "offered_slots": offered, + "missed_slots": offered - len(samples), + "completed_in_window": len(latencies), + "achieved_pairs_per_second": len(latencies) / args.observation_seconds, + "in_window_create_drop_percentiles_seconds": { + f"p{percentile}": latencies[ + math.ceil(len(latencies) * percentile / 100) - 1 + ] + for percentile in (50, 95, 99) + if latencies + }, + } + + def observe( + start: float, end: float, target: int, gids: list[str], filler: dict + ) -> dict: + samples = [] + publication_seconds = None + while args.observer == "poll" and time.monotonic() < end: + request_start = time.monotonic() + remaining = end - request_start + if remaining <= 0: + break + sample = { + "start": request_start, + "budget_seconds": remaining, + "timeout": False, + } + try: + # Requests timeouts bound connection and socket inactivity, not total + # response time. Keep the observed overrun, including body/JSON work. + bounds = catalog_bounds((min(1, remaining / 2), remaining / 2)) + assert_filler(bounds, filler) + sample["passed_target"] = all( + bounds.get(gid, {}).get("elements") + and bounds[gid]["elements"][0] > target + for gid in gids + ) + except requests.RequestException as error: + sample["timeout"] = isinstance(error, requests.Timeout) or isinstance( + error.__context__, ReadTimeoutError + ) + sample["error"] = str(error) + sample["end"] = time.monotonic() + sample["overshoot_seconds"] = max(0, sample["end"] - end) + sample["straddles_observation_end"] = sample["end"] > end + if ( + sample.get("passed_target") + and sample["end"] <= end + and publication_seconds is None + ): + publication_seconds = sample["end"] - start + samples.append(sample) + time.sleep(max(0, min(0.5, end - time.monotonic()))) + return {"samples": samples, "observed_publication_seconds": publication_seconds} + + def assert_filler(bounds: dict, filler: dict) -> None: + changed = { + gid: {"before": bound, "after": bounds.get(gid)} + for gid, bound in filler.items() + if bounds.get(gid) != bound + } + assert not changed, f"sparse filler bounds are not stable: {changed}" + + c.down(destroy_volumes=True) + with c.override( + Materialized( + additional_system_parameter_defaults={ + "enable_catalog_read_protection": "true", + "enable_expression_cache": "false", + "enable_logical_compaction_window": "true", + "max_tables": str(counts[-1]), + "max_objects_per_schema": str(counts[-1] + 2), + "catalog_read_protection_publish_interval": f"{args.publication_interval_ms}ms", + "persist_inline_writes_single_max_bytes": "0", + "persist_compaction_heuristic_min_inputs": "2", + } + ), + Testdrive( + name="testdrive_no_reset", + no_reset=True, + materialize_url="postgres://mz_system@materialized:6877", + ), + ): + c.up("materialized", Service("testdrive_no_reset", idle=True)) + shared_setup_start = time.monotonic() + if args.active_collections is not None and args.filler_kind == "indexes": + execute_each( + [ + "CREATE CLUSTER publication_idle SIZE 'scale=1,workers=1', REPLICATION FACTOR 0", + "CREATE VIEW publication_constant AS SELECT 1 AS a", + ] + ) + shared_setup_end = time.monotonic() + created = 0 + for count in counts: + active = args.active_collections or count + setup_start = time.monotonic() + # Tables advance without DML. Zero-replica indexes must demonstrate + # stable governed bounds, while views measure only catalog item size. + execute_each( + [ + statement + for i in range(created, count) + for statement in ( + ( + f"CREATE TABLE publication_{i} (a int) WITH (RETAIN HISTORY = FOR '1s')", + f"INSERT INTO publication_{i} VALUES (0)", + ) + if i < active + else ( + ( + f"CREATE INDEX publication_filler_{i} IN CLUSTER publication_idle ON publication_constant (a)" + if args.filler_kind == "indexes" + else f"CREATE VIEW publication_filler_{i} AS SELECT {i} AS a" + ), + ) + ) + ] + ) + created = count + table_rows = query(""" + SELECT t.name, t.id, g.global_id FROM mz_tables t + JOIN mz_internal.mz_object_global_ids g ON g.id = t.id + WHERE t.name LIKE 'publication_%' + ORDER BY substring(t.name, 13)::int + """) + assert len(table_rows) == active, (len(table_rows), active) + filler_rows = query(""" + SELECT o.name, o.id, g.global_id FROM mz_objects o + LEFT JOIN mz_internal.mz_object_global_ids g ON g.id = o.id + WHERE o.name LIKE 'publication_filler_%' ORDER BY o.name + """) + assert len(filler_rows) == count - active, ( + len(filler_rows), + count - active, + ) + filler_gids = ( + [row[2] for row in filler_rows] if args.filler_kind == "indexes" else [] + ) + object_counts = dict(query(""" + SELECT type, count(*) FROM mz_objects + WHERE name LIKE 'publication_%' GROUP BY type ORDER BY type + """)) + if args.active_collections is not None and args.filler_kind == "indexes": + assert query(""" + SELECT count(*) FROM mz_cluster_replicas r + JOIN mz_clusters c ON c.id = r.cluster_id + WHERE c.name = 'publication_idle' + """) == [(0,)] + deadline = time.monotonic() + args.timeout_seconds + while True: + bounds = catalog_bounds() + [(storage_shard_count,)] = query(""" + SELECT count(*) FROM mz_internal.mz_storage_shards s + JOIN mz_internal.mz_object_global_ids g ON g.global_id = s.object_id + JOIN mz_objects o ON o.id = g.id WHERE o.name LIKE 'publication_%' + """) + assert storage_shard_count <= active, (storage_shard_count, active) + if storage_shard_count == active and all( + gid in bounds and bounds[gid]["elements"] + for gid in [*filler_gids, *(row[2] for row in table_rows)] + ): + break + if time.monotonic() >= deadline: + raise UIError( + "generated collections did not acquire nonempty bounds or storage mappings, including zero-replica filler indexes" + ) + time.sleep(0.5) + filler = {gid: bounds[gid] for gid in filler_gids} + bound_count_before = len(bounds) + [(catalog_id,)] = query( + "SELECT id FROM mz_objects WHERE name = 'mz_catalog_raw'" + ) + sampled_ids = { + "catalog": catalog_id, + **{ + table_rows[i][0]: table_rows[i][1] + for i in sorted({0, active // 2, active - 1}) + }, + } + history_before = { + name: inspect(item_id) for name, item_id in sampled_ids.items() + } + shards = { + name: sample["state"]["shard_id"] + for name, sample in history_before.items() + } + setup_end = time.monotonic() + rounds = [] + for revision in range(1, args.publication_rounds + 1): + update_start = time.monotonic() + execute_each( + [f"UPDATE publication_{i} SET a = a + 1" for i in range(active)] + ) + [(timestamp,)] = query( + "EXPLAIN TIMESTAMP AS JSON FOR SELECT * FROM publication_0" + ) + target = int(json.loads(timestamp)["determination"]["oracle_read_ts"]) + update_end = time.monotonic() + assert_filler(catalog_bounds(), filler) + # Open the DDL connection before sampling. Only metrics and the + # configured load/observer execute within the counter window. + with ( + c.sql_connection( + port=6877, + user="mz_system", + startup_params={ + "statement_timeout": f"{args.timeout_seconds}s" + }, + ) as conn, + ThreadPoolExecutor(max_workers=2) as executor, + ): + conn.autocommit = True + before = metrics_snapshot(shards) + start = time.monotonic() + end = start + args.observation_seconds + ddl = executor.submit(concurrent_ddl, conn, start, end) + observer = executor.submit( + observe, + start, + end, + target, + [row[2] for row in table_rows], + filler, + ) + time.sleep(max(0, end - time.monotonic())) + # Sample before joining workers or inspecting shards. A slow + # CREATE/DROP or observer may straddle this boundary. + after = metrics_snapshot(shards) + ddl_result = ddl.result() + observer_result = observer.result() + tail_end = time.monotonic() + for sample in [*ddl_result["samples"], *observer_result["samples"]]: + sample["overlaps_end_metrics_fetch"] = ( + sample["start"] < after["end"] + and sample["end"] > after["start"] + ) + assert_filler(catalog_bounds(), filler) + before_metrics = before["shards"]["catalog"] + after_metrics = after["shards"]["catalog"] + assert before_metrics["series"].keys() == after_metrics["series"].keys() + deltas = { + metric: after_metrics["totals"][metric] + - before_metrics["totals"][metric] + for metric in ( + "mz_persist_shard_diff_size_bytes", + "mz_persist_shard_cmd_succeeded", + ) + } + assert all(delta >= 0 for delta in deltas.values()), deltas + outer = after["end"] - before["start"] + inner = after["start"] - before["end"] + committed_before = before["catalog_committed_updates"]["by_kind"] + committed_after = after["catalog_committed_updates"]["by_kind"] + committed_deltas = { + kind: { + metric: committed_after[kind][metric] - value + for metric, value in values.items() + } + for kind, values in committed_before.items() + } + assert all( + value >= 0 + for values in committed_deltas.values() + for value in values.values() + ), committed_deltas + rounds.append( + { + "revision": revision, + "target_timestamp": target, + "update_start": update_start, + "update_end": update_end, + "observation_start": start, + "observation_end": end, + "start_sample_lead_seconds": start - before["end"], + "end_sample_lateness_seconds": after["start"] - end, + "tail_end": tail_end, + "ddl": ddl_result, + "observer": observer_result, + "before": before, + "after": after, + "combined_catalog_counter_deltas": deltas, + "committed_catalog_update_deltas": committed_deltas, + "committed_catalog_updates_per_second_bounds": { + kind: { + metric: [value / outer, value / inner] + for metric, value in values.items() + } + for kind, values in committed_deltas.items() + }, + "counter_sample_outer_seconds": outer, + "counter_sample_inner_seconds": inner, + "combined_catalog_diff_bytes_per_second_lower_bound": deltas[ + "mz_persist_shard_diff_size_bytes" + ] + / outer, + "combined_catalog_diff_bytes_per_second_upper_bound": deltas[ + "mz_persist_shard_diff_size_bytes" + ] + / inner, + } + ) + if any("error" in sample for sample in ddl_result["samples"]) or any( + "error" in sample and not sample["timeout"] + for sample in observer_result["samples"] + ): + print( + json.dumps({"failed_round": rounds[-1]}, sort_keys=True), + flush=True, + ) + raise UIError( + "measurement request failed, see failed_round evidence" + ) + verification_start = time.monotonic() + target = max(round_["target_timestamp"] for round_ in rounds) + while True: + bounds = catalog_bounds() + assert_filler(bounds, filler) + if all( + bounds.get(row[2], {}).get("elements") + and bounds[row[2]]["elements"][0] > target + for row in table_rows + ): + break + if time.monotonic() - verification_start > args.timeout_seconds: + raise UIError( + f"publication for {active} collections did not pass {target}" + ) + time.sleep(0.5) + verification_end = time.monotonic() + history_after = { + name: inspect(item_id) for name, item_id in sampled_ids.items() + } + print( + json.dumps( + { + "workflow": "catalog-publication-measurement", + "schema_version": 3, + "clock": "time.monotonic seconds", + "cost_scope": "committed catalog row payload by kind and combined Persist consensus state-metadata traffic", + "metric_units": { + "mz_catalog_committed_updates": "row updates including retractions", + "mz_catalog_committed_update_bytes": "packed catalog rows, excluding timestamps, diffs, compression, and network framing", + "mz_persist_shard_diff_size_bytes": "encoded Persist consensus state diffs, not catalog row payload", + }, + "common_observers": "boundary metrics, out-of-window SQL/INSPECT/catalog dump, EXPLAIN TIMESTAMP per update", + "generated_object_count": len(table_rows) + len(filler_rows), + "advancing_table_count": len(table_rows), + "filler_kind": args.filler_kind if filler_rows else None, + "filler_count": len(filler_rows), + "governed_filler_count": len(filler), + "collection_count": len(table_rows) + len(filler), + "object_counts_by_type": object_counts, + "storage_shard_count": storage_shard_count, + "catalog_bound_count_before": bound_count_before, + "catalog_bound_count_after": len(bounds), + "generated_tables": table_rows, + "generated_filler": filler_rows, + "filler_bounds": filler, + "filler_bounds_after": { + gid: bounds[gid] for gid in filler_gids + }, + "observer": args.observer, + "ddl_rate_hz": args.ddl_rate_hz, + "publication_interval_ms": args.publication_interval_ms, + "observation_seconds": args.observation_seconds, + "retention_ms": 1000, + "setup_start": setup_start, + "setup_end": setup_end, + "shared_setup_start": shared_setup_start, + "shared_setup_end": shared_setup_end, + "verification_start": verification_start, + "verification_end": verification_end, + "history_before": history_before, + "history_after": history_after, + "rounds": rounds, + }, + sort_keys=True, + ), + flush=True, + ) c.down(destroy_volumes=True) @@ -2342,7 +3457,7 @@ def parse_ts(text: str) -> datetime: def workflow_default(c: Composition) -> None: def process(name: str) -> None: - if name == "default": + if name in ("default", "catalog-publication-measurement"): return with c.test_case(name): diff --git a/test/testdrive-old-kafka-src-syntax/audit-log.td b/test/testdrive-old-kafka-src-syntax/audit-log.td index 22993506fa14c..6e3dac58fbb12 100644 --- a/test/testdrive-old-kafka-src-syntax/audit-log.td +++ b/test/testdrive-old-kafka-src-syntax/audit-log.td @@ -33,9 +33,7 @@ $ kafka-create-topic topic=test # and `replica_id` fields are dropped for versions where the audit-log struct # uses `#[serde(skip_serializing_if)]`, and source `external_type` is # exposed as `type`. -# Exclude consistency-checker interval changes from this fixture's newest DDL events. > SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events - WHERE (object_type = 'system' AND details->>'name' = 'catalog_read_protection_publish_interval') IS NOT TRUE ORDER BY id DESC LIMIT 4 create source "{\"cluster_id\":\"\",\"database\":\"materialize\",\"id\":\"\",\"item\":\"kafka_src\",\"schema\":\"public\",\"type\":\"kafka\"}" materialize create source "{\"cluster_id\":null,\"database\":\"materialize\",\"id\":\"\",\"item\":\"kafka_src_progress\",\"schema\":\"public\",\"type\":\"progress\"}" materialize @@ -49,7 +47,6 @@ create cluster "{\"id\":\"\",\"name\":\"audit_log_kafka_src\"}" materialize FROM LOAD GENERATOR COUNTER (UP TO 100); > SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events - WHERE (object_type = 'system' AND details->>'name' = 'catalog_read_protection_publish_interval') IS NOT TRUE ORDER BY id DESC LIMIT 4 create source "{\"cluster_id\":\"\",\"database\":\"materialize\",\"id\":\"\",\"item\":\"counter_src\",\"schema\":\"public\",\"type\":\"load-generator\"}" materialize create source "{\"cluster_id\":null,\"database\":\"materialize\",\"id\":\"\",\"item\":\"counter_src_progress\",\"schema\":\"public\",\"type\":\"progress\"}" materialize diff --git a/test/testdrive/audit-log.td b/test/testdrive/audit-log.td index 496f5fc607d99..ad2ac883501c3 100644 --- a/test/testdrive/audit-log.td +++ b/test/testdrive/audit-log.td @@ -36,9 +36,7 @@ $ kafka-create-topic topic=test # and `replica_id` fields are dropped for versions where the audit-log struct # uses `#[serde(skip_serializing_if)]`, and source `external_type` is # exposed as `type`. -# Exclude consistency-checker interval changes so newest-event assertions see this fixture's DDL. > SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events - WHERE (object_type = 'system' AND details->>'name' = 'catalog_read_protection_publish_interval') IS NOT TRUE ORDER BY id DESC LIMIT 3 create source "{\"cluster_id\":\"\",\"database\":\"materialize\",\"id\":\"\",\"item\":\"kafka_src\",\"schema\":\"public\",\"type\":\"kafka\"}" materialize create cluster-replica "{\"cluster_id\":\"\",\"cluster_name\":\"audit_log_kafka_src\",\"logical_size\":\"${arg.default-storage-size}\",\"replica_name\":\"r1\"}" materialize @@ -51,7 +49,6 @@ create table "{\"database\":\"materialize\",\"id\":\"\",\"item\":\"kafka_sr FROM LOAD GENERATOR COUNTER (UP TO 100); > SELECT event_type, object_type, details - 'replica_id' - 'billed_as' - 'internal' - 'scheduling_policies' - 'reason', user FROM mz_audit_events - WHERE (object_type = 'system' AND details->>'name' = 'catalog_read_protection_publish_interval') IS NOT TRUE ORDER BY id DESC LIMIT 3 create source "{\"cluster_id\":\"\",\"database\":\"materialize\",\"id\":\"\",\"item\":\"counter_src\",\"schema\":\"public\",\"type\":\"load-generator\"}" materialize create cluster-replica "{\"cluster_id\":\"\",\"cluster_name\":\"audit_log_counter_src\",\"logical_size\":\"${arg.default-storage-size}\",\"replica_name\":\"r1\"}" materialize From e606fc1efa435f721f898a1f1c6a2435511717f4 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 23:44:47 +0000 Subject: [PATCH 31/36] prompt: tell to re-read prompt when compaction happens --- doc/developer/design/20260903_decoupled_coordination_prompt.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index 95958be1846d9..0754b3a240fdc 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -145,3 +145,5 @@ upstream or other branches without asking. owns storage capability accounting and critical since handles. The fixed critical-reader identity and epoch fencing support handover, not independent owners aggregating their local holds. + +Re-read this prompt when compaction happens! From cc2bac51d244477157b685b45fb503d0fa9a1a6c Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 10 Sep 2026 01:51:24 +0000 Subject: [PATCH 32/36] test: Bound milestone publication measurements to 1,000 objects Compare 100 and 1,000 generated objects while retaining the shared-view index topology and diagnostics. Keep larger cohorts available explicitly, and defer full-scale performance work beyond this milestone. --- .../design/20260903_decoupled_coordination_log.md | 7 +++++++ .../design/20260903_decoupled_coordination_prompt.md | 8 ++++---- test/restart/mzcompose.py | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination_log.md b/doc/developer/design/20260903_decoupled_coordination_log.md index ffd0c49ffd9bb..7609ac44ab121 100644 --- a/doc/developer/design/20260903_decoupled_coordination_log.md +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -487,3 +487,10 @@ lifecycle work. Do not suppress slow-message diagnostics to make that case pass. Milestone 1 remains active. Next: resolve that scope split and finish payload-aware CI measurements. + +### 2026-09-09: Bounded performance scope agreed with Aljoscha + +Milestone 1 measures 100 and 1,000 generated objects. Larger-scale performance +work is deferred. Keep the shared-view index topology and diagnostics, rather +than adopting the independent-view proposal. The workflow still accepts explicit +larger cohorts. Next: finish payload-aware comparisons at the agreed sizes. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index 0754b3a240fdc..0768d3dacd226 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -29,16 +29,16 @@ are integrated. Complete their production evidence before declaring the mileston CI, resolve integration findings, and demonstrate protection through actual compaction and uncached recovery across maintained types. Keep claims about bootstrap-drop ordering limited to the ordering the evidence establishes. - The large sparse fixture's identical indexes expose quadratic notice and - dependency work, amplified by cache-miss reconstruction on the coordinator. - Its scope split is awaiting Aljoscha's decision in the latest handoff. Do not - suppress the slow-message diagnostics or silently substitute a smaller cohort. 2. Publication costs: finish representative measurements using the existing workflow, including DDL latency, follower lag, and retained history. Separate changed-record work from baseline catalog snapshot and storage-metadata cloning costs. Distinguish publication, workload, and observer costs with controlled comparisons, and record the numbers in the PR. Do not make a general catalog redesign a prerequisite. + Aljoscha scoped this milestone's measurements to 100 and 1,000 generated + objects, retaining the shared-view index topology and diagnostics. Larger-scale + performance work is deferred. Record the known quadratic notice/dependency + costs without making their redesign a prerequisite for this milestone. 3. History: the 09-09 commits, including `wip: Integrate maintained recovery protection`, are not yet one coherent story. Squash them before milestone 2 work begins. diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index 972a9a956835e..601fcb3fc28a5 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -2013,7 +2013,7 @@ def workflow_catalog_publication_measurement( """Measure the production catalog path. Requires disposable composition volumes.""" parser.add_argument( "--collection-counts", - default="100,1000,10000", + default="100,1000", help="Total generated objects per step, tables plus filler views or indexes", ) parser.add_argument("--publication-rounds", type=int, default=3) From cbf055e258fa03102cb956d061726a3f03535fcf Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 10 Sep 2026 02:14:44 +0000 Subject: [PATCH 33/36] test: Document catalog payload metrics and close bounded recovery evidence Include the generated inventory entries for committed catalog row counters. Record the maintained-lifecycle handoff and keep experiment-only dispatch out of the regular pipeline. The production measurement workflows retain 100/1,000-object defaults and explicit larger-cohort support. --- .../20260903_decoupled_coordination_log.md | 9 ++++ .../20260903_decoupled_coordination_prompt.md | 46 ++++++++----------- doc/user/data/metrics.yml | 12 +++++ 3 files changed, 39 insertions(+), 28 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination_log.md b/doc/developer/design/20260903_decoupled_coordination_log.md index 7609ac44ab121..6b94fdcd22bfd 100644 --- a/doc/developer/design/20260903_decoupled_coordination_log.md +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -494,3 +494,12 @@ Milestone 1 measures 100 and 1,000 generated objects. Larger-scale performance work is deferred. Keep the shared-view index topology and diagnostics, rather than adopting the independent-view proposal. The workflow still accepts explicit larger cohorts. Next: finish payload-aware comparisons at the agreed sizes. + +### 2026-09-09: Maintained-lifecycle handoff + +The next ownership boundary is MV compute installation from committed state and a +production maintained-lifecycle subscriber. Creator-local plans, locally installed +collection assumptions, and sequencer-supplied MV requirements remain transitional. +Keep the bounded performance scope, baseline catalog cloning costs, and deferred +identical-index scaling work explicit rather than folding a general redesign into +the ownership transition. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index 0768d3dacd226..c738ad0e477e3 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -21,34 +21,24 @@ Check which of these review findings remain unresolved, then choose one coherent change. Remove resolved steering from this prompt. These are implementation priorities, not additional design requirements. -Milestone 1 remains active. Sparse index permission, no-wait reconstruction, -changed-record publication and validation, and owned diagnostic reconstruction -are integrated. Complete their production evidence before declaring the milestone. - -1. Production proof: run the restart and read-only index workflows in draft PR - CI, resolve integration findings, and demonstrate protection through actual - compaction and uncached recovery across maintained types. Keep claims about - bootstrap-drop ordering limited to the ordering the evidence establishes. -2. Publication costs: finish representative measurements using the existing - workflow, including DDL latency, follower lag, and retained history. Separate - changed-record work from baseline catalog snapshot and storage-metadata cloning - costs. Distinguish publication, workload, and observer costs with controlled - comparisons, and record the numbers in the PR. Do not make a general catalog - redesign a prerequisite. - Aljoscha scoped this milestone's measurements to 100 and 1,000 generated - objects, retaining the shared-view index topology and diagnostics. Larger-scale - performance work is deferred. Record the known quadratic notice/dependency - costs without making their redesign a prerequisite for this milestone. -3. History: the 09-09 commits, including `wip: Integrate maintained recovery - protection`, are not yet one coherent story. Squash them before milestone 2 - work begins. -4. Ownership transition: prioritize a production maintained-lifecycle subscriber - over more standalone APIs. The bound subscriber is not that outcome. Address - creator-local plans, prepare_state's reliance on locally installed collections, - and writer-side responsibility for complete maintained requirements: the - sequencer supplies an MV's requirement as its own op, and the transaction - validates but does not derive it. These are transitional dependencies, not - evidence that the current single-owner path fails. +Milestone 1's implementation, production recovery demonstration, and bounded +publication measurements are integrated. Confirm final regular PR CI acceptance +before starting milestone 2. The PR owns the evidence and validation status. + +Milestone 2 starts with MV compute installation from committed state and a +production maintained-lifecycle subscriber, not more standalone APIs. The bound +subscriber is not that outcome. Address creator-local plans, prepare_state's +reliance on locally installed collections, and writer-side responsibility for +complete maintained requirements: the sequencer supplies an MV's requirement as +its own op, and the transaction validates but does not derive it. These are +transitional dependencies, not evidence that the current single-owner path fails. + +Milestone 1's performance scope is 100 and 1,000 generated objects, retaining the +shared-view index topology and diagnostics. Larger-scale work and the known +quadratic identical-index notice/dependency costs are deferred. Baseline catalog +snapshot and storage-metadata cloning costs remain. Keep payload, Persist metadata, +and current-state batch footprint distinct in reports, and do not treat observer +timings as isolated subscriber latency or current-state bytes as total disk usage. Keep the draft PR description accurate about what is implemented and what remains, with validation status in the PR rather than the design log. diff --git a/doc/user/data/metrics.yml b/doc/user/data/metrics.yml index d42be02e4edd0..316d28efc4294 100644 --- a/doc/user/data/metrics.yml +++ b/doc/user/data/metrics.yml @@ -315,6 +315,18 @@ metrics: - collection source: src/catalog/src/durable/metrics.rs visibility: internal +- name: mz_catalog_committed_update_bytes + help: Packed SourceData row bytes in acknowledged catalog compare-and-appends, including retractions and bootstrap writes. Excludes timestamps, diffs, Persist encoding, compression, and network framing. Not blob or network bytes. + labels: + - kind + source: src/catalog/src/durable/metrics.rs + visibility: internal +- name: mz_catalog_committed_updates + help: Number of catalog row updates in acknowledged compare-and-appends, including retractions and bootstrap writes. + labels: + - kind + source: src/catalog/src/durable/metrics.rs + visibility: internal - name: mz_catalog_info_metrics_reconcile_seconds_bucket help: Time taken to rebuild the catalog info metrics from a catalog snapshot. labels: From a89eb08f0e3d9daf18da68e1c621553827abb339 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 10 Sep 2026 03:29:37 +0000 Subject: [PATCH 34/36] catalog: prepare maintained lifetimes independently of installation Derive complete MV recovery requirements at the catalog transaction boundary, preserving query references eliminated by optimization and distinguishing replacement targets from reads. Validate surviving birth promises and writer identities against final transaction state. Prepare storage metadata from final catalog membership rather than locally installed collections. Preserve shared-shard aliases and orphan reclamation without authorizing physical finalization. These are prerequisites for independent lifecycle following. Compute installation and controller servicing remain adapter-owned. --- .../20260903_decoupled_coordination_log.md | 22 ++ .../20260903_decoupled_coordination_prompt.md | 13 +- src/adapter/src/catalog/open.rs | 2 +- src/adapter/src/catalog/state.rs | 1 + src/adapter/src/catalog/transact.rs | 373 +++++++++++++++++- .../inner/create_materialized_view.rs | 1 + src/catalog/src/memory/objects.rs | 4 + src/compute-client/src/as_of_selection.rs | 1 + .../src/controller/compaction_tests.rs | 1 + src/storage-client/src/controller.rs | 3 +- src/storage-client/src/storage_collections.rs | 290 ++++++++++---- 11 files changed, 628 insertions(+), 83 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination_log.md b/doc/developer/design/20260903_decoupled_coordination_log.md index 6b94fdcd22bfd..3ca4849df70b7 100644 --- a/doc/developer/design/20260903_decoupled_coordination_log.md +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -503,3 +503,25 @@ collection assumptions, and sequencer-supplied MV requirements remain transition Keep the bounded performance scope, baseline catalog cloning costs, and deferred identical-index scaling work explicit rather than folding a general redesign into the ownership transition. + +### 2026-09-10: Lifecycle extraction cost question + +Controller servicing, protection publication, table registration, and managed-cluster +reconciliation still depend on the coordinator. Moving installation into implications +alone does not establish adapter-process independence. A full catalog follower must +also preserve transaction boundaries rather than pass several transactions' parsed +updates into implications as one unconsolidated batch. + +Pending guidance from Aljoscha: the proposed extraction keeps the controller bundle +in an independently running lifecycle service, with single-adapter controller access +initially. This brings the required cross-process access boundary forward, but not +milestone 3's independent clients or durable client protection. The service extraction +is paused on its implementation cost. Writer-owned MV admission and catalog-owned +storage metadata preparation are bounded prerequisites that can proceed independently. + +The writer derives complete MV requirements from query references and committed input +permission. A sequencer-selected frontier is optional, with birth promises checked +against final transaction state. Query references are reconstructed from SQL and kept +separate from replacement-target dependencies. Storage preparation uses final catalog +membership, including all collection versions, rather than installed collections. +Shared live aliases prevent shard retirement, while orphan mappings do not. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index c738ad0e477e3..e85e81daa6596 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -21,17 +21,12 @@ Check which of these review findings remain unresolved, then choose one coherent change. Remove resolved steering from this prompt. These are implementation priorities, not additional design requirements. -Milestone 1's implementation, production recovery demonstration, and bounded -publication measurements are integrated. Confirm final regular PR CI acceptance -before starting milestone 2. The PR owns the evidence and validation status. - Milestone 2 starts with MV compute installation from committed state and a production maintained-lifecycle subscriber, not more standalone APIs. The bound -subscriber is not that outcome. Address creator-local plans, prepare_state's -reliance on locally installed collections, and writer-side responsibility for -complete maintained requirements: the sequencer supplies an MV's requirement as -its own op, and the transaction validates but does not derive it. These are -transitional dependencies, not evidence that the current single-owner path fails. +subscriber is not that outcome. MV and metric-sink compute installation still +uses creator-local plans, and sink alteration still has sequencer-side effects. +Resolve the lifecycle-service scope question in the latest handoff before +starting that extraction. Milestone 1's performance scope is 100 and 1,000 generated objects, retaining the shared-view index topology and diagnostics. Larger-scale work and the known diff --git a/src/adapter/src/catalog/open.rs b/src/adapter/src/catalog/open.rs index 406fe4107aa51..51a3a99402f08 100644 --- a/src/adapter/src/catalog/open.rs +++ b/src/adapter/src/catalog/open.rs @@ -851,7 +851,7 @@ impl Catalog { && born.contains(&mv.global_id_writes()) { let inputs = state.logical_collection_inputs( - mv.resolved_ids + mv.query_ids .collections() .copied() .chain(mv.raw_expr.depends_on()) diff --git a/src/adapter/src/catalog/state.rs b/src/adapter/src/catalog/state.rs index 7c1b6aeb615eb..2b514badc6977 100644 --- a/src/adapter/src/catalog/state.rs +++ b/src/adapter/src/catalog/state.rs @@ -1727,6 +1727,7 @@ impl CatalogState { locally_optimized_expr: optimized_expr, desc, resolved_ids, + query_ids: materialized_view.query_ids, dependencies, replacement_target: materialized_view.replacement_target, cluster_id: materialized_view.cluster_id, diff --git a/src/adapter/src/catalog/transact.rs b/src/adapter/src/catalog/transact.rs index 28a7f9b645803..c9aa3c808755f 100644 --- a/src/adapter/src/catalog/transact.rs +++ b/src/adapter/src/catalog/transact.rs @@ -40,12 +40,13 @@ use mz_catalog::expr_cache::{LocalExpressions, latest_item_version}; use mz_catalog::memory::error::{AmbiguousRename, Error, ErrorKind}; use mz_catalog::memory::objects::{ CatalogEntry, CatalogItem, ClusterConfig, ClusterVariant, DataSourceDesc, DefaultPrivileges, - ReconfigurationState, ReconfigurationStatus, ReconfigurationTarget, SourceReferences, - StateUpdateKind, TableDataSource, + MaterializedView, ReconfigurationState, ReconfigurationStatus, ReconfigurationTarget, + SourceReferences, StateUpdateKind, TableDataSource, }; use mz_cluster_controller::ctx::RefreshWindowDecision; use mz_controller::clusters::{ManagedReplicaLocation, ReplicaConfig, ReplicaLocation}; use mz_controller_types::{ClusterId, ReplicaId}; +use mz_expr::CollectionPlan; use mz_ore::collections::HashSet; use mz_ore::{instrument, soft_assert_or_log}; use mz_persist_types::ShardId; @@ -1000,8 +1001,25 @@ impl Catalog { let mut storage_collections_to_register = BTreeMap::new(); let mut updates = Vec::new(); + let mut born_mvs = BTreeSet::new(); + let mut updated_requirements = BTreeSet::new(); for op in ops { + if preliminary_state.catalog_read_protection_enabled() { + match &op { + Op::CreateItem { + item: CatalogItem::MaterializedView(mv), + .. + } => { + born_mvs.insert(mv.global_id_writes()); + updated_requirements.insert(mv.global_id_writes()); + } + Op::SetReadProtection { requirements, .. } => { + updated_requirements.extend(requirements.iter().map(|r| r.id)); + } + _ => (), + } + } Self::transact_op( oracle_write_ts, session, @@ -1029,15 +1047,59 @@ impl Catalog { updates.append(&mut op_updates); } + // Validate the final requirement, including a creator's optional frontier + // selection. Dropped owners need no protection. Publications may advance + // recovery, but may not change the logical inputs that define it. + for id in updated_requirements { + let Some(entry) = preliminary_state.try_get_entry_by_global_id(&id) else { + continue; + }; + let CatalogItem::MaterializedView(mv) = entry.item() else { + continue; + }; + let requirement = &preliminary_state.maintained_read_requirements()[&id]; + // Applying a replacement can retain aliases of the retired writer. + // Their completed requirements describe the retired definition. + if id != mv.global_id_writes() { + if requirement.frontier.is_some() { + return Err(AdapterError::internal( + "materialized view read protection", + format!("retired writer {id} has an active recovery requirement"), + )); + } + continue; + } + if requirement.inputs != materialized_view_recovery_inputs(&preliminary_state, mv) { + return Err(AdapterError::internal( + "materialized view read protection", + format!("incomplete logical inputs for {id}"), + )); + } + if born_mvs.contains(&id) { + validate_materialized_view_birth(mv, requirement.frontier)?; + } + } + match mode { TransactInnerMode::Commit => { // `storage_collections` can be `None` in tests. if let Some(c) = storage_collections { + let live_collection_ids = if storage_collections_to_drop.is_empty() { + BTreeSet::new() + } else { + preliminary_state + .get_entries() + .map(|(_, entry)| entry) + .filter(|entry| entry.item().is_storage_collection()) + .flat_map(|entry| entry.global_ids()) + .collect() + }; c.prepare_state( tx, storage_collections_to_create, storage_collections_to_drop, storage_collections_to_register, + &live_collection_ids, ) .await?; } @@ -1756,6 +1818,20 @@ impl Catalog { } CatalogItem::MaterializedView(mv) => { let mv_gid = mv.global_id_writes(); + if state.catalog_read_protection_enabled() { + let inputs = materialized_view_recovery_inputs(state, mv); + // Join logical-input permission, not physical sinces. + // None is the empty frontier, above every timestamp. + let mut frontier = Some(mz_repr::Timestamp::MIN); + for input in &inputs { + let permission = dependency_compaction_bound(state, *input)?; + frontier = match (frontier, permission) { + (Some(a), Some(b)) => Some(a.max(b)), + _ => None, + }; + } + tx.set_maintained_read_requirement(mv_gid, inputs, frontier)?; + } if let Some(target_id) = mv.replacement_target { let target_gid = state.get_entry(&target_id).latest_global_id(); let shard_id = @@ -3355,6 +3431,63 @@ impl Catalog { } } +fn materialized_view_recovery_inputs( + state: &CatalogState, + mv: &MaterializedView, +) -> BTreeSet { + state.logical_collection_inputs( + mv.query_ids + .collections() + .copied() + .chain(mv.raw_expr.depends_on()) + .filter(|id| state.get_entry_by_global_id(id).is_relation()), + ) +} + +/// Birth protection must retain every result promised by the stored definition. +/// The output shard's permission is separate, especially for replacement MVs. +fn validate_materialized_view_birth( + mv: &MaterializedView, + frontier: Option, +) -> Result<(), AdapterError> { + let initial_as_of = mv.initial_as_of.as_ref().and_then(|f| f.as_option()); + let (Some(frontier), Some(initial_as_of)) = (frontier, initial_as_of) else { + return Err(AdapterError::internal( + "create materialized view", + "missing readable birth or initial storage frontier", + )); + }; + if frontier > *initial_as_of { + return Err(AdapterError::internal( + "create materialized view", + "input protection exceeds initial storage visibility", + )); + } + if let Some(schedule) = &mv.refresh_schedule { + for refresh_at in &schedule.ats { + if frontier > *refresh_at { + return Err(AdapterError::InputNotReadableAtRefreshAtTime( + *refresh_at, + timely::progress::Antichain::from_elem(frontier), + )); + } + if initial_as_of > refresh_at { + return Err(AdapterError::internal( + "create materialized view", + "initial storage visibility skips an explicit refresh", + )); + } + } + if schedule.round_up_timestamp(*initial_as_of) != Some(*initial_as_of) { + return Err(AdapterError::internal( + "create materialized view", + "initial storage visibility is not a refresh timestamp", + )); + } + } + Ok(()) +} + /// Returns birth permission compatible with a source's storage initialization dependency. fn source_initial_compaction_bound( state: &CatalogState, @@ -3797,6 +3930,242 @@ mod tests { use crate::catalog::{Catalog, Op}; use crate::session::DEFAULT_DATABASE_NAME; + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] + async fn test_materialized_view_birth_admission() { + use std::collections::{BTreeMap, BTreeSet}; + + use mz_expr::CollectionPlan; + use timely::progress::Antichain; + + use crate::catalog::state::LocalExpressionCache; + + Catalog::with_debug(|catalog| async move { + let database = catalog + .resolve_database(DEFAULT_DATABASE_NAME) + .expect("default database exists"); + let database_spec = ResolvedDatabaseSpecifier::Id(database.id()); + let schema = catalog + .resolve_schema_in_database(&database_spec, DEFAULT_SCHEMA, &SYSTEM_CONN_ID) + .expect("default schema exists"); + let qualifiers = ItemQualifiers { + database_spec, + schema_spec: schema.id.clone(), + }; + let prefix = format!("{}.{}", database.name, schema.name.schema); + let birth = catalog.current_upper().await; + let permission = birth.step_forward(); + let visibility = permission.step_forward(); + let mut base = catalog.state().clone(); + base.catalog_read_protection_enabled = true; + let mut state = base.clone(); + let mut snapshot = None; + let mut ops = Vec::new(); + let mut ids = BTreeMap::new(); + for (name, sql) in [ + ("mv_input", format!("CREATE TABLE {prefix}.mv_input (a int)")), + ("mv_hidden", format!("CREATE TABLE {prefix}.mv_hidden (a int)")), + ("mv_view", format!("CREATE VIEW {prefix}.mv_view AS SELECT * FROM {prefix}.mv_hidden")), + ("mv_birth", format!("CREATE MATERIALIZED VIEW {prefix}.mv_birth IN CLUSTER quickstart AS SELECT * FROM {prefix}.mv_input UNION ALL SELECT * FROM {prefix}.mv_view WHERE false AS OF {visibility}")), + ] { + let (id, gid) = catalog + .allocate_user_id_for_test().await.expect("allocate test item identity"); + let item = state.with_enable_for_item_parsing(|state| state.parse_item( + gid, &sql, &BTreeMap::new(), None, false, None, + &mut LocalExpressionCache::Closed, None, + )).unwrap_or_else(|err| panic!("parse {name}: {err}")); + ids.insert(name, (id, gid)); + ops.push(Op::CreateItem { + id, + name: QualifiedItemName { qualifiers: qualifiers.clone(), item: name.into() }, + item, + owner_id: MZ_SYSTEM_ROLE_ID, + }); + if name == "mv_hidden" { + ops.push(Op::SetReadProtection { + requirements: vec![], + bounds: vec![CollectionCompactionBound { + id: gid, + frontier: Some(permission), + }], + }); + } + let (next_state, next_snapshot) = catalog.transact_incremental_dry_run( + &base, ops.clone(), None, None, birth, + ).await.unwrap_or_else(|err| panic!("create {name}: {err}")); + state = next_state; + snapshot = Some(next_snapshot); + } + let (mv_id, mv_gid) = ids["mv_birth"]; + let inputs = BTreeSet::from([ids["mv_input"].1, ids["mv_hidden"].1]); + let CatalogItem::MaterializedView(mv) = state.get_entry(&mv_id).item() else { + panic!("expected MV"); + }; + assert!(!mv.locally_optimized_expr.depends_on().contains(&ids["mv_view"].1)); + assert_eq!(state.maintained_read_requirements()[&mv_gid], MaintainedReadRequirement { + id: mv_gid, inputs: inputs.clone(), frontier: Some(permission), + }); + assert_eq!( + state.collection_compaction_bounds()[&mv_gid], + Antichain::from_elem(visibility), + ); + + // A selected frontier remains an optional optimization, not authority + // to omit a logical input or skip promised output. + for (selected_inputs, frontier, succeeds) in [ + (inputs.clone(), Some(visibility), true), + (BTreeSet::from([ids["mv_input"].1]), Some(visibility), false), + (inputs.clone(), Some(birth), false), + (inputs.clone(), Some(visibility.step_forward()), false), + (inputs.clone(), None, false), + ] { + let mut selected = ops.clone(); + selected.push(Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: mv_gid, + inputs: selected_inputs, + frontier, + }], + bounds: vec![], + }); + let result = catalog + .transact_incremental_dry_run(&base, selected, None, None, birth) + .await; + assert_eq!(result.is_ok(), succeeds, "{:?}", result.as_ref().err()); + if let Ok((selected_state, _)) = result { + assert_eq!( + selected_state.maintained_read_requirements()[&mv_gid].frontier, + frontier, + ); + } + } + + // Permission on the optimized-away input still constrains admission. + let mut incompatible = ops.clone(); + incompatible.insert(incompatible.len() - 1, Op::SetReadProtection { + requirements: vec![], + bounds: vec![CollectionCompactionBound { id: ids["mv_hidden"].1, frontier: Some(visibility.step_forward()) }], + }); + assert!(catalog + .transact_incremental_dry_run(&base, incompatible.clone(), None, None, birth) + .await.is_err()); + + // An intermediate incompatible birth promises no results if the + // same transaction removes its owner before committing. + incompatible.push(Op::DropObjects(vec![super::DropObjectInfo::Item(mv_id)])); + let (without_mv, _) = catalog + .transact_incremental_dry_run(&base, incompatible, None, None, birth) + .await.expect("dropped birth has no surviving read promise"); + assert!(!without_mv.maintained_read_requirements().contains_key(&mv_gid)); + + for (name, sql, succeeds) in [ + ("mv_refresh", format!("CREATE MATERIALIZED VIEW {prefix}.mv_refresh IN CLUSTER quickstart WITH (REFRESH AT {visibility}) AS SELECT * FROM {prefix}.mv_hidden AS OF {visibility}"), true), + ("mv_refresh_old", format!("CREATE MATERIALIZED VIEW {prefix}.mv_refresh_old IN CLUSTER quickstart WITH (REFRESH AT {birth}) AS SELECT * FROM {prefix}.mv_hidden AS OF {visibility}"), false), + ("mv_replacement", format!("CREATE REPLACEMENT MATERIALIZED VIEW {prefix}.mv_replacement FOR {prefix}.mv_birth IN CLUSTER quickstart AS SELECT * FROM {prefix}.mv_hidden AS OF {}", visibility.step_forward()), true), + ] { + let (id, gid) = catalog + .allocate_user_id_for_test().await.expect("allocate MV test identity"); + let item = state.with_enable_for_item_parsing(|state| state.parse_item( + gid, &sql, &BTreeMap::new(), None, false, None, + &mut LocalExpressionCache::Closed, None, + )).unwrap_or_else(|err| panic!("parse {name}: {err}")); + let create = Op::CreateItem { + id, + name: QualifiedItemName { qualifiers: qualifiers.clone(), item: name.into() }, + item, + owner_id: MZ_SYSTEM_ROLE_ID, + }; + let result = catalog + .transact_incremental_dry_run( + &state, vec![create], None, snapshot.clone(), birth, + ) + .await; + assert_eq!(result.is_ok(), succeeds, "{name}: {:?}", result.as_ref().err()); + if let Ok((created, created_snapshot)) = result { + assert_eq!( + created.maintained_read_requirements()[&gid], + MaintainedReadRequirement { + id: gid, + inputs: BTreeSet::from([ids["mv_hidden"].1]), + frontier: Some(permission), + }, + ); + // A replacement inherits the shared output's permission, + // not its input protection or its own visibility frontier. + assert_eq!( + created.collection_compaction_bounds()[&gid], + Antichain::from_elem(visibility), + ); + if name == "mv_replacement" { + let apply = vec![ + Op::SetReadProtection { + requirements: vec![ + created.maintained_read_requirements()[&mv_gid].clone(), + ], + bounds: vec![], + }, + Op::AlterMaterializedViewApplyReplacement { + id: mv_id, + replacement_id: id, + }, + ]; + let (applied, applied_snapshot) = catalog.transact_incremental_dry_run( + &created, apply, None, Some(created_snapshot), birth, + ).await.expect("apply replacement after retiring its predecessor"); + assert_eq!(applied.maintained_read_requirements()[&mv_gid].frontier, None); + assert_eq!( + applied.maintained_read_requirements()[&gid], + created.maintained_read_requirements()[&gid], + ); + for (frontier, succeeds) in [(None, true), (Some(permission), false)] { + let mut retired = + applied.maintained_read_requirements()[&mv_gid].clone(); + retired.frontier = frontier; + let result = catalog.transact_incremental_dry_run( + &applied, + vec![Op::SetReadProtection { + requirements: vec![retired], + bounds: vec![], + }], + None, + Some(applied_snapshot.clone()), + birth, + ).await; + assert_eq!(result.is_ok(), succeeds, "{:?}", result.as_ref().err()); + } + } + } + } + + // A final drop retires protection, including a same-batch creation. + let mut dropped = ops.clone(); + dropped.push(Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: mv_gid, + inputs: BTreeSet::new(), + frontier: None, + }], + bounds: vec![], + }); + dropped.push(Op::DropObjects(vec![super::DropObjectInfo::Item(mv_id)])); + let (dropped_state, _) = catalog + .transact_incremental_dry_run(&base, dropped, None, None, birth) + .await.expect("drop retires MV protection"); + assert!(dropped_state.try_get_entry(&mv_id).is_none()); + assert!(!dropped_state.maintained_read_requirements().contains_key(&mv_gid)); + + // Feature-off catalogs do not acquire a maintained requirement. + base.catalog_read_protection_enabled = false; + let mut unprotected = ops; + unprotected.retain(|op| !matches!(op, Op::SetReadProtection { .. })); + let (unprotected_state, _) = catalog + .transact_incremental_dry_run(&base, unprotected, None, None, birth) + .await.expect("feature-off creation needs no maintained requirement"); + assert!(!unprotected_state.maintained_read_requirements().contains_key(&mv_gid)); + catalog.expire().await; + }).await; + } + #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] async fn test_storage_birth_inherits_dependency_permission() { diff --git a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs index 8330f84e3a1d0..9e563164202f4 100644 --- a/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs +++ b/src/adapter/src/coord/sequencer/inner/create_materialized_view.rs @@ -755,6 +755,7 @@ impl Coordinator { desc, collections, resolved_ids, + query_ids, dependencies, replacement_target, cluster_id, diff --git a/src/catalog/src/memory/objects.rs b/src/catalog/src/memory/objects.rs index 5afb4a2688185..37f7c3cfe5e4b 100644 --- a/src/catalog/src/memory/objects.rs +++ b/src/catalog/src/memory/objects.rs @@ -1425,6 +1425,9 @@ pub struct MaterializedView { pub desc: VersionedRelationDesc, /// Other catalog items that this materialized view references, determined at name resolution. pub resolved_ids: ResolvedIds, + /// References in the defining query, including reads eliminated during planning. + /// A `FOR` target alone is not a query reference. + pub query_ids: ResolvedIds, /// All of the catalog objects that are referenced by this view. pub dependencies: DependencyIds, /// ID of the materialized view this materialized view is intended to replace. @@ -1552,6 +1555,7 @@ impl MaterializedView { locally_optimized_expr: replacement.locally_optimized_expr, desc: replacement.desc, resolved_ids, + query_ids: replacement.query_ids, dependencies, replacement_target: None, cluster_id: replacement.cluster_id, diff --git a/src/compute-client/src/as_of_selection.rs b/src/compute-client/src/as_of_selection.rs index a77741264c1b1..f3d83156a7c55 100644 --- a/src/compute-client/src/as_of_selection.rs +++ b/src/compute-client/src/as_of_selection.rs @@ -1031,6 +1031,7 @@ mod tests { _ids_to_add: BTreeSet, _ids_to_drop: BTreeSet, _ids_to_register: BTreeMap, + _live_collection_ids: &BTreeSet, ) -> Result<(), StorageError> { unimplemented!() } diff --git a/src/compute-client/src/controller/compaction_tests.rs b/src/compute-client/src/controller/compaction_tests.rs index 789a1406593d5..672a10053c099 100644 --- a/src/compute-client/src/controller/compaction_tests.rs +++ b/src/compute-client/src/controller/compaction_tests.rs @@ -797,6 +797,7 @@ impl StorageCollections for EmptyStorage { _ids_to_add: BTreeSet, _ids_to_drop: BTreeSet, _ids_to_register: BTreeMap, + _live_collection_ids: &BTreeSet, ) -> Result<(), StorageError> { unimplemented!("storage is not used by these compute tests") } diff --git a/src/storage-client/src/controller.rs b/src/storage-client/src/controller.rs index 3935b724ca2a2..977b8224aa014 100644 --- a/src/storage-client/src/controller.rs +++ b/src/storage-client/src/controller.rs @@ -240,7 +240,8 @@ impl StorageMetadata { pub trait StorageTxn { /// Retrieve all of the visible storage metadata. /// - /// The value of this map should be treated as opaque. + /// The value of this map should be treated as opaque. Mappings can outlive + /// their catalog collections, so their presence does not establish liveness. fn get_collection_metadata(&self) -> BTreeMap; /// Add new storage metadata for a collection. diff --git a/src/storage-client/src/storage_collections.rs b/src/storage-client/src/storage_collections.rs index feeb3d198259b..1fca832d870b1 100644 --- a/src/storage-client/src/storage_collections.rs +++ b/src/storage-client/src/storage_collections.rs @@ -223,7 +223,19 @@ pub trait StorageCollections: Debug + Sync { >; /// Update the given [`StorageTxn`] with the appropriate metadata given the - /// IDs to add and drop. + /// IDs to add and drop. This does not require locally installed collections + /// and does not physically finalize shards. + /// + /// When dropping IDs, `live_collection_ids` must contain every live storage + /// collection ID (all versions) in the final catalog state of this transaction, + /// including same-call additions and registrations. It may be empty when + /// there are no drops. Durable mappings alone are not evidence of liveness. + /// + /// Drops take precedence over additions and registrations for the same ID. + /// Dropped IDs must not be live. Added and registered IDs must not already + /// have metadata, and the two sets must be disjoint. Registered shards must + /// not already be eligible for finalization. An error requires aborting the + /// transaction, as metadata changes may already have been staged. /// /// The data modified in the `StorageTxn` must be made available in all /// subsequent calls that require [`StorageMetadata`] as a parameter. @@ -233,6 +245,7 @@ pub trait StorageCollections: Debug + Sync { ids_to_add: BTreeSet, ids_to_drop: BTreeSet, ids_to_register: BTreeMap, + live_collection_ids: &BTreeSet, ) -> Result<(), StorageError>; /// Create the collections described by the individual @@ -1393,6 +1406,7 @@ impl StorageCollections for StorageCollectionsImpl { new_collections, BTreeSet::default(), BTreeMap::default(), + &init_ids, ) .await?; @@ -1765,21 +1779,8 @@ impl StorageCollections for StorageCollectionsImpl { ids_to_add: BTreeSet, ids_to_drop: BTreeSet, ids_to_register: BTreeMap, + live_collection_ids: &BTreeSet, ) -> Result<(), StorageError> { - // Durable metadata can outlive its collection. Reconcile it with live - // collection state so orphaned mappings do not block finalization. - let mut active_collection_ids: BTreeSet<_> = { - let collections = self.collections.lock().expect("poisoned"); - collections - .iter() - .filter_map(|(id, collection)| { - (!ids_to_drop.contains(id) && !collection.is_dropped()).then_some(*id) - }) - .collect() - }; - active_collection_ids.extend(ids_to_add.iter().copied()); - active_collection_ids.extend(ids_to_register.keys().copied()); - txn.insert_collection_metadata( ids_to_add .into_iter() @@ -1791,31 +1792,21 @@ impl StorageCollections for StorageCollectionsImpl { // Delete the metadata for any dropped collections. let dropped_mappings = txn.delete_collection_metadata(ids_to_drop); - // Only finalize the shards of dropped collections that don't have a primary. - // Otherwise the shard might still be in use by the primary. - let mut dropped_shards = BTreeSet::new(); - { - let collections = self.collections.lock().expect("poisoned"); - for (id, shard) in dropped_mappings { - let coll = collections.get(&id).expect("must exist"); - if coll.primary.is_none() { - dropped_shards.insert(shard); - } - } - } - let remaining_metadata = txn.get_collection_metadata(); - let (referenced_shards, dropped_shards) = partition_finalizable_shards( - remaining_metadata, - &active_collection_ids, - dropped_shards, - ); - if !referenced_shards.is_empty() { - mz_ore::soft_panic_or_log!( - "dropped collections would finalize shards that active collections still use: \ - {referenced_shards:?}" + // Every dropped mapping is a candidate, including aliases and collections + // not installed here. Only live catalog references protect a shared shard: + // orphaned durable mappings must not prevent reclamation. + let dropped_shards: BTreeSet<_> = dropped_mappings + .into_iter() + .map(|(_, shard)| shard) + .collect(); + if !dropped_shards.is_empty() { + let (_, dropped_shards) = partition_finalizable_shards( + txn.get_collection_metadata(), + live_collection_ids, + dropped_shards, ); + txn.insert_unfinalized_shards(dropped_shards)?; } - txn.insert_unfinalized_shards(dropped_shards)?; // Reconcile any shards we've successfully finalized with the shard // finalization collection. @@ -3665,36 +3656,57 @@ mod tests { use super::*; - struct TestTxn(ShardId); + #[derive(Default)] + struct TestTxn { + txns_shard: Option, + metadata: BTreeMap, + unfinalized: BTreeSet, + } #[async_trait] impl StorageTxn for TestTxn { fn get_collection_metadata(&self) -> BTreeMap { - BTreeMap::new() + self.metadata.clone() } fn insert_collection_metadata( &mut self, - _: BTreeMap, + metadata: BTreeMap, ) -> Result<(), StorageError> { - unimplemented!() + for (id, shard) in metadata { + match self.metadata.entry(id) { + std::collections::btree_map::Entry::Vacant(entry) => { + entry.insert(shard); + } + std::collections::btree_map::Entry::Occupied(_) => { + return Err(StorageError::CollectionMetadataAlreadyExists(id)); + } + } + } + Ok(()) } fn delete_collection_metadata( &mut self, - _: BTreeSet, + ids: BTreeSet, ) -> Vec<(GlobalId, ShardId)> { - unimplemented!() + ids.into_iter() + .filter_map(|id| self.metadata.remove(&id).map(|shard| (id, shard))) + .collect() } fn get_unfinalized_shards(&self) -> BTreeSet { - BTreeSet::new() + self.unfinalized.clone() } - fn insert_unfinalized_shards(&mut self, _: BTreeSet) -> Result<(), StorageError> { - unimplemented!() + fn insert_unfinalized_shards( + &mut self, + shards: BTreeSet, + ) -> Result<(), StorageError> { + self.unfinalized.extend(shards); + Ok(()) } - fn remove_unfinalized_shards(&mut self, _: BTreeSet) { - unimplemented!() + fn remove_unfinalized_shards(&mut self, shards: BTreeSet) { + self.unfinalized.retain(|shard| !shards.contains(shard)); } fn get_txn_wal_shard(&self) -> Option { - Some(self.0) + self.txns_shard } fn write_txn_wal_shard(&mut self, _: ShardId) -> Result<(), StorageError> { unimplemented!() @@ -3729,7 +3741,10 @@ mod tests { NonZeroI64::new(1).unwrap(), false, ConnectionContext::for_tests(Arc::new(InMemorySecretsController::new())), - &TestTxn(ShardId::new()), + &TestTxn { + txns_shard: Some(ShardId::new()), + ..Default::default() + }, ) .await; (controller, persist) @@ -3748,7 +3763,10 @@ mod tests { NonZeroI64::new(2).unwrap(), true, ConnectionContext::for_tests(Arc::new(InMemorySecretsController::new())), - &TestTxn(*leader.txns_read.txns_id()), + &TestTxn { + txns_shard: Some(*leader.txns_read.txns_id()), + ..Default::default() + }, ) .await } @@ -5013,26 +5031,158 @@ mod tests { await_persist_since(&persist, shard, frontier(5)).await; } - #[mz_ore::test] - fn test_partition_finalizable_shards() { - let active_shard = ShardId::new(); - let dropped_shard = ShardId::new(); - let collection_metadata = BTreeMap::from([ - (GlobalId::User(1), active_shard), - (GlobalId::User(2), active_shard), - (GlobalId::User(3), dropped_shard), - ]); - let active_collection_ids = BTreeSet::from([GlobalId::User(1), GlobalId::User(2)]); - let unfinalized_shards = BTreeSet::from([active_shard, dropped_shard]); + #[mz_ore::test(tokio::test)] + async fn test_prepare_state_drop_before_install() { + let (controller, _persist) = bound_test_controller().await; + let id = GlobalId::User(1); + let orphan = GlobalId::User(2); + let mut txn = TestTxn::default(); + controller + .prepare_state( + &mut txn, + BTreeSet::from([id]), + BTreeSet::new(), + BTreeMap::new(), + &BTreeSet::new(), + ) + .await + .unwrap(); + let shard = txn.metadata[&id]; + // A durable orphan sharing the shard must not prevent runtime cleanup. + txn.metadata.insert(orphan, shard); + controller + .prepare_state( + &mut txn, + BTreeSet::new(), + BTreeSet::from([id]), + BTreeMap::new(), + &BTreeSet::new(), + ) + .await + .unwrap(); + assert_eq!(txn.metadata, BTreeMap::from([(orphan, shard)])); + assert_eq!(txn.unfinalized, BTreeSet::from([shard])); + assert!(controller.collections.lock().unwrap().is_empty()); + assert!(controller.finalizable_shards.lock().is_empty()); + + // Acknowledgment removes durable WAL entries, but retains the local + // acknowledgment until committed metadata is supplied back to storage. + controller.finalized_shards.lock().insert(shard); + controller + .prepare_state( + &mut txn, + BTreeSet::new(), + BTreeSet::new(), + BTreeMap::new(), + &BTreeSet::new(), + ) + .await + .unwrap(); + assert!(txn.unfinalized.is_empty()); + assert!(controller.finalized_shards.lock().contains(&shard)); + } - let (referenced_shards, finalizable_shards) = partition_finalizable_shards( - collection_metadata, - &active_collection_ids, - unfinalized_shards, - ); + #[mz_ore::test(tokio::test)] + async fn test_prepare_state_shared_aliases() { + // Both an existing alias and one registered with the drop protect the + // shared shard, even when no collection has been installed locally. + for register_with_drop in [false, true] { + let (controller, _persist) = bound_test_controller().await; + let primary = GlobalId::User(1); + let alias = GlobalId::User(2); + let shard = ShardId::new(); + let mut txn = TestTxn { + metadata: BTreeMap::from([(primary, shard)]), + ..Default::default() + }; + let registration = if register_with_drop { + BTreeMap::from([(alias, shard)]) + } else { + txn.metadata.insert(alias, shard); + BTreeMap::new() + }; + controller + .prepare_state( + &mut txn, + BTreeSet::new(), + BTreeSet::from([primary]), + registration, + &BTreeSet::from([alias]), + ) + .await + .unwrap(); + assert_eq!(txn.metadata, BTreeMap::from([(alias, shard)])); + assert!(txn.unfinalized.is_empty()); + controller + .prepare_state( + &mut txn, + BTreeSet::new(), + BTreeSet::from([alias]), + BTreeMap::new(), + &BTreeSet::new(), + ) + .await + .unwrap(); + assert!(txn.metadata.is_empty()); + assert_eq!(txn.unfinalized, BTreeSet::from([shard])); + assert!(controller.collections.lock().unwrap().is_empty()); + assert!(controller.finalizable_shards.lock().is_empty()); + } + } - assert_eq!(referenced_shards, BTreeSet::from([active_shard])); - assert_eq!(finalizable_shards, BTreeSet::from([dropped_shard])); + #[mz_ore::test(tokio::test)] + async fn test_prepare_state_create_drop_overlap() { + let (controller, _persist) = bound_test_controller().await; + let id = GlobalId::User(1); + let mut txn = TestTxn::default(); + controller + .prepare_state( + &mut txn, + BTreeSet::from([id]), + BTreeSet::from([id]), + BTreeMap::new(), + &BTreeSet::new(), + ) + .await + .unwrap(); + assert!(txn.metadata.is_empty()); + assert_eq!(txn.unfinalized.len(), 1); + assert!(controller.collections.lock().unwrap().is_empty()); + assert!(controller.finalizable_shards.lock().is_empty()); + } + + #[mz_ore::test(tokio::test)] + async fn test_initialize_state_repairs_stale_finalization_wal() { + let (mut controller, _persist) = bound_test_controller().await; + // Keep the background finalizer from consuming the bootstrap queue. + let idle_task = mz_ore::task::spawn(|| "idle-finalizer", std::future::pending()); + let finalizer = std::mem::replace( + &mut controller._finalize_shards_task, + Arc::new(idle_task.abort_on_drop()), + ); + Arc::try_unwrap(finalizer).unwrap().abort_and_wait().await; + let live = GlobalId::User(1); + let orphan = GlobalId::User(2); + let new = GlobalId::User(3); + let live_shard = ShardId::new(); + let orphan_shard = ShardId::new(); + let mut txn = TestTxn { + metadata: BTreeMap::from([(live, live_shard), (orphan, orphan_shard)]), + unfinalized: BTreeSet::from([live_shard, orphan_shard]), + ..Default::default() + }; + controller + .initialize_state(&mut txn, BTreeSet::from([live, new])) + .await + .unwrap(); + assert_eq!(txn.metadata[&live], live_shard); + assert!(txn.metadata.contains_key(&new)); + assert_eq!(txn.unfinalized, BTreeSet::from([orphan_shard])); + assert_eq!( + *controller.finalizable_shards.lock(), + BTreeSet::from([orphan_shard]) + ); + assert!(controller.collections.lock().unwrap().is_empty()); } #[mz_ore::test(tokio::test)] From 215bb462a55dbf8ec4d438ac7ef8dbb1ce27283e Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 10 Sep 2026 19:19:11 +0000 Subject: [PATCH 35/36] doc: Decide lifecycle placement and the query client for milestone 2 The controller bundle moves into one independent process whose interface is catalog following, enactment, and publication. It neither serves controller state to adapters nor gates their catalog writes, so it can later dissolve into per-cluster followers. Adapters read through a query client that owns their read requirements, learns storage frontiers from persist and compute frontiers from the fast protocol, and carries durable client protection from the start. Table appends and DDL stay with adapters. Once publication leaves the adapter, adapter DDL and lifecycle publication are two cooperating catalog writers, so that much concurrency is now in scope. Re-cut milestone 2 as independent maintained lifecycle for one adapter and one query client, and milestone 3 as multiplicity and isolation of clients. Order the prompt's steering so each step lands in-process before the process boundary moves. --- .../design/20260903_decoupled_coordination.md | 63 +++++++++++++------ .../20260903_decoupled_coordination_log.md | 21 +++++++ .../20260903_decoupled_coordination_prompt.md | 33 +++++++--- 3 files changed, 88 insertions(+), 29 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 8069feb3c299b..aa46dadcc77bb 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -15,8 +15,10 @@ queries may fail. Transparent query or session failover is out of scope. Working multi-adapter operation is the destination, not this deliverable. Boundaries must support independent adapters becoming catalog writers and query -clients without another ownership redesign. Enabling concurrent catalog writers -and deploying multiple adapters are not required here. +clients without another ownership redesign. Once lifecycle enactment leaves the +adapter, the adapter's DDL and the lifecycle components' publication are two +cooperating catalog writers, and that much concurrency is required. Arbitrary +numbers of adapters and their deployment are not. Initial implementation and validation target environments initialized under the new protection rules. Conversion of existing environments, builtin schema migration, @@ -210,6 +212,24 @@ SQL savepoint. Read-only bootstrap follows [Index reconstruction](#index-reconst without waiting for the writer. A local savepoint write grants no compaction permission, and there is no startup-specific writer protocol. +### Lifecycle placement + +The controller bundle may run as one independent process that follows the +catalog, enacts maintained state, and publishes protection. Those three +responsibilities are its interface. It does not serve controller state to adapters +and does not gate their catalog writes, so it can later dissolve into per-cluster +followers without another redesign. Collection lifecycle and compaction belong to +it. DDL and table appends are request-scoped and stay with adapters. + +### Query client + +An adapter reads through a query client that owns that client's read +requirements. It learns storage frontiers from persist and compute frontiers from +the fast protocol, where frontier reporting is best effort. It issues peeks and +query-local dataflows and receives their responses. Its protection is the +[durable client protection](#client-read-protection) from the start. No remote +controller access API or volatile hold forwarding is introduced as a bridge. + ## Alternatives ### Plan-specific recovery protection @@ -288,30 +308,33 @@ system-catalog collections. Measure publication and retained-history costs as th real path becomes available. This milestone can use the current single-writer arrangement. -#### 2. Catalog-driven maintained lifecycle +#### 2. Independent maintained lifecycle -Cluster-side lifecycle components establish and follow maintained state from the -catalog without sequencer installation closures or the originating adapter. -Creation, changes, deletion, and compaction work across recovery. Losing the -adapter does not interrupt maintained work. +Lifecycle components establish and follow maintained state from the catalog in a +process that is not the adapter, without sequencer installation closures or +creator-local plans. Creation, changes, deletion, compaction, and protection +publication continue across adapter loss and recovery. Adapter DDL and lifecycle +publication commit as cooperating catalog writers. The adapter reads through the +query client with durable protection, and a cluster accepts its lifecycle +connection and query connections at once without one replacing the other's state. -Demonstrate a production subscriber applying committed changes without -creator-local plans, including same-batch dependencies. Include concurrent and -delayed application of committed compaction permission. +Demonstrate: stop the adapter while maintained dataflows, sources, sinks, and +compaction continue, then restart it and resume queries. Include same-batch +dependencies and concurrent or delayed application of committed permission. One +adapter and one query client suffice. -#### 3. Independent query execution +#### 3. Independent query clients -Query clients use the fast protocol without acquiring ownership of maintained -lifecycle. Catalog application and query readiness remain correctly ordered. -Responses, cancellation, query-local dataflows, and disconnect cleanup are +Several query clients use the fast protocol without acquiring ownership of +maintained lifecycle. Catalog application and query readiness remain correctly +ordered. Responses, cancellation, query-local dataflows, and disconnect cleanup are isolated between clients. -Demonstrate independent clients without requiring concurrent catalog writers or -a multi-adapter deployment. Cover one client advancing or losing its protection -while another retains an older timestamp, recovery of the components enforcing -compaction, and an expired client returning. Together, these milestones complete the -fresh-environment decoupling outcome, subject to the correctness and performance -acceptance criteria above. +Demonstrate independent clients without a multi-adapter deployment. Cover one +client advancing or losing its protection while another retains an older +timestamp, recovery of the components enforcing compaction, and an expired client +returning. Together, these milestones complete the fresh-environment decoupling +outcome, subject to the correctness and performance acceptance criteria above. Implementation history is in the [log](20260903_decoupled_coordination_log.md). Workflow and current steering are in the diff --git a/doc/developer/design/20260903_decoupled_coordination_log.md b/doc/developer/design/20260903_decoupled_coordination_log.md index 3ca4849df70b7..55dcb25e4996e 100644 --- a/doc/developer/design/20260903_decoupled_coordination_log.md +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -525,3 +525,24 @@ against final transaction state. Query references are reconstructed from SQL and separate from replacement-target dependencies. Storage preparation uses final catalog membership, including all collection versions, rather than installed collections. Shared live aliases prevent shard retirement, while orphan mappings do not. + +### 2026-09-10: Lifecycle placement and query client agreed with Aljoscha + +The controller bundle moves into one independent process whose interface is +catalog following, enactment, and publication. It does not serve controller state +to adapters or gate their catalog writes, so it can later dissolve into per-cluster +followers. The adapter gets no remote controller API. It reads through a query +client that owns its read requirements, learns storage frontiers from persist and +compute frontiers from the fast protocol, and carries durable client protection +from the start. That protection is pulled forward from milestone 3 so no bridge is +built twice. Table appends and DDL stay with the adapter. + +Adapter DDL and lifecycle publication become two cooperating catalog writers. +This is required concurrency once publication leaves the adapter, distinct from +arbitrary numbers of adapters, which remain out of scope. The cluster side must +accept a lifecycle connection and query connections at once. + +Milestone 2 is re-cut as independent maintained lifecycle with one adapter and +one query client. Milestone 3 becomes multiplicity and isolation of query clients. +Next: build the query client in-process, then the connection split, cooperating +writers, and the process move, in that order. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index e85e81daa6596..b3bdd40778545 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -21,12 +21,27 @@ Check which of these review findings remain unresolved, then choose one coherent change. Remove resolved steering from this prompt. These are implementation priorities, not additional design requirements. -Milestone 2 starts with MV compute installation from committed state and a -production maintained-lifecycle subscriber, not more standalone APIs. The bound -subscriber is not that outcome. MV and metric-sink compute installation still -uses creator-local plans, and sink alteration still has sequencer-side effects. -Resolve the lifecycle-service scope question in the latest handoff before -starting that extraction. +Milestone 2 is active. The lifecycle-service scope question is decided in the +design's Lifecycle placement and Query client decisions and the re-cut milestone. +Order the work so each step lands and is verified in-process before the process +boundary moves: + +1. Query client inside the adapter, replacing the adapter's direct use of + controller frontiers and holds: storage frontiers from persist, compute + frontiers from fast-protocol `Frontiers` responses, peeks and query-local + dataflows through it, durable client protection as its only protection. Bring + the client protection record shape and the incarnation expiry and reclamation + rule to Aljoscha before building them. No remote controller access API. +2. Cluster-side connection split: a cluster accepts one lifecycle connection and + query connections at once. A new query connection must not replace desired + state or reset maintained dataflows. +3. Cooperating catalog writers: adapter DDL and lifecycle publication commit + independently. Bring the smallest mechanism to Aljoscha first. Do not route + DDL through the lifecycle process. +4. Move the controller bundle out: MV and metric-sink compute installation and + sink alteration from committed state, then the bundle in its own process with + catalog following, enactment, and publication as its interface. Table appends + stay with the adapter. Milestone 1's performance scope is 100 and 1,000 generated objects, retaining the shared-view index topology and diagnostics. Larger-scale work and the known @@ -45,9 +60,9 @@ Standing rules - A durable record is added only when it carries information that cannot be derived from existing catalog state or durable progress. A record whose value is constant at birth is a signal to look again. -- Milestone 2 starts with MV compute installation from committed state and a real - catalog subscriber. Client protection stays in milestone 3. Do not pull its - machinery forward. +- Milestone 2 builds the query client with durable client protection for one + adapter. Multiplicity and isolation of clients stay in milestone 3. Do not + build a bridge that the design's shape will replace. Prefer changes that remove a dependency on the originating adapter and demonstrate that through a production path. Preparatory work is appropriate when it unblocks From 8328f7f12d4ae9b492c62af2b4da71e8a18aa734 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Thu, 10 Sep 2026 20:23:10 +0000 Subject: [PATCH 36/36] catalog: preserve materialized-view read versions across replacement MV replacement changes the catalog item ID but retains storage collection aliases. Pin collection versions in stored references independently of table schema evolution, and preserve explicit GlobalIds in session-catalog lookups. Downstream recovery requirements then continue to match reconstructed SQL without treating shared shard identities as interchangeable permission. --- .../20260903_decoupled_coordination_log.md | 18 +++ src/adapter/src/catalog.rs | 24 ++-- src/adapter/src/catalog/transact.rs | 119 ++++++++++++++++++ src/catalog/src/memory/objects.rs | 7 +- src/sql/src/names.rs | 21 ++-- 5 files changed, 173 insertions(+), 16 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination_log.md b/doc/developer/design/20260903_decoupled_coordination_log.md index 55dcb25e4996e..7a811fab7ebaf 100644 --- a/doc/developer/design/20260903_decoupled_coordination_log.md +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -546,3 +546,21 @@ Milestone 2 is re-cut as independent maintained lifecycle with one adapter and one query client. Milestone 3 becomes multiplicity and isolation of query clients. Next: build the query client in-process, then the connection split, cooperating writers, and the process move, in that order. + +### 2026-09-10: Stable MV read bindings and client-protection proposal + +MV collection versions now participate in SQL name resolution independently of +table schema evolution. Replacement changes the catalog item ID while preserving +the referenced collection version, so downstream recovery requirements and SQL +reconstruction retain the same input identity. Session-catalog GlobalId lookups +also preserve the requested MV version. The internal state-level lookup still +defaults MV aliases to Latest, an adjacent inconsistency left outside this fix. + +Pending approval before building protection: a never-reused client incarnation +with a heartbeat sequence, plus per-incarnation collection requirements with +frontiers and input lifetime bindings. The proposed reclaimer observes an unchanged +heartbeat for five minutes on its monotonic clock, then compares and closes that +incarnation atomically with removing its requirements. Renewal is proposed every +minute. A restarted observer waits a full observation window. Closed incarnations +cannot be revived, and closure must fence late query execution before compaction +passes the reclaimed protection. This is a proposal, not an agreed decision. diff --git a/src/adapter/src/catalog.rs b/src/adapter/src/catalog.rs index 51028b35cf6de..c536694d54fe8 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -2275,10 +2275,14 @@ impl SessionCatalog for ConnCatalog<'_> { id: &GlobalId, ) -> Option> { let entry = self.state.try_get_entry_by_global_id(id)?; - let entry = match &entry.item { - CatalogItem::Table(table) => { - let (version, _gid) = table - .collections + let collections = match &entry.item { + CatalogItem::Table(table) => Some(&table.collections), + CatalogItem::MaterializedView(mv) => Some(&mv.collections), + _ => None, + }; + let entry = match collections { + Some(collections) => { + let (version, _gid) = collections .iter() .find(|(_version, gid)| *gid == id) .expect("catalog out of sync, mismatched GlobalId"); @@ -2298,10 +2302,14 @@ impl SessionCatalog for ConnCatalog<'_> { id: &GlobalId, ) -> Box { let entry = self.state.get_entry_by_global_id(id); - let entry = match &entry.item { - CatalogItem::Table(table) => { - let (version, _gid) = table - .collections + let collections = match &entry.item { + CatalogItem::Table(table) => Some(&table.collections), + CatalogItem::MaterializedView(mv) => Some(&mv.collections), + _ => None, + }; + let entry = match collections { + Some(collections) => { + let (version, _gid) = collections .iter() .find(|(_version, gid)| *gid == id) .expect("catalog out of sync, mismatched GlobalId"); diff --git a/src/adapter/src/catalog/transact.rs b/src/adapter/src/catalog/transact.rs index c9aa3c808755f..6214eee3c6f70 100644 --- a/src/adapter/src/catalog/transact.rs +++ b/src/adapter/src/catalog/transact.rs @@ -3930,6 +3930,125 @@ mod tests { use crate::catalog::{Catalog, Op}; use crate::session::DEFAULT_DATABASE_NAME; + #[mz_ore::test(tokio::test)] + #[cfg_attr(miri, ignore)] + async fn test_materialized_view_downstream_publication_after_replacement() { + use std::collections::{BTreeMap, BTreeSet}; + + use mz_expr::CollectionPlan; + use mz_sql::catalog::SessionCatalog; + + use crate::catalog::state::LocalExpressionCache; + + Catalog::with_debug(|catalog| async move { + let database = catalog + .resolve_database(DEFAULT_DATABASE_NAME) + .expect("default database exists"); + let database_spec = ResolvedDatabaseSpecifier::Id(database.id()); + let schema = catalog + .resolve_schema_in_database(&database_spec, DEFAULT_SCHEMA, &SYSTEM_CONN_ID) + .expect("default schema exists"); + let qualifiers = ItemQualifiers { + database_spec, + schema_spec: schema.id.clone(), + }; + let prefix = format!("{}.{}", database.name, schema.name.schema); + let birth = catalog.current_upper().await; + let mut state = catalog.state().clone(); + state.catalog_read_protection_enabled = true; + let mut snapshot = None; + let mut ids = BTreeMap::new(); + let mut consumers = Vec::new(); + // The persisted upstream output is the downstream's recovery boundary. + // Consumers created between replacements must pin the latest collection, + // even though the relation schema does not change. + for (name, sql) in [ + ("t", format!("CREATE TABLE {prefix}.t (a int, b int)")), + ("mv", format!("CREATE MATERIALIZED VIEW {prefix}.mv IN CLUSTER quickstart AS SELECT a, b FROM {prefix}.t AS OF {birth}")), + ("mv_downstream", format!("CREATE MATERIALIZED VIEW {prefix}.mv_downstream IN CLUSTER quickstart AS SELECT a + b AS sum FROM {prefix}.mv AS OF {birth}")), + ("rp", format!("CREATE REPLACEMENT MATERIALIZED VIEW {prefix}.rp FOR {prefix}.mv IN CLUSTER quickstart AS SELECT a * 10 AS a, b FROM {prefix}.t AS OF {birth}")), + ("mv_downstream_new", format!("CREATE MATERIALIZED VIEW {prefix}.mv_downstream_new IN CLUSTER quickstart AS SELECT a + b AS sum FROM {prefix}.mv AS OF {birth}")), + ("rp2", format!("CREATE REPLACEMENT MATERIALIZED VIEW {prefix}.rp2 FOR {prefix}.mv IN CLUSTER quickstart AS SELECT a * 100 AS a, b FROM {prefix}.t AS OF {birth}")), + ] { + let (id, gid) = catalog + .allocate_user_id_for_test().await.expect("allocate test item identity"); + let item = state.with_enable_for_item_parsing(|state| state.parse_item( + gid, &sql, &BTreeMap::new(), None, false, None, + &mut LocalExpressionCache::Closed, None, + )).unwrap_or_else(|err| panic!("parse {name}: {err}")); + let (next_state, next_snapshot) = catalog.transact_incremental_dry_run( + &state, + vec![Op::CreateItem { + id, + name: QualifiedItemName { + qualifiers: qualifiers.clone(), + item: name.into(), + }, + item, + owner_id: MZ_SYSTEM_ROLE_ID, + }], + None, snapshot, birth, + ).await.unwrap_or_else(|err| panic!("create {name}: {err}")); + state = next_state; + snapshot = Some(next_snapshot); + ids.insert(name, (id, gid)); + match name { + "mv_downstream" => consumers.push((id, gid, ids["mv"].1)), + "mv_downstream_new" => consumers.push((id, gid, ids["rp"].1)), + "rp" | "rp2" => { + let target_id = if name == "rp" { ids["mv"].0 } else { ids["rp"].0 }; + let (next_state, next_snapshot) = catalog.transact_incremental_dry_run( + &state, + vec![Op::AlterMaterializedViewApplyReplacement { + id: target_id, + replacement_id: id, + }], + None, snapshot, birth, + ).await.expect("apply upstream replacement"); + state = next_state; + snapshot = Some(next_snapshot); + } + _ => {} + } + + let mut requirements = Vec::new(); + for &(consumer_id, consumer_gid, input_gid) in &consumers { + let inputs = BTreeSet::from([input_gid]); + let CatalogItem::MaterializedView(mv) = state.get_entry(&consumer_id).item() + else { + panic!("expected downstream MV"); + }; + assert_eq!(mv.raw_expr.depends_on(), inputs, "{name}: downstream reads stay pinned"); + let requirement = &state.maintained_read_requirements()[&consumer_gid]; + assert_eq!(requirement.inputs, inputs, "{name}: downstream protection stays pinned"); + + let session_catalog = state.for_system_session(); + assert_eq!( + session_catalog.get_item_by_global_id(&input_gid).global_id(), input_gid, + ); + assert_eq!(session_catalog.try_get_item_by_global_id(&input_gid) + .expect("retained MV collection exists").global_id(), input_gid); + + let mut publication = requirement.clone(); + publication.frontier = Some(birth.step_forward()); + requirements.push(publication); + } + if !requirements.is_empty() { + // Progress publication must accept the same protected storage + // history after the upstream SQL identity changes. + let (next_state, next_snapshot) = catalog.transact_incremental_dry_run( + &state, + vec![Op::SetReadProtection { requirements, bounds: vec![] }], + None, snapshot, birth, + ).await.expect("downstream publication succeeds across replacements"); + state = next_state; + snapshot = Some(next_snapshot); + } + } + catalog.expire().await; + }).await; + } + #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] async fn test_materialized_view_birth_admission() { diff --git a/src/catalog/src/memory/objects.rs b/src/catalog/src/memory/objects.rs index 37f7c3cfe5e4b..a7510bf2b6c8a 100644 --- a/src/catalog/src/memory/objects.rs +++ b/src/catalog/src/memory/objects.rs @@ -4291,7 +4291,12 @@ impl mz_sql::catalog::CatalogItem for CatalogEntry { } fn latest_version(&self) -> Option { - self.table().map(|t| t.desc.latest_version()) + match self.item() { + CatalogItem::Table(table) => Some(table.desc.latest_version()), + // Replacement versions share a schema but have distinct collection IDs. + CatalogItem::MaterializedView(mv) => mv.collections.keys().next_back().copied(), + _ => None, + } } } diff --git a/src/sql/src/names.rs b/src/sql/src/names.rs index 1fd703a9d8885..20f9ea6bc369f 100644 --- a/src/sql/src/names.rs +++ b/src/sql/src/names.rs @@ -1534,8 +1534,13 @@ impl<'a> NameResolver<'a> { let alter_table_enabled = self.catalog.system_vars().enable_alter_table_add_column(); let version = match item.latest_version() { - // Only track the version of referenced object if the feature is enabled. - Some(v) if item.id().is_user() && alter_table_enabled => { + // MV replacement must preserve the referenced collection's lifetime, + // independently of table schema evolution. + Some(v) + if item.id().is_user() + && (alter_table_enabled + || item.item_type() == CatalogItemType::MaterializedView) => + { RelationVersionSelector::Specific(v) } _ => RelationVersionSelector::Latest, @@ -1611,11 +1616,13 @@ impl<'a> NameResolver<'a> { // If there isn't a version specified, and this item supports versioning, track the // latest. None => match item.latest_version() { - // Only pin a version for user items, and only with the feature on. Mirrors the - // by-name path in `fold_item_name`. Builtins are not user-versioned, so pinning - // one strands the reference if the builtin is ever converted to an item type - // without versions. - Some(v) if id.is_user() && alter_table_enabled => { + // Mirror by-name resolution. Builtins are not user-versioned: + // pinning would strand references after an item-type migration. + Some(v) + if id.is_user() + && (alter_table_enabled + || item.item_type() == CatalogItemType::MaterializedView) => + { RelationVersionSelector::Specific(v) } _ => RelationVersionSelector::Latest,