From 907a31b16928d2267e95f83927661dc5130dc380 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 21:36:48 +0000 Subject: [PATCH 01/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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+9IvCQi4JYL1FwIlIvJuGfoeqjleC2sUwlKsi6ZEtmCjNCMXtKSDrIujoi4b2LL0ZuyLrwkLqrQSc8TU7wkaSW8JCCtyrvv70l4rS98JCAr/CrnoMKDkIM4LqCwqU1L/CflbQiChRCEm1x4aCr4LGVOvCbg68nMi/Cqgo0QjJ70ahR4K+AISI88JGwgEbwnLyQImon8JCSoirit5U5WsOC4KuM8J+BjzpsMsON8J+VtAomQiQiyLo9yLrwkKC88J+ituC6ifCdlYbwkY2xJeCqiHvgvZzIukUKNEIyYzTwkYyA8JGAiGLwkKC3wqJcYCV7JT3gqIok8JCWlNGoWU7wn6K0JuC7n8Oa8JyxkCQKJUIj4riYSCXgrqgi8J+JhCrwkLm48JGkle+5sPCfn7Dhi4NGR0cKJEIia/CdlJrwnoCj4LuRPS8kyLrhvZ3wkIag4KuJwrZk8J6XvwoyQjDIujQkwqXhi7RU77+9wqZ3Lio98J2Uuy974KqIJyYiYFxa8JCguPCqqbDRqC4615EKI0IhW0bwlqSO4KqKcPCQhJ80YDHhvKAg4KeEOlB9WfCRippGChRCEibDklhP8JCBhD8kV+GfsNG0JgoCQgAKrgG6AaoBCkwKA2tleRJFQkN08JGPgi7wnrG3w43wnYeHw4Rr4KysXduu4pGD0ahtP/CdkqLwnZKpYOC3g/CRr7I6Lmskw5jwmImeXWrqrLnwnZSbCloKA3ZhbBJTugFQCk4KBEZsYXQSRkJEwqXwkZuQPOGpvPCeuaREPybwnZKuVWAm8J28lmNN8JGNjFPwnrmyYPCQlZzCpSdK4pGAMOGltPCfh6Yv8JOlqvCflbQK7AW6AegFCk8KA2tleRJIQkZI8JGcseqglOCuvvCdhLRrRnsmQsK48Jy9hWguL82+8JGyszjwr6Cy4Kaq4Kme8JCVkvCQvIR7ITPwkZGe4Ky58J2SuzplCpQFCgN2YWwSjAW6AYgFCoUFCgZTcWxTZXQS+gSyAfYEChxCGvCflbQ+8JuBjSB38J2FkPCRjaE6Jlx74LGaCjBCLu+slTh7IvCeuINw4KehTz8lJScr0ahAMWVg77+9Sjrjgq/wkYyQJdGo8JGKiEwKLEIq17NO4LeYd/CRmrM/4rqJ4raM4LOH4KuNLyUmWfCego/wkLyZ8JuEsmAlChlCF+CrjS9cbPCegJzqpaDgp7FMYD0q77maCg5CDPCWv6Ai8JCguCBbTAoqQijCpT064YuD8J64pFHgsI9vIHLgtoLwkICq4b6+8J+VtCXwkI6+4rqICjJCME0i4aW0LeKBjfCeuKfCv+CxlvCQjqlh4aCH8J66hl7wkYydP+CtqDomJTw/JzouXQomQiQm8J64ofCRg7nCp+CxhMKs4LS/ZvCQuoJzPXkuJH56P3hGWlwKGkIYPHfDgCDCvfCWhLc/1rdPSPCflbTwkLW4CglCBz8q4ZuTyLoKJkIkYO+/vSLwn4WUJ/CQvLzIumA/8JGytdGq8JC6sXvDsuG9nWc9CglCB/CdlYAvLkIKNUIz77+9PfCdkq9m77+9VCrwnri1J+qppFk+KT0/8JCOiFZr8JCguEDCo/Cdjbc9YD174KqzCi9CLVxTefCflbTwkr+FLyzgspUiYPCRmZIvP/CRsIEnI0gn4Kq94K6pJXUp76moYQopQifwkKaMXC9c8Juyk/CRj5M9WeG9llEk4pK98JC5rCU9OkVATnnhqogKOEI2e+CssC3hn6fCscK68JKQuPCRq57wnoCDPeCzh/Cfoqda8JCjrzrwkZmXfOGgj8i6e/CRnL8vChFCDyXwn4yt4YuA4Yqg8KyogAoJQgckKuCyslVgCskEugHFBAoKCgNrZXkSA0IBLgq2BAoDdmFsEq4EugGqBAqnBAoGU3FsU2V0EpwEsgGYBAoeQhxpw4BceSXwn5W04b2ZOsKl8Ji0gyQ5Mz/RqDdLCkxCSuCwhHt+4raL77+9KvCWvIZ+IvCQoIjwn5W04Ymd8J2RvOC3hPCQtLRpM1DwnruwL/CYtIbguYTwn4KmYe+/vXtc4bOaxJTwlq6KCgJCAAoUQhJ6Jirwn6OAL33wkoSm8Jy9hEkKLEIqJkzCpeC1hC84wqVDJjFCJD/wkYqGP1BkwqXDrS/gsZZ7efCRipHwn4OPCkBCPuC2oy4swqXjhosi8JGPguCsnPCdkZ8q4aam8JGvsDxE0ajIuu+/veGKueCmsvCfrLE68JuFpU/gr4bDpWwqChVCEzwiwqXwn6CeV+Corz9EO+ComD0KN0I1XDxc8J+VtHAi4LOeKT3wkbWS8JG1kCrCuvCehYfwkYy2ZCBcLzvhipp78JCXovCdlIcq0agKRkJEey8kcPCRvYti8J+VtCpN8J+btHzwnrinIuK2sjrCpSU/8Kyoqu+/vfCbgq/RqD8i8J2YkO+/vfCQhpAq8JCkv/CcvJ0KSkJI8JCwue+/nPCfiYNgXTtl0ahdwqrgqpHwkL+E8JCsrTTwkJa5J/CQj45RefCeuq158JGPjEB8MPCflbR077+98JCVol/Ius6bCjpCOFkj1ZXDqDwm77+9JjUqdmvhgZcgVPCRgpvwmJa56qusdCck17B2w4fwnrmU4by3WfCbsoDwlq6PCpoPugGWDwpJCgNrZXkSQkJAKizwkJ618JKRsiHgppDhv7R68JCzv/CRiprIuml78JGsiOGtrPCrn43qpK8/c3s8PiVgXHtu4b65PD0v8J65sArIDgoDdmFsEsAOugG8Dgq5DgoGU3FsU2V0Eq4OsgGqDgoqQihg6qSHVXTtn6tl4LSLJW45RFvwlqWRLlzwrrOC8JCPk1Yq4Y2Y4b2ZCkBCPl5Q4Y+68J6AvSJaWuGxo/CWq7BDJ++/vdaD4LC34KeM4Zyg8JGRi/CQo7VgP/CQoLwlYmDwmI+V4rWZZys/ChdCFfCRjZdPfPCeoYg44K2IKULwkIScXAomQiTwmKm98J+buTo9XlTwlr+xfvCQgKg/8JC8gybgsZkmW/CflbQKBkIE8JCougovQi3wnrii8JGxgSTgqqPvv71yJnTwnYWJVfCeuZlc8JGvnipGJmslKOC3ilxTLlEKA0IBOgoUQhIlIvCRh6PCpU46yLol8J66pkwKCkIIPGrgs6Dvv70KLUIr8JC8mDw6bSo/6qeV0ajwkYWAacO18JGOnS8vYSQ6YHUmN1JpIibRqHMqNAohQh88JvCeupsn8JGYjci64q+U4Kq2KjXwkb6w8J2SojNgCgxCCu+/vcKleDrDlCcKFkIU8JGTmPCQsojwn5W0SHNj8JK/jCUKOkI4KjckL10kLuGVo/CRsZk68JyzmOquofCQrortnrvvuZ09LiIm77+94LuNL2nIuirhgonwlryKVzoKAkIACihCJidN8JGMiWwk8JCurjp06qyh4Y20T2zgqrItOvCSgocn8J+VtCouCjhCNiU/XPCQsIvwn6uo8JaEhFwqdkfhqqN74KmH4ZGX8JGPk/CRoqVmIjzDsysm4LaqKvCdkqI9VQoaQhhBJuCmkMKl8JG2kdmZXPCQqIVcJkMiOj8KDUILwrJS8J2SovCWv6AKHUIbLipx6qeR8JGMiypq8J2Ai2Y9avCeuZ/jhotcChlCF/CSkbHwnZSOLjQn4KGE8JasjEPwnrmXCjhCNj3wn5W0MErqoLg4O/CQlqgqeGrwnri78Kqwjy9cPGEgP+CyuDTvv5siblzIukXCpeGJmMi6VAodQhsqIy97IWJM0ahjJy97P/CRr5ohPNGo8J+VtDwKKEImTiY8YuCtnDhVXPCQk5B94Ki4Lnsx6q+OL++tgC/IuvCflbR7PG0KFUIT8JaskeGxtzwqzongrIZFJeCmjwoHQgV+6p6rOQoxQi8qJvCtvJpeJChF8Jaopllg8J+JouqsgvCeuoNpWz/hvZtg8JGZmCXwkY2r6qKnLwoQQg7IuvCQkZLIuirgsr3CqgoPQg0n8JGPhfCfq6Nqe1xwCj1CO/CRv47goZHRqD7wlryMJvCfiIIn77+aKlwwPSbhsJx7Oizwlr+h8JGKi++/veCouPCRjI9J8JSYgC9oChJCEOKAgDo78J+bttGoe+Covy8KKkIoUjwk4oKV8JG1mWMu77+94Kuv4LWd8JGDtEUv8JCgqeC7jCQz8JCjtQoGQgTwkKGCCgdCBfCWvp98CiNCIVlcJvCfqq9OekjqkqvCpWBczp9G8JyysGvDlvCRo609JgpDQkE3LT/wn4mB8JCetjfwnrmHLuGwr9GowrguPEPwn5+wWvCQv6Iu8J6EklHwkbSB6qOP8JGmoz3wkLKMJkZx8JGmowowQi4nLyTgpr/wnZOCJT0lP++/ivCeuKLgqJB88Ji0hCRqcGdpPSrit5Q6S2jwkaeSCiNCIURm4LeWJe+ukOqsi/CeuYlyOs6Me++on9eX1ZJf8JarrQoFQgNAYzoKEEIO0ajwnZWGOuCjmHZvSnMKMkIw8J6EpC/wkoay77+98J65pC/wn4mjRPCQjIHwlqu1PDjvuJXwkIGBXHzwkY6m0agvCiFCHzzwnriC4oGwaiXwqquuaEHgsJNvKj/wnrqr8K2st2UKCEIGanXwlquBCjBCLvCTkYJZ4K2WZks5JPCRoIsq4rah8JCWmijwsai5QG8iXD/wkLSye3LDrfCQto4KFUITLuG/s+GgufCQiq17w5g+8JGDsAoNQgvhppbRqMKl8JCajQopQifwk4iDRD9gJWMl8JastCfCpdGo4KiwJXRPJWDitpbwkLCtOjzrobwKBEICSWUKFEIS77+9Jj3RqPCegZF78JCOhk9MCgdCBWA644KgCgpCCPCQtLElJj0kCipCKD1j8Jy6hcKlViIk8JitvvCbsJjwnpOiw5bwkbaQ8J6lnj3wlqyQJXsKNEIydjrhjrdc6qCVdyLwnrm68JGZmfCQurHojYHqpLtW4b2dIko/e/CeuZ/gqrXwkJq2PHAKI0Ih77+9eX3CpT7grI9LdTJMJWo28JGNkCnwn5W0PFzwkaSFChtCGe+qsiTgroNs8JimuvCRvYtgw5rwnaqfNCIKH0Id77+90agu8JCkjy7vv71XLzLCpfCeuYfCpeCgly8KJEIi8JuFlTw977mKIuC0g3MmSCXgrZw64YON8JCKieODpsi6Lwo1QjMkI+CrkD568JGIsG8/4Kmc77+9OlxVwqU9N8KlO+CqtiVcLsOrJirgtr1cbWrigJHitK0KULoBTQowCgNrZXkSKUIn4K2f8J2Uj0csOuCvgSUkPPCfqa1yYiVg44aW4reCIuGqk+Cun3s6ChkKA3ZhbBISugEPCg0KBEZsYXQSBUID77+9CsUHugHBBwozCgNrZXkSLEIqPzzgspBIKChY8JCesuqppEvwlqmMKuCri+Cmvjwmzb974LGZYsKl6q6xCokHCgN2YWwSgQe6Af0GCvoGCgZTcWxTZXQS7wayAesGCg5CDPCQrI/wkLKOyLpcdgoyQjDwnrm78J+bs108IvCdvKXwn4eyM2hsNmBQfD3IunFQyLpBbS9rYCMuIDrvrYDhpbQKCEIG4Kaf76eJChdCFWAlZ/CflbQn77+9JyLwkYu3LuGMlAorQinwq5+1KiI9IlYyJjzwn4iuKiQqWeCwnvCeuJ468J64uTg677+98J+hvAoQQg5cXGDwnoCC4ae+4ragcgodQhsq8J2LkcOnLjwl8Japuy8k8JCCoyJg77+9Jk8KEUIPSHBgIjp14K6U4K6+4YmYCkVCQydMdvCQjpY88JCWsOGqiPCQrajvspTwkY6Ow7PitrTwkLuEwqVhWdGo8JCMvvCQrK/gr4fhiZjwkYy1e2cm8JSVtjwKLUIrOu+/vXvhr5LCpT9YJi0/8J+vsuGgv8OE4Ki88JGOkD1uUe+/vSg4e++/lwo/Qj0qP3rgqYhLOk7gqInDuvCSi4klPcOjfi3vv70sYPCfqp0vL3rCqUt5PPCQloPhj73wnaqf8JCgt/CRg7hECjFCL1zwkJSfSTM/wr9NIDom8J64qSVqJuCwvvCQh70g8JuxuCFqe3zwn5W0JiVbYCYnCghCBlkq8J+EpgoiQiDgsprgsZXwkY6OJfCRpInwnaqvPXAn8J+VtOChniLRqApHQkXwkayJUGvwnrqh8JCHoC0k8J+VtC7wn5W0wqbwnri3PErwkYS88J+ro/CRjJDwkJebYD/wkY6LVETwkY+FXFzhoqbgpp8KPkI8JOG8mkxyPWUkXfCQgYbwkLSG4K6O8JGmpSLCpfCQoLw+4aWwJifwnrmHOj97Py7qoopJ8J+giCrwkY20CitCKfCSko7gpo8kNHp74aelKuCrvicm4Z2u6puYJ8Kl8JC5oeGBkHo6w5B7CgNCAToKFkIULfCegKM9X13wlr6V17BF8JC0lFwKFUIT766yJOGtiFnvv73wq5+e8JCWgQoQQg5g8Ja+n/CWvZ4v4ra0OgojQiElflfhvZngp5fhp77itqwv4Z2u8JGwiHjwkIGD8J+JkUQKGkIYMPCdkrvwnZOze3dk4LenOi/RqPCflbQpCgJCAAoGQgTwkaCvCgNCAToKK0IpPT1N76y577KJXCrwkJKh8JGNjPCfm7BcJHXwkY2D8JGNgeqvsvCQtbwKcboBbgoXCgNrZXkSEEIOw7B78JGCpiJiOvCQvLcKUwoDdmFsEky6AUkKRwoERmxhdBI/Qj088J65m+qsgmnwkY+C8JCphtGo77aiyLp7JjY98J2Hh3LihKk/4aC3fC7wkLy0JyQk4Ki44K6cJfCQoLw/CpkaugGVGgo0CgNrZXkSLUIrdiXgt5PwnYulKiE9JuOBi+qfky/vrL4rPOqjlkE88JGshOGznuGqhy/RqArcGQoDdmFsEtQZugHQGQrNGQoGU3FsU2V0EsIZsgG+GQocQhpUwqXRqPCehIjvrINo8J65vF4+4LqlXOKBhAo6Qjgu4YCIYNibVFh58J2SovCQirLOiSZ78JaEnfCRmIhSfSdozoUlOuK2tdqq8J+CqyvwkYiyPybeowoZQhdc0ahzOvCdkL3DvteSXDpNYMKl4aqYJgocQhrgrIbwkoW0yLrYlEHCpTXhpah7w49c8J2NtAoLQgnIuvCQq6bgtY8KFEISLCUu77+9djDwloSw8J2NoiJDCgdCBSg96qyWCjBCLiLwnbyVe2wvPW7hmqHhm7Xwnou/KvCQj4/gqI/it4skLnxs8J+ggm7wnZObwqUKC0IJSO+sgfCeubkuCjVCM8i6Rl5C4KeHKiY4ScKl8J6KofCeuZQ/8J+VtPCflbQ8ejrwrrOGL3t1ScKlRTvDhO+/vQo6Qjjwn5u88J6Ep++5s8K0NeCykCJlOirRqOClleGdrvCRjbAk4LeRyLonL/CQgI3gp61qb++/mz9KPwopQiciPM2+8J+VtOqshfCeuqPJlmBdLjw18JCuq8KlMSYqJOGQivCRnLIKQ0JBdfCRtajwkY+X8J+BuuC6jy/wkZuBb/CRpqJEWiF7YDo0e/CfjYvRqPCUj4M+8JC0iuCru8KlPzxQ4a6yJOCmoi4KPkI84LeYYPCflbQnw4kq0agmJT3wmKycYtafQOqVhlwmw4jwkIyb4K2jUTbgtoIiw6JjK/CQgIgm8J+VtCIxCiBCHu+/veGNmcOI8J+cnPCQoLxVLTrwkKeNzoXwnZKwOgooQibwn5W04LqBJ08kNe+sk1Dwn4Oew6TgpoVqTPCRtZnhiZjIuu+/vQoyQjDwn5W0YHtN8JCzrTEiTWw/VHAk4auHKsKlJ+G0izw3wqU677+98JC8g1ssJeCovG8KGkIY8JGmpVoqJ++5jjvwk4GmSuOAmvCQgI8/CghCBj3wn4mQJwowQi7RqDzwnrqlOsi6Pz1TKm8u8J65nTzNuyvwkKGUK07gqZxn4KqzfiA06qOXeEEvCjVCMz/guI/wkYqF77+9didSw6nwq4OS4LSQJFrgsKHhnp1r8JC6rWlgPcKjIS/CpSvwkISMSgoeQhzwn6GR8JO7glzIujrwnp+u4KikLeG8qn06wrAkCj5CPPCeuJ/wkY+hOyd7VvCRvrBKTHbwkZyVUD0m4LuCJGvDvXxO4KuAMvCQsqJLJt+E4b+0MEhB8J65kuGiggokQiLwn5W0NeGkoVEtyLrDssi6KlIv8J+VtPCRkZ9EyLrwnZKlCkhCRuG/sjrwn5u7Y/CYnIzwkJW3b3db1o0k8JKRsn3wnrqB8JGJgfCRu6Ft4ZykV/CRkLMg4b+gOkritI468J+VtOKCmfCQhKAKAkIACg9CDSUlL+C2vfCQpK49eycKHUIbYGBP8JG0iOqslPCRr5vwkY2j8JG1gPCQgZlMCihCJkt7KmVB8JGFktGoaSTgrL57Wu+mp3s4eEBcPfCQsoUmXzfRqMi6CidCJSXwkLm28J+VtOCogUdxXUkve3LwkYiIP3FWIlxaPXcuIibIunMKC0IJPOGKtXh4LicgChtCGci68JColuCqsOK5jybwnrqTW3soJPCRvYMKDEIK8JGMkPCQqI9FRwozQjFJI8i68JGvsyXwsJ+Z4bKJ8J+VtCDCpeGJkPCflbRiPXDgsLB78JC6sPCfm54lwqlcChhCFiTCpSpc4K6KfDdc4KONQSIl6qa0b3IKM0Ixc1RxZ1x4aHPwnaKeOuCrjGDihIvhn6B54KGewq8m8JCiqO+/veODjmBPfeCosOC7nQoIQgZ74rStQ1MKFkIU1o3gqIFsJCvqmbrwn5W08J+HqV4KG0IZaPCRsL7vv73grIkp0ajIumAg8J+VtO+/vQoVQhMo4Ki8WyY3UDTwlquD8J+vhVE9Ch9CHeKAnOK2tOGKvsi617HhiZov4LGVJ/CQg5gkey8iChVCEyXgqLwn8JGmozgl8J65otGoJFIKR0JFwqB7ZXx78J6Ahndq4b6KXC8v4aqWPOCgtUBq4bOy766JXPCRnJDwnrmZ8J65gvCQrK4v8Ji0gCXwnoGC8K61i/CQnaBaCilCJ+GJjOqgmS5cbvCego9NcOCptHFzwqV8JScn4LSPMyXwnrmd8JCHuQogQh55OlPguIUn8JGvkVgudlc5MSA6LUHhi7VQL/CepZ8KFEIS4raLJ33vv4TwnZKx8JCug3kkCiJCIC5ue0Xwnrih4rWm8JCGlCM7MuCxv+CtiGXwkY2X4L6HCjdCNV1UUPCSko9cIndg4KGS8J2QhUbhi4vhva1b4rO5J8OH4Ymd0ajhjJM8wqVEJdGoKi7hpLUgCipCKGsn8J64rz97d3Xwr6GV8J64gWLwmLSEPdGoPfCRm57wkoGa4aCtPDUKEUIPM2I+4KmcZfCfm7nwn4aWCi9CLWFb4LeUYPCQlJh78Kycuzx78J66jT881o/goaHDnV868JCjtGLDqeCosltgJgo0QjLgqILwkJOjRFDwnLymJvCRtpDgsrnigIBeJOGpsuGdrOC9o9GoKWPir5Qm77+9QV5RYAoxQi/gqY3CpeCsgm/wkY2M4Kme8JCug/CQoLgh4LqG4KeqYPCflbRge/CQgYLwn5W0QQojQiHwkbWGyLrwnZOBQyZI8JGxpWXwnrmL8J66gfCQnoB7Kk8KKEIm4YmN8J66rVbwnZWDJ27wkJ63byLRqPCRiohBwqXwkr6yP/CWvpwKFUITOWM86qOZwqXCpeCvh3vwkbC9JAoeQhzgvKw8PfCWrolH4KiQJOC9mSrqnrp7NSrvv70/CiBCHiIuJW97UT8m0ahgP/CRq4I6JSXgq7vwsaSIKu+wpQoFQgNcRicKD0INKvCQoIg98JGxlOK1mAo3QjXwn5ujJ2Bc8J6frmRie/CQgL0kP/CRsYTCpcKlWiY88JGvhsi6MS5WIsi6JO+/pivCpVDDngoRQg/hqpngqYc98J2EvuK2ukgKFUITJlniu6tc8J2fpOC7hipT8J65vgo8Qjrwnrmu8Ja6gMKlUEHhprzwnoCIXPCRsIbhpIp7J2A8J8KkMvCQoYrwn5W08JGcmlwiTkvhv67Djci6CjRCMnFH8JOFqC9Qw7BpXy5E8Jarh/CQlZUm4KeM4Lqxw6dbL/CTkZLCpVwtJy7wkYyz4K64CgtCCeqfkOKHkFxuRwoYQhZkJOCotcOUPC/wkKiGSyQ2wqXwkYy3CgZCBF5OL0QKOkI4L/CQhKrgq5BCLz0r4bGJMWxcZklR8JGPmC/wkbaX8JCureChnvChlqDwn4K/8JGPhfCWrZ7vv70KCEIGIs6Vw5RmCjRCMibwkJaS4Y+5eVx48Jy9hSRhQfCflbTwkaSjyLouReG/oCrwkYiQ8JCBm/CRjanhqocmCiVCI8Kl8J64tuC2pOConvCfgrbRqOGJjfCdkqnwkY+R77+9e3okChNCET0nJ9Go8JGnm1zgrpzwkJ6cCiVCIyZE77+hPe+/vUHwnrmnIuC7gfCRpYVC8JGvt/CflbTwkZmrCiFCHyrwkK268JGkt8OiRtGoWCzwkI2bPzx7fGBbPfCfg48KKEImZELwkY6L4K6cYPCRpInwn5W0QTFHIi1gX8KlTeCpjVkiROCyqkQKD0IN8JGlgsKlyLo98J2qmwojQiF04ruw4LKP4KyCJ/CRr7AlJOCvivCRjZdcPyXWlPCeuZ0KJ0Il4b2Zd/CflbTCpS/wkKi48JCWtsKlRSpc6p+4R+CqlXvwkbWAPQoCQgAKEEIOOuCyjiXgsZhw0ajhnIIKCEIG8JGMj2AiCj9CPfCRjKThir3gqILgrpPwnruwIjDRqHvwnrmUJsKlIifCoDxoKiJt8Ja/sUPCsWDgsKo8V++/vfCQvoIzJicKNUIzQCIsMSXwkKe3e+CoreC3ltqaJVgi8JCqlOGnmX5jI+GhpuCqvO+/vfCeuYnRqCfwnrmhChlCFy574K6jPV0i77+977a46q+yfj8wLsKlCglCB+OGhMO+JSUKBkIEIuGoiQo0QjLwnri7MD8k0ajhnrDqp6omQDzwn5W01Lcm0agm8J+VtNuuK3njh5/wl4ygXD8jOj1YTwoVQhNyPU8gJSlqICIn8JGOhzzhsYAjCidCJTbwn5W0MTon77+PPeC6gsOIKvCRpInjiIfwnoSGWi3grKDvrYEKN0I1Ou+3j2DRqDx6IvCRg6Vg8JCsmWgmROCroSFbaFkvP/CRg7ND8JC+hiJc4YuWJU7IujzgrZUKJUIjLi7gqLwmL0c98JCMgFImMyLwn5u0NEEg8JCgvO+sgiPvrJcKFEISISI8IktgOkEu76ac8J2TgmRTCgZCBO+/vWAKXLoBWQoJCgNrZXkSAkIACkwKA3ZhbBJFugFCCkAKBEZsYXQSOEI28JG8pz3gt6tT8JGKheGmk3vwlrmsPUYmUfCRvLrDuy7gq6PwnLaX8J64r+CxmuGJrvCflbQuCp0LugGZCwoYCgNrZXkSEUIP8JCkvz1bSuqlkS5DJsKhCvwKCgN2YWwS9Aq6AfAKCu0KCgZTcWxTZXQS4gqyAd4KCkRCQjTwkIyWKnvgraNNXOCsvPCRiozgq417KXbwn5W06q+1LlV9wqU88Ja+hvCegKh38J+DiTXwn6uyPfCfhYHCpeGftAo7QjlYL/CRjajgqInCpcOC8J+Wu0AtfPCbhLLRqCo/SPCforBFwqU8PGBqPHbwkKC84Leb4Z2I17LvvYUKQEI+d9GoXDpg8J65rzfwkKi4P2nqo5A7Ii5c4LedPUbgrY168JCWlOCunPCQrabwkI6QwqAl8JGKoeGxgC7guoEKFkIU8JGMiOCtqEdR4Ka3KnkldvCRjJAKG0IZwqVb8JCrkz/gq4fkv4nwnrqo8JGPguqoiAoGQgTIuj1OChpCGDDwm7G2cCdPKy0s4bGHXOCgifCQp4xVKgopQifwnrmCb1sne3vwnri58Ja1rtGoXuChimBg4Ka3JeCurmDwnYysyLoKB0IFfu+luz0KJUIj0ajitqU04KuH0ahcXTxhYid7QPCQgZtgJeCwiHt5J0siyLoKC0IJ4ra0JENFwrwrCg9CDSZj8JCegDA8JvCRtL0KOUI34Y68YMKlP0PwkJygNzPwlqm7YFvCpSJMPMi68JCet/CQqIYlNzpLLiTwkYqN6pOp3o5e8JaongozQjHXsfCRjLPwnZWCKy7wn5W00aglPXvgqpEk8JGkgFjgoarwkJCi4KmBUFzqo7rgrZU9Cj5CPD0ge+GKjVHqoac5US/wkKiG77+X77+9QiQ9JuC9puCzrPCbhZUjPD/hspnqpoBg8JCphV9c4LOV8Ja/sAoKQgjwn6qG8JCzvAo8Qjp7LklLOvCdlZ/wnrqn8JC9tuK2pHvRqCbhi5Ei4KiDNu+/veCngsKl4ae+TPCQlpJQVi97R/CRjKwzCi5CLOqmoHvqrI4i8JC1j2LCpfCRhaw8XOCzll0/JvCRjYfDgS9PyLo16qyqPci6CghCBnUl8JGcmgooQibwnrmCcmDhnKFye1NcI+C6gvCRtqDwnrS5NvCfq6Qn8J6AoVrVtwpAQj7Iuj7CqcOa4Yi/8JOBtj3wnZWMKjlg8JCPiTzwnZSaw7jwkZy5762A8J65m+GJmOCpgjxg77+94o6fWCXIugotQisv8JatoPCeuLbhsKw9KvCdlYPwkJaVdVdgJSoqJD1PIic18JCOtsOEPztZCgRCAsKlCiJCIGfhpKTwkYywQvCcvpvwn6K24aeHKm3CptGoPkfwkK2FCjRCMuOGlGIo8J64iuGcoiTgqJjwn4mRPWtPYCxucUwlw6YzRk51YOKBv2DqpYfwm4O64amzCidCJXNh4KiwSlzwnoCj4pGHLuCujvCfiaLwkYqZyLo877+9Kis/XX0KSEJG8J+VtGDvv70/IlU+8J+HtfCQgJjigbHgt5I88JaujS9077+98JCuhfCflbRZ77aG77+MJeGglPCen6rwkJ6QOiVi8JGMgQoHQgXwnqWVPgoSQhDwnrmLbHt7YXQ0JPCav7g6CkNCQTpv8JGEvvCQgJDwnrSoRSY9L3vwkpGy0agmQHbwkKi477+9LSfgt5loPS864aCS8JGOocOue+CtneCytm3wnoCICjxCOvCdkrvwnrmLJOC6pci6IfCfiZDwkL+w8J65umvDufCflbQ6Oi8nIj/ijZPwn6qn4LST8JCoji/goL4KEkIQ8JCwjD0kefCRvYwu44iIMgo8QjrhpJsywqU44Yq777+9We+/vci6KvCfoIbwkY+F8JG0unt+fPCfqr5cKHvgt7MqMOC2tnt04b+74KyLChBCDsKlLC/vubbvv4wkNCQqCh9CHfCfh7/qqbg4JCXjg4XVqeqfleGlkzTqorbgsKg9Cp4WugGaFgovCgNrZXkSKEImOOCoglw8L8OSXCo8TibDjC4+dX0uPns68J65pHs/8LG4iOCxiCIK5hUKA3ZhbBLeFboB2hUK1xUKBlNxbFNldBLMFbIByBUKOUI3YOC1iMKlbiLhn6bRqMKle/CRsrRc8J64q+GtguCrgeGmiuC6lvCRh6FL8LGsm/CflbTwnp+5PQo6QjjDkPCWqa7DlTTgrILwkZaudXtT8JCUg+GkpCXgsIMkLvCRu7g/wqXwnrmC8JCgs1I6yLpOw5kjPQoQQg7Iui8iTHklJiVd8J66owolQiPgu4Lgr4ZnJO+/vfCRirjCpci64KmB77+9J+ChjirhnbBKUwo5Qjc/8J+hksi6Ij/grppufuC6geCxhMOp4YWQ4ayQJSY6J+CknSIlYFlDOlXvv6DIuvCRhLvIuiJKCjBCLvCflbTwnriB8J+tjvCQla8ofeqdpj1hyLp2PeC8tSVDU++/vHXwnLCS8JCAgigKIEIeL+CzomDIujzgrL0l8JGZpirwkKe6PGPwn5W0fXAuCkRCQiXgrYzwnZCbfT3hibfwkZmReyR78JC8jC7igbHwn6CI8Jasu/CRpqVoLCLwkJap4Z2zPCTwnrmJw5FcZVpR4KuhPwojQiHwnqWeXU4o8Juwk+Gwi2Rl0ahbyLrvqbhiQvCtrIZWfTwKE0IRLlPwn5W04K+NbvCRtaDIuiYKPEI64KuFSuC6pTrhi5DCoU/gqYzCtuCyqzxJW2AqWCbhsrXwkZKa6qyq4LqN8JCtmsKlXmbwkZiXKD9qPwovQi3wn5W0RybwkI264KaK77+94piV4KGefsi6NsKl4YuF05/hp5ZWNy8xTGQ/w5MKJUIj8J2Hk++/veCxmD9LLnvvv71gwqUv8JCLuvCdlYF5L/CeuLcKMEIu8Jy5slbwn5W0yLpc4Y2fYSFcYFg64LK4PeCuv2MkKPCdvIbDuk7hg40/4K6PQwo3QjXDjvCQto7gr4vwkbS6e2V78JGSqT3RqPCWq4lqfeChniZqJFzwnrm3Jz/gtpTwnrii8JC0tQoYQhbCpTlgaUlX8J+VtOCpnPCfoK3wnYK6CiJCICfCpcOPPPCRsINAK1Tqp48uSfCQlpU7aFwoPSLwn5W0CkZCRPCRjI/CvuCpkeK0p8uW4KqD8J65t/CflbRp77+9YCXqo5Hwn5W04q6DJGDwm4Wk3bs64KirZ1TDk/Ceuqzwlr2q4ZOLCiNCIcKl44CLXDsvXPCQl4VK8J+rtuC1myHwn5+w8JC+g1ZaXAouQixM8J6ApypV4LWLKjHqr6o8PCThqpfitr1gfWBO8J64pz/CpVhtPVzCuFBqXAoXQhXvv70i0ahP8JGnmuqSr/CWq4nhraUKPEI68JCgvHvwkJK08JGKmHvCpWXwkY+CJCLgrKJceyrjgKfwlq2mJmsrYPCRiLbwkZy7QsOR8J65iyVQLwoYQhbwn5W0J+K0l/Clvb3CpfCen7fvv70kCiRCIm/wn5W0wqUv4Z2P8JG8ou+/vWAgUOGKl+qttPCeuYJrPVgKGkIY77+9LvCQlp3wkY2XS+CkteCssvCRm6EmCi5CLOGds8i64byzXmA64rqOOSfIundH0agkQEUnPfCflbTwn6GXZvCRipHIuiolCjBCLlLvv712Om3wkY+AKfCfrrXguobwnZKeKvCfiZAqcdqie/CRhLfwn5W08JGFklUKG0IZafCRjawnLickWCfitoLwn5W0wrEkSykkNQo9QjvwnZKedyfvv73gqpxUJTkg8JCKri/wnp+uOSbwn5W08Ja8uGvRqCI98JG2pvCRjZDgtb4z77u66qOVPAojQiFBJl5deCfRqD3wmr+w4KiHP8K0zpprw79cwqVre/CQs4UKCUIHJnsv8JuFlQoLQgk68JatvvCQkoEKCkIIJvCdkLfgr5AKDUILRD9X8J6Aksi6dDoKJUIjLmfDgT3hoqQvwqU6Se+/veK3gfCRioh6JWDwkY+XKvCRhagKMEIueCrjgpZIWuC3n1x3ZuCwj/Ceuqwoe/CeuafIuuCunNewP/CWqagu4oieeyooLgoGQgQ/4LOpChZCFMKuXfCbhZImPfCQgKlN4L2NJmAvCi1CK/CflbTIunE877+9M/CflbR88JGlkT8mOjo98Jy+jSbCpSIqYNGoJ/CRk5gKFkIUOuG8muGmgjxc8J6AiD3wnrmpKjUKDEIKZmPwkYu18JuynwpFQkPvrJMmw5Hqr7A6JOC0kE/wn5W0JMKlRO+/m+GDh/CflbRxOvCRi5M84K6p44OvXGhLZidc4L22YPCRvJ3hiZTwkYyWCkJCQOCzrDgq8J2GvEHhi4BIPeCto8i6PfCdkqUm8J+JkUs8JFhc8JGPl+qmiOC6seGhs/CeuqEm8JGjqS8qXCR0yLoKMkIwfirwkY208J28pSVgwrJ1P9WKPWrwkLqs4ZyN8JCFvOCpnuC2gsK4cfCQpL8yYFRnCklCRy9c4LWPPPCRlpTgtpIsPeOAvvCflbRoIuCvkNezwqXwkKij0ajwn6CW8JCdpOCyj9uqJS7wkKiGe0Tgp43wnoqU8J6Km82/ChhCFlLwlqylIiM68JCkgeqgsXt04KqQT0sKQUI/17JhP+Col+C3ikYk8JCeiUXRqMi68J66hPCdkrtE8JG1oTpx8JCWlCVR8JaqviLwnZSaYPCWroFcfci64Le0CgVCA++/vQogQh7gqII8a/Cdh5wu8JuFpmLXrz1C4Kqv4Ku98J+pumoKNkI08JGlkjzgs6B38J2qrCLwkYyQJz97L+Cwgj/wkY+X4oG3w53wkaS9IvCeuZvwnZikXFQkJQoYQhZrJnhQ8JCWqSXwl6KR4K2V4Ke64aqHCjFCLz8q86CFjuqpjTrDuzp2MSzDmsi6YEXgtYrCpeG9nfCQnonitK0n8JarmeGwkiU2CgdCBWkm44KHChhCFuGfkkAtJ/CRlo3wkJGl8J+VtPCRkqYKLEIqL/CfqqfitrJcSuKugUThvpok8JGIiSM68JCggyJLOvCeuY4u4b+M4aChChhCFiTwnqWTUSXwkbCb8Juyne+/vTzgvqAKRkJEe/CbsbzWjsO+4Z2m8JGkieCxuGY+77+9PfCflbRcLsi6KuC7nk4mL/CeuKRueOCzs9GoLCXwnriB4LGIIuC6iPCflbQKIEIeP+CmpvCfnKxc4aG3NWBvLyPvv70kPOC1iGLwn5W0ChdCFXvwkIeoP2tyOu+/vWzgu4Yi8J+llQokQiI88J6FjiLwnri5P9GoIiLwkbCy8J2FlDd+JOCumVzwnrmkCjFCL/CctrTwkZKM4aabUD8/OGTvrYQqeeC2sUI5OuKAm8Kl4KeE0ajvv70n8JGMpzVzChJCEPCQoqzgqZ7CpVrwkbWz0agKDkIMPfCfgqlZ8J65mz06CgJCAAoCQgAKR0JFLz8h4ay08J65mWMq4Yuc2bt+avCeuKLwlqukIvCen63qmbzwn5W0LjHCpeC2gWrwnrmZefCRnJfwm4mz77+c6qiE77+9CgtCCSIoM2jwn5W0fQoUQhI7P+CpnuGnlOCosyInJPCdkq8KKUIn8JGxqjfIuns68J+VtCXwnbymw5tqJj1TJdy0PMKl4KG78JORjGciCgpCCOGhtfCdlJhyCgJCAAobQhlRP++/vSXCpSVv4ragSDxcJfCRj6Lhir09Ci1CKz3wlqya4aSwezPhjIbitqPwkJ6XVfCeuYfwkZGhTUNzJk3vv706yLp7w5EKGUIX8JuEsiLCpeCsifCfiaLwn5W08JariXgKWLoBVQoQCgNrZXkSCUIHwqwx8JGvsgpBCgN2YWwSOroBNwo1CgRGbGF0Ei1CKycu6qOZPGBeaW/wnrqJL0x9WFLwkbGr4KefP2Ml8J+VtPCSkpY68JC6kzUK2AS6AdQECk0KA2tleRJGQkTwnZSuQtGow4XgqbNFw47hqoPwkJa8YE7vv60i8JCTuSR6S/CToIjwkbSOPfCflbQn8JCElPCfoq3DtNGocD/hoZIvWwqCBAoDdmFsEvoDugH2AwrzAwoGU3FsU2V0EugDsgHkAwoaQhjita/wn6uDfe+4v+G9lzM/Lzzqr7HjiJoKR0JF8JC1rj978JGklmRg4rSn8JG0uvCQk4kiTnJz8J+Bv8KlL++/vSfwnri0wrfvv73wn4Sa6p++PeqfkSNc4KejP1wn4KyQChdCFUDihZPguZPCpTbwnLyzP2DDkyUtNQoDQgE8CjJCMPCRj5XwnrmJw5rit5FJRT06762DZe+/vfCeuZ3wkYyPL+C6hDxrOPCWq5rwka+TOgo9QjvwkIC88JSXgibDnOCzgmovJfCflbRBR8Ot8JGNszwuTD3itoAhJvCQgYYmP/CdlYrRqCVm8J65qeOEkAojQiHCpT0zYPCdvKbqqa7wlr6SRfCdqqnIulzwmr+xwqXgsosKE0IR8JCApSZyXCRh4KuL8JC6g0sKP0I94LakSSfwnZS8KlzwkKiX8JCtqPCehKkx77e78JOJjfCQuqtDS+qfkz/wn5W0JmDgp5fgrZ3wkKenKuC0hgo8Qjp76qiQ4LueOsKp8J+gijp74oCH8JCwqzvhoJk64aqhJvCehY8kIlzwnYWKZfCRvZg94b+DPPCepYI4CjNCMSJ1PMKszoouMF8u77+9eztd8JCKikRqw6Zu0aguaVxJIuCnh+CvkMKl8JCKgi5MYFYKN7oBNAoVCgNrZXkSDkIMXHAieHLguYTwkY+FChsKA3ZhbBIUugERCg8KBEZsYXQSB0IFM/CRjLIKQ7oBQAoUCgNrZXkSDUIL8JCrpnHwn5W0aCoKKAoDdmFsEiG6AR4KHAoERmxhdBIUQhLwkYqb8JGKiPCRgozvv73ZvicKVroBUwo0CgNrZXkSLUIr4K+HOTxCLj9gPEdTIPCdkrtp4YuQ0ag2Ry5vTSXhi7zgs7HwkbWCSSYqPQobCgN2YWwSFLoBEQoPCgRGbGF0EgdCBW5XwqU7CpkTugGVEwolCgNrZXkSHkIcI2DqrJPvt4JgLsO54YyTIuGppTrgrLVW8J2NsQrrEgoDdmFsEuMSugHfEgrcEgoGU3FsU2V0EtESsgHNEgopQifhv7LvrJYuXPCRspQmIvCYtIB9Uu+/vT8i8J66reCsryJF77+Fe2kKLUIraV9xIvCRpqBgPSQ64bC+8JGXkFQ6MOCmrOCyniQuT+K2vuK2ojol8JG1lAoNQgvgrL0kYEpi4KCSUQoYQhbwkLCS4Lez8JGOjnXwkYqI8JCirdGoCgZCBCfIukEKLkIscjvgqoEu8JGMh3fvrYFcP+C3tCYh6qyV8J+GreCqmPCflbQqKuCmjyfOmSoKA0IBJwobQhlgL9GoNeCtjH3vv6I68JGpujrwn5W04LKPCjtCOeGthPCRiIpmciEq8JG7syXho608w6jwkaWF8J66rU5Ww4XwkY6+8J6fosO577iZ8JCguPCdjbE8XAo4QjZhXCXgt6t6w5x7Qj/Cpe+/lvCflbR6POCxmuOIie+5s/CRjJ/RqCdew7DwnZKeJj/CpfCRmKQKH0Idw5d48J+JoWBv8JCjtCVmK0h78JOInyXDlsOf1okKBkIE8JG1qAo9Qjvwn6miLsKlRV7itrtG4LaB8J64juCrhWUiLu+/vTvwkr6Ze3xebDzwkouQY1DIukRc8Ja/pDxh8J+rsgoSQhDgqY3wnoCC762EfvCdqooqChBCDsi60ag98J65tWBOW8O5CjVCMyVlKSXCoV498JG2lvCeupQ677+9JSo8OEbwkIqL4K6OczJoR/CQp6oiyLp3MU06TOGdrgoxQi9TQsi676+C4KGg8J+VtEV70ajOjNGo8J6KomQuJ++/vfCQqJDvv71vI/CQspkqXApKQkhU8JCjtTzihL7wn5W0P+Cut+KEuuCrkMKlP/Cdkrs9PcKle+K0gS7wn5W08J2UvuCug/CflbTwnoKPw6HIuvCRvKhc0ajgqZ4KAkIACjxCOjPhjLTwlIisXHsuRPCYtIHgo5tcIiw977+9L/CQnoIlIj/wnZKfJPCQq7Vg4oKQbPCfooTvv70wyLoKOkI48JuygfCRjLbIuvCegKrCoG9V8JGatOGnlyJ7PPCepLLwkJSHJ0o+OvCfgrQq8JGBpy9TPfCflbQKAkIACjtCOSYr8JGNl/CQp5bwkZOXVSdc4LyM8JCqie+/vfCehY49Iu+/vSUo8J6Ap+Cru/CRq7dBXPCfq7BRJAo2QjQq4YKWwqU9PfCflbQq8J6Cj/CRlpQ9Je+2sfCRiLBG44SkLSLguq5eJ+Cng/CRr7Rcc1pSCjVCM3fwkKSLJ+2fsMKl8JCqmyUvNWBqXOCrou+/vfCQoLxs8JGqhPCRjIzCoizgsYwl8J6LvwoNQgtT4rSn4aqJOuGsowodQhtc8J+pu0XwkY2yInTwkpGudsKpJj7vv70/w70KGEIW8JGMj+Cvi/Cdkp50aVzwsZyU77CHXAoTQhHhjYPgp5zwkJ6zIlXIujo/Iwo3QjUv8JuxtvCeuqFHZeGqk2LwkJW38J+VtOK2snLgq4dUN2F74b2K8JG7svCRpLhryLo88J+VtApBQj/qn5Ei8J+VtC8leCQ80agqMOCssvCeubnRqNGoOvCegKDwn6ua8J65uSvgrbIvL/CfiJPwn5W08J+VtOGqp2AKIEIePzxWP+G9nT8l4YmcQvCRoKHRqC538JGkrPCRv55jCjdCNSRCayTwkJa3wqXIuijwkZGhw5/vv71K8J6fqfCRjLMlftGoRjNjPG7hoJXwkKO1deC/knlbCidCJS7wkZuJ8J+VtPCflbTgr5c6OkLwkKCrdD0vJ/Cbsp86PfCRl4EKHEIaeyI34reWMi4vOtGo8JCdjizwm7KAYOCtnVcKDUIL4Z2oIybwkI+CJH4KMEIuZEUuK8i64aqq8Jasg8Kl8JGYovCQioPgsqzwkY2x766DXPCQrpok8JG1g++puQolQiPvv70j4b+36pKZL/CQv7E90ajhv5Hgs6fwkI65YNGo8J+VtAoeQhwnJ0NT4ZyOTfCflbQmR2/qrIVF8JGkt0PwkKC4CjRCMvCQoL/wkK2eTT1aLsKl8Ja/oSRn8J+uryTwnri74KiJ4q29W++/vTLDpCfRqCbwnZK7ChpCGHsm77+9P04t4LKuMS5Y8J+VtC8p8JGkgQocQhpnKvCRhIQ2PS0nTeCrkPCfn7Dwn6u08JGkrwozQjHwn5uj8J+VtEEifCTzoISj6qiI76uB8J+JiGLIui8qQ14vwqXwkKSEzog6L0Ni4bG7ChJCEPCRjI89wrrhoIDhvZ1bwqUKAkIACglCB31aR/CdkrsKOkI4fMKpyLpM8JCgmeGpv3sn8J6EsPCdmbvwkJ6T8J64omDwnp+u4Kef8JGnoeC6hPCfoIlHXOC7n20KIkIg77+94L6QJiRkd+CuqljwnrmS8JCggjo94LWt77+9b10KDUILQeCvl8i6J0bRqCMKPkI8J+OCnfCRtpDwkJKp06Mu8JCVuVY7ZTrgqLNR8J+VtOG/lj9OQycv4Ke3J+qejPCflbTgqYI5wqXwn4mICjlCN3vwlqmp8JGkt+K9piRs4rqQPHvwkaeicvCutoJzb1E8L/CQtKLCvk7IuiYv4rStXD9lNyR8JyYKGkIYL+C+rfCRrIbvv70lPHvwkaO/4Ky1Vy86ChdCFe+5gPCflbTvt4/wnrqRJWAuL+CmhQoeQhzhn7dgwrErJz8lVU3wnZWG8JCDgmA/OsKl4LKBCjtCOSThna57L3ov8J+buDzwnrmpe8O3Yy/gsaLIuj/grorgtYgvYDzhoLPgpojCqeGLkjHvv71aRuCupAo1QjMn0ajwmr++8JCVvTxWL+K3jGA6L8OibcKlKkzgt6jwnrin4LGH8J+VtPCRkrk+OvCbgJgKAkIACjhCNn3wkaS/8JCwoeGlsHsvyLrCu1Xwnrqj8J+VtPCUmKnhgaEpJPCQl7An8J64u+GDjTxWLjonbgoLQgk68J64gmA6WjYKD0INWfCeubRGS2okezYkLQoOQgwkJ+GJmCJjSe+/vTwKOEI2JvCQqI7wn6qBwqXDgVZ78JCkrMOsZcOuLUIlM9Go8JCOjsOn4YmR8JCblmLgqYfqn5M84LKgChJCEPCfnbR18J6Lk+ChuWDit4kKPEI6KeqflmDwkYKN77+98JC0nic38JGNjUDgqoLwkbWkTDxaIvCcvLLCpSZzTOCtszE/8J65izx78JGvtgoPQg1Xw5894KatXPCRjYx+ClC6AU0KHAoDa2V5EhVCE05r8JG8lC900agmJCfwkKCD0agKLQoDdmFsEia6ASMKIQoERmxhdBIZQhfwn5W0wqXwnri58J6fpcKld/CRjYF7RArIDLoBxAwKIgoDa2V5EhtCGSLhvIjRqO+/vfCQlrfhnIVH4pCdLvCQrJ4KnQwKA3ZhbBKVDLoBkQwKjgwKBlNxbFNldBKDDLIB/wsKDkIM8JOPhuC/mfCTga9JCghCBj/gs5UkPQoxQi95KsKlJci6dixgJeG8mTzjgZ9gbiUyeOCviFkkQPCflbTDq3Dhv7NJ6qSH4b6FLwo4QjY66qa14KyqUCbgrbUn8JuxqjUwP96mPCU9wqUm4KGxavCflbRQPOG9nfCeuqEkRFPwkY2wbVwKQEI+J+CxonvhvrvwkaeLJCLwkbWGTyLwkJ6hefCepZXwn5W08Kynj8KlZ3vjgp7gupnwkKCI77+94LCm8J+itlUKLUIrSnU8ZeK1r2V2w5zIuiYleuKCm+CosuC9mD3vpJ9g8J+VtMOYWvCRtIN9JQoYQhZs4K2zJ/CQvIbwkaekNz17Y+GegngkCixCKuCvi+G/sjVTQzrgu5zwn5uwMsKn8Ja/o/CQjpTwn5W0IeColl9Z4L2sJAovQi06V+CnoeK3nMKl8JCphGwqWiXwn4Wpdz488JG0iTc677+V8JCEgfCdqp0/0agKCkIIOjIvwqXCpTgKEEIOIuC3pmjwkK2B8J+oj0IKF0IVJvCRjIouLmAq76y78Jq/s8OMLNGoCh9CHfCeu7AiejrwlquE77+94ra+4KqRRuK0meCsiy8nCi9CLXvgrpLvv71KJSdT4LOe4K6I77+94LqELi4mPy7wkbS16qiT4K6f8KOakOC7iQo8Qjrwn5W04raiJvCfq6fhv6rwkICuTS/gqYhge3dCyLrgp5fIuiQo8JGNlzzIus6M8JGLtPCQqLPhpbAhCjtCOSbhv7zwkLuE4LeALWDwkY+I6qyNYCQkNTU04YuZ4L6i8JGNkDU1IvCQhKfwlquwPXkg76uIw7N3NQonQiXCpfCfo4HdkMKlWdGo8JCumk0n8JuFleOEsfCfg44u77+94KaMChRCEu+/oUXhi78uaCbigpnwkICSKgokQiLRqOCnn+CviOG8uX0qbuCroi5Q0ajwkJa88J6AhSLwn6mgCkJCQOCuk3xS8JGKjG88OGTRqD3wkY+M8J+bsvCflbTqoIFPyLrwm7KF4b2JNPCQiq5RWEHCpSXwkY6LUS5a4Y+gZTcKB0IFd/CflbQKDkIMJF3RqPCRvY5bwr93CitCKS/wn5W04Z2jJCpR8JGShDwudy8nddGoPeC3iiY9IfCQi68k4rqP4aOlCiRCIkFpJ92bfO+/vSTwnrqMJfCQk4DwkIagSksvICokL8Ojw5cKLkIsKj9JKvCRjZ3wkLqs76yl8JG0g+C6giLgr5fhsYYi4KuxJEDwkaWTKTUxOlwKRkJEKkXDgNGo8JGNiPCel7888J66kfCflbTvtrbcpVMkXCIvJsK1ePCRjJnvv71Uw5/Vj+GKivCflbR9LvCRtaU68JGKglgKCUIHPyLCpS7alQpBQj/IutGo4YmL77+94LuG8J6fpVDwnrq3Ry7vv73gtrPvv73wkaidyLrgqZnwnrqh4oGMOvCWq4bwn5W0I0Pgq68KPUI70aghP/Crnp884by04b2ZLVzwkYSb8JGMrTc08J+VtGDwkKO0IvCdlY0r8J+VtD3DtyNH4oCzJOCouDQKPEI6e/Cbsp/wkKO177+K8J6LvyJ7Yj/wkKazez3vrYAmw5Lvv73wn6K78J6Lsl/hranCvm7wkaWR0oJcJwoWQhThvZUlLz0v8JCnnEBK4YONJeGomgpWQlTwkKSIw4rwnYWb8JGwuvCUlbPwkYarfuK2viI/8JC6sfCflbTigZ3Dv8KlPeqssfCRjIsiPMKl8JCEkPCRvKUneMKl8JCVvPCRiIrCvuGPvPCepZAKG0IZwrBb8JCgiDxN4rauSnjhvJzvv73hpKXIugoKQggkOeejj+C2ggopQic8LWY677+9Ii7CpW10bMORJiYm8JGNkOOIinA38JC+hyLDmPCQpqsKS0JJ8J6jkO+/vTzRqOGmvns+8J65ieGdsuCvl+C3pybqqZjwkI6V8J2GjzrgrbQ58J2EhC9YReG9jcKl8JGIiuCth37wrIukKiZUPwofQh3wkJG9Iu+/veGNiC7wnrmb4qeL4LGCLlPIuuGBngpTugFQCicKA2tleRIgQh7CpS4n0ahcPMOXYOC2gu+3jyTwnZWEXkFNXjkmw6wKJQoDdmFsEh66ARsKGQoERmxhdBIRQg/wkZyHYCUy8J6An/CQqJ8KWboBVgofCgNrZXkSGEIWKvCSv58/PMi6evCeuI3wlqylbuC3igozCgN2YWwSLLoBKQonCgRGbGF0Eh9CHSThi7tFJ/CflbRAyLpO4KyCNsKl77+98JCpkVc9CoUBugGBAQoeCgNrZXkSF0IVJS57Ok3gr5c6SeCmrMOZ8J2Mq8i6Cl8KA3ZhbBJYugFVClMKBlNxbFNldBJJsgFGCjlCN8K/VT1sd8i62rLvv70qJO+slynwkIOQ4LGiJyXCp0DRqOGcqj3gsLDwkKiVJfCxrqHhsL83yLoKCUIHWj/wnZK+KgqHELoBgxAKHQoDa2V5EhZCFPCRkZ7go718PXUvM+CvrPCQtZQmCuEPCgN2YWwS2Q+6AdUPCtIPCgZTcWxTZXQSxw+yAcMPCiNCIT1sYDQgPcOa4Kq54aSh8J67sDxOOuGwvuCthyrwkI+CXAoQQg7qoYE94Ki4XCcl76y+OgoWQhQmP++/vfCdi6pfKuGDjfCYjLYuWgoYQhbwkYet8JCVnuGKjCfwn5W0yLrwlr+jCiNCIXfwn5W0RvCflbRH4LuESvCQgIXhqoJ7w6QidNGoJENvMAovQi3CpT8me33wnYOEJvCRsbbgtYjCseCzsTpPJ0AiSuCpizoq8JGMgyd7Z/CfoYMKNkI08JGkofCRtpF7TyRb0ajhvZLvv6khKEBgeMKl8JC1sTpHS+C3nfCRjZfhnabwnrmUPOquogozQjEkYU4lYMOh4KiBJsKl4Y2p4ZiGOPCbhaYlby/wkKmB8JGDgEzigr89KyLhv4xzQDwnChtCGfCQspvvt48mIsKl4LK5Qi4lMMON8JGIh0EKAkIAClVCU++/vSZ38JG0iOG+vlzit57gsYzIuvCav77hj4Hwn5W0YMi6w5fhjobigoTwm7KTNvCQsq/Dm0bhir7wkIC94aSo8J6ApvCRh6jwn4mRKPCbhZUrCgZCBOCvsXMKM0Ixe++/glTDo3hg8JCVuFzwkZmgMT1k4ratOu+/vSLgr4cu8J+hmfCQhptzVsi68JCXlgoCQgAKCkIIyLpK3ZfDhDoKDkIMReGkji8qOuCsoyV7Ch1CG8KlyLo9bmtMyLrio5vwkbC5J/CQvrIuJ2UzLgouQix777+9ImDDkHg8d1vwm7Kd6qa877+9Yy0qTSo3w5d7a/CQvZYiNSnvv706PAouQiwkYSptXHzwlrmeb+CosvCQrpBgN/CQoq8977+T8J65i9Go8JGkjuCxhngkOwoGQgQgRktpCghCBiTRqNuAJgodQhsi8JCrtPCRjLLCunbqqpFx4K+qOlU64LqBJSkKBEICJDwKHEIaLyXqrINH8JChq8OOXCY8OEFJwqUkVCLvrLkKQ0JB8JCoui8jLvCflbQvSeCrjfCRqaQnwqXwnZO0KEXgqpBG8JatmWYq4K+A77+9OuKCkeGfuPCQgKTwkKugJ/CfooUKHEIaPm/DmS5BZTwi8J65oSE/J2Q24K+QLfCfhqUKS0JJ0ajwnaGQ8JGRoSThvKXgpqfgq5Dgsrbgp5fwkI2s0ajwkZGJJ/CWvpVSRvCeuKckWCpBJ/CbhZE/XEN38JaqlPCYpZrqqZfDvgotQivhi4DwkbS6S+GLmirwnZWze/CdlI8m4raowqXwkJaUL3LwkausyLrIutGoCkNCQeCzh/CQo7Qk8Ja/sWB54Lqy8JCAguCpsPCWqbzwkbaBwqXvv5MmbXPgr4HqrKk/d1AuaCcnIu+/veGMqS/gqLVcCkRCQuG/tHtM4KmeM9Go8JCglXjwkZapUPCego/wkpCm06chJ/CQgIDwnL6owqV0yLrgrZbgq4haIDJi6KeRe+GnsOG/mwoJQgdvKj3wsbuaChZCFNGo4LqJJvCRm5Hwm4WSNCnRqEQqCiJCIGA9QuCosjbwkKC8JyU84K6Z4KuL8JGwhCdOKS8kL08mCg1CC+CqkGUq4Ki5LsKlCi1CK2BMIu+/vdGo4LOI8Ja6heGkpvCRh7Fw4KiI8J65iSJcLuKjtOGzpPCdiK0KLUIrLnA74K6p8JG0gfCeuLcmZfCRjLMlJyfwnoqsJi8z77+cOcKl4aWV8JG2kwoRQg8nwqU38J2Tpj/wnrmXZFwKD0IN4aK94b2JTvCRpIMucwoOQgxH8JGRn9Go4KGeyLoKCkII8JGMtuCqkTsKCUIH4ZOgYiZpPQo1QjM/8J2Su/CThZ4lW0d34Ke74L6B8JCns++/vSvvv710b0vhnIsvfPCfrqAqYUAkOjzgsZ0KJ0Il8J6fri/wkbah8JatnuGJmMKlJz3CpTPDm0PwkZujKlwmw4Ldgwo4QjZc8JGPmPCvooPwnoSzOj3gu4bwmKGhTeGJi/CfiYIs8J+hlDvDjyTgqZHwkLSOZiAuY2Pigo4KLUIr8JGPl8KlJ23RqPCRpJBxYNWP4La6RsOodvCSvqQ08JCAnipAJCrDseCmtwoFQgPqoKYKJkIkJfCeubRc4Yq1wqU977+9P37bqcKlKiQuJ8i60ajqrKDgqLMmCjxCOvCflbTwnZWG8JC7guC3qvCWq6YmPe+/vfCehY4nPVzCue+/vSVc4aigZsO1Ok3wkK6a77+9J1jvv70KQUI/wqV4bTzgu4Y78JCohsKw8J+VtC4m8J+VtPCQhKfDp+CouTt0YfCRmZjgtonwkKOv8JC/sD3wn4mI4aWx4bOkCiZCJC7wkaSrUiY/UfCRtZfguoTjgIFv4KC6VfCflbQv4YuCe+Gqugo2QjTCpcK+XDvvv70s4KqVYFF7Pzo+8J+VtG/CpeChk24mJXUnJsOL8J66jXrvv73hqpDwkYyBCg5CDNGoP/CRpInRqMKlPQoaQhjgprJ94KuQXEBcPMOIPlzgqIJEQ/CThrkKHEIa8JK/mfCfiaEqUic08JKQjOKutyJ1VOCrkE4KLUIrL/CdlK884KS2P1cm8J+isPCfq7JY8J65ieCulT8h8JOGpPCRtaAm8JGPoQoxQi8lNjw6P/CeuLvigJ1Rez08JvCfgqtc0ajwkJ2gRS9cLu+/vU3wnoGQyLrgrqpZJQqJAboBhQEKMAoDa2V5EilCJ3vIuu+/vX4kQOCmqC4mRyLCpd2A8JGMr2V2Lk7RqPCRj4I38J64pwpRCgN2YWwSSroBRwpFCgRGbGF0Ej1CO+GDkPCflbQ90agq8J+fsMOE4YOHez86JPCeuKfCpVnwkKSLLvCeuIpb8J+VtFMleUHwkKKvVz/gsYczCuUEugHhBAo/CgNrZXkSOEI2YuGKiyRc77+P8JGvuXt7XSLwkYikL2DRqGDwnqWVPe+/mnXRqOOCt+GLgvCdkIEiwqXOjjo/Cp0ECgN2YWwSlQS6AZEECo4ECgZTcWxTZXQSgwSyAf8DCiJCICcuMntc8J+VtPCSkJDvv70t8JGNly4777+9yLrhoodHCihCJltcJvCehYcqKlnwlquxIfCRjotkPeGqtWVV8JG1kj9cPDJc4aGEChRCEk3hsbY677+9XNGo8J64ouqgswolQiNiUOC0sD3RqDzwkY2MfOqsldGo8J+EsnIv8J6lkPCflbQqewpEQkJ18J65vuC2qz7qrK5B8J+VtOGJmOC6hC5VJPCQi6Lvv73WlfCSvqfwnbyW8J+VtEA68J2VhigiwrXCoOCxo/CQtZ8KQkJA44Cj4LOi8JCWlfCRrIbit5BJ8J+VtHQm4KqR8J66meG/mvCbsJrgsrfgu5/wkZya8JGMgj/hnadfLz8vSeqSvgolQiNcL8OsyLrgrL/grZwmVWLgvbjhvYnwkKC3JVomQzhg6quccwoUQhLwn5W0IlIu8JGPovCQjpHhirsKDEIK77+9e8Oi762xYwoCQgAKE0IR8K65iDo3ey9U4Y+68JColj0KOUI3XOCggyLgrZ0mL/CflbQjLsi6JXPwnoWO8J+elsKlfFQn8J66lfCegKPCpVVcJCZ74LaR4rmUQwpJQkfDiDwl8JCsv/CRrIgqS3vwmKWE4bKC4K6cJPCQk7bgr4bwkYGz8JCWlOCss+Covjpb76yU8JGPiCd1W+Chnirvv6g/yLomcQp4ugF1CkAKA2tleRI5Qjci8JGgkeGnmCTwkYuY4YmY76y1fC4z4bCZOjwk4YuFT/CQnrpgfkQmVTXwnZWPXCpB8JGMv8KlCjEKA3ZhbBIqugEnCiUKBEZsYXQSHUIbYPCQlIrhgawx8Jarkz9gNsOM8JC6sfCfq7E6Cl+6AVwKKQoDa2V5EiJCIPCQk6FD77mx76S34KmeQPCeuqXwkL+o8JC8uSTwkYOxCi8KA3ZhbBIougElCiMKBEZsYXQSG0IZO/CRjosqY/Ceu7FXyLokZ3tjOXPwlq2VLwqWBLoBkgQKCgoDa2V5EgNCAWIKgwQKA3ZhbBL7A7oB9wMK9AMKBlNxbFNldBLpA7IB5QMKKUInfdmB8J2LrDfwnaisJsi6JjbDuX5g17Mle8OpJS7hr5TgsrLwlqK8CihCJifRqPCQjojDr+Chni9AcSdP8J+VtPCdkrl3K1w9YOGnnybvtp86CiRCIjbvv71rLycnIuCtofCbspPwkbaVwqXitqvqrY494LaNQWoKOkI48JC8nOCvkETwkZmlLvCfjojwnoWJ8JGDnCTgsYcs8JaElWbwm7KdJz/itJzqoJ/vuYjwn6KzwqUKCEIGKPCRjog8ChZCFOCvl8i6fvCbi6gvPu+/vTx5e1AwCjhCNkDwkY2Ne+CrvvCQhqDwnZK78JCgiOCxmVxj4KqiP/CesbRBMFwqeyU/YD8hOsi6Je+/veG/swoZQhfwnriCJj/wkIGYJC890ah6fsOUPFbDjgovQi1SQOCznj4qyLpU8J2UkCPCpU4p8JGMueqsivCeuLnRqOKBkvCfq6AueC9gRiUKS0JJwqrwn6Cb8JuwoEviupPwn5W044CeeyHiuoTwnriTXNGo8JGnnlk8a++/vcKl8J+VtGXhv6wwLfCRvL/vv6EkYOGltPCflbQxLwoKQgjOlPCQtb8+dAorQiknO/CRj4Im4Yq6TGDwkJKhPSUnYGBoPTpQ8J6FhmAqWS/wkYqI4LuGPAqXAroBkwIKOAoDa2V5EjFCL/CQrqzgqLzDlDziroFkYCLDh1HwkbS68JGEk/CQs6rwkYCU8J+VtFxwP/CRjrwiCtYBCgN2YWwSzgG6AcoBCscBCgZTcWxTZXQSvAGyAbgBCkBCPkskXPCfqrfwkLqdYvCflbQv6qS74Ky24Y6E8JCdjeCovnLhna/wnrqO4YONPzfgu4vgqLjhvZ3jgJ7wkYy2ChBCDkFM8J2qny/gppBaJ1A5Ch1CG/CRrIg0ZPCdlJnwkYGvXCQ2zb4qRsi68J65ogoOQgxcZ/Cei79syLozwqUKM0IxJTUl8JeDlfCav707yLpL4LezcU87Uy7RqDMyKvCyh75U77+94LGV8JGQunsmJVwvKgpxugFuCgoKA2tleRIDQgEkCmAKA3ZhbBJZugFWClQKBEZsYXQSTEJK4LqGIuGxiPCeuazwlqu08JGOoj9v8JGIk++svsi6KuCoj3fwn4CfLuCtnXsiP/Cbh4An8JCurfCRpLdjfOGgiPCRtL3gp409Kz0K2Be6AdQXCkcKA2tleRJAQj5fJ2jguongs57tn4LwkYiZyLpc8J6AmFBsIsKlalXwkJ2n4K6pJibwnaqeP1BKaeK3lfCQpppl8JGkvyQqZgqIFwoDdmFsEoAXugH8Fgr5FgoGU3FsU2V0Eu4WsgHqFgo1QjPgr4tt0agvfV9R762BRz8l8J6KnVUqwqUh4LuOfNGoe++/veCjtj8g8JCMkUwmJ/Cdi4UKL0ItLyo60agmLzwzOtGo4Z2z4LGWyLrCs3hL8JGcsCTDifCfooA6JTon8JCRrSYiCjhCNlzwnrSEJlxlXPCQjLHhv5I/8J+Cozw98JCgiPCQoYTCpfCvobDwkYyz0ajwkYiJ0ajitqzCvgoTQhFT3Khl8JC7guKClMOAae+/vQpDQkHvt4/gt6rwkbyq8JK+tfCQlo9H4Ky4w511SDwi8JCGoPCdkrtyRWQ6fCVc4La96pOzPfCRj6HhvZvwkaSWXOG8mQoiQiDgsZlU4YONKS43XDQvIvCeuY9lYvCQp59LJSbCpTxzIgpKQkhoWGA/XC9gMSjCsvCQra7wnrqiVF8q4KCU8J6fqPCQoIhH8JGrnFjwmr++deCzlVThqpXwkJaH8J66hT/wnY2T8J2GtPCQubgKIUIf8J+hpjLvv73qoLI944S1XifwkJOH4KmtMsOM8JCiqgpCQkDWjTzqqZZgJeCyjvCdnLbwkKO9JCQ84LqB8J+VtMKlw5nvv70n4oSGe/CehJ8/cCY64KuQeiLwkLuDP/CQgJdIChpCGGXOiPCeuqFgOSpj4KGe8J60pOqvlntUPwoOQgw68JCAgHtV0ahkQEwKLkIsNjdB8JGKl1vgq4jwkYWwL2LhoJN6IiJeMvCQoLw9JTXwkbG3WfCRj4rhjJUKCUIH6qGCOMKpYQokQiI1OiZOP++tg/CbhLLIuvCdkLhNOjrDtOqrqOCvlyTwn5W0CjJCMPCeuqcuRD86a2LhjrHwnqSaRPCfqbF8efCRtJfIuvCRjIvwnZKI4LOdXHvwn4e5PwoKQghEJfCQgJMlJAoCQgAKG0IZJu+uh9Go4bKlwqXwn6u1w5BgLnvhpo5feQozQjHRqPCeuYLgsKpl4aqZLuC4oCbwkY2QLiLCtfCQoLjgp4Tgt5w48J2UqvCbhZHwkJ2OCjRCMipg4rWCdPCumYhc8JCBhfCfiZF2JjnilZPwn5W0TWvgs5Umwr9jZ/CQrq7wnYSQyLovCitCKSbXsCXDksOb8J65jjzgtqVJ4LKtL/CQv6Y5KPCRipVoOcKl4Z6z4aSyCg5CDPCen6FgPW5U8J6TmgoIQgbOny4oVD0KOUI317B78JGkheCmuWHigJVK4rGnOnlv6qyV8JCdjPCQnZJlRlE8avCfiZEvPz/CpOCqg3jwkbaRXAobQhlWYPCSkbLwkIGEa/CRlr7qnZfwkIGQPcKgChlCF/CQhptS4Ku5ZjbwsZu9ciLRqMOOTk85CjdCNci6w7zwlq2UTzfwkY2Q77+98J+VtF8vYkl18J+qiSIiOFTwkbSgL3vgoofwlqmk151CaMKlCjRCMvCWo6B24K+LJzjwkLmo4LGd8JGcv3XRqF3vv71WJMO88J+VtFxqLuCtquCngsOo77+9CjpCOOCziFzwkoWhYHxF4La48JiIlvCRjZcvJWAk4aSiLvCQl5wt77+9XFzwlq2/POCtiFDwkaCnISd5CiFCH/CflbTwkbaQPnt04b+zX+GciuGKoyRD8J6fpH3gs4YKMEIu4LGBInxX34glJNGoKiUvJ+qvtSMlYfCRmarqpaXvv71g8JGXlmY/J+CwvcKlcAoXQhXwlqC8yLrdqsO3bHIn77+9JOG9nWEKNkI0KkPgqIrRqPCRsJNO8JaujCLvv70qV+CnjvCQlp164KmZJ/CRmoRv1bTwkKC8wqV2YyY8Igo3QjVswql26qihdCXhv7RtKPCflbQ/euC2u/CfnIzwkKGZYPCYoIhkJ1wmKsKnY/CQgZjCpTp7YAolQiPwn5W0PEPwnYC3VuC7gSZN4q6Aw45O8J65ufCRhZzwnoWPPwo4QjYuJjTvtrovI/CRpYQ/8J+VtDAm8JGnnV578J+isz14IvCQh5XwkZqiPS3gsafgp6B9JTXgp4cKQkJA77+9JOqfkz0u8JC6o+CviMKq8JGcly88PCzwnZWGaHfIusi6Xi866qelPfCTkZPWjs6JUPCepLxl8Jargu+/vQo0QjLIuj3wkbWlLj3wkK26XOCpkfCRhK9cJPCQsJIl4Ku7P+CytVDwkLWSJC7vv73IuuqvtApGQkQ26qyKJ/CfiZHwn6ir0ajwkYqj4K6QPD98ZPCWubRcwq49e/CtlLfwkYOkSHvhvJjwkJa78J+MueC7huOAuvCQhqA/RQoNQgvqp4fhvZllZOqflgoRQg8oL/CRkInjiIM+w6sqKjIKLUIryLrwmK6K8JGNg+C4p+G9m/CflbRb4LG5JSJV8JColWXwnqS+8J+JgDw6LwohQh/wnrmXWOqfmGA0dic9e+qsjPCQp57vtq1oT0N2e8i6Ci9CLfCRpInwkJazzoTwkYyy3Inwn6Cn8J+vrirgvqfwkKCIPCQ/8JC+t04/8JC5oAoYQhbgtInwlr+xLUnwkbKU44ObfvCflbRfCgdCBWrwn4OJCkNCQSRx8J6kjiXwnZSjMvCQoox74YuDZc29W2YnP+C7mPCQjqHCu2BFYH3hn4fwkL2O8J+hufCQv6dH8JCdky7gqLggCiJCICLwn5W04oOh8JCNiS/wkY+A4KGe8LGIqPCQoaA/JCpACjlCN1Dqk4HwnrmdJi/wn5W0IlzwnruwPeC6nPCYrZ5g4LC1YDJZ4bGG8J+JgvCeuZnwnrmU4KegyLoKFkIUe8KsLOCtiC514Yma8J+VtCdgXUYKE0IR8Juwv1Z7cD9a4rWBLuqbkz8KFUITe/Ceua7wkJa2XFrwnZSB4K6PJwoQQg7wnZSHU1kkzbwl44KLIgoHQgUlcuGLkQpIQkbRqCQl8J6AoO+/ofCfgIngtbjwkYqfwqzgso7grZ9cM/CRmp5ESldgJPCQlpQsJuCsj/CRj4LwkJSn8JCUgiciWvCbsLMxCjpCOHI8eG/gqZEmOfCeuZs7YyrwkaS/YMO48J+YosKlUT1u8J2SnvCRh4hSI+qpjNGocDQ8S8O1O8i6Ch1CG1hs8J2Upy9hJ/CRr7RcJeqqo/CdvJXhpKJ1bgoTQhFAJ2A60ajgsZ0qwqXwkbGDWgoEQgI6YAojQiHwmr++16BLIsOD44C14rSt8JGWoGQ/X/CQl7LwnqKian0KKUIn77mz7Z638J6XnfCQlJTDqGzwkKeF4LGjw6Tvv4zwkIC8T3jWrSdXCgJCAAo+Qjw38J+JhkHwkaSsXC4/8J2DplpyPPCSkbF9ZHHwka+UJfCutpvDnTrDrlXhoLs6RyxS8JCOheCoifCbhZUKFUITUPCfqbLwmK2q8J2SuyIp0agiPAo7Qjngq4ArcPCRvZnIuvCdi7HIuuCok/CflbTitrQ68J2NtvCRiopA4reIwqVGJ/Cfm6hB0ajhpoU/LzwKREJCUnvwkKO14b2bV/CRjYPwkbS04LeE4LOyXHYk4raiyLrwn4K9JPCRjaNh4KysTvCSvrwt8JGBqeCsrPCQsYYu4KyqCgtCCdGoP/CRpYBGXAowQi4n77+EXOC0kMKlyLouNuGLgsOd6qqK4KmsIiZAfeGdrvCRsIUvVSR88JGqnlw5Cj5CPO+slvCRjLg98JGPhSbwkIyxJGDjgq7wn66qRSp8N9Go8J+VtCZ58JCKlPCRjqx78JC0oyfgr5dEXitXLgoLQglSLsOU8J+VtDwKPEI6KT858JC9j/CRtaTwkLS14Z2yZuK1r/CRtLzwm4mI0ahw76y7MVI+4oWLTlxg0ag88J+VtO+sleG9gwo7Qjl74LWuJ+Gxo/CflbRremZ3XC864LeK8JCorOC6gcOX8J6fq/CbsbTDrn148J+VtMi6OEfwnrmJXC4KDUILKz0n8JCng0U/cnUKBkIE8J2LrQq0EboBsBEKGgoDa2V5EhNCEfCdkqJx0ahabO+tgS7DsyRwCpERCgN2YWwSiRG6AYURCoIRCgZTcWxTZXQS9xCyAfMQCjZCNPCfg4Ym4LGN8JC6rVR78J+DjcONe3lsXuCrkF0zdCTwkYKWYvCdnIjgtZbwkayH8JatvToKJEIi76yV4LCP6qmV6qGI8JCPiz8l4K6C8JCTu+KlteCph9GoPAoTQhHRqGvhpIjwkJOm0ahpRzw/JwopQicnIihofnrigpfDknvIuiQ2yLon4KiCPPCRtYNeanjIusi6JPCQlI0KHkIcPzrCpXsvYCpx4K2cReCunPCeuLngro7wnrm6YAobQhkuYEgk8JuBkvCRu63wlr+k15NBMvCflbQlCkFCPzXwn5+kTfCfo4E6UXtYIlXOneGJtScx4K+X8JGMk+C3rHvCpVHwkJaS8JapqCp4wqVhdiLwnqOOwqPRqO+/vQoeQhzjhagmX/CQq6LqpYVS4LSi4Lql8J6jkmjCtSYjChdCFe+/h8O6NfCfrYlT4aWA4KaqciVzVwo6Qjg14KyBOtGo8JCetWMqIPCQgJBV4LqPJvCdvIfvv73CtsKlVCc6PSQi8JG1oCRoJmritqxg8JC6qQoTQhHgsrfhpKXgrYfwn5W0dyYieAolQiMiJy8nb+K0reG9gfCfqrHwn5W04Y25PVxpL+G9lExx8JCegAoUQhLwnqWQ8J+bsPCRsZxDV8Klw5kKKUIn4b2ZNfCei40uIOG/tE1aPfCRkorwnpetw5J7YPCQpLjguo1qwqVAChhCFvCRqIc18J65vOGxhu+/vSI/4aq4eyIKA0IBKgoLQgkjJvCflbTgt5YKIUIf8JCXjiQ54LeRavCflbTwkKau4KuQwqXgs50n4rqWJAohQh/wlrWQ6q+2MeGLq/CQtbrwkJa8zoV7Lzxt8JCgvCpICg1CC/CfrqLwn5W0YMODCi9CLfCav7An8JCoheGcjC/qmIjCofCflbQiZyfwnrux8JGCviQ576q98JGWsjEvOwo7QjngsqHihods77+9YGDCs3rgtYfgtY3vv708KcKo8JGblmA/w4LIutiaXCdoKkkqyLrwnri5V/CQoq4KN0I18JGnlPCQjLlW77+9PCZLP3Ditr7hv7nwkLSyX+GJjC5KPEHDkk06XOCxlTrDsjrRqGJ7wqYKQEI+PfCQoYwle/CeuKJUfWbwn62P762E8JGGgnJobfCRg4JS4KiuPCRVwqVcMOCuhT/DrcKl77+98LCEqfCQhoEKI0IhOvCfn6Y6wrg6JzpgOsi6JEIo0ajwnrqDeXvgoZjwkJaUCg5CDPCQqIXIuuCoh+GbuAohQh8m4LOd8JC1jfCflbQ+NvCRpInhna49Oi/DvSXwkIWMCjNCMfCflbRmL+G8mD9b8J+vryfhq4MkS+qpojs/4YONP0o94b+04Z2HQjnwn6+ARPCdlJAKHUIbPEjhp5Dgu53gsafwn6CIKvCRoJHRqC4mXMi6Cg5CDHtcYCPgrYc38JCGoAoUQhJMwqVcw5Zcwrdc8J+VtPCQhIIKDkIMJ/CQurAjZuG9nS97Cg9CDSTwnp+uwqXRqPCQgZgKJUIj8JatoWtRJVxSWeCqqmBc77+98J+VtCbwnqWe8JCur/CeuIIKJkIkJyfCpSRJ8JGPh/CRjojwkY2IJ/CdkrN7PWNvIXvIuuCqhSYoChxCGkVgXNGo8JCol1UneiLvrYFFIkRKYOCpkUwqChdCFS7RqD3wkaSJP3BFYPCfqp8i8J6LvwoNQgvwn4KN4aWAXOGOmQoiQiA84Y6P77+98J+VtDzwn6GC4LSaUe+/vfCRj4IjIuCpmwocQhrgrqpywqXwnrmbfvCwsavZl/CRjqTwkbKLVwonQiVP77+g8JGxg33Dm/CvoYngprfRqCHIuuGVumDgv4kkTSTwka+yCj1CO/CQjoprQzw/XPCQo7I88JGMhzbwkKedWjrhvZvCsmAkOipjLyzgqbTwkYyuw4LIuuKAhdGo4KuJ4KGRChZCFG7VqNGo4Y2f8JChgOC2vVLDnSY8ClZCVPCeuI/wnLyGKEPgrajwnYSML2Pvt48m8J6ftuqgtyzwkY+X8J+hpvCfoqHwn5W08JCSsPCbhZLvv6Hgr43vv709YPCQsqck4K208LKDpDwi4K6kVQpCQkDwkaSV8JGkve+/vfCQvr5uefCRiqXwlq2c77iOIvCdi4nvv71N8Ja8jO++uW89PeGJje+/vT1w6p+TKsKlSDFRCh1CG/CeuZ3wn5W0OuG9my9t8JCmviVtMUDIuuqlvAoGQgTwkY+CChFCDyXwkKS/OsOf8J+tm+CpngoSQhDvv6YlJsi6VPCeuKfwn5W0CixCKnvwkIGDYPCQubrio7vgr4ckOmd6RPCRjIkl8JCBlm3wkY+HOXouNuC+iQoWQhThirNJL3vwn5WQaX7hvJwzPeqlvAorQinwnrqYzb7gso8v8JCcqi7wrL6CI+OFvci64LCx4KmcePCeuoB78JGYlwoQQg4iyLrhs6Hwn6qJ0ajDjwoPQg3vv71nL+qrny5uO8i6CgxCCvCflbQm8J2NkycKL0It0ahgL/CflbTCpfCdna4qLi9GzpRL8JG1ujrvrYHivpjvvoAh4b+GbvCflbQkCgRCAiovCjdCNfCRtInwkY+IU/CbhaXgpovwn5+PQTNtJeGdkCztnrkvNOCskHYn8JCEgHvgq6cs77+9zowqCiRCIk0m4YOHSntB4KeiPeGftPCdkp5qMmNB4LecJz/wkbWERy8KIUIfQC3vv71cJmDwkKm977+98JGMh++/vXtv4Lql4ZyyPgoKQggpc9GoYCzWjgpDQkHIuuGLgPCRmJQufSXhpbTCpci6JfCeoKds8JCWlDpBJvCQk7LhkKFR8JCKl1fwkYSXwqXDrO+/vfCbgpfwkLuDQQoKQgjwkIC98JGKkAoEQgI6LgoDQgE/ +CnMKcboBbgobCgNrZXkSFLoBEQoPCgJpZBIJQgdFeHBsYWluCiMKBGtpbmQSG0IZQ29sbGVjdGlvbkNvbXBhY3Rpb25Cb3VuZAoqCgV2YWx1ZRIhugEeChwKCGZyb250aWVyEhDCAQ0KCwFzCCaTmCN1AVCc +CsgBCsUBugHBAQp7CgNrZXkSdLoBcQpACgRuYW1lEjhCNj/hqqPwn5W0az8uJeqpgG8u4K6cw4w8PUdj8J+itSJ2PC864bGY8J6Esz9JbvCfm7tmPFrDqQotCgpyZXBsaWNhX2lkEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwFygWhXlRZCJSZMCiQKBGtpbmQSHEIaUmVwbGljYVN5c3RlbUNvbmZpZ3VyYXRpb24KHAoFdmFsdWUSE7oBEAoOCgV2YWx1ZRIFQgPvv70= +CoMCCoACugH8AQprCgNrZXkSZLoBYQoyCgRuYW1lEipCKD1cJyPwnrik8JGPoXtE4Kef8J+VtHlcJXvwk4GUJ04v4KmeOvCQs4YKKwoKcmVwbGljYV9pZBIdugEaChgKBFVzZXISEMIBDQoLAWlmhjBggDRFSEwKJAoEa2luZBIcQhpSZXBsaWNhU3lzdGVtQ29uZmlndXJhdGlvbgpnCgV2YWx1ZRJeugFbClkKBXZhbHVlElBCTiRLPeCtiHvwkKO18JGWuOKugPCRiqbqlLTwnou/YPCflbTDj2Yn8Ji0gvCflbTwmr++8JGNoPCQsKTqq6DwkZmsUjbit4LCoPCQuqxbVA== +CqECCp4CugGaAgqKAQoDa2V5EoIBugF/CjwKC29iamVjdF9uYW1lEi1CK1zwkL2JPO+nlC5Z8JKBq8OLYOqVivCfgIw676y5ezDigovhqpFs8K64tWQKFQoLb2JqZWN0X3R5cGUSBsIBAwoBnAooCgtzY2hlbWFfbmFtZRIZQhd48J6XsyTwkbKOJ/CQq7Eq4LOhb0w8awoUCgRraW5kEgxCCkdpZE1hcHBpbmcKdQoFdmFsdWUSbLoBaQodCgpjYXRhbG9nX2lkEg/CAQwKCplIJ3KIEIE2OSwKKgoLZmluZ2VycHJpbnQSG0IZyLpg4LCOL2xq4b2dMdGo6pKrLi8/IuGKiAocCglnbG9iYWxfaWQSD8IBDAoKgglyNZMABlMGPA== +Cp8oCpwougGYKAouCgNrZXkSJ7oBJAoiCgJpZBIcugEZChcKBFVzZXISD8IBDAoKhUBhhhFVcANlTAoQCgRraW5kEghCBlNjaGVtYQrTJwoFdmFsdWUSySe6AcUnCiwKC2RhdGFiYXNlX2lkEh26ARoKGAoEVXNlchIQwgENCgsBIXdkcCdyFZUyLAo9CgRuYW1lEjVCM1tp4YCvN++/vWAlwqXwkbKtdPCRtL17IvCbsp4/YcK88JGTkPCRr4fgsZYy4LOV8JGkrgocCgNvaWQSFcIBEgoFA4ADllwQ/v//////////AQoUCghvd25lcl9pZBIIQgZQdWJsaWMKoSYKCnByaXZpbGVnZXMSkiayAY4mCnK6AW8KLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBRWgjFWMCMpBlbAopCgdncmFudGVlEh66ARsKGQoGU3lzdGVtEg/CAQwKCjI4ApIYMychmYwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKjQG6AYkBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKl1kWRxWYdGRmHAoqCgdncmFudGVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwFwAZWSaBVTZAJMCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCidUkoNBB4VyiWwKcLoBbQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCpQDiAdzYFcAl2wKKAoHZ3JhbnRlZRIdugEaChgKBFVzZXISEMIBDQoLAUaFGRg4OVh0ESwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKgQG6AX4KLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBMpQBCIUJQHZXjAoTCgdncmFudGVlEghCBlB1YmxpYwo4CgdncmFudG9yEi26ASoKKAoKUHJlZGVmaW5lZBIawgEXCgoDiAGJMXeVGURcEP///////////wEKhgG6AYIBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKNCBylyNFiBKZfAopCgdncmFudGVlEh66ARsKGQoGU3lzdGVtEg/CAQwKCkeTAmV4J3OAEJwKJwoHZ3JhbnRvchIcugEZChcKBFVzZXISD8IBDAoKFpl5dwCVQ5JQTApwugFtCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAQZHgghAcVmDcFwKJwoHZ3JhbnRlZRIcugEZChcKBFVzZXISD8IBDAoKBFCVYESAIDOBnAoTCgdncmFudG9yEghCBlB1YmxpYwqTAboBjwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBJjiDR3F0cDcgXAo0CgdncmFudGVlEim6ASYKJAoGU3lzdGVtEhrCARcKCghBA3cTQRWHKZwQ////////////AQooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBR5QYBjQhhBlVPApwugFtCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKOJUJVlWBaJMTXAoTCgdncmFudGVlEghCBlB1YmxpYwooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBUXZQEkBzQSdGXAqNAboBiQEKKwoIYWNsX21vZGUSH7oBHAoaCghiaXRmbGFncxIOwgELCgmSlYAhQJdmA2wKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLARMxmAk1cBGYdywKKgoHZ3JhbnRvchIfugEcChoKBlN5c3RlbRIQwgENCgsBRXhnMxASBhgCfAqLAboBhwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBMjcgd2dWMCZinAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgo5J2aQkilEdwR8CicKB2dyYW50b3ISHLoBGQoXCgRVc2VyEg/CAQwKCgUzAHeDGCFQYSwKcroBbwosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCkZCljIQiCOFczwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKKgoHZ3JhbnRvchIfugEcChoKBlN5c3RlbRIQwgENCgsBBAdzcggFVmFhPAqSAboBjgEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBdgglRZACdygYjAo0CgdncmFudGVlEim6ASYKJAoGU3lzdGVtEhrCARcKChJAcGZXAzFzM3wQ////////////AQonCgdncmFudG9yEhy6ARkKFwoEVXNlchIPwgEMCgqWkUCCSXgxIDZ8Cna6AXMKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpzNkFxF5EQFDI8Ci4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwFXNYQ1aTFpVmEcChMKB2dyYW50b3ISCEIGUHVibGljCoYBugGCAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCnGRgxCJUlRkVSwKKQoHZ3JhbnRlZRIeugEbChkKBlN5c3RlbRIPwgEMCgpgYiARc1FUCSdsCicKB2dyYW50b3ISHLoBGQoXCgRVc2VyEg/CAQwKCjhwlQCFRyZHAIwKcboBbgotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEBNBNgM1WDghQ8CigKB2dyYW50ZWUSHboBGgoYCgRVc2VyEhDCAQ0KCwEZE5ECGGBFNFWcChMKB2dyYW50b3ISCEIGUHVibGljCogBugGEAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEVSDgmVgWERiJMCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKdFACMoETeDVxjAooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBgjBylnE1VxGVfAqLAboBhwEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoVlCcXeUiHgyhMCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKCkdmSWCTUpeJI2wKLgoHZ3JhbnRvchIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAVR5eThjVwYkZ5wKiQG6AYUBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAQkmd5cpVHEHRiwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBFnklGZZRmRVJnAooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBJ5aTGCEhByiZHAp2ugFzCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLARB5dWQYB3VBODwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKeSkyNWhmQzVkPApbugFYCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKgkcSJWUhM0EnbAoTCgdncmFudGVlEghCBlB1YmxpYwoTCgdncmFudG9yEghCBlB1YmxpYwqNAboBiQEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgohMoAlIwlhVkVMCi4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwEYiQGHOEMTAJFsCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKaXUhNTEyABkCjAp2ugFzCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKOJckMCVggmNRHAouCgdncmFudGVlEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBgGGZIwkjQGdBXAoTCgdncmFudG9yEghCBlB1YmxpYwpyugFvCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKMlJxhClxJBGZLAoqCgdncmFudGVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEYEAWBOJVzhliMChMKB2dyYW50b3ISCEIGUHVibGljCoYBugGCAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKChkiUQeVaZExl1wKJwoHZ3JhbnRlZRIcugEZChcKBFVzZXISD8IBDAoKdwABWUVlmRIETAopCgdncmFudG9yEh66ARsKGQoGU3lzdGVtEg/CAQwKCiKJcZM0c4cFA0wKkgG6AY4BCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAXYyAkhGQGKCAWwKMgoHZ3JhbnRlZRInugEkCiIKBFVzZXISGsIBFwoKBiIBSJVHEClTnBD///////////8BCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKFoZWRTRYIxaRTAqHAboBgwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBcVUoVpBXl0AUnAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgqCiRNZSTkWZ3UcCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKMEhDcwRTBRgJjAqSAboBjgEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgp3mZGJYwlQUHGcCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAVeWAZKVdik5k4wKMgoHZ3JhbnRvchInugEkCiIKBFVzZXISGsIBFwoKFAdkMAOAiJQ1PBD///////////8BCocBugGDAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwGCZ1KGYQcSgEMcCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKQXVFSBggZEJTLAonCgdncmFudG9yEhy6ARkKFwoEVXNlchIPwgEMCgooUANxRYWXSJeMCnK6AW8KLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBZAVWaQBjZlRAHAopCgdncmFudGVlEh66ARsKGQoGU3lzdGVtEg/CAQwKCpkFZVNncRNkICwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKkgG6AY4BCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLASlmUHUVNmhiYSwKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAYEhYhdYJEOBSRwKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKmWRVmINVkjFjTAqRAboBjQEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoYGUcGKRJGBUdcCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKQyWJgVN4aSERXAoyCgdncmFudG9yEie6ASQKIgoEVXNlchIawgEXCgoEAVhzGTZYU0IcEP///////////wEKlgG6AZIBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLASKIIRYVmIEXYkwKMgoHZ3JhbnRlZRInugEkCiIKBFVzZXISGsIBFwoKF4Z5SXOWkhhTPBD///////////8BCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKChYHkgQXYEcImXwKcLoBbQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKClRmR1N1JIWEUUwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLAXZgQIcCR5UoaDwKcLoBbQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCil5F4h4eYBhRywKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLAQNQdiEWAJdJQSwKkgG6AY4BCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKMjhZViSDOICIbAouCgdncmFudGVlEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBRiZHYwNVATU1PAouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBVQhZUSlXGDlZXAqLAboBhwEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoGWUdjIjmARBBcCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKCjmCBwd3YyEoFWwKLgoHZ3JhbnRvchIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLASkwRxR1JkQTNIwKeroBdwo3CghhY2xfbW9kZRIrugEoCiYKCGJpdGZsYWdzEhrCARcKCgViACY2FRJYdpwQ////////////AQoTCgdncmFudGVlEghCBlB1YmxpYwonCgdncmFudG9yEhy6ARkKFwoEVXNlchIPwgEMCgpSUkg5kiZZCYUc +Cl4KXLoBWQoVCgRraW5kEg1CC1R4bldhbFNoYXJkCkAKBXZhbHVlEje6ATQKMgoFc2hhcmQSKUInJuC+nvCforNt8JGPjzw/PsKl8J64u/CRkZ4v8JCdhkJPfCfwn5W0 +CkUKQ7oBQAoiCgNrZXkSG7oBGAoWCgVzaGFyZBINQgvvv73gqrV7NNGoPQoaCgRraW5kEhJCEFVuZmluYWxpemVkU2hhcmQ= +CmQKYroBXwpBCgNrZXkSOroBNwo1CgVzaGFyZBIsQionLPCflbTvtqA1aUFfYHRSP0M/UmU6UMi6O0A94b+WPC7jiJfgto1xLy4KGgoEa2luZBISQhBVbmZpbmFsaXplZFNoYXJk +CroCCrcCugGzAgqdAQoDa2V5EpUBugGRAQoxCgtvYmplY3RfbmFtZRIiQiAhwqXIulxc4Ka94LeKJz3CpcOIL1zhv7rwn5W0JuCvhgoVCgtvYmplY3RfdHlwZRIGwgEDCgGMCkUKC3NjaGVtYV9uYW1lEjZCNF7Dj3Nna0vDk+GKo/CfoofRqDfRqCXwnYWC8JuyiOCuqOChtFzRqFwuKjnIulw/JvCehYcKFAoEa2luZBIMQgpHaWRNYXBwaW5nCnsKBXZhbHVlEnK6AW8KHQoKY2F0YWxvZ19pZBIPwgEMCgpnghVCcDZpAAWMCi8KC2ZpbmdlcnByaW50EiBCHnrwn6K1PSrhnJ8ieUc08JGNlzQ8JfCQrabqkppWJQodCglnbG9iYWxfaWQSEMIBDQoLAVV2hJmRIFaICCw= +CplpCpZpugGSaQouCgNrZXkSJ7oBJAoiCgJpZBIcugEZChcKBFVzZXISD8IBDAoKmRgnaXlEaEYxHAoQCgRraW5kEghCBlNjaGVtYQrNaAoFdmFsdWUSw2i6Ab9oCi0KC2RhdGFiYXNlX2lkEh66ARsKGQoGU3lzdGVtEg/CAQwKCpVlB2UiJ1QCISwKQgoEbmFtZRI6Qjjgq4lc8J66qfChiIMmOiLgt4NcLu+slCZaUPCQlozwkaamL/CQuqxv6qyNNlMl4K2MzofDlmfDrQoSCgNvaWQSC8IBCAoGA0kFOXdMCioKCG93bmVyX2lkEh66ARsKGQoGU3lzdGVtEg/CAQwKCmF5lAAoGTRWh0wKiWcKCnByaXZpbGVnZXMS+mayAfZmCnW6AXIKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgozgoInBCAHiGJ8Ci0KB2dyYW50ZWUSIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCoAhhCKXQWSAUZwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKiAG6AYQBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAXYVBlJBZGIUZjwKKAoHZ3JhbnRlZRIdugEaChgKBFVzZXISEMIBDQoLASVXBkIhJ0BokUwKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgplNliWlVc5mGlsCpEBugGNAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwGBKWQUmGSGSTR8CigKB2dyYW50ZWUSHboBGgoYCgRVc2VyEhDCAQ0KCwFTY5RDkyAmdoicCjIKB2dyYW50b3ISJ7oBJAoiCgRVc2VyEhrCARcKChJlAZiCOAVQEXwQ////////////AQpbugFYCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKgSkhlnUVMkOVTAoTCgdncmFudGVlEghCBlB1YmxpYwoTCgdncmFudG9yEghCBlB1YmxpYwqIAboBhAEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpEllGJNVQJESeMCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKkFQ5UUARMTAlnAopCgdncmFudG9yEh66ARsKGQoGU3lzdGVtEg/CAQwKChEXGZmTRGmZRXwKmwG6AZcBCjcKCGFjbF9tb2RlEiu6ASgKJgoIYml0ZmxhZ3MSGsIBFwoKEjQ1mZNiVVJBTBD///////////8BCjIKB2dyYW50ZWUSJ7oBJAoiCgRVc2VyEhrCARcKCgFYEIM5RIiEEywQ////////////AQooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBaQgjNwkXMjMnXAqGAboBggEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBSCg2d5kyh0YUXAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgo5EhQHlFUAgHOMCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwFGJDBgJWUwmDicCo4BugGKAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFYJgkSlzZkM4V8CikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKWCc1MUmRdRETjAouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBFhIziWI0NCORbAqKAboBhgEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgqZWAICcWKSCSUcCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAQdpURgSADeTEWwKKgoHZ3JhbnRvchIfugEcChoKBlN5c3RlbRIQwgENCgsBZnBpQGlAZnRwfAqPAboBiwEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoBFkmJODZSIVEcCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKChd1JjCVUYF5YUwKMgoHZ3JhbnRvchInugEkCiIKBFVzZXISGsIBFwoKEUBUEQR5ATBFHBD///////////8BCqYBugGiAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCmElBxV5U2FzQIwKOAoHZ3JhbnRlZRItugEqCigKClByZWRlZmluZWQSGsIBFwoKCEdYlyEmJjQCPBD///////////8BCjgKB2dyYW50b3ISLboBKgooCgpQcmVkZWZpbmVkEhrCARcKCgk1eENJhQREiXwQ////////////AQpzugFwCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLATYEMgAiiDVBYRwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBaQVjUxgllHSCTAoTCgdncmFudG9yEghCBlB1YmxpYwpxugFuCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAUeDmGQmNkFiGHwKKAoHZ3JhbnRlZRIdugEaChgKBFVzZXISEMIBDQoLATRGKFQ2OREgd4wKEwoHZ3JhbnRvchIIQgZQdWJsaWMKhgG6AYIBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKiFERcYABUAcWPAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgqHY0BkEheHWSOcCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKkXQmRSAjYkdpbAqKAboBhgEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoyIIZYGYeUJYk8CicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKCjljRkNRZnJhAVwKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKaGkJlQMTIHhoTApzugFwCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAWVBaEUmhjQSFZwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBcCaTcmRjk0mFLAoTCgdncmFudG9yEghCBlB1YmxpYwqMAboBiAEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBd1QzhjYAGANCHAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpWdBVARwBJIVAcCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwFGVTMzlCUXV5N8Clu6AVgKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoJaZcmgoIjUZacChMKB2dyYW50ZWUSCEIGUHVibGljChMKB2dyYW50b3ISCEIGUHVibGljCnK6AW8KLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBdmgxcXFRdiFwnAopCgdncmFudGVlEh66ARsKGQoGU3lzdGVtEg/CAQwKCgiXZyR5UUCSAzwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKcLoBbQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCnEwNZlWOEATiEwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLAVNAhQRSgwlwIHwKlQG6AZEBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLARlXIjVWkIQIUlwKJgoHZ3JhbnRlZRIbugEYChYKBFVzZXISDsIBCwoJhYOFUklmhxV8CjgKB2dyYW50b3ISLboBKgooCgpQcmVkZWZpbmVkEhrCARcKChKFhxh0BoZAMpwQ////////////AQpvugFsCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKBFCEVykJeZcEnAoTCgdncmFudGVlEghCBlB1YmxpYwonCgdncmFudG9yEhy6ARkKFwoEVXNlchIPwgEMCgpxNWQyRTcEFYAsCogBugGEAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFEeXZVESEyATSMCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLATRAEHB3OZZVCFwKJwoHZ3JhbnRvchIcugEZChcKBFVzZXISD8IBDAoKVxQigDkJRQFnfApvugFsCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKOGhJVocYYZQwnAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgo5ZYmZmAhUZyMsChMKB2dyYW50b3ISCEIGUHVibGljCo0BugGJAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKClZxBicWE3EkYpwKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAVdZaBKWk0JVACwKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgoZYlAzdlZQVoh8CoYBugGCAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCol0mBEHAYJwaDwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBMAQolRFIIkIXjAomCgdncmFudG9yEhu6ARgKFgoEVXNlchIOwgELCgkkNUaEkGYGgkwKdroBcwosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCpKEWXk5ZzEwRBwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKLgoHZ3JhbnRvchIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLARVShZAVBjVoU0wKiwG6AYcBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKNJBJAzkFOBhHXAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgoWKAVwh1WZVSecCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwEwlTSRRzNyiVMcCna6AXMKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpnRQOBUFiGgGV8ChMKB2dyYW50ZWUSCEIGUHVibGljCi4KB2dyYW50b3ISI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwEXWRJJUwKCEDVcCocBugGDAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFYJ0dEiDUIhiRsCigKB2dyYW50ZWUSHboBGgoYCgRVc2VyEhDCAQ0KCwEFcSEIkkczRSl8CigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwFGFjcWBDFkiDIcCoQBugGAAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCkiFIgBRVniBglwKJwoHZ3JhbnRlZRIcugEZChcKBFVzZXISD8IBDAoKdAcpV5BASYIhnAonCgdncmFudG9yEhy6ARkKFwoEVXNlchIPwgEMCgo3OBIXUnYlkjc8CocBugGDAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCkUkBVNJBSEEVhwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBZEmJYTRiR5IyfAonCgdncmFudG9yEhy6ARkKFwoEVXNlchIPwgEMCgoIRHhUF3MDGTV8Cm+6AWwKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgqWYhIyEXOAk5NcCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKCnOHmQQkUUUSNEwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKhgG6AYIBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKEjKURVl0VWU4fAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgpnUImEZxg5NzZMCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKWIU5aBRQhoVHXAqIAboBhAEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoniJeZKYdnloiMCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLATRJQZOAhBeTUkwKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLAYFgZ3ZQBnZXSUwKmQG6AZUBCjcKCGFjbF9tb2RlEiu6ASgKJgoIYml0ZmxhZ3MSGsIBFwoKFwSQOIQ1BiAnLBD///////////8BCi4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwFjQgQgRlA0IpKMCioKB2dyYW50b3ISH7oBHAoaCgZTeXN0ZW0SEMIBDQoLARYphJcTYDgFE2wKhgG6AYIBCjcKCGFjbF9tb2RlEiu6ASgKJgoIYml0ZmxhZ3MSGsIBFwoKFWBEUSVUISaHHBD///////////8BChMKB2dyYW50ZWUSCEIGUHVibGljCjIKB2dyYW50b3ISJ7oBJAoiCgRVc2VyEhrCARcKChMlQlBCaFkmZhwQ////////////AQqLAboBhwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBMBk1NwlVSGgpfAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgqEklZSmFhjF0d8CicKB2dyYW50b3ISHLoBGQoXCgRVc2VyEg/CAQwKCniEAgk3F2GGg5wKlgG6AZIBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLARiDEyUDN3ZpCCwKNwoHZ3JhbnRlZRIsugEpCicKClByZWRlZmluZWQSGcIBFgoJQ3dWeVkQMkdsEP7//////////wEKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLATNDclmYkHVyVBwKjAG6AYgBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAThUhHdHSBgZJ1wKJwoHZ3JhbnRlZRIcugEZChcKBFVzZXISD8IBDAoKlokCGDWHBJGGfAouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBASJgmZcmcgVZbAqQAboBjAEKNwoIYWNsX21vZGUSK7oBKAomCghiaXRmbGFncxIawgEXCgoRUBNId3JzczKcEP///////////wEKJwoHZ3JhbnRlZRIcugEZChcKBFVzZXISD8IBDAoKhZSZkykjAIQIjAooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBMTOGdmNJV2AITAqSAboBjgEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBR1U0RiBxFBZZHAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpFN3I1RWlCdJVcCi4KB2dyYW50b3ISI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwF4M3dXdxQhl5IcCo0BugGJAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCgM3OEYCJkI4QnwKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLASMFSUaFMncBhkwKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgoJOEZTkHJ5cASMCogBugGEAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCoUlBFOFIUBDlUwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBNhkzmJlYFDYkjAooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBc3SAJXeRIzEInAqYAboBlAEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBEJd3ZEZwZVhRPAo4CgdncmFudGVlEi26ASoKKAoKUHJlZGVmaW5lZBIawgEXCgoRAGGFNFeUWYOcEP///////////wEKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgpXKHUSaIV2aXA8CoUBugGBAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKClZVCUQoFWSViTwKKAoHZ3JhbnRlZRIdugEaChgKBFVzZXISEMIBDQoLAXVzFoOYSBWFBowKJwoHZ3JhbnRvchIcugEZChcKBFVzZXISD8IBDAoKIkBHVimCEIiRLAqYAboBlAEKNwoIYWNsX21vZGUSK7oBKAomCghiaXRmbGFncxIawgEXCgoBl4QwAHJ3NzAcEP///////////wEKKQoHZ3JhbnRlZRIeugEbChkKBlN5c3RlbRIPwgEMCgoDhYgYcph5aSgsCi4KB2dyYW50b3ISI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwF4NwlnSUCJYoUcCnC6AW0KLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBUwYHMoc4SDCWjAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgohMIZjISdSIEh8ChMKB2dyYW50b3ISCEIGUHVibGljCna6AXMKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpTIVUxlYGIgGZ8Ci4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwEROSJSglhEVEUcChMKB2dyYW50b3ISCEIGUHVibGljCm+6AWwKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgqFI3BDSHJZiJKcChMKB2dyYW50ZWUSCEIGUHVibGljCicKB2dyYW50b3ISHLoBGQoXCgRVc2VyEg/CAQwKClaTYGY4kZBGIowKiQG6AYUBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAWKZBUFXYohzc4wKKAoHZ3JhbnRlZRIdugEaChgKBFVzZXISEMIBDQoLAVkSMpQjU3EVVlwKKgoHZ3JhbnRvchIfugEcChoKBlN5c3RlbRIQwgENCgsBBpIYgoclcpUHTAqJAboBhQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBYFk3ZDiSJkF5nAoqCgdncmFudGVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEJeBMYaGGCaDgcCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwEyCCg3EgBVl2ZsCly6AVkKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBZiOZc0KWGEUWnAoTCgdncmFudGVlEghCBlB1YmxpYwoTCgdncmFudG9yEghCBlB1YmxpYwqGAboBggEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpZaSJTKRVweWccCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKMCZxRoRQF3mXLAonCgdncmFudG9yEhy6ARkKFwoEVXNlchIPwgEMCgoBYWQScimYZWAsCokBugGFAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEklTYZiCVCI5IsCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLATEwgFg2JWZJdRwKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLAWCCUhODKCdmQiwKigG6AYYBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKV1ZgUAAwByUBHAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgqZIZSGJkhmNgmMCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCpVig3gVEQQyhmwKiwG6AYcBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKMgmIkpaCJyGQPAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBaZBVFnOZJjhVbAotCgdncmFudG9yEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgplByUWhEUnlDc8Co0BugGJAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEwgVFBRhd1SVGMCigKB2dyYW50ZWUSHboBGgoYCgRVc2VyEhDCAQ0KCwFyZgMUlxFmAnZsCi4KB2dyYW50b3ISI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwFAkzkCdUCZl3F8Cny6AXkKNwoIYWNsX21vZGUSK7oBKAomCghiaXRmbGFncxIawgEXCgoGcmgiEnkHIIGcEP///////////wEKKQoHZ3JhbnRlZRIeugEbChkKBlN5c3RlbRIPwgEMCgpWE2czh5eAJIRMChMKB2dyYW50b3ISCEIGUHVibGljCnK6AW8KLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpCaQCEKTKCRRBMCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAUlAUZhmiDkCVjwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKlQG6AZEBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAVknZ3QzVVlXRxwKLQoHZ3JhbnRlZRIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKKYNxaYE1YgMDjAoxCgdncmFudG9yEia6ASMKIQoEVXNlchIZwgEWCglZMCOBWHZnmEwQ/v//////////AQqLAboBhwEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgqDEEYSgFBClmaMCi0KB2dyYW50ZWUSIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCklROYMgUlOHgxwKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLAXAjYUEmAZl0GXwKdroBcwotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFDACg1AVWUlyGcChMKB2dyYW50ZWUSCEIGUHVibGljCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCmdnhHcDRBZ2MzwKiQG6AYUBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLARUoiBRWRyUHgmwKKQoHZ3JhbnRlZRIeugEbChkKBlN5c3RlbRIPwgEMCgoJIhGViCYkMJkcCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKkBg2ETeGhlR4XApyugFvCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKV2MleJdlRRSDPAoTCgdncmFudGVlEghCBlB1YmxpYwoqCgdncmFudG9yEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEgA5MygCdCFFiMCocBugGDAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEwNVdpMnmUUHgcCigKB2dyYW50ZWUSHboBGgoYCgRVc2VyEhDCAQ0KCwE2mDURcpQ1QmEcCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwFYiWAyInkik4lcCpEBugGNAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCmEDMWEnYxlyB4wKNAoHZ3JhbnRlZRIpugEmCiQKBlN5c3RlbRIawgEXCgoHdJCFdyhIZjGMEP///////////wEKJwoHZ3JhbnRvchIcugEZChcKBFVzZXISD8IBDAoKlgKBWUMWAWhWXAqGAboBggEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgqYKQhZaWhBdYVcCigKB2dyYW50ZWUSHboBGgoYCgRVc2VyEhDCAQ0KCwGCNEgCZFdhGAksCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwEWRjiUVRQkRAacCpABugGMAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCndIKUWFdySVEIwKLQoHZ3JhbnRlZRIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKYiF4N2cYFwBIfAotCgdncmFudG9yEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpAQYRCOXBFETicCogBugGEAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCkk1JJcHdoWIBlwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBJQdWB2k0iBJnLAooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBKEJWMDFiYUcAXApbugFYCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKB1UZKDQ2YyOQjAoTCgdncmFudGVlEghCBlB1YmxpYwoTCgdncmFudG9yEghCBlB1YmxpYwqRAboBjQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBRmYCWDhxMTICPAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpjhEcTKVQwFYZMCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCllUEAkXRiZZQkwKiwG6AYcBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAWgmGXAllzZBRCwKLQoHZ3JhbnRlZRIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKcTMHIpaAV3KJbAonCgdncmFudG9yEhy6ARkKFwoEVXNlchIPwgEMCgowJwWEFRFWUnBsCoEBugF+CjcKCGFjbF9tb2RlEiu6ASgKJgoIYml0ZmxhZ3MSGsIBFwoKF1AoMDFXFYBCbBD///////////8BChMKB2dyYW50ZWUSCEIGUHVibGljCi4KB2dyYW50b3ISI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwF1AViIZhAkCSAsCly6AVkKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBgpJEIDaZSZeCLAoTCgdncmFudGVlEghCBlB1YmxpYwoTCgdncmFudG9yEghCBlB1YmxpYwqiAboBngEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpmmSMzBYKDA2AsCjQKB2dyYW50ZWUSKboBJgokCgZTeXN0ZW0SGsIBFwoKFSUkQQRBdWARTBD///////////8BCjgKB2dyYW50b3ISLboBKgooCgpQcmVkZWZpbmVkEhrCARcKCgFYQXRAmSUgCUwQ////////////AQpcugFZCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAXaDZDJhBJNTCXwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKEwoHZ3JhbnRvchIIQgZQdWJsaWMKcroBbwosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKClYFJ5ZzU5WGNHwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBM3giZTGTeESEnAoTCgdncmFudG9yEghCBlB1YmxpYwqGAboBggEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBMDCQZ5eXGXAGXAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBQzQGdTApJlgVTAonCgdncmFudG9yEhy6ARkKFwoEVXNlchIPwgEMCgp0WFaQZDlTQSRcCpIBugGOAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFERThReTAWN5dcCi4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwF2QUU2N0chEWY8Ci0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCkFHlGgAJkOGIGwKiAG6AYQBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLARJBQoI2JWGWEjwKKAoHZ3JhbnRlZRIdugEaChgKBFVzZXISEMIBDQoLAVVgIBgiI2eRdlwKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgoQZJSTZTMzJQCcCogBugGEAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKChcodncAJoQHQUwKKQoHZ3JhbnRlZRIeugEbChkKBlN5c3RlbRIPwgEMCgpJhzAgmFCXEFRMCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKdkYDhBlkeHEmLAp2ugFzCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAYEXgjdEeRBDmCwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKBxUXYDSDWXmHXApwugFtCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKKFAWKIV1U5k2fAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBYVJ2J5R4EWRoHAoTCgdncmFudG9yEghCBlB1YmxpYwqTAboBjwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBJAaWZWEAUxlyTAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBgIgVmEACJBZ1fAo0CgdncmFudG9yEim6ASYKJAoGU3lzdGVtEhrCARcKCgYyl1KIkQQYCIwQ////////////AQqLAboBhwEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpAJzUSSQE5hhScCi0KB2dyYW50ZWUSIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCkFXAnFTABCXSDwKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLAVVIVWcZAiEIIywKhwG6AYMBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKFgIgJpWCICMhjAopCgdncmFudGVlEh66ARsKGQoGU3lzdGVtEg/CAQwKCnSBA1NkhJSJlHwKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLATRGFhUUIHlXVjwKigG6AYYBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKRCCHVAYRQHl5LAoqCgdncmFudGVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwGAGRAwgBJCVHZ8CioKB2dyYW50b3ISH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAVl1ZzEFFZA0IDwKkQG6AY0BCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLATdRaDFCBkgWQRwKMgoHZ3JhbnRlZRInugEkCiIKBFVzZXISGsIBFwoKFyAjQlFoQyN2TBD///////////8BCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwEpYmkpFTlXmHFsCo4BugGKAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCkCAUAiAV3kIMXwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBR3I4FYJyMZk2XAouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBB2EHEzJ3MkhILAqKAboBhgEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpGYjRFVmAyZChsCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKCgSCaWQ4NDNJkCwKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKWQZXZ3CWdmgYTAqNAboBiQEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoolIkwaYcQOXhsCi0KB2dyYW50ZWUSIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCjNykTgUhSdilkwKKgoHZ3JhbnRvchIfugEcChoKBlN5c3RlbRIQwgENCgsBcWh2gTJQEnSUPAqFAboBgQEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpmACkZVxRjABAcCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKCnMJRiZDdAl1JGwKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLAXhEWFdXl5dRFxwKcroBbwotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwERZlUHFyR0AQeMChMKB2dyYW50ZWUSCEIGUHVibGljCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKYEYBV3AgkHB2bAqWAboBkgEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoSFIKDRkIjMhE8Ci0KB2dyYW50ZWUSIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCiliNDiIMlVHFiwKMwoHZ3JhbnRvchIougElCiMKBlN5c3RlbRIZwgEWCgk4IQYyUpdYMlwQ/v//////////AQqNAboBiQEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgp5VQmSGFYphGFcCi4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwEBd1WDmYVUk5ecCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKMGMwBpIJMGVRHAqLAboBhwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBEUNTYkATc2dEjAoqCgdncmFudGVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwF4VJeUVEOZV4aMCioKB2dyYW50b3ISH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAWQgKIWUVQgCMZwKjAG6AYgBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAYB0lGJAlIFgRmwKKAoHZ3JhbnRlZRIdugEaChgKBFVzZXISEMIBDQoLAVmXMgklh0BmaYwKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKZiEXViMoc3KJLA== +CqIBCp8BugGbAQouCgNrZXkSJ7oBJAoiCgJpZBIcugEZChcKBFVzZXISD8IBDAoKMkcJJ5lTU0h0fAojCgRraW5kEhtCGVN0b3JhZ2VDb2xsZWN0aW9uTWV0YWRhdGEKRAoFdmFsdWUSO7oBOAo2CgVzaGFyZBItQisv4K2pIjjCpOCymvCQl5vwn5W0UTfwkYWRPHsiPPCdna5Va/CSvrN+4aGr +CtcBCtQBugHQAQpDCgNrZXkSPLoBOQo3CgJpZBIxugEuCiwKGEludHJvc3BlY3Rpb25Tb3VyY2VJbmRleBIQwgENCgsBJJCQgoAxllcJfAojCgRraW5kEhtCGVN0b3JhZ2VDb2xsZWN0aW9uTWV0YWRhdGEKZAoFdmFsdWUSW7oBWApWCgVzaGFyZBJNQktnJmngqLLgrad28J+rt+CpjPCQjbk/8JChiFbwkZmr8JG2gvCRvrDwnp+jLi7qp5c/Ljsq8JCKguCuqOGLifCRp5rwm4WV4qesWGI= +CuqYAQrmmAG6AeGYAQoxCgNrZXkSKroBJwolCgJpZBIfugEcChoKBlN5c3RlbRIQwgENCgsBKVdYNImGYhcoHAoRCgRraW5kEglCB0NsdXN0ZXIKl5gBCgV2YWx1ZRKMmAG6AYeYAQr6RQoGY29uZmlnEu9FugHrRQqcRQoHdmFyaWFudBKQRboBjEUKiUUKB01hbmFnZWQS/US6AflECh0KF2FycmFuZ2VtZW50X2NvbXByZXNzaW9uEgIIAgobChVhdXRvX3NjYWxpbmdfc3RyYXRlZ3kSAggECskSChJhdmFpbGFiaWxpdHlfem9uZXMSshKyAa4SChlCF2E90ajwn4K28J+pt/CepJ7gu4bDuHcuChNCEfCdi7Lwn5W08JKQoybwkZujCilCJ+Giuci6TWAr4LCS8JCehSLvuKRx8J2SnlzjgIDvv718OnjCpeCyvwoOQgzwnqSNJCjwkbaULkwKJEIiVPCRiowqWTAqLjpSw5/RqEUnPPCQnpAk8J66lmQudeqmjQoOQgw3Unrvv4ZMOvCbsYIKJEIiJy9F4amDQC/IuiLco8i68J+ns8OOw6jCsyZy8JCrm++/vAoJQgfwnoCj4aWiChpCGCbDseCmifOghKY10ahNJC9yXu+/vVwqVAoMQgp78JGKiOCkmNGoCiFCH2Al8JGNjeCpnvCRqLPwkLSGJ/CQgJ8lJEJ4KnrIukcKEUIPRMOE8J+VtOCsjyJgaC4tCjhCNmoiJPCdlIhs4LamJPCeuYvwnrise3LvvZLRqOqmvGAm4oG/KPCdi41nJS7wnrqIyLrgtoMiMgo0QjIl8JGHn1xi8JCenPCfoZPwmLSFbsi6IMOJ4rWw4LWgdfCfn6ldci/CpT8i8J+VtEHCpQo/Qj0x4L+Szp3wnZOsImnCpVAlJirwkKC38JCXnTYqKuG8m03IuvCRjJXwn5W08JCjq+K1rzwhIvCbsoHgrZdGCiJCIHzqp59IYPCRiojwkJCvzobgvafwnYyJViDvv73wkJaVChNCESpRwqXguoTwnriyaPCRiqB0ChhCFvCQgII88JG1py/vv4PguoRd6peDQ04KEUIP76y8ayjwkaSJJFzDijs5CiBCHnNIPXxIJPCeuYsv8JGHlsKl8JGPgj9cPXnwn5yqKAorQilETScl8J28iiYmIuGyleCpkfCdqpxGP1g6NTzCqSXehWnCpXzwnqWeWQoaQhhXPiZLJyV18J+VtCffjVzhpqfivojIujoKPEI6Jy4kX2BR8J+VtOCxh3vwlqud8J6AqDA6Y/Cav77elCVk6qyLYvCdlLNgwqUieD98P++3sFBw8JCwoAoxQi/wnZCTXyjwlrWX77+94reE8JCCtT/jsZzwkIOK8JuFpvCRtpDgu5Iud96Zw4DRqAoVQhMzyLrwkbusJ8Kl6qW4JeCnjMKzCh5CHOGKoC7vuZom8J6LkfCQhpnwn6uTbuOGruqtiyQKHkIcVPCeuZ/hg4AvTGDwmr+6ST06Pu+/ve+uill7MAoGQgTwkLWIChVCE+Cxr+K4t/CWuZY10ajgqLMnWiQKKkIo0ajhp5Pgrofagj5z8J+ElG9fUmrwn5W04KuN8JCgvCc3ZVtZ8JGNlwoTQhE48J+VtCfhgJQlfOCssM6MJQocQhrwnp+h8JGNjeGQrsKl8JCinsi6XMi6Oj3OiAoMQgrDgCoq8J+bsD0pChBCDjwn8JC8kOOChkbwn5+wCj5CPC8k4b2ZJPCQlpXgt5DwkI+OLzwl8Ja/sWDDgTxb8J2JhPCRjL/wkYy98J+VtPCfgro9UuC5kPCei7/DlAoIQgbIunvqr7IKB0IF4K6+RiYKB0IFw51BIjgKP0I98JCWlcK34K2MRSR7YCQ6KvCQurHwkY+CKSTgqYF1e/CQg6ZwXD1hVsKlW/CRvJbwkYeudsKl4rak8J+VtAoFQgPIulIKNkI0w5JLdvCRnJnwkYyqV/CRjoslJSVSJ/CQlZsl8K6QnnkqKjp6X++/jvCRspzYkvCRipAlJgozQjE9JPCWubFQ6peod1Pgs7PCpfCQkqUnbEvwkY2o4KybTXvwkayJwqXgsYcn8JCAjS46CkFCPzo/8JGOgfCdkrku8J6KofCeso4m8JCspGpg8JCemmBCJsi6U+CrkDbwkZuJ8JC0m/CflbQlIvCeuY8m8J+JkAoFQgMkY2YKIkIg0ahNw7LWnELhm6Uv4KG7ezpgbXtgIuCuguKCjj0yKkoKIUIf16/wkbGXIyjgqrUkw5zgsZpZPfCeuqPwkbWFLTRlPQo1QjMyRik68J2Vjy97e/CfiYAmfvCfgr5lOuCmlUTgrYdmyLpf4Yq88JG2kOG8qi8l4YqywrQKNUIzIvCRiowiJMOtVMKlKioi4K2uPPCYsbJWLuqdm3law5BX8J+euGnwkbyz4KqCYOCoqsi6CgJCAAoZQhfgr4jwnrqifvCSlL0gYFJZafCQlrHCpQoYQhZANTBgQjxdQfCRsKLwnZO0YD1CwrYmCiRCIjDIum7vv73vv71sw5PhpJNgwqzDs0rIuj89JifRqPCdkY0KOUI3YHvwkKuX8J+GivCRoJVOKPCWrb7wnqKDQ3xcJmnwkbS/e2NjJu+su1k88JCgg+C6pdGobuqsjQoZQhc64K+10ajwnrmZ4Kilw6rwkbaHJGLCugogQh5u4LWHKkRN4KyyL1LIuvCxlI/wkJSl1YRuePCWv7EKEkIQX/CRiI/DkvCflbRk4b2ddgofQh3wkJabyLrwkIehLvCfqq3wn5W0wrZ+PEMi4LOzeAotQivgqZrwlryH8J+chTo88J2Vjj3gvL/qp5HqrKDvv5Nw4LGL8J+qryfqn5BpCjVCM+C7nvCQlKZzOj1mwqjgoJRiUSc68J2Snic6PT168J+ho++thCbgt6zwkLSXJuKuie+/vQodQhsu8J65uydoYMi64KmN4K2B0ah7eW5cXEQqJFwKH0IdOjrDvUY8LvCbhaZ58JC7vns/YOC3mMi68JGLoT8KN0I14ZyUYDw88JuKjfCegKdk4K+N4LOrOik84YuM8J6Apsi64KmbPUF7XyNKXFol4LuL8J6jiEoKPkI8PD3CpfCRgZImP/CeuLBI4aqgL9q4PPCeuZvRqPCRpKjqq6d777+9RvCRjoXwnqWe77+9L/CQvoM90ahjCjlCNyLwnoCnL/CRhKfDtfCdjIZGIikv3qklLsObVuCzi+Ggg+GdovCeuKdZJknhiYw48JG8k/CRsJoKGkIY4auGXfCeuY4lJD3vv73wm7KQ0ajgro9gCkJCQFbwn5+hc/Cbsbwk0ajwkKiM4aS1w5zwn56qdMOU6p+Q8J2TguK3imwzI+G/tPCeuZLRqHtO6p+aP++/vTTCpXsKDkIM6qSIcGc637fwrrODCixCKivgqLPwnri04aeBfvCRvK9g4LqC8JCoheCqm/CflbQ9TVzwm7KX8J66kQq4AQoFYnVyc3QSrgG6AaoBCjAKCmJ1cnN0X3NpemUSIkIgJsKlc/CflbTwnrmU8JGFkSXhpJoifsi68J66o/CfqogKTwoPbGluZ2VyX2R1cmF0aW9uEjy6ATkKHgoFbmFub3MSFcIBEgoFBAWIRCwQ////////////AQoXCgRzZWNzEg/CAQwKCmlyECMQiIlxAiwKJQoSc3RlYWR5X2h5ZHJhdGVkX2F0Eg/CAQwKCgdzhQEolQkWVXwKYgoHbG9nZ2luZxJXugFUCj8KCGludGVydmFsEjO6ATAKFAoFbmFub3MSC8IBCAoGAiR2aHhMChgKBHNlY3MSEMIBDQoLATcnNoAnNiiZgBwKEQoLbG9nX2xvZ2dpbmcSAggDCsgkChtvcHRpbWl6ZXJfZmVhdHVyZV9vdmVycmlkZXMSqCSyAaQkCki6AUUKLgoEbmFtZRImQiTCoC9yPPCeuY/vv71fQiU24LOWcuqrsCIkR/CrpJ3qn5M/J3YKEwoFdmFsdWUSCkIIe10nYPCflbQKaLoBZQouCgRuYW1lEiZCJCo6XD/vuYPwkIeo6qykJ/CfgY91ROCumvCflbTjgZrgsZ17aQozCgV2YWx1ZRIqQijwkKmAdmHgsIVjQiJow6HRqCIlTVJgTiTgu5xcTlUq8J+VtPCQkqRMCkO6AUAKEAoEbmFtZRIIQgbqpIY6JHoKLAoFdmFsdWUSI0Ih4KyCOifwkICwblnwkZGgJDLvs5fwkbK0VVzigbTwkYyLCkG6AT4KCwoEbmFtZRIDQgFcCi8KBXZhbHVlEiZCJC8n77+98JKQuiXCpeGLg8i64KeXPeGdqibwn5W0VyLwkI+QagpiugFfCicKBG5hbWUSH0Id0agkJXvCpeOIjzpA4Kay8J+VtOC7huGdiz/gtLsKNAoFdmFsdWUSK0IpSPCeuKQ6LzzgrKPhnbM64KqQ8JCHmSPhnYbhvo7wn5W0LvCen7t7JCIKbboBagpHCgRuYW1lEj9CPSZwXvCRpInwlqme8JG1p+C7kWLwkKC34Y6L77+9Ujw8wqXgrINcS9GoKFzwkJabw6LwkKGp4oW68JC5uycKHwoFdmFsdWUSFkIUKu+3vPCdlIolw5fwnrmdaPCflbQKS7oBSAoUCgRuYW1lEgxCCiXCt2dY77+9OmAKMAoFdmFsdWUSJ0IlQzwnaDpZNjE677+94LGDPfCQurFI0ajCsycqLvCRjbBc4K2IYAoyugEvCg8KBG5hbWUSB0IFfvCei5UKHAoFdmFsdWUSE0IRV/CfqbLvv70jKeCxiu+3j1wKQboBPgoSCgRuYW1lEgpCCOGIhi/hqKx7CigKBXZhbHVlEh9CHfCbsbon4Ki/4rS7N+CouTzwkKCITvCdqYfhnok6Cie6ASQKDgoEbmFtZRIGQgR0yLouChIKBXZhbHVlEglCByYy8J2UiXsKbLoBaQo1CgRuYW1lEi1CK/CQu4I/4LuTKkjwkbWCQ+Cxg/CWrKpqVWove2lbyLrgqoYo4Z+kwrUmJX4KMAoFdmFsdWUSJ0Il8Ja9r/CQlZXgoZZG4LqC4LWee/CflbQ98JG9kPCfoa1+O+GJmgo1ugEyCg4KBG5hbWUSBkIE77+9XAogCgV2YWx1ZRIXQhU98JCpiPCQlb4l77+94KaywqV3JkcKRroBQwoNCgRuYW1lEgVCA+qSpAoyCgV2YWx1ZRIpQic68JCohiQm4KKDJeGuuiU64KeI4rq28JC6sXtiOuC2glIkPz9CYEMKYLoBXQoYCgRuYW1lEhBCDtGo8J+VtDrwkIuDLyZ0CkEKBXZhbHVlEjhCNiIy8J+VtCXCpWBJLyY50ag08JGLs3R78J64gSUlXOCgjPCWupYu8JCeueK1ryR6PeCvhmBfKwpEugFBCh4KBG5hbWUSFkIU8Japh8i6QPCflbQvLvCtqaE6XCYKHwoFdmFsdWUSFkIU8Ja+lOqkluCrv++tgTx+JvCflbQKX7oBXAocCgRuYW1lEhRCElty8JGbgMi6IeC3iuqvsO+/vQo8CgV2YWx1ZRIzQjHwn5W04reDYCc5JMOmJytVSeCxueGKvl3wkaSEJE5i8J+JoD/gprI74KaQIfCehYB+Ck26AUoKEwoEbmFtZRILQglp8J+CvPCQlowKMwoFdmFsdWUSKkIoJG7wkKCIP/CRh6Yk8Jy8vsKlVmAu8J+fsHbwnrmdVT06Pe+/vW/DvgppugFmChcKBG5hbWUSD0INJ2HCpfCen7Twmr+9PwpLCgV2YWx1ZRJCQkDvv73vv70n8J+VtOCrjWAq4b2XyLp3Ij/wn6CH4aCKV2Dwka+1czQ9L/CdlYYh8J64q/CRjYDwkbaQ8JCNgsKrCki6AUUKGgoEbmFtZRISQhAmw7rwn5W0PG/RqCRy4KGlCicKBXZhbHVlEh5CHDLwkLWi4KaFPC4kWe+/pPCdlYbwnLqOVELDjyUKfroBewoyCgRuYW1lEipCKPCQp6QqTtidcTNX4b+aXOGhinE/JeG/s9Go0ahRyLrita9tb+GPui8KRQoFdmFsdWUSPEI6JlzhsrB7P1wl4qmVL+C6gvCeub7wn5W08J+CtEwlcdGo4L6mJiXwkIagwqdf4K2LP9G38JapmiNWPwo/ugE8Ch4KBG5hbWUSFkIUKEk9P3Hhv6bgtYbwnoKPNfCRsYAKGgoFdmFsdWUSEUIPL+C7gfCeupIiZEXwn6q8Cne6AXQKOwoEbmFtZRIzQjEuYDrwkYyQ8JG0rVksdOCyjMKlbj3gqoMm8J6ynmUv8JGctfCQqZE6dzLRqF5k76SyCjUKBXZhbHVlEixCKi7wkIClwqVwJiYuJeCorD8qzoTqoZXDstGoPyUm8J6AgjzCpdGo0agmbQqFAboBgQEKTgoEbmFtZRJGQkQm4LOz4oOi4K+QNfCrnqkl77+9a/CRpJbwkpOMIj0m0agi4LOi8J65m/CRkZpc4K6C8JGZq1fwnrq1R+C2vVgk8J64pwovCgV2YWx1ZRImQiThg4dvJyRHJzxbOi97czrwnZSY4KqC4q+MJ2HRqFct4YuAJj0KT7oBTAohCgRuYW1lEhlCF3HwkL+D77+9NDE90agkLeCnne+tlyU9CicKBXZhbHVlEh5CHNS64Kite20q8J+VtCfwn4mE8JG8iWPvv71PREMKVroBUwofCgRuYW1lEhdCFUrDiHA8KjlgJFUzS2Agw7DhpZ0qaQowCgV2YWx1ZRInQiU68JasofCdkpjwkICDPOCyteGPrC87wqXwlqusJkJg4LWILSU5Cme6AWQKHQoEbmFtZRIVQhPwkaSQ4K2mMXA9TiZDWsKg0ahCCkMKBXZhbHVlEjpCOMKlUWMvJkHIulUi8J65n+Gfoy8kOu+3vz/RqO+5qS9c8JCbkzkwP/CQnqXgqI8q6pukw4Zi0ahyCl+6AVwKHwoEbmFtZRIXQhUqJ2bgv5M8V8i68J2ZlndY7Z+FWWoKOQoFdmFsdWUSMEIuJfCbhaYuJ1LwloSaZvCfr53hi4Ul8JarpSU9POK1keK0lcOH77+9J2A9762EPwpaugFXChEKBG5hbWUSCUIH8J6Es0PIugpCCgV2YWx1ZRI5Qjdt4rap8J66gfCflbTwn5W04LOW6p+b0ahLIj/hnazDkOGghD7vv73wnLKLVVwl4LOHPDxuOnsuClO6AVAKMwoEbmFtZRIrQiki4YON8J6xu2EqffCRj4g1Osi6OvCflbTwn5W0PfCek7DwrL23JyIiIgoZCgV2YWx1ZRIQQg4u8JGwpz9c4K6GyLoqIAqCAboBfwpFCgRuYW1lEj1COy/wkKmD77+9N/CRkqDNvSrgs5bwkbaY8JGkvDYl4KmHTDpcdPCRjLAq8JK+p/CRgp7hjJM5PXXwkIGcCjYKBXZhbHVlEi1CKz7zoIS44YaSw63wkY6OZ/CQpqjgq6bvrKdcdtGo8JGOjj3wkI2rOmhpR3sKTroBSwoaCgRuYW1lEhJCEPCQj40nKvCWhIdGWfCeuo4KLQoFdmFsdWUSJEIiJCopRcKl44W1JEA98JColfCeuZ/wnZGl4K6j8Ja/sT3bqwpJugFGChIKBG5hbWUSCkII6p+byLrgtI8KMAoFdmFsdWUSJ0Il8J+VtHsvIvCWqaYi8J6ElCfwnouZyLp7JfCflbROKjrwnZK7Rgo+ugE7Ch4KBG5hbWUSFkIUSFxc4YOHVMKlPEFc8JG1hvCdiIoKGQoFdmFsdWUSEEIONi7itrHCuCbwnZKmTkMKgAG6AX0KOQoEbmFtZRIxQi/ihK/CpT4+0ajwmr+2VFw8JSgr8JCgg/CRmZQk8JCetWlMJyciKvCflbTwkIWJPwpACgV2YWx1ZRI3QjVccMKufuC3lOG/kfCeuIN98J6Lv1ZPJCRCJz3wn4miQHzhvYtUJfCfgJp6Tkk8PXvwlqmpOgqHAboBgwEKNwoEbmFtZRIvQi1GL2DCvT1pJmBMJOCoiFvOiibwn6ub4reWdyo98JGLsuGhny8s8J+GkT/qkp8KSAoFdmFsdWUSP0I9Ilbvv7158J6freOErDc9VsK98JC2j+GcsHvguoTgrpzwnp+oXEZS4Kut8J65mcKxJPCQsJrwn5W08JGymQpQugFNChIKBG5hbWUSCkIIPyTgtYg8Kl0KNwoFdmFsdWUSLkIs6qmT8J+sgOCokD/gsIss8J65juCovntLyLrhsLXIuvCegIA98J2MvvCeuKwKdLoBcQpOCgRuYW1lEkZCRPCen6YuP9Go8J64nO+/vfCQu4LCpSXwkbGA4Z6wJPCWhLI88J2TnUvstbHIuirwlqmp8J65ifCeuZTgtr17NC/wkYK0Ch8KBXZhbHVlEhZCFO+/vNGoNPCWvZvhiZjwkZaQ4aOzCke6AUQKKgoEbmFtZRIiQiDgpr8mJu+/vfCeuY9He3hoPcKlX+GljPCRjo468J+VtAoWCgV2YWx1ZRINQgt7ZCw88JCTt1wkYApgugFdCiwKBG5hbWUSJEIiKidRNN+T4Ymb8J64mMi6XEY8dmDqq5/wn6KdLvCQralcPAotCgV2YWx1ZRIkQiIgIvCRr7nwkJaUwqU5S+Chv2k8JybwkLCqP3siVyXIusKlCki6AUUKGQoEbmFtZRIRQg/vv73vrKPhvbrwkbGFK0QKKAoFdmFsdWUSH0Id8JC/ieykqTouQ3Eh8JGbg0HhirnhirzwsISrKncKeboBdgo9CgRuYW1lEjVCMyfwlrmULlngsZbigqfwn5W04b2sJzjwn5W08J+VtPCflbTwnrm+dCnwn5W06p+c8JGwlAo1CgV2YWx1ZRIsQipcJknwn5W04L+R4Ka58Jy8sF8g8JKQoirRqPCQj4898JGkgyrIulBAdFwKe7oBeAo5CgRuYW1lEjFCL/CdgobgqL7wkJakXC/wnou/J+GfqOGggOGkiPCQoITCpfCdlYNa8Jato2NTw4pECjsKBXZhbHVlEjJCMCvgsYfwn5W08JGvs+Crg1zwnrG8QHsuJVkuYMKlNfCRhIgnKy7gqJN26qifLOCriAo6ugE3ChUKBG5hbWUSDUILJ1Ba4K+G77+9Tz8KHgoFdmFsdWUSFUITceKDqOqpmCV3Y9ug6qC24Z2wJQpfugFcCkwKBG5hbWUSREJCLj3wkKKYIinwkbGk8JGPgvCQurDgqozvuahXPfCfn6lg8J+VtPCflbREKvCflqbgsZZcP/Cav7rgqZrjh4vwnrmbCgwKBXZhbHVlEgNCAToKTboBSgoqCgRuYW1lEiJCICZue/CeippSyLrwn5W08JuFlSrwkY6q4LCI6qeR77+9ChwKBXZhbHVlEhNCETQmJeGotTrqn5NHKlPwkauuClS6AVEKJgoEbmFtZRIeQhw2LvCRtKVB8J+Esm11YPCdi5FrfSbhqaNrwqU8CicKBXZhbHVlEh5CHPCdqpxzLmBD8J6EsWkkMHM98J6KovCRq4XDqiIKbboBagojCgRuYW1lEhtCGUQ9IlXwkYqU0ajwkYa2SlNE8J2SqvCQlq8KQwoFdmFsdWUSOkI48J+VtGBgw7/wkIq/IEfhqY7fgsK1RUon0ajgpoXwn5W08JCAkPCQmaFgJ8KlYCcmOWDwkaS+YD0KbroBawpECgRuYW1lEjxCOsOmYO+/vT3wkL+g8JGNsTxg8J+VtPCdkp914KmcP2Dwn5W0SfCbsoVe8J+imWDhn7LwnriW6qyNJlEKIwoFdmFsdWUSGkIYJy7gtrvvv70k4b+WJcKlU/CflbQqJVtgCmi6AWUKKwoEbmFtZRIjQiHwkYexUu+/veODmuCxvj3wkauA4Y21JdGo8JGPlPCflbQKNgoFdmFsdWUSLUIrKvCdlZAl8JGkluCxhy/RqPCfro498JCWlDVk4ay7Oijwm4WR4aqq4YmaJAqYAboBlAEKTAoEbmFtZRJEQkLdhdaP4KuC77+98J65ifCfrqRxJW0uT+qvtmDitKfqrIoq77+9IPCRtLrjgqHOjC8+wqUiw4ZwLvCQgZQ/8J2QsDoKRAoFdmFsdWUSO0I5JvCflbTgrY0l8JGNiPCfpax+P2DwkbKhVeGelSp80ajDrSQuQS/hrrzhjIYlPeG8m+CvlzMk77+9CsAJCg9yZWNvbmZpZ3VyYXRpb24SrAm6AagJChwKCGRlYWRsaW5lEhDCAQ0KCwE0gnMxaJEpiTlsChYKCm9uX3RpbWVvdXQSCEIGQ29tbWl0Ch0KBnN0YXR1cxITQhFSZXNvdXJjZUV4aGF1c3RlZArQCAoGdGFyZ2V0EsUIugHBCAodChdhcnJhbmdlbWVudF9jb21wcmVzc2lvbhICCAMKlwcKEmF2YWlsYWJpbGl0eV96b25lcxKAB7IB/AYKN0I177+9Lz/wkbaOSuKBsS7Cp/CRgatcfXVhSG3it53CpcOk8J+VtPCRgaQ7IuK1r+K3jmDhoIcKNEIyJy5gYD5D7YiCyLolyLoqSS3wkJOf8J2Sqc6y8J+bsifjg4MuSeyHrvCeoIleVvCdlIcKGEIWMfCRtavvv7088JGxuS8/eOC7iEPCpQoZQhc/YCpSKmLwn5W08Ja5kfCeuZ86P9uaOAorQillduKAmSLgtZXwnZKmfm8777+98JGKlyYkSci68JGLkOGBmz0p8JywsQoeQhw4Vz97XyrRqO+/vfCQnoPhs4fwkK6a8JC6gX1xCkZCREbwlr+h8J2hszzDuvCRqrLgqoLitoZkLD3CpT9EJvCwkbjhgKtgJfCQrb3wkJ6BPOG9m+qjlfCWvY9a8JCgiHXwkaaiCilCJ2slTifgqIPwm4exfe+/vUjwnrinSdufJVrwnYiLw7HvrY4+8J2SgwpAQj49YDzwn5W0PfCehIhH8JCeq/CQnqrwkKiX8JGplPCRgpHhir06XOCsjOC3m3MvKlHgtZ02Li1g4rahKuGmgQoYQhbwnZWA8J+VtD/wkJGFOnnhg43wkK2PCj5CPPCRmapc4Ky38JaEiPCflbTvv6PwkY+X8Ja8j2AnwqV74KyU8JCkv+GnqGMj8JGctvCfq6RDZXMuVeGyvwpEQkLwnZGl44OYIS/tnrw28JCkiOqsgyY24rSa8JGFlvCRj5jwnYaByLpi4KGi6p6vcnPwkaWCNknwkJ61eyp7L/CRqJQKC0IJe/CflbRuLiouChNCEfCflbQ6L++spz1VY2/wnqSuCgtCCUcu77+98J64tAomQiQq0agv4bGlPSfwkIWG4rStKiQmaDrwnrm18JCegOCrgj3hnI8KPEI68JGPgihcWWB+eyfwqruC8JCBkOGgkifRqCfRqFXgp5cv8JCpknPit5grUNGowqXDq++/vSLwnp+9awoLQgnqn5dcLsi6wqUKIkIg4oKl8J2LryrhnLUn4ZqZfPCRiqbDgnDIumvRqPCflbQKD0IN8JCaj/CQurAmV9GoJgoSQhBFJvCrnaAnKvCWrojwn4KuCgtCCSVu4ruGw5LCpQoMQgpgLjzwsYmE4b+ACi5CLFxY4Z+54oC+77+98J65izxWyLrwkYaxOnnwnL2TTPCflbQlIiLRqOK3iMi6CjEKB2xvZ2dpbmcSJroBIwoOCghpbnRlcnZhbBICCAQKEQoLbG9nX2xvZ2dpbmcSAggDCiEKEnJlcGxpY2F0aW9uX2ZhY3RvchILwgEICgYBhEeDlXwKMAoEc2l6ZRIoQibIuuCrh/CRg6YmUmJxJuClimJc8J2MhS3qqZDCpXsnJvCRpJbDlwogChJyZXBsaWNhdGlvbl9mYWN0b3ISCsIBBwoFhYUlZ1wKcAoIc2NoZWR1bGUSZLoBYQpfCgdSZWZyZXNoElS6AVEKTwoZcmVoeWRyYXRpb25fdGltZV9lc3RpbWF0ZRIyugEvChQKBW5hbm9zEgvCAQgKBgEpiIAjXAoXCgRzZWNzEg/CAQwKCnITOBQTCVNSEDwKLgoEc2l6ZRImQiR74Z6lL/CeuKkkcjo1dCo/c/CYsLo8JvCegKDwlrWMYPCfn4kKSgoOd29ya2xvYWRfY2xhc3MSOEI2JeC6qz3wmJ+qRz0i8JCGllvIuj/qoLJQJiom8JC6rCp8JOGdsvCQoJDRqMOl17IvPXfvv70hChEKBG5hbWUSCUIH6p+b8J6BnQooCghvd25lcl9pZBIcugEZChcKBFVzZXISD8IBDAoKITiCGAJSgDZHLArKUQoKcHJpdmlsZWdlcxK7UbIBt1EKkgG6AY4BCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKBigTkAOHEGgJnAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBMDlFiUk4KYVZLAo0CgdncmFudG9yEim6ASYKJAoGU3lzdGVtEhrCARcKCgloaZAoZlIZJHwQ////////////AQqNAboBiQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBGHGZdFaWYJY5nAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpYeTJCITIkERRMCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKNxYRRRQFeScnLAp3ugF0Ci0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAVQUUWVYBZZyd2wKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKLgoHZ3JhbnRvchIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLASeTRRYogFeXk3wKlwG6AZMBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKUyJFhjCEUkB3XAo0CgdncmFudGVlEim6ASYKJAoGU3lzdGVtEhrCARcKCgEkkTOSYFBFQJwQ/v//////////AQotCgdncmFudG9yEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpYRXhEZCYFdCacCnq6AXcKNwoIYWNsX21vZGUSK7oBKAomCghiaXRmbGFncxIawgEXCgoEYgYYdUVzhzJcEP///////////wEKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKJwoHZ3JhbnRvchIcugEZChcKBFVzZXISD8IBDAoKeTMIlwiWJAgJjAp1ugFyCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKRDBxBjk2A4lpnAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgojkTI3MyUhFDdsChMKB2dyYW50b3ISCEIGUHVibGljCogBugGEAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwGEQGWDcxIQZiEcCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKGYKAkphoIDABPAooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBIwF4g1J2mEMhnAqTAboBjwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBACclgxlARmMmTAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBEjSScTCWWBlXnAo0CgdncmFudG9yEim6ASYKJAoGU3lzdGVtEhrCARcKCgM1AUGCAiQ2A4wQ////////////AQp3ugF0Ci0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLARNSVQZFY0Rpd1wKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKLgoHZ3JhbnRvchIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAYBnmRcJiQiGWRwKd7oBdAotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEiVXeFEkYUVEYsCi4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwFocTZGAxQYRlFsChMKB2dyYW50b3ISCEIGUHVibGljCnW6AXIKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBRmA0IyIzCWkWPAosCgdncmFudGVlEiG6AR4KHAoKUHJlZGVmaW5lZBIOwgELCgmIkRiFCBKTdJwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKhgG6AYIBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKdmMCMiiAYpNhPAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgoIR4KZKVkpSEFcCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKkIQDcXICYicEXAqXAboBkwEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpjaSgDNZBiCWhcCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKKBKWRpQGMBQofAo4CgdncmFudG9yEi26ASoKKAoKUHJlZGVmaW5lZBIawgEXCgoDR1RIlBcTRZQcEP///////////wEKjAG6AYgBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKhFhAICMSITiCXAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBUgQpMXCWZoIJjAouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBNTkjBzhzJ5EAPAqNAboBiQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBKEFjFHE0OTJkbAouCgdncmFudGVlEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBQmWXVhhWOQUonAooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBcGMCMBOCIhMHbAqcAboBmAEKNwoIYWNsX21vZGUSK7oBKAomCghiaXRmbGFncxIawgEXCgoFQBMUeQQxNGCMEP///////////wEKLQoHZ3JhbnRlZRIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKNjAlYjkUlgFkXAouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBB0JCM5GXkTSZbApwugFtCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKlCNxl0JTQwJJPAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBQ1N0BoA0F1VjbAoTCgdncmFudG9yEghCBlB1YmxpYwpwugFtCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAWCRcYeZFTRBZYwKJwoHZ3JhbnRlZRIcugEZChcKBFVzZXISD8IBDAoKGQYCRwYyWUZhHAoTCgdncmFudG9yEghCBlB1YmxpYwqJAboBhQEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgqWiYN0R3IIgxA8CikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKgZKRNDNZFlOXHAoqCgdncmFudG9yEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEleZEXKFQ4GEGcCnK6AW8KLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgqVFENRY4IEeUBsCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLATCDlYI4JxE0dTwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKd7oBdAotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEDYSU0NUB5UDF8ChMKB2dyYW50ZWUSCEIGUHVibGljCi4KB2dyYW50b3ISI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwEGWAQlMZdyGRQsCn26AXoKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBOFdxFjBDYYSUHAoTCgdncmFudGVlEghCBlB1YmxpYwo0CgdncmFudG9yEim6ASYKJAoGU3lzdGVtEhrCARcKCgMxBWhRBWhDB0wQ////////////AQpxugFuCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKJVSQVlYJYGAlLAoTCgdncmFudGVlEghCBlB1YmxpYwopCgdncmFudG9yEh66ARsKGQoGU3lzdGVtEg/CAQwKCoZxlmdEhjODOFwKdroBcwosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKChcEUDgEVDMoSTwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKLgoHZ3JhbnRvchIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAUIGNZGUJgl3FTwKc7oBcAotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwE1UQhIZZVFiEmcChMKB2dyYW50ZWUSCEIGUHVibGljCioKB2dyYW50b3ISH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAVd0hTREJzE4BFwKkgG6AY4BCjYKCGFjbF9tb2RlEiq6AScKJQoIYml0ZmxhZ3MSGcIBFgoJiEcjI0eDQ5dsEP///////////wEKKQoHZ3JhbnRlZRIeugEbChkKBlN5c3RlbRIPwgEMCgoZmHhxhUCJBJZ8CikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKUBhUNyR4ZSEEPAqLAboBhwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBYlBkFgIXKYcYbAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgoZIZmANXMmmVNcCicKB2dyYW50b3ISHLoBGQoXCgRVc2VyEg/CAQwKCmUXZmmXk4M5iDwKc7oBcAotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEjCEAkJRFRMIecCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLARgmIpQBlFeBmHwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKjgG6AYoBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKkoQHUJE2NSkCbAouCgdncmFudGVlEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBQgZlIzhGdUkyXAoqCgdncmFudG9yEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwFHWDJIghgiMBOMCpcBugGTAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFDZReAEnVgZnecCjIKB2dyYW50ZWUSJ7oBJAoiCgRVc2VyEhrCARcKCgJFWRB1hBMhgUwQ////////////AQouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBGVVXg4cBAlOAjAqLAboBhwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBgWKGaXKZIpJojAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgp4c1CYFiN4gUGcCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCmF0JIYGYkVEeRwKc7oBcAotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFXBwIpGEKSMBKMChMKB2dyYW50ZWUSCEIGUHVibGljCioKB2dyYW50b3ISH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAYN4JCiBIxARBFwKb7oBbAosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCncJhEJEGZg5UVwKJwoHZ3JhbnRlZRIcugEZChcKBFVzZXISD8IBDAoKIQBIAYRoNCOWnAoTCgdncmFudG9yEghCBlB1YmxpYwp3ugF0Ci0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAYAJRAREWHcTKEwKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAScEZniWliF3ECwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKiAG6AYQBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKgidHdTE2c5IpjAoqCgdncmFudGVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEEhoE0MGMRZAaMCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwEwgyN0kJMxiDdcCpIBugGOAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEymSYDJHdyVHZcCjIKB2dyYW50ZWUSJ7oBJAoiCgRVc2VyEhrCARcKCgY4mJc0hTIShnwQ////////////AQopCgdncmFudG9yEh66ARsKGQoGU3lzdGVtEg/CAQwKClAoYXJUQhGYdEwKkgG6AY4BCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLARWJMhNpIDc2eBwKKQoHZ3JhbnRlZRIeugEbChkKBlN5c3RlbRIPwgEMCgpGOXcXQnFwchBcCjIKB2dyYW50b3ISJ7oBJAoiCgRVc2VyEhrCARcKChd3IhBFN5RiIYwQ////////////AQqYAboBlAEKNwoIYWNsX21vZGUSK7oBKAomCghiaXRmbGFncxIawgEXCgoBZAMFdplWCIYcEP///////////wEKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLARmDYWFyNYEgFTwKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgpTQHZnEyF3hFFcCnG6AW4KLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgp5JHlpJCJBF0KMCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKhRFJhIeYSFAUfAoTCgdncmFudG9yEghCBlB1YmxpYwqNAboBiQEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpiMod3MQVilyRsCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLARJIgHWIEEARgDwKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKY2kVFzAlEik4nApbugFYCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKZTVVInUWWUdXfAoTCgdncmFudGVlEghCBlB1YmxpYwoTCgdncmFudG9yEghCBlB1YmxpYwpyugFvCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAXZ0MBd4gEl5WZwKKQoHZ3JhbnRlZRIeugEbChkKBlN5c3RlbRIPwgEMCgoZBUlQZmZIIFAsChMKB2dyYW50b3ISCEIGUHVibGljCpEBugGNAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFXBiGRZZiRgkMsCi0KB2dyYW50ZWUSIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCoVQSHFImDcRNywKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKc3EpZANoCFE3TAqIAboBhAEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBJkcleRM3AoKVLAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBAnZZNpNggkKGjAopCgdncmFudG9yEh66ARsKGQoGU3lzdGVtEg/CAQwKCiGRJ5ADk2EDAowKhgG6AYIBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKA3AUaRKFYTRwjAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBF1BhY1WIg5SIjAooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBGIIggkmFFQiVTApzugFwCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLATFGeCN5GZRRJiwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKKgoHZ3JhbnRvchIfugEcChoKBlN5c3RlbRIQwgENCgsBBEiGgpAyGGWFfAqRAboBjQEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgohMWlTBDMHhZMsCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKGEYAEzQ2lJVFbAoyCgdncmFudG9yEie6ASQKIgoEVXNlchIawgEXCgoIGEBoQUF4ZxCMEP///////////wEKdroBcwotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEwRZCWAEJylxE8Ci0KB2dyYW50ZWUSIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCjllhYJCM3BIV5wKEwoHZ3JhbnRvchIIQgZQdWJsaWMKdboBcgosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCmAYiHNiQ4ZleFwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKUHcRYWl1JpQjHApwugFtCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKJIEnNJJ1kHFCTAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBVUAhRolWAhI0PAoTCgdncmFudG9yEghCBlB1YmxpYwqGAboBggEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoQMgUpZFd4mAFMCigKB2dyYW50ZWUSHboBGgoYCgRVc2VyEhDCAQ0KCwEkMiQAcgV1EXOcCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwEwJhkxUiMSI0icCny6AXkKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgqIgwUCMpN4iZNMChMKB2dyYW50ZWUSCEIGUHVibGljCjQKB2dyYW50b3ISKboBJgokCgZTeXN0ZW0SGsIBFwoKASM1iWYpCBdUbBD+//////////8BCnK6AW8KLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBFYIzM5ZHhiERLAoTCgdncmFudGVlEghCBlB1YmxpYwopCgdncmFudG9yEh66ARsKGQoGU3lzdGVtEg/CAQwKCpkwJ0A5SQAGGUwKlAG6AZABCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAQUZkEQUMnGJE1wKNAoHZ3JhbnRlZRIpugEmCiQKBlN5c3RlbRIawgEXCgoUCXgkJkN3ciMcEP///////////wEKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgoZZFCVhjBgcTksCo0BugGJAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCmVhQiFkJklEMxwKLQoHZ3JhbnRlZRIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKCSZ2hHVBIQRRLAoqCgdncmFudG9yEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwE1RBRIh5ICQwdsCnG6AW4KLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoHIZdWNyAwNmCcCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKUpmRQzNQMhkCjAoTCgdncmFudG9yEghCBlB1YmxpYwqPAboBiwEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgqSeIdBFBAlQRgcCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKCjCHGFI5aYRBmBwKMgoHZ3JhbnRvchInugEkCiIKBFVzZXISGsIBFwoKBwUheJIYEBlYHBD///////////8BCpABugGMAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCpliJniTkUCVQpwKLQoHZ3JhbnRlZRIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKBXYhIgcQdTE4TAotCgdncmFudG9yEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgowYHiVlidVUQEsCpEBugGNAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwGAUQeWMDUSNGZcCjIKB2dyYW50ZWUSJ7oBJAoiCgRVc2VyEhrCARcKCgcBiUV1QQaVAHwQ////////////AQooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBWCeSlhgliBZYHAqJAboBhQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBglk1UAF4gWUULAoqCgdncmFudGVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEJFWkBQIFRIXVsCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwFHVGMpQWkEWFdsCokBugGFAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwF1J4hykyVTQJJcCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAXFmMIJneJlSNZwKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLAUJVNZA4NmIUeVwKcroBbwosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCiIQMBRBVFVpSWwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBEEUTEldmmBJRTAoTCgdncmFudG9yEghCBlB1YmxpYwqnAboBowEKNwoIYWNsX21vZGUSK7oBKAomCghiaXRmbGFncxIawgEXCgoUZJhXNRlHIoBMEP///////////wEKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAXCGmZaEOJRVSYwKOAoHZ3JhbnRvchItugEqCigKClByZWRlZmluZWQSGsIBFwoKFxIniYYHkGkXPBD///////////8BCoYBugGCAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwGCNzhWaIAnOIKMCigKB2dyYW50ZWUSHboBGgoYCgRVc2VyEhDCAQ0KCwEIdIVml2GXhlJcCicKB2dyYW50b3ISHLoBGQoXCgRVc2VyEg/CAQwKCpKZRFiZeZgXNSwKnQG6AZkBCjcKCGFjbF9tb2RlEiu6ASgKJgoIYml0ZmxhZ3MSGsIBFwoKCSI0CDQyI4dYHBD///////////8BCi4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwExmRNhYGMoOBlMCi4KB2dyYW50b3ISI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwFXVZQgFxGHAUEsCnW6AXIKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoTJwdRQYRXk1UsChMKB2dyYW50ZWUSCEIGUHVibGljCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKChkphoIGM0eVkUwKjwG6AYsBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAXRCljQjJjYYl1wKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLATBGJYEZFycQIYwKKgoHZ3JhbnRvchIfugEcChoKBlN5c3RlbRIQwgENCgsBQ2IzNXlXEIYJjAqMAboBiAEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBCVKXVWYjKRhVnAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgqTMWMHSWUgVWdcCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwEVEQaUM5gTiHicCnO6AXAKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBVyMCcJk4VgMlPAoTCgdncmFudGVlEghCBlB1YmxpYwoqCgdncmFudG9yEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEiERcCZhaBEYM8CpABugGMAQo3CghhY2xfbW9kZRIrugEoCiYKCGJpdGZsYWdzEhrCARcKCgJQVwc5mIY0MDwQ////////////AQonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgoTeQCGJFAJFVWcCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwFTQmYgY2SRVwMsCnO6AXAKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBB3Q1NWYkFCBQHAoTCgdncmFudGVlEghCBlB1YmxpYwoqCgdncmFudG9yEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEhaBNJdXKTSAKMCpsBugGXAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCnRDkhCJBgNwJhwKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAWGQZpYhUUNEVjwKNwoHZ3JhbnRvchIsugEpCicKClByZWRlZmluZWQSGcIBFgoJGTAgVJaDk3k8EP///////////wEKlwG6AZMBCjcKCGFjbF9tb2RlEiu6ASgKJgoIYml0ZmxhZ3MSGsIBFwoKBGYQGWFHQnETXBD///////////8BCi4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwGDmXeHGRmGKAgcCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwFTCGInE4FwCGNsCne6AXQKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBaDUmcHYwVHZ4PAoTCgdncmFudGVlEghCBlB1YmxpYwouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBNyJUSGhIMUIzXAp1ugFyCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKYSQ4ZwJ0SIESjAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgohhyQRlSciQSiMChMKB2dyYW50b3ISCEIGUHVibGljCowBugGIAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFJSZA3eGSSMTRcCi4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwFVgoaXUpSJACccCicKB2dyYW50b3ISHLoBGQoXCgRVc2VyEg/CAQwKCkJGEHmUYUdRQJwKiwG6AYcBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKARkHNQKACRdQnAouCgdncmFudGVlEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBOHIXOBNoR5kUPAonCgdncmFudG9yEhy6ARkKFwoEVXNlchIPwgEMCgpHhYcyRIlnkJQ8Cna6AXMKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBgCRBBEWQGIQlXAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgo3OViJV2mUIFZcChMKB2dyYW50b3ISCEIGUHVibGlj +CtGSBQrNkgW6AciSBQoyCgNrZXkSK7oBKAomCgZzb3VyY2USHLoBGQoXCgRVc2VyEg/CAQwKCjhAUzkpZChmhGwKGgoEa2luZBISQhBTb3VyY2VSZWZlcmVuY2VzCvSRBQoFdmFsdWUS6ZEFugHkkQUKspEFCgpyZWZlcmVuY2VzEqKRBbIBnZEFCuYeugHiHgrHHQoHY29sdW1ucxK7HbIBtx0KGkIY8JG2pvCepZ/gt5bhoJNTXuG/tmDwn5W0CgNCAScKCEIG77+976qNCihCJk3hopp3PUsi8J+rnMK0NPCQjpjvrojRqPCdi6MtYCbgoaHhoIhwChdCFTrvv70lLu+6lOGaqcKlJfCRjro8MwoxQi/CpfCdkrvhi4NrJ/CfpY0nJeCmg3c9JHtGQSnwnrmJJu+/vfCSlLF5OvCeuIPRqAo/Qj3vv7zwka+CXEx3cS7OiuCspvCinpY8YOKApjXwnZSJXci68J2crypdJPCQkLEu8JOHq+GwqPCflbRMPS9cChdCFe+/vci6PPCdi7BGdPCeuosuPyUvYAowQi7wkKikT2oi77+9RXk9aUsq8K62reGyvUBnYC4kcy3wkLSxJz/ivZUmXEXwkY65ChpCGE5z4L238J64p1c8I+C1iCpy8J6AqMKlSgobQhnwkbKt77+9JCfDh+GlgEHwkICQKW5VXmE9CjRCMmElJ+CslfCQk6Jc8J+VtC3hv7ogJfCWvoY54Yq8THgq8JCKr8OLLvCfnazDpuCxgHsmCgdCBfCQipZHCgpCCOCssmBtJz9jChpCGHfwnpeUVVPhhKHwn5W0OvCSv5xc4KuAJwo9Qjt7Y3bqo6gi8Jaugj7hg4c18K64t/CRpYEodi9n8J+Foi7gs4h18J2SvvCQurDIujRa4LqCLO+3j0EnSApCQkDvv71VyLrvv73gsIpbPTw8POKAkvCRoIjvrYMuzptW8JapqSLvv73wnoCc4Kyl4r6SfvCflbRc8JGMoOCulMKwCgpCCPCflbTgs6k6CipCKOCqtlQ9LmbwkLSaOuCpmuqpgOqpl/CbhLIqaPCeuKI9JVzgoZ4kKioKIEIedGA/K2Dgrp8k4LefyLpn4K+C4rStIuCzhj7wmr+yCi5CLD1+wq7wkYyj4LOL4ra1yLrwk5GVPOK3njNCw4zgoZYn77moPVVnKsi6b3w9CghCBj898JuFlQobQhlYVfCegZV+M3nCpWpW4LqVw5/wkLm8wqNoCgdCBT8iOyM9CidCJSTdgXvgqJvwnZSUJT454Ki8YzAmyLo/ZCI9UyMiyLrqrItgwr4KJkIk4YmYOEPvv70k8JCVtlglQk5rOuK0rSUhSyfigINAdNGoPyklChdCFcKl8J6ApCTIunrwkYKx8J+VtCZjMAoOQgw/We+/veGfqfCRpLcKJkIkPUMkdPCQlpcn8J+Jo1/wkYy7PO+/vWjwn622YCki8JGonSo/CgpCCMK/bCYnKmclCi9CLUw4IuqsrtGoR09E8J+VtMKwJifigYVcyLol4Yq14LqCalbgr4xGKeCvl3AvVgoaQhjgqLzwkJaMdToqLlwuP+K2rETgv5bIulkKJEIiLyLwkaSJJyZoNsKj8J+VtHckQy7vv71NJCTgt7LDrNGoPAo1QjM/Ojo/feGkq0E/U3BeOj/grofhjaTwn4mFIuC6hOCugvCfoobgppDIuvCQgKQnw6pgXngKMEIuY8Kld2Ap8JG8r/CQuq0/Wi/wn4OY4Ka9YOCvgPCQnrXit5TwnrqT4YySJci6NQo0QjJo4L+H0ajgs53Co8KlPUwv77+9NWxc8JGvtn7wkKiz77+94oKQXFLwsay0JE3wnZKmbQonQiXhnaNUXOOCifCfq6fgtaPwn6mBLzTgqI/RqOCziz178JCGmC06CgdCBXfwlq2/CglCB07wnpOzJiQKRUJD8J+gquK3gT3gqpDho54kKSJDJPCRjafwkKC4yLoqLvCQkrLwnYujQCfDrFrvrINT4KaQVvCflbQkXcOJ8JGPidGoeQpBQj/gqLnwkJa7WirwkY+iwqXhpZ9hXCrwkbWoKVxfSDd7WiTwm7KZ4YmA8JOPkfCQqIZg8J28gyM6SSRLwqXitoQKQkJAyLrwkbCl8JGMkFnwkY2Hw7ZKQ1zgvrZYJWvvv73gr4HwkaShPT3grYvDjtGo8J2QkVwn4K6a8JGLgUou8J6frQosQiov4Y6XJTLwnrmXPVgn8J6fruGdklY64LinJjx3XCTRqC/wm4ShSe+/vVwKLEIqwqUv8J6fru+/vXt5efCQk5pgw7V2YDrwkY2QKig876qe8JGMtnvgqZE6Cj5CPE1QwqVg4KaPYvCQoYngraEiJfCQjpLgrqTvv71h4oCR8J6EheKDqHvFnj4r8J+VtPCTkYIuZPCfr7ZdRQoEQgIqTAoQQg7wnZOBRFMv8JC6q+C+tAobQhlcXOGsu+GLmV8xSOCuvyrdny9vdCXIunR7Ck9CTTMu4KuQ8J+MguCwlicneMi64KCV77+9JUHwnrq58JCHoCLwkaeeUeC7nvCav7fhjIzwkb+t8JGqlzp+e/CRpInwnaqc0ajRqOCniMO5ChdCFeCvuD3wkISeLe+/vT3wkJaMOlF7ZQoZQhdOXvCQrbLgspk8XPCQvYsnPjzvqqgmXApQQk5RPDrwlrmPwrvwnp+44ra04KCd8J+foSfgrLM9Ku2BmfCeu7DhgKrhvZ3wnYWG4aKY8JCWkPCRjYtLRTw/4Lqv8J65lPCRpZg94bC+wqUKPkI88JCynErgtbBh8JCFi+GftfCehY/WmuCuh+CumT5g6q2iw7V4bEUnJvCts4EnbmAk44G1UjooSCPwlqysCh9CHT3wnLSeYFHgr5Dwn5W0fPCfq7d7VOCrkMK84LCQCgZCBPCRu6MKJEIiSlx8w5x8LtWZc/CQg4Y64K+QVyXwkaiCYPCflbRCek1BPQovQi09zoVbyLpi77+977+9e0488JC6q+C6hHvgq4vwkbyGevCeuZnwkKCOwqA8YC4KHUIbbkpyby7vv70t4b2bYVYm8J+VtCXwkLSgaj0nChtCGWNBTOCyoCJm4Kay4LC3eyTgvLUs8JGNl0UKNkI06qyW0ag68JeZrvCeuLnwkJiA77mf8J+VtDBcYyZo8JCxhMKlKljwkbKyIOCqgXvwkJObJQoSQhDgrqN7YMKl8J+VtPCRm4I6ChBCDiLwnbyq8JCgvPCWrIo2CiNCIfCrn63DqeGynVMn6qO38J+VtCxpKsKlTsi64L6K8JG0ugomQiTot4ss1o/wnZu/J0XDiyXwn6mkXPCeub7gp47Do3sm8JCSoU4KLkIsfiRLbeK2ofCdhJgv8Juxs/CfnpfiuZ3gqoc88JGmoPCRh6XhvZ1GYPCflbQKEkIQ6qWvYvCflbQiJ8Ol8Jq/twoJQgfigIhdw694CiBCHuC0j++/ve+/vSdKyLol8JKUscK+PSJUROKAke+skwoGQgRg0ag0CglCB3vwnL+AUWAKPUI74KuIUTwk4Kay77+9YiLhsLvgsaxqY8i68J+Ev/CfgKQ84KiP8J65vvCeuKHwnrmHXCbwn5W0XFzIui4KNEIya2HCpVPwnrux4Lqo8K6yny7hi44/4La98J6ApCVc4LOGVjon8KuGouGzsvCWrZvgrJAKHEIaIj1AeSVa8JGPgvCflbTIuuGlpOGDh/CbhZIKHEIa4aCW4bOCKeChnuG8reCzgjrRqCTwkbWTwqUKD0IN4oKN3b8n0agg8JGLsAoOQgzwkLyY8J+VtHHhvoQKRUJD4LuV8JGjn0Pqn5bRqDIl8JGMhvCflbTwnrmL8JGYnD3wkIaRU/CQoIJF0ah14aqJwqUu4KmLPPCRi5Ey4aqBJWgkJwoOQgwqYOC3hirhprI8OlYKLUIrJvCQoq48wqXwn6qh4LeQ4La94ra64La976y7zb/wn5W08JCoo3PCpeqjugpDQkF2Li/wmIyPXWHwnpe/8JCWr/CRioUq4oOr8JGYscOwPDsxc8Kp8JGHoSd78J2UiGUo4reR8JGOuXvigZDwkL23Ygo3QjXgqZE98J65kS7it47snJBJXvCeub7hvbXwkYyzUfCQoasqNznIuuC6hiTwkK6B4K+x8J+EswoZQhdRez9gJCdoJSc68J+Jo2DCsEwv4aCIIgoXQhUnMNGoKj9K8JGktFjwkKCIMPCen60KNEIy4b6b4LaH8J+btOCzsmTwkZORYDrvv73vv71+JWA186CHo2UnbvCrn73wsZ6/4LSPNEMKGUIXcPCRioZzJvCrn7t78JGassOR8J+hk1MKF0IV17Dwkou24KaFRCRg8J66huCov1FfCi5CLCcmwqXwn5W0VeC2tz170ah48J+ptSXhgJ48J+CwkPCRtIg64KmIVvCRjbQuCi9CLWA68JCeiXdFW8K74L278Janj13wnZWB77+9w6MqJE0u8JGNl0HiiqDhi4U8awo0QjLqrIwl4Ymc8JCol0vqqofwkY2XYDDgsZrRqFzCpSci8JG1ki7wkJ6CXO+/veCuvnl7SApEQkIiWy/wkbWj4Kiw6qeQP82/JD0iLj09yLo/ZDzwkaSV4oCD8J2Snu+/pfCQpL/vvJE/8J2TguGqgXbwm4mrd0bgp7YKL0It4Keg8JC/gkbwnp+w4LGWPOqsoj8m4oCF8JG1oiZ7RT7gqo8y4YqX8J+iozogCjdCNeCvkHAi8JCtuD/grpxR4oG+wqU8MPCdgYBxJOC7hnvVuS4s4LGsJETwnLyVKCcnJXvhn6B7CiNCIXVN8JColTpV8JCqkVwv4K+sLvCehY4mPSwvbiLqn7InUwoqQihO8J+htPCflbQq1o3wkYyH8J+VtPCehYYi8KuIk/CRsbpeLvCQs4lVChRCEjPgu5U9di/hropRIjo18JCriQofQh088J65pEIswqUh4KSVLjo68JG1p0dT8JCojuGqlApBQj/vv70n8JapiifCvi7wkbaYPH5HOjbDj+CzqO+/mk7wlrmKwqHgqYguJOC2uzLwnrik77+98J6lmcO+ITzgqoMKQEI+8J66lvCRtLwuXDo/8LC4qmbitrI/cDLgpaDgrp4ge/Cfg5dcwqXwn5W0J2ku8JuFkDrwnbyLWHvRqCbitqgKRUJD77+M4KiI4K+Q4K6y4by04LeFJ1I/PPCbhabIuuCylPCfq4Jc4aqS8J2Gm/CRvJDig7An4r++w7oqw7Dhqqnwlr2+PwpSCgRuYW1lEkpCSPCQjIU/8Ja1t2DgrLjwn5W0VvCQl6jhqpnDnDrwkKCANiJ7L/CRr5AnXvCQj4zwn4KiVvCTg6Tvv73vv73wnYS2e+CtgicvQQpCCgluYW1lc3BhY2USNUIz8JGDlfCflbTwn4mFyLritbDwnoWCPfCfoKBf4aaUeCJ88J+VtPCfiaTRqHnwkauW4rC6CrkVugG1FQq/FAoHY29sdW1ucxKzFLIBrxQKI0Ihw7/tn5UkYH7wkaS8efCRtafgrJB9PWPgtqNBP1PwkISBCgRCAj17ChFCDzbwkaSTedGoPGg977+9OgpBQj9yYC1c0ajwkYGoPirwnrKWJvCfmbHwq6av77m04KC9PMOeYXYv8JCFsi/wkbSJ8JGOjjo64LGIRvCQtZnCo20KD0IN76y+6qys4KK4T+C1lwoCQgAKOUI3Je+/vcKiwqU64KanRzciw6DqqYR5ePCQlrtqJsKjJeOAjPCfq6Jp8J64g/CRp5zgr4oi8JGRnwoQQg4myLrgtZXIuvCWhJHViQodQhvDkSfqpa8u8JCurPCego9J8J+cviXwlq2bIicKF0IVe8i6OmRlOVs6w6/wkbaTPMKl4Ki2CkhCRiIj8JGCuMOzQ/Cdqp0vS/CQloTIuj3hjpNQ77+98J+VtHh8Kifgr4YiXNGo8J66kvCflbQ48J+VtFhw4aqj8JCLp/CRjYsKJEIiQDE+77+9eD174pas4qelWiR+8JGLmj3RqPCflbTwkL6GPQogQh7hg4dO8J2Spijwn6uUP1zgroPwkKShVcKl8J64uU0KKEImOOC+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+XPCRtLrDvDDgupkn8JCum+GglVLwn6Gq77+98JGFguCvkCbwnrmJVk0qdk0gIiV2yLohJDAkZgoqQijvv73gqoxU8Jy9hSLCpdyc8JC1oeCyji8v8JK/qCo88J6jkeGggcKlChhCFtGo8JG0uvCdk4MqPci64L6+J/CeubkKJ0Il8Ja1rDk/U0DgvKPhn6M6POGfplxCIifigpXCpS7wnrmS8J+JogoFQgPvv70KG0IZLuC1nEN78JGMgj834K+I4aCAVF3hs4FAagoPQg3RqO+/vXvhi7rwn5W0ChxCGuCmj/CRioMmezzwkJa7wqXwnoCFOmHwkbuiChlCF+Gkji8i8J+qhvCRjZckJMKiPeOGoDEuCjhCNiQvRTV0cT0vfuKBmfCQloM9J/CQoYMuSkQmL0Mk8J6FhPCQoI/wkY2L8JyylD0k4LqEOu+/vQo7QjngrLYo8JGNrC4vJOC1n3Tgq5A+JyIuPOqflu+/nGrwnrqh4KquP/CeuZQq4LSP8JG1uFTwkJa2Xz8KH0IdKnjDoibwkISze+qqg37CpXvvv71377+9R/CvpLUKGEIWa3t+WzrwkY+J6qyO4bya4Kay8JC6iQoCQgAKL0ItbysxJ1Q6TvCWrZd78J+gns2/IkY/8JGPouC7gy9cwqUs4K2M8J2LrvCRioU8CgdCBfCehYBVCiZCJGsm8JCniXtBwqXgrYckTu+/vU/wkLSTP0oqLvCRi5tgLkjRqAofQh3wm4qx77Cx77+FOO+/vci6S/CflbTCpfCdi5EpaAoCQgAKI0IhZvCdho7IuiYkIsO14Z+g77+9JU5g8J65h0pv4K6Q4KeOChdCFe+5l++/vXsmPu+qjOqSk+CumSROJQocQhpcJNGo76uML30kIi4v2ZHRqMKpLy7CpTNJPAoHQgVh8JKHgAooQibIuuC2oiZx8JatnuCnhzzwkKi4PMi6N2l2V2Bg4LGIyLok8JCkggo3QjXwnqWJ8J+VtCQ84K2cYPCRg5xv8J6gm/CdlLx68JCjsPCQqIbwkaSOJ9Go4LSQckQ78JGWtQoJQgc8Ti7wnp+gChZCFC/hna5Jw6TwkKSJ8J+EhW0/4L6rCipCKPCRp6R+8J65nS4qffCbhafwnrmdwrMq0ahhMyI3PDI4YHsvL/CQs5gKLUIr77+9JC8tJWjVr++/vcKlX2DwnqON4KaPwrPgqrVhJTUkLkQgRkTwlq2WYAopQidDSi81cDzDo/CsgoA9yLo/4Yu4J/CeuaTwrbmO8JuwmlzwkaaiS2wKI0IhaFwlbOCtoFxnL/CeuZto4oK7JfCflbRBYMi6e/CRtapcCjxCOu+/m/CQqJNgNW8nduqgt3s8T8i6JsKlYkTgsZ0i77+98J+CqcO38JCOgeKzi0vvv73qrK3wmLSAyLoKI0IhwqUnOlzhjqBQOnTwm7KV8J6AjvCeuYdGLmw/OCU64K2LCkVCQ826OeChqu+2tWfgrI/hor1B4LeKe/CQqLnwn6G64L+UOjtcJOqhqT1lRDIu8JCksmDvv73IuvCRtqfwkY2Q8J+prDwKEkIQUPCQlqMv4L6g86CFg18newokCgRuYW1lEhxCGlLRqD1wPyLgrZzwlryvLvCRjoZMw69477mUCi0KCW5hbWVzcGFjZRIgQh5fJOqsjeCsgz884Ym6RdS4UCInYzYm8J2Su+C9jCIK/wO6AfsDCo4DCgdjb2x1bW5zEoIDsgH+AgoSQhDgs4fIuibhn7A8cEnwnL2sChBCDsKjJe+/vSdeYT/wka+dCj1CO186JsKl4K2V8JCpl3pcVT3gp7omesKlOPCflbQl8J6Eh/CRqKnwn5W0eOCni+CsrFgr77+9PeCxmTRrCh5CHOqnj1zwlr6c8JKHjDrwkY6L4Kmb8JGHsfCdkp4KJ0IlclzwlqyDJHsnJFxc4KmZLVxqdT0uwqUgXGzvpIku4b2Z8JaltwogQh7wkZCYKvCQvIHwn5uj4KmR8JCWg3tYdTo/L+CqsyoKLEIq8J65mSd38JGMrScvOvCQl7DwnYWJ4Ki4Oilg4Z+24K6cXyfhuJ7wkKybCh9CHeqslfCdkrslPOC9u+K2knhDJ1wie/CflbTwkYOWCj5CPMi6wqXhg43wkL6+8JC9gCTwkYiGYDoleyTwlqmC8J2SjmBRcljwkJaK4rqJ4Ky2OfCQto/wnrip4bOFLwoCQgAKGUIX4rqMw5DgrJDwsKO5XeCojyI/YWsuLioKKAoEbmFtZRIgQh5u3ZXwkJ2AcuCpkT3qo5fiuZIuK+qSsTpgP/CQoKMKPgoJbmFtZXNwYWNlEjFCLzcu4LOx8J65i2B2w48iLsOFLuCrkCXRqPCRjZ/hqoTgo43gtrkn8JGZpvCfoLlrCtYLugHSCwqWCwoHY29sdW1ucxKKC7IBhgsKBEICPTsKEEIO4aSHXEc63Is8Q+G8my4KI0IhyLrwkIyjLnvwkY2QJu+4tzrvv73wkLSyJ3LCpfCRjIBgCgdCBTpcSUUiChBCDu+svnvIuuqvnvCWralgCjRCMinwnrm1KvCRr4BpYFtN8JGKhj/wnoCLKkdU8J6ApPCRgqXwkY6u8Jarn3tcPMKl4LqCCjpCODpKwqXwnp+lKvCel5HCpe+3tVw68JK/kfCRi7ngr4rwnrqm8JGkt1vCpSQjTic90ahs8JGyllA2Ci1CK/CYtIHigJFe4KGe4LGHe8i6KjrwkJOQTXQ6XuCutyorItGoJ8Oj2IdHcUsKCUIHwqXguoRsJAo8Qjpg8JGRnic/4YqL77+94oCGUfCdiLh48J+JkfCflbTwn4eoPU/IuvCQqYQvJ+Cvl/CflbRX0ajhrr4qCihCJsi6Jns9yLrRqPCWq7Xvv70u8J+VtHPhq4Hwq52/w7Pwn6iGwqVcCgZCBOCzjCYKCUIH4b66YOqsjgotQivwkKaVJfCQhIJT8JGkjSI9WeC9oiVg8J6EtFxgLGDwkL+uPlThnbPwkb2ZCjxCOmDwkbS8P2rhvZ1kw7/grqrwkYKvfD/wlqyHJCRc4YiLJHvqrJTwn4OaI/CflbRAYPCfn7Dguok9UCYKMkIwYOK0reCzqvCRk5DDreKAhfCforrCpWDwnrmXIuCvl++/veK0rSbgsZ3guKBgKNGoCiBCHvCQq5virpPjhrRsdkDwkKC8e/CflbTqlrbDlzouJAosQipgOuGostGoKvCRjK4k0ajwlq23L/CbhabgqZw18JG8ryXRqPCeiq3IunIKDEIK4aCWwrvwkLmnPQovQi3vv70mez/qoJ3wkLaPP/CQjqngqLk4e/CSv5A6L1LCpeGghlZ5YmDwn5yTKWYKKUInMOqjlsOgTDwn4KmaJGA94Lqzdz0n8JC5qCzvv70u8JGKgsi6XEwqChdCFeKuieCmvMK1MfCQpqlw4Yq577+9PQoMQgrwkaSJ4L6d4o6WChtCGcOTXMi66qOPKuGgj/Cdhr/qop5B762Bw50KA0IBKQowQi5sKk9c8J65mW3DuTpke/CRtLog4K6S44aNL10uYC/wnoWAJuChlWnwkIaS44aNCgdCBWx8yLoiCgtCCe+/vSdg4LezJgoUQhJnWCfwnZSILtez8JGTkytFJT8KFkIUKjHgt5bhvo4u4KaCROCgsirgrZ8KEkIQIiTRqOC7ifCflbQn8J2UigoXQhUm4Yqy8J+VtOKRgvCRkaHCpeC3rz0KGkIY8JCij++/vfCQipzwkYux4bGF8J+VtG55Ci9CLSdnUT/Cv++/vSvwlry4dz1SOlbwn5W08JGSqiEiXDrwkaSWw7HwkZmj8LGMoAoEQgLDlAorQinwkJ6CKiUl8J66o0/wkLWbOirgr5Dvv5bRqGTvv70/4K+LXMi68JCrgAoaQhjgqZl2Km/wlrWX8J2glWA/8J65lPCegKcKHUIbe2/wkaSV77+90ag6wqUxKvCeuZ9LP+Gdo180CiNCIfCQj4ku8J2cinvRqDzwnp+pPi1oP0Pwn5+w8J+VtOGykwoSQhA94b+hXGtvIvCWrozwn4KtChlCF1nwnrmSVibwkKeSwqXwn4K+YCvwkpGyCidCJV8n8Jq/t2nIuiXwkLaPWPCRjYxLw4xRZ/CQnoXIuiUxe/CSkKYKQEI+8JGov8KlOkjwsZWc8J+VtD7vv6Yu4b+M8JGXkfCen70n8J6FjvCRpqMnw5RcW+qmmOCjjvCeuZvCqPCRprsKIUIfPfCdmqUkwqUk4LOseicn8J+VtNGo4K2sP+K4oStsLgohQh8mLzJq8J+VtOCspS3gtIVFd3E/YGA98J2UmGDRqCUiChAKBG5hbWUSCEIG8JCpr0xgCiUKCW5hbWVzcGFjZRIYQhbhpp978JGPj3IubfCbhZBhLvCepZZ9Co8IugGLCAr2BgoHY29sdW1ucxLqBrIB5gYKDEIKyLrIumDwkY6LKgoiQiBrIi0m6quzJUhceUvwkZeS4aeSUU0i4KmRaG7RqOGlpwohQh9fw7nwkJa7PU4q8Jy8n1jwkbCgd+qloy/qrZXwqqijCilCJ++/iu+/vXDwkLyP4qO+8KugmOK6kcOm8JGPisKl8J65lOGfg29UJQosQio8Pz/wn4mI8JCng2Eq44W7YHDgppBJWeKupGBgJ+C7iUXhqrrgrrhiaTwKK0Ip4Z2yOvCTsI7vubPwkZGg4Ki58JCKmmlxJiZFYDDhnI09cj9We0Xvv70KAkIACidCJcK84Ki4LvCeuYnRqCTCpuCovCRc4Lqbd/CQp4pcfDrgpIInIlwKLkIsUPCdlJHwkZmkMjzwnoCj4LazL1/IuvCQgIvRqEjvv73wkK26JC/hvJjhtocKSEJGJifwkaOqyLrwkKC8ZuK2j/CQs7Dwn5W0wqXhv44iIjrwm4WV0ajwnrqj8J+VtCXwnrmJO8OWQXvhsZLwkbSQ4amKP+CpmQoFQgMlJyIKR0JF8JCplsi64K+X4K6Q8JGxkmpm77+94oOXe/Cdkrt2Z/CRjo7IuiTwkKmtw6xc77+EL2Pwmr+58J+VoVQuJD3wkJa877S1CilCJ0Ai4b+5KPCRiJNcLy7gsZo/yLrwm4SZ8J6Lvz/gqoPDoWEiw6l7JAooQibwka+w8JGNnuCzh/CflbTigpVc0ahL8Jy3vvCQrpvwnrm+Jj3DiQonQiVPwqUzL/CflbR44Ki+6qy08JCAinJgRmQueMKlLO+/vXvwkb6wCidCJWjDo8K78J+VtCZc8JG1hc6G8JCtpeGlgPCRmpXCouG/jPCQir0KKEImXsOj4LuGJy4nIe+/vVwudyo9buCwhuK0mV5jLjw7w7h7IVwhfT8KEkIQ8JuygfCRvIRgMmLwnZS7bQoLQgkiT2t+8J+VtD8KE0IRw5ph4rar4aWz8J65n+C2vVsKEUIPKuCniPCflbR94YyT76yTCg9CDS/wkIagKVxk8Jy4gksKL0It4LuG4K2IV/Ceua7Ctkw/OvCQlbp777+9Wzvgp6BtPDBfRcKjPGA28JC6l0AvCgxCCvCeubImOvCQlbEKKEImyLouLidc8JG2kfCTkYXwkY+PSDwnVy/wkZuI8J65gvCRr7Dgra0KQAoEbmFtZRI4QjY84KmZPXvgsYxgJsO5X/CQoIVgcW4n8JCNg/CRpZcuYPCfm7jhp4dg8J6TozY5SvCRk5MlaHQKTgoJbmFtZXNwYWNlEkFCP2PwkaScInQ8VmA9wqXqrKYu8JC6rfCQs63gtr3vv70kOntg0ajwkZeQ4YyT8JGwvibwkLqQ4aq/XDQ8PNGoYAr/DLoB+wwKswwKB2NvbHVtbnMSpwyyAaMMChJCEC830ag88J+VtCcqL/CWv6AKFUIT8JGNl1JEwq8nVPCfqoIzXuGihwoEQgJKPAo2QjRA8JCVtMi6c1jwn5W04YqQeyLwkJSFLi7wnrmCJ/CQjJvgr4c8LiAq4Ka/8J65u/CdqL1cCgdCBVvwm7KeCgZCBPCRmrYKBkIEe+GkpAoCQgAKFkIU4LaK8JCWrvCQqYjwm4OGez3vt48KO0I5JSLhvrTwnaqoYCLwlq2+4Km1Q3nwkKiV4KuQ8JatoT/RqHHhiotkdT3wkZyWbsO3LuK1sPCQrq9vCiFCH1kj8JatmCrwnoCkPfCQgr7hvJgmLsi6JvCRvInhs4QKDEIK8JGNhOCzoj8uPwo0QjIk8JC/oUoowqLCrjo/zIPgrolkL8i6PfCeuYfwn5W0Li4+JfCQhodlUXvwkY+C8J+kswoKQghj4Yyn6q2qbgpDQkFK0ajqo4LqrKzDiWwmYCVhPOqhreClne+/veCgvi824YmU8JGNkPCdvKfwn5W0VX5g6p+18J64pPCQrLpR8JGbnQoyQjA0UeC9jNGoau+/veqsk8KoJi48VvCdjKLwn6K6P+G9mTxhLz3hg4c94KuHOeCmiyQKBkIEMSU6XAoHQgXhpYA2LgogQh5gZfCQq5Y/4Kiq8JartO+3j0Qk8JCTolDvv71ayLoKIEIeKuKApci6OtGo8JGblNGo4K6J8JGkgvCQlrxZL0UiCgpCCCJl8J2FhSc/Cg5CDFbCpT3hpbRaZEnRqAoyQjByRWcoKu+/vfCeuYvDlVDwm7KV77+9yLrjhpLqk4YvajrwkpGz8JCUluChmMOM1ZEKFEIS8JCQlkg5L/Cfq5jwkaOJbsi6ChBCDiLCtlh78J2Sq0vRqHI/Cj9CPXBsfsi6Ly48ceG9l212OPCeuo0w8J2BiyfwnYuL8J2Loi7hrbtg6qKq6pa3eCbIuuG8nFDwkLWHbPCQrZwKFUITYD88cCXRqD/IuipWICXwkJ6cJgopQici8JC0ljpRYPCRtIh78JColfCfoaJRdj3Wji7wkKiWVvCeuYtpw70KEUIPVsOL4Ku8cVPRqPCRprN5ChhCFiQ88J+pofCdlIpzJPCQqIVc0ajqoLEKKEIm8LGMnvCQrpxH4LSs4Kyl8JGOpvCWhJbgoIHikYUgTDomw4g/clkKBkIEb+C3lgpAQj5h8JGNiOCopD3gr4fqqZNc4reb4K+X77+94KeXU/CeuIEk6qC3cntA4oCHL8Og8JG0vci6POCrsXLwnrmpXQohQh8k8J65p+C+qHLvv71X4rirLiVrW/CQoLzgrZbwkJaBCjNCMeCviMOB4Yuo8J+VtCknw7zwkbKq77+9XvCRpLXCo2Bc8JCrrzPwloSa8J2UufCflbQKHkIcSfCRm5om8JCrgSXtn4U74LGv4KmMJGhK6qGHaAoFQgPgsaEKQEI+wqUu0ajRqD3goKnhiaYu8JaimfCQtbTvv73qoZXwn5W08J64ofCfn4TwkKaJXPCdi4Pgp6Pwn5W077mYKjoKOkI4e19OYCThj7o8UDomXkt7OvCdlL7IuiDgsp/gr4AkLl3gvpLYivCQu4TCpfCegIbgsZ17Ku+5s2oKCUIHJD/hqqHCpQpAQj7qoZ948JC6jfCRnYPhqapl77i+JfCeuJ098J65nWnwlqyzPCLwmr+18JCtjFxX8J+VtGDIulp68J2SouGNsgo8Qjrvv4NcwqVXezw98J+DhsKg8J+pp1RQ8J65knEnJfCRtZTgraDgqKw677+98J2Ri3s977aO4KiB4aGLCgpCCFzwnrm3ezxtCj1CO1xUPXss4YqKYC9+4oGwLyXgu4DgrY0uKduVY+qsgvCRnKM636fqkqLwn5W0UHsq4rS68J6ln/CflbR7CjJCMPCRm5/wm4Syb2jDh2Dhi4jCsGDwkL+zJuCume+/vfCRjZfhqoPgqr17eOCih+CtswopQic68J64g+KzuVI9PPCwp4zhvr8/NipuYPCbsZRgYHsnJyXvv73hs6EKKUInMT8/dvCflbTwn6CuLVMuey7gsJAmZ1rIumZz4ZyNZsKlwqVd77+rCgxCCvCQgYLwkZmV2JsKEwoEbmFtZRILQglCPPCepZJcSmAKLgoJbmFtZXNwYWNlEiFCHypeeWo08J6jjCBE8J65n/CQjq/gt4BY4LeoIvCflbQK7Qu6AekLCroLCgdjb2x1bW5zEq4LsgGqCwoTQhHih4LgqI/RqPCQgLM94oW4fQo3QjUk4KizPfCWv7A6PeC7n+C9k/CflbQiW1xe8J+VtPCepZHwn5+q8JGkm1nwkICJ8JGvlFAqbgoEQgJ8MgoOQgwq8JC7g1wnPfCRg7MKNEIy4K6yRC8uUsOqwqA97Z+jWyXCpSM/wr8/4KK06q+fIl49LvCQto8m4reULPCctrd+JiAKJkIkwqXIuickPTo58JORkOC4qsKlV3DwkKujPF098J65vPCehYUqCitCKcKlxY7RqOGnoPCeuYta4LeWw4/wkbCVw7V7LFZhL++/veG/ssOC4aWACghCBvCRtZTDtQooQibgr5Dwn6uoavCRvJw6XG9cWfCRtIInLCZ+Ljs9KuKBij/ita8/PQowQi5Sw7svPcKle3LgroNc8JC6iCbjiIPgo4A+TMi6JCTvv71APDTjh4E7KuCmh1FwCjhCNtaPaPCcsbvvt4/wn5W0Ij8wOErwkbaiJkcnwrUvJmo6Jljvv5o6de+/vdGo8Jatv1zwnrSRKAohQh/hv7PwkaegbMi64aCHcU49SeChp/CRjaMvLyY7YMKgCiZCJO+/m/CdlIjIuiI64rW/4L62OtGo8J+VtDZH77+90ajWk2A6ZQopQid38J+VtHvhoKo9Z+qohD/wkKKv8JC7gns0PT3zoIS+4q6NKsKlXSUKKEImJOC2vfCeuo86NifwkYS2XUs/KiUtKvCflbQuciTCp+C8qfCdlr4KBEICfi8KMEIu8JGNszxFa0vwk5GPP1HwkKOu8J6Ap0hFLuqrpuGlgFl88J2DgGpo44Gl77+9KgpBQj97Rlzvv73gppNk6qCm8JCEgnHgraPRqPCflbQz762AJOGfjU3wn5W0Sj86wqV+LTjgqZ404LSQ8JGCvvCRg6UKJkIkVl90Vi7Cqk51JmBQ8JGPhcKl8J65uVwn8JCzhjrgsYTwnZKfCgJCAAo3QjVtKipUyLpu8Ja/seK3i+G9m+GqhTDvv73grZcvJSngq6JpLmAvQycwbFwv4KaI4Yq78Jy3tgoCQgAKLkIs0ag64Kay8J67se+/vVQp8J+VtEMw4aWrP/Cbg7Pwn5uxJ23wnZSX3KLirbsKOUI377+94LGm4Ky44b2dQzrwmKS/w6gvL++sgydlY1/Cpci6YEE/MXY84LqU4Kysd/CWqYwk8J+VtAo5QjdT8JGKjPCQvoHwn6yAyLomJuCoj3I/76yA8JCegUY8Im46YMKuPy4/8JGYvT9cb/CflbQlPScvCg5CDG1C8J65kUUsVD0kQQoUQhLtnr7wkYOy8JCplMKl77+90agKIEIeN+K1sPCeubF7Wjlc4LCIP8KkJvCav77igoXwlrWQCjBCLvCRnLPwnrm+8JGbmvCbsoQ8KPCehLxheyIp8J+VtPCpjrMhJOGdsOKBsPCRpIIKAkIACh5CHNGo8JGcn1JgXCQ/e1xX8J+cv2bwnrihKOCzlUgKDUILOkfgs4h54YqbWiYKH0Id4YupOsKl0ahr76yWL/CQk4nito97JTpv77+9YEMKMEIuJi/hjJLIuiIt4KiWYPCdhKbvv71cJDrRi/CQvb5SJ3HDj/CflbTwn5W0IjclJwozQjE8L/CRrIU/J++/vfCQqZQiTOC2vVw/XO+/veGLjCUu76yBwqVM8JCPjFxyY2XwkaSJCiFCH+C/mCPIukpc8JC9i8i6JWQm0ajgs6Mu8J6AnFzgt4AKT0JNRcKlLvCQo7FO8JCouCTIuvCfq7jwopOs8JGbgyUi8J+htvCRspjgtqhc4LOGw7PwkJS/XGXRqOK7sT3itK3vv71g8JGPguGdpVfgqagKLEIqZ/CfrpYoJ1A8Jns/P+ODnWDwn4WL8J+VtGDgprZ88J2StEHwn5W076yVChhCFu+/ve+/vfCdlJnCpVwnffCeuaLRqGQKGQoEbmFtZRIRQg8myLrhi4DRqOGKrXNl0agKDwoJbmFtZXNwYWNlEgIIBArZELoB1RAK6Q8KB2NvbHVtbnMS3Q+yAdkPCgNCASoKI0Ih8J+qhzDgq4wv4oCDZOGsgeC7geGxqyQz0ahc8JuEsms5CgdCBSvjhYd7CjRCMvCQqbokJvCeu7HqrJEu4raFW1Mn0aguIuK3jWbwnqWV4LWPYSp74LKBYEDgt5vwkIaKCiRCIsi6c2fgppjvv73vv67wn4izw6LRqPCQhbNgfvCflbTCpVIKO0I5JSLIunzCpSrDlydcT1zvv70mKuCps2Dwn6CJyLrIuiLCpfCbhIY2PfCQgrLwnoSwNiREIeK3hdGoCg9CDSvIuu+/vfCQgLUmYCcKHEIae+CnhMKlPPCRhYFp4aGf8JG0uuGfo2DgsaIKMEIu0ahcwqXvuLvRqErgurYnJ+Gnv+CuseGlse+/veCjjFzCpWrgqpvwnriCTCdGLgomQiRT4LGNLzw9KS7hoZ0i4Ymu4oGH4Kaq4LGd8JCgg1QgZyzfpi4KH0Id0ajgp7U/6p+RJmAn4Y6YXjLCtOOAhVpyJvCRtpcKNUIzI+Gds/CflbTwnZOYL/CWtYPIutGo8JuxmCzwsJKCPfCRjLI98JGKkPCRtJPigpLwkKKsCkFCP+CrkF/vv73hiZg6TPCel59Q8JCAh3nCpfCeuLklIiXCqPCRj5NnPu+9uT/wnqCXPeCtgNGo8J65vtGo8J+iuwooQibRqDjvv7102IkwQSPwnYOhYSbwn5W0KS9OU8i6Ny468J+VtD7IugoQQg7gp5/wkYyyTyrwnZWGRQodQhsiUuqltPCetIpRItGo0ajIuirgprJZOirRqDcKBUIDw4cmCgxCCi99QOCxiOGJjD8KMEIu8JuFlXsiPz/irpUmKfCeuKQ/Il868JaptCJCJcKlPcqwJvCQgLw6b+CupOqimwogQh5xPC5ce2BZ8J2SpsKldvCQnoJqInMzwqV7XGBbPz0KKkIo8J+VtPCQpL8u8JGkgS7CtFXvv73wkburQOG4tzjwn5W0yLrRqGTRqAo4Qjbwn4mhcfCRsZ7wnLyb76y8XTomXFfwnp+pe1wicOqPqOC5hMOd6qmNXu+/ve+suci6JeC/ijEKH0Id8J+VtHDwkKiN8J2VjfCfq5o5XOCmsPCflbRVZUQKBUIDZzx+ChFCD/CdkqY/8Jy9vSYl8JCrqwo+Qjzvt49177+P4rS44b2bY/CQs4zwkZGdJS7wnrqiw77IujvCpeCwv/CRkZjwkKCqcDzwnqKVIvCehIHvroAKDkIMez3IuiRC4YOzLnIvCgVCAyTNugoUQhIl8J+VtFcuezzgpqU9JPCWrZ4KKUIn4pGD8JCgt1HRqFwi8J+VtEfwn6KUbeCsg/CfqpA9W2DDr+Cmh3snCgVCA+CssAoFQgMnSD0KBkIELtGoPAo6QjjqoZckcvCehI9nI8KlPX0sJSI+8J+VtEleyLpg8J+VtDw8P3fwn5W0P+C6hCbCpeCnjvCflbQ0aAohQh89J+GghvCeuZ3wnriBT2DqpbF2J/CepZ/wkZChcyJ3ChxCGsKl4KehyLrvv71E4KuscGTDje+/vV7hvZs6CkRCQuCssiXhsrPhsrHhvKdW6qiU4Kiw44S0ID8k4LevL+C+vCTwlryT4bOpVT3wkKqLwr7wkKuYLTpQyLpgWOGMoe+suworQikv8J2MtuC3quCpkeCmsvCflbTwkZePZjRgIvCWrZ7gqIUlIuCnsUk8OgpIQkY68JOzviXwn5u68J+VtDUl4Yq6UOqjkFrgraDwnYum4LqEPPCQpr8/yLpj8JCOmT/gt4pi4Kq96qi18J+VtMi6KuCyrMi6ChRCEi5yQeCxllLwkbKtP/CQqJYvRgoZQhd7MfCRq6Um77iXdio68JODn3Mg8JKRtAoaQhjwnp+lXvCQq4pcP+Czo+GptlzwkL+jeycKOEI28JCMlk568JG2leGOlj9gOvCRgK8uduC3kvCdkrjDieqmu1sjXD8s77+jJfCfgqYi77+GREA8Cj1COzzhpqtCJPCflbTDnnnvpKYu8J60u/CRmrguPCs6PzjVvuCskD1gbTl4LlfwkbSQ8JGjoeCnhPCflbQ6ChNCEWLXtPCYrosq4LaBOkF777+9ChtCGc6X8J64mvCflbTwmr+1SPCeua55bFnhop4KH0IdO1rwkJKmwqU/UvCbsbvgqLnwnZKDaPCRpIzhip0KS0JJ4ra8w5bgqa5T8J+VtPCRjaklT8i6YCLwkJq/PfCflbTvt4/wn4O18JGOjmXwkK28w7rhibjwkKe3NOGJkSJt4b+9IuK2vuC2twoIQgbtn5QjJn0KPEI6wqXwkKeC8J65nUfwnri2J+GzhCRhLiZcddGo17DwkYqKPGHCvvCeuZ09LNGoJPOghIFg8JG2qeOFrgoPQg3grrNkPPCflbQu0ahuCgRCAsK5ChhCFvCQrIEvPPCcvKJg6qWGe3MyL2Pvv70KK0IpWCjwkIyTI+Cwj0Dwn4miYmDCpTzitKdE4KaIQNGo4L2GPyY/8J+fkicKFEISyZ08fTrwkYy5w6UmaXkvYF0/CkRCQjrgrZYvyLolNuqogfCRjIFu8J2SqXnwkYyyOsKlM/CRtaXwkJapXDbgp5fCoDjwkaSJLnoi4LSP8J+jgGs1MO+thAoyQjDguqUkRzpb77+98JGPmMKl8J65n+CpniJc4byr4ZKqPUlY8JGvjVxG4ai1fG92UkoKOQoEbmFtZRIxQi/CpfCRjo7OjNGo8J2Uvl/wnpOZ8JChhOCnl+CujiRZJD9m8JGMhz1nPkwgL2RgQQosCgluYW1lc3BhY2USH0Idcci68JCjr+CunC5KzbvCq2pgPu+5qEU8aSou1o0KnhK6AZoSCswRCgdjb2x1bW5zEsARsgG8EQoYQhYlQOGeqy5GJOqssOGqvi40w64u4ZyQCjxCOmEm8JC9gT3wkI6sL+CtgMi68J+diyLwnrmCYEThnazwnZKq4rWiIlHhi5LwkJOjwqXvv73vv73itrYKS0JJ4LWze/CeuKLCu3vwnqGJ8J+VtGXhoIHgqLPIujrwqr2+PMKl77+9wrPgq41d8J67sfCQlrUn8JKUlCQp4K6QYGA64LKg8J6fugobQhkuVOKHtvCQrLk9PG/vv73wn5W0d/CRp40kCi5CLDA/4LSA8JGxhETwqo2eP/CQuqTwn5W08Ji0g/CflbTwnZ6CXGDwnrqjedGoCgpCCOCmsipf77+9ChdCFdGoV+C6iNGo6qGu0ajwkbyPRuCpkQoqQijhobRUyLritKdcYMOgJeqWjDNmKEc64Lam4L+QwqXCpSckKjrwkZmWCjhCNl17XOCmhSolwqVV4aWA4LKqwqVcUSfwn5W0bz91PVMlJWnwkaSV8JOJscK/4auFdW9F8JCThQoYQhbwn6654YmWKu+wtfCQoKTCpSfqrIJhChJCEDwkOu+/vS4+77+9OSQvL1oKE0IR4b2Z4Lqw4aqW4byd4YmYfSYKEEIOyLrgpo8m4YyV4L6rXD8KAkIACgtCCVnwnrqtJiLCvAoSQhDRqMOO4KmCJlTqn4HwkYy/ChZCFD/hvLYpe++5qcOZyLplInp0XC8uCh1CG0RcKm0ke1zitrEqXEUvPOCxnV1gYPCQhK3DvgowQi7XsO+/vVBaWkxp8JCrsy9bWko64LaC76yWw5ZRdfCQkqHwnoCd8KyCrCI/KnQmCjBCLvCegLbRqHvgtpPwkKiFKj8m8J6lksKlVyovJvCQqIXwkYOBfCbwkL+uNe+/vW4KGUIXX+CnnSU6SXsrID8lLvCWq7UiKcOMwrgKJ0IlL/CbhZXgq43gs6BvZ289ezDwkYCs4KC0UEQy4aCUYOG/vuGihQomQiTvrJQ8bXvDuvCTvJTwkZyiXyI/LiUjUmDwnrm34Lep8JC1vi8KM0IxT2Xhv6cu4bCK8JGXk+CptuGDh8K68JCWkCbCpeCnqMONJsKsOfCcvLjXtDTvv71SUQokQiLhg4fIuvCRr7bgtoHguZB8XHXqqItH4Kiu8JGoonJHXi4lCjdCNS/XsCFAPzrRqCLwkKCI8JGcsjp74Z2BXPCRkZXwkIuy8Ji0hty2OsOFwqTwkY2mdjrwkZmlCjJCMG9mKjzvv71gYO+/vSHIumgv8JGKg/CRsYXwkY+CfUFu8J60pj8h8J+fp+CsuHnCpQopQidxe/CRu6Eu8JuynS/fszpgwqU/KlbwrrKn8JGjmzTjgIUuw67vv70KOUI38JGDoCbRqDPwnqSMLnTwnrmUIi8/4Ki1S/CeubvCq/CflbTCpeCuqfCflbTvv5POk1N7JS4qNwouQixRJC9kd/CRiohb4Lea4bKWcuCxruCogWrwqYOAPeGNrvCSkJvwkbKS8Jywkgo6Qjg6L+GisUTwkYy78J65j/Ceo4gu8J2UifCQnrlYKnvvv6xgXeKCufCQi6pgPOCvhuGdry4n77+9JQo2QjROXGJmPfCWqaEu8J2SuyVt4K+H8J+guCzwkpOlYGjhiabwkIupUe+/vWDwkLqsbPCflbRrCjtCOfCRjIlSe2DitrRcKlIk8J65lMi64KuQJy/DiPCRioDCpVTgvp3wkLqxQdGoPDjRqHzwkY6V8JCtmQo3QjXwn5W08J66oj/wnqOSXDzgtJArYOCwhCY94amk8J+hl+CnoiVTLXvgrrF3PeCxoXHgrorCpQoQQg7wn5W04rSt4LCQ8J+BiAoGQgTwkJaMCg9CDXnwn4KxIvCWubrgqIMKAkIACglCB++rj+ahrFwKEEIOwq8mO+CskN6n8J2qqTsKKEIm8J+rtfCfhJBOP+GNpvCflbQ6VeCvi/CQo6LwkKKnY8K/8J+VtC8KHEIaIvCdkr7hs4cuw6UvXPCQvrst8Kqep2snez8KD0INPSzgsrfwkJalSXbCpQopQifwnrm1PfCQl55j4LeQIXJdyLo8Iip7PO+/rF5MVnvwkY+i4reVJU8KK0IpJTTgsYHiur/qnbvgqYg/JU3wkJSBUvCeuoZN6qmJPSTqpIMiOPCbspQKGkIYyLrvv71m8J+VtPCflbTCpU3qrKjhuq49Cg9CDeGjkfCeuZnguoE6Jk4KC0IJ8JCWrvCRtYQ1CipCKCx58J+qgS8qOsKl4byc4LibJCnwkbyDfeCtl0PhirVOYPCflbTvv70KI0Ihcy3RqG9l4LKd8Ja9kHJgPDwq4YmVe0zGpiRc4aSozow9CjZCNCrRqPCflbQzPHFp4LGaYFw2V8O+WyLCvvCdhLVx4YqLWFzwlq2Z0ajwkI6/VzrwkJ6qLzUKKkIoNlzwn5W04rWw8JuFpHvwnLmp8JGBjWQv8J+irDrRqDvgt4Vye3rDlApHQkVEffCQlqot8J6Xv0Qxw73wkYqNyLpgJvCQnadKez1cLvCfqYwn4Z2QW/CeuLZh8J+areCniPCQkZzgp7gk8JC+uvCtspAKLEIqZXxc4oKbPkE88JGIhPCflbQ9XMKl77+94KGMXC4v4Z2hXcKjQC/wnrikCjVCM1Q/J2g/WlzwkJa7MyjwsLOLyLrgr7PIumcn8J+rmCR0ckA84Ly58JePjjtf4KqwWuCvkAouQixS8J+VtHUmPcKlw5PhoY7grJ4oVO+/veC3lkbhpY3IuvCQrYkmKiw/Pe+/vQoUQhIm77+jbS7hvpR7yLp88J6AjEEKEEIObPCepZNM8JGah8KlwqUKGUIXXCfwnriDIiLwkKCt0agx8JCStPCRiI0KA0IBJQoPQg1Z4L2AIu2fr1LwkKirCgZCBGDgsZ0KNUIz4b+Z8J65m+CvrCIu6p+TUSZS4KiB8JKFrsKl4ayLwrTwkKO1T++tgfCei4Ik8JuFlT1CChJCEPCQoZUvJkA9JmA5TvCSirMKHAoEbmFtZRIUQhLwlq2pems88K+mgfCfoJkuM2AKKwoJbmFtZXNwYWNlEh5CHPCdkL818J+rmmDCpcO78J+fpfCdi61hN/CflbQKxBO6AcATCtgSCgdjb2x1bW5zEswSsgHIEgorQiki4ratbUfwn4mFLkUi8J66hzgi8JGbhyI8eyZ7e8O7QPCWv7DwkIOzXQosQirwnrmdwqV7ZuGzg/CRl5tX4a6de3FRJi7wnaqjwqXwlq23N1I84K6jyLoKD0INwr7DllxJ4Ka4beGWjQonQiVtNjxHXO+/vWkk0ajqrIngs7NeO15e4Y2eKmPwn6qB4b2Z27k/CidCJeK3iTolI1LwkZyH4LOd4LaO4KGNYlwuzqHgr4A98JGqg/CflbQKJEIiPnAiKkR10ahE8JG2qWjDkMi6YfCflbTwkZiieO+svuCqhwpQQk4/8J2MieCqs+ChiSpg6qyr8J2qnfCRtqThnobIul7itKclOPCRv6DwkL+i8Jq/vUjiuobvv7zwkbWT8JCkh/Cek5vlnp5rPXQqyLrgqq8KNEIyw4118JC6rTzgt5bvrIDwn5W0J3k9w4dEXMKlUCJ7wqVcwqXwnZOBJjrgprYxwrzgprwKEkIQ8J2qn/CRmat8PPCdk5khZAofQh06PCXhj7jCpeGssOKDrmBg0ag777+9LyLRqOCovAoFQgPguqUKDUILQUDwnriu8JGNkEsKAkIACjpCOO+/vSLwkZOYfvCdpZ8qImBtNiJ0wqUi0ajqo4BDPSbwlq2Z8JCkjWXwkbahJT0nPPCRgorwkb6wCkVCQ++/k+CovvCQhpXwnrSoOCcq0ajRqDrwnoKPPyVgIuKAotGo8J64ifCdkrHwnYiqWuOIk8OML+OGpOCxnSdg4aSoVmwKKEImTPCforsiYfCfrYMh8J+Jh8OmPci6e+G9mz1y4KayLiLita8gXT8KDEIK8JGZp1Fp8Jq/vQoTQhHIumV5yLrvv73vv73wkY+KLwonQiVU4L2e4KqsLvCSgZrgrpUmW8O1JDE8IizCvPCfq5pu8JG1gUl7CilCJ/Cxj7fvv70v76y5L8Ox8J6hrvCflbTwkJ2T8Jy0vmDwkJS/4KitPQomQiRWw6fhnapWOmnwnrikJ9CvJOqsqPCflbRc8JOGhfCbspYwyLoKLkIsLi7hopzwn5+lJvCdnKXCpfCfqoU9XPCen6jqqqxE4bKyIklp8J2ZjPCeubYKKUIn4Kacwq/CqCnhsYMy44Sm8JC6q1PwnY2m8JGonWA24LC+4LSxOiJmCiJCIMOz4KqQcXvRqNGo4LuB8JCrsN+mL2Tgu4bvv70v4Z2GCjtCOdGoYNGow4o5YOCph/CflbTwnY2vL9GoJuCpkVvwkY+XO/CRhLjwmr+x4KmR4b+yYDJ6byDwkaSJYwohQh/IuvCWrZLwkK2VRmYv4bKgPzHwn4Kx77+9VlLwkJaMCj5CPOKAhiRgIvCQv68kOdiVZe+/m+K1rzZC77+9IvCQoZDwnrm7Ik8l4Ka44Ky8LT9AJfCflbQ88LGpjOGNsQojQiEn6p+R8J6ku+C3lm7gvbrDt0U/XCsuPPCehKAk34wpw68KA0IBKQolQiNc4L+W8JGRnlzwn5W0OiJm4LCZ4LKrwqXqo5PDlMKs8J6KqAoxQi8kJc6e8J28nD3wkZeWJUU98JatrCJwfOqgs+Cun0jCpSRWITLhv5hsIj/wkKqGPQoUQhI8Jlwk8J+bpzPhna8iLuC5gkkKFEIS4Yq0Lip20aglIzpTQHsmUSYlCh9CHXHitqNw4LGW8JG+sNGo8JGKiPCbsbZgYFxI4LGVCkJCQPCQjLE/NPCQgZx2OXbwnZOs8JuFle2fovCsvp1g4Ki2LvCfq5Hwn5W08JCGoPCdlYbgrYPvv73wkI+JwqV7LioKFkIUYeCmnXw68J+jgMOwIMKl4rO9MGQKGkIYeC57JT3RqGDwnrqtP9WMNCbgt6vwnL6HChNCEXZ3RkNg4L+PWeGwqFnigpdgCjZCNPCdnbbRqDo/JDElJOC5jWtbJ/CQhLHwrrmPTfCQhZvprKDwnoCkc/CdlIpp8JGvj/CflbQKDkIMIjsiZfCdkrDgrrU7CglCBy/RqG85Li8KKUIn4ayVJPCQvLXhjqVyZXs9Jjgv4LS1e/CegKPwkbWg8J65myfwmKKaCitCKSbwnbyW8J+VtEsqdSTgu4PwkY+h4Y6L8Kudh8+E4LGC8JG9jj/wq5+UCjFCLz1rbe2fkifit5km8JGPh2DwkaSRLy9kVPCQq67DrUUq77+90ahtJlzgr4fwqqm3CjNCMVzwkZmVWlLhuJ7wn6y+2IbwkZyh8J2LiSI8JfCWra0mfPCegZviuqhh8JatnvCflbQKMEIu8JGLlfCRj4Ve4aSMyLpmJUV78JCHpjzgrZZX8J6Lou+2se+sljJDMSXIuizNugoTQhHhpKfgoKDgrqPwkaSJ8JC7vwoUQhIkJjQ8SvCflbRH8JuFpPCdi6UKBUID4LWnChNCEfCflbROWVw84LaBYH3wnrm+CjtCOXvgsYHCpV18JuCov+KAgPCdkqwmQ+GboOCwn/CQlrXwn5W0P2wqIion8JuFpu+/vVg/efCRj4XDjQo2QjTCpeCuvkBcJncxVvCav74mfiLwkb6we/CRjI/gs6Dqn5ds8JGxpkPhipVcOl178J64onsmCjFCLzRl8J60ocKlc8K54YOHVOGgry/wn5W08J6Xv3tc8J6AnfCWraF24LOe4Ka/NMi6CkNCQfCen7Ik17Lgs4h5XCrgu4Ei8JGCpeGgqy4v4K2M8J+JhFrCpcOTPyUk4LqlLj/wnZSx8JGGi/Cen60/4KK3asOHChZCFHU8w6nCpeG/u2Dvv73DmT/vv70uChJCEPCQrY1gP1zCpT3wkIaYJCQKHkIcLvCegaPvv4Jg8JCGoOCunMOHfnF78JCAsOG/uAoJQgc38JCrtic9CiZCJGfwm7KcwqVr8J2VgU4kJSfwkL+18J2UliQvWDrhqJLRqC8xZgoaQhjgq7lc4Leu0ajvv73wnqWUe2bwkaiFLyYKEEIOXOqrr2PwkbSaTvCflbQKFEISKSLwnLKi8JejueC0kPOghK9gCihCJjNKPUx28J6yhvCeuaJ58J64ucOLyLo28JCPjPCRpI48NGDgv4kuCh5CHPCeub7CtOKRglx38JGNjOCxlvCRnYVNYC570agKLEIq8J65pFx0PuC3qnsuPSTIutGoL1zwn5W0JSQoL+Gds8i68J2SonLgqYwxCg5CDNGo8KyrofCen6BBawo4CgRuYW1lEjBCLvCfiZF7KyXIuuG/syZCWCTwkIGNL8Klw5h7b0si8JCpkvCQgYXgq5DivZfgrbQKKQoJbmFtZXNwYWNlEhxCGvCRspw/8J6BhTrgt4Hvv707XO+/vS/wkI6YCrIYugGuGArCFwoHY29sdW1ucxK2F7IBshcKA0IBcgo2QjTIuiXjgITwkaee4LOtT+GqoWkq8J+ip1jgqYhAQirwnoSSL+Cmh+Czo0Ei8J6AkvCeipI9ChxCGkrwnqWXPy84wq7wkY6R0ajRqD9Fw5PCpV17CjVCMzHwn5W0eUNg77+98JGgh+qnkWErJPCjvLRVNuG/s/CQqI088KWdtvCQloknPVzvv73Iugo9QjvwkLaOwrrwkJWGRTjDq/CWq7F0SvCUkZM6OOGzpSfvv73wnp+q8J+VtDnwkYiwLvCSkbDCpfCRjK01JAoNQgs8zbx7J/Cdkp/CqwoqQijwkLOM4YuAw5BtdPCQs51gJDrRqHM/JeC2geG6iXYh4bODLlzwkJ6FCgxCCjzwkaS48JCgrXMKDUIL8JGjv2rgprLhs60KIUIf4rGTJz0kJi8l8J+fsEFh8J+qu+qSvUbqoYvwnou/XwogQh7itq7IutGoUybwkKiWJe+slCbwn5W04YuC4Yq6WGYKK0Ip4bCv8Jy8v8KpcGBQ8J6Tru+/vc6PwqU4wrJvJvCfiYfwn5W04b+OPz0KE0IRLvCRh60+V/CeuqE78JG8qSQKE0IRXUbwkKS0wqVd8J+VtPCQkqMKEkIQeMKlLW7wnri1XEjwkaWYJAoFQgPhsJwKHEIaKjvCpXtTcPCRg4LwnoKPw7Z70ajwnYuLPCcKIEIe0ahK8Jq/tci6P09cOUQ8TDvguodgS/CRjZDgrp8iCghCBmDwkYS9UwonQiUl4Y2f4KaPR+GdqvCWv7AvJT3wkIaR8JatsD7wnY2yYCrwkbu4CiFCH2/wnYuPIuGlgPCflbRx4KmR8JG0iCXwn5W08Ji0hGAKO0I5SvCdlIg9yLom8Jq/tSspOu+/vcO5e8O84rSt4Kma6qylJlnjgpt0afCdmqrhpLTDouCugyXwn5W0ChFCDzhjPD8l0ajRqMi6YOG9kApBQj/gsZXwnaGYIvCdlJfIuuqtjXpgyLoqMHB74LOdWOqsokfwkY6LPTMu8J65jy5ZeyDwlqmGJj3wnZSRTfCRjJAKKkIoXCRcPPCRh5zwnZ2g4LSZ77iN4Ky3wqVpcfCeuKLhjqs/8K2djj9bYAoTQhHhsZkk77+9WPCflbQvP+C7ggoOQgzvv6XwkYqb77mrLCwKKUInUO+tgPCQlrgkS+CtoS4vYns6cmAmw7jCpXlkP8OJ8JC0uGVc4aaqCihCJjzwn6qGe/CRsKrDuCQk8J2Hn8K2e/Ceua3wnrmZ8JKRs/CQkqRKChBCDlXwkLuCXOGfpC7gvqVrCi5CLPCeuarwkKCpLuK3hGB78JCooyMkI9Go8JGkuCdzUiTgsr0n4rqTe2DwkbS9CiZCJCfCpcOT8JCil0k68JCVv3sw8JGcn+CvkCp5JfCRspTwkJ6EPwolQiPhv5g8JUXwn5W0yLol8Ja/oeGJocKlTUsqIjfgqIHwsoafIgoyQjDjh4nRqDLCsyLgtI5iflEv8J+VtPCQnrfiu6J7YMOsKiDgsKPgsaPwn5W0JfCbspcKFUITKOG9kvCRjo7wkJa4M1zDoMKlXwoZQhfCr+qlhfCRj4XIuvCRvrAqVUbgqrgmPwoJQgfig6ThsYE9CgtCCXvhrLngrJs/Jgo6QjjRqHY54LKw4aScL+Czr/CflbTwkZGdfSTwkICBPS8m4Zum6qum8J+VtHs68JGDteGxhXzvv70pfQoUQhJ0ezw9Lj8kdmA04YuDXuGPuyYKQ0JBOnrwkKi44reM8J6TnS8rLip0JfCcvazwn6K677+98J6EmvCfoLQj4LCTMfCUk5rwkLuDPfCQkqRFIsKlX/CflbQKDkIMKu+/vVs6XHvgu4YkChhCFjrhmrFa4LGdVPCflbTzoISLQsKoJH0KEkIQYPCdlI53O+CquGLhiblwIgoGQgQl772IChRCElbwnoCX8J66geGDh/CflbQ/ZwpGQkRG77+9ey5E4oCE4oC4be+tgPCQj450Y/CRpJXwkKiO8JGHkTzwkK2T4reS8J2SrEA98JGXnCVp8J2Qgsi677+98J65hwoNQgs6IUvhjbY68JGPggoeQhzwkYWlZMi64Ky16queUyQqKvCQlrvhqqQqXtGoCh9CHdecOPCQgLJAPybRqGckyLrwkYGh8JCGoO+/vSQvCgZCBO+/vV8KKEImS8KlcfCRm4A977+9MfCQoqvhpJvwn5W0XC4q8J+ltCbIuvCflbQKOUI38J28pipfe/CQhpTDqEXgvaok8JGkuCbhsLFFw4rWlPCflbQk8J6foGnwn5W0ePOgh57vv70rJQocQho6csi6yLrDlSLwkJ6Zeybwn5W08JassuK2sQoUQhIoaUzwkYKOYHUiOj3qoKHhi4IKJEIiM8OYdfCdgY4iPyYmwrfvv73wnp+qKsKlJCTwlqqK8J+VtAo9QjvwkaioP8i64rWvJyfihJDwkYqCJvCTp7fwkaa6SkIqKlDwkKiFw5zwn5W08JGkifCTkYrgrYslKeqlpwonQiXwkYuxJj3wkauHYci6PO+/vSTwnrqXPPCcvIJD77+9XCo64bC3CgNCAXwKRUJDIlbqrIwkREk1cvCWq7Xwlq2n8JKQt+GNp/CWqYDwrra7buK3hmR38JCBiGo/77+9SPCesoIi8JCEgcOD77+98JGDuQoRQg8877+98J+VtOCtoUngrYcKOkI4KsKlKmDigrBU8JaqsPCdlIfjhqvgoZ4i8J+VtHTwnYSaTEzqo7k9b1Hvv73wnriMayXwnZSZV3sKG0IZ8LCHoPCRmIfwnrmX8JGNjXTwm7G2U+CrvgoCQgAKGkIYZvCRg6PDgfCWvpUv3oJ8YCI/4KyP6pKQCkFCP0Hwk5CRITxZ8JCppG1SOjrwn5W08JCegSol4KCFIk/CpfCehLw6MS7wkbKb4LuY8JC+iGTwn6+aJi4877m0JQpCQkDCpfCfqaZiVMO68J6Fge+5sfCQhqA1dDw6YC9I8J2UifCRj47wkJKbLlE34aqn8JCFn0IvIjwycvCRpLdc4raKChVCE/CRjq4677+98J65lOC6gmLhpZQKNEIy4b+rfSQqMzZcUeC3ky/wkYK/8JaokFRG4LeY8J64g11S0ahG4Z+k8JuyiMK/0ajvv70KEkIQQeGqp/CQp5fwkpGz4LSrVwoRQg/RqOGNjPCRpqM98J+itFwKCkIIIntgOvCRm4kKPEI6QeCxhy9A8JCtvvCQoIIu4LGdJvCdi6rwn5W04LiT8Ja+mF8877S/S/CdlYx+8Jy8jVzwkL+LJ+C7jgo6Qjgi6qe84LeK4LGY8Ja5kfCQrYrit4Vo4K2fPeGltHVx8JGNokp9KvCTjJFW4b+u4bC0P/CWvavCpQoGQgQ64Ki5ChxCGvCRpJHvv70u8JCukGDwnouT4Lqp4Ki8RsO+CipCKDTDtWlvUWMm8JCcpdGoIVxE0ah96qePdvCcvIzCqirhibki8JCXkzQKJkIkdi490agu4YGs8JGHhlQmLkzwkYO177+98JCqkuCqg/CegJ0/ChxCGuCrsfCeoJp7aj4n77+9169r4b+SQlrwn5W0CiNCIWBJPMi6XDYi77GtZvCQgZTwkKGEXPCego/gqJgmYCXJvgo0QjLjg6lB8JGTl+CmrTpXyLon4aWA8J6Kl/CflbR4w5RSM0LvtZh94KmI8J+VtPCfm6l7YAoRQg8i8JagiT1gJeCqgWBWJ0QKMEIu8JCehC5cPic68J6LjMO74KKAJiciYcKsUMi6XvCegKY9dV3wn5u8RFDwn6OBLgo5QjfhpJfitqok77+98JGMl27CpTpncuCrv2s6ZGzwkYyPaci6TMOjPytjLuGJqvCeuYIy8J+gsnUnCiNCITwmOtGoPSXitqPIuuC3itGo4KajJOCiiOGfosOq4KaIPQoeQhzit5E64KuAJOCtsy/DgfCRjrDRqCTwkYS+6qypCidCJfCQsLdKXOChnlzgt5l2LyrDqu+/vea5huCpkV8obVnDlPCQvroKGgoEbmFtZRISQhDwkaSVIuCssnXhvbxg4Z2yCksKCW5hbWVzcGFjZRI+QjzwnZSbwqXwnZSNLWok8J65ifCfoZQ8JXTCpeC3lu+/vci6PWBgU8i6ZC5OyLrwlJS18JuxusKz4oOhPzwKuwm6AbcJCpYJCgdjb2x1bW5zEooJsgGGCQovQi0n77+9RXs+YPCfgIpUyLp7Zi4m8J6EuCXRqEPhvZvgr4glwqMiZ+K0keC8vyoKGEIWW/CeuZtgffCRjo7gqohc4LSQ8JCWngoOQgzwkYyPJ/CRspdLJzoKDkIM8J2BiOGDjeK0pzo8CjVCM+G/oci6JjonZ/CQlKBPPMi677+9Lz3gq7pg4Kqw8JG0iFTwlr+wc+KCkPCflbQi8J+VtAo6Qjgvez/wnZCnPSTqrb/wkLmxZeC6lT8y8JGQrTTgqYddPXvCpSfRqDHIunHvt4V7yLrDgFfhjpNyRgoyQjAm4KeX77+9LvCdkqvCpfCRj4U6YHvwkIygLvCRioLwkIC4L/CRtahDdz/wsaeffS4KQUI/8J+VtHBke8i6TfCQj5XqkrXwnpe/JO+svPCQkLl48Jakh/CWrZc1yLrgpobhvYgv8JGPiD99bvCflbQ/UiJgCilCJ9+/w6BcPCXDr0NL4rqG8JGqm+Goky5w8J+VtDzhpYDgq6pbZy4lUQo8QjrCqOC7gibgv4bRqPCeuajwn5W04Y2kMuqsqeCss0jqrKLwnZKZJuGouyLgtrbitKdee+GFreGlgF5mCkxCSnJcxLTwkKOlwqUvL+CpnjzRqOCkkuGksWQi44ev8J6LoGHwnZSH8J+VtPCQlbrwlrqYXT/ZofCRkJ7wn4mgYPCav77vuL9g4K6fCidCJS7wnoWGPCvwkKO1KvCQqLh7JcKlOmU/8J+hgsKl77+9Jj/CpScKQkJAROqvs+qSv18q8JCjtDrwnoqTOuC6uPCRm5fwkbCHLvCflbRb8JCBhy/gtp0kyLrIuvCQu4Jc8J66odGo8J2HiwocQhpgPVfwkIyfJmAi8J2Su8i6Rj3wkZuI8J2VhgotQis8wqVnJsKlJmJK4LyOKuGjl+G/jzNiWVZ9Ilzwn6OB8JGAg3bwlr6S6qyLCgxCCirCpSY88J+VtCIKFEIS4KCpJF7wsLeYPybwkJSPwqguCgJCAApDQkHhiYrwn5W0MOGfqWrgr7dY4reUyLrgraLvv7118JatpCc/Lj1S4LONRMOA6qyRLsK58JC1sWFOJPCQtbxZ8J2qogocQhpuLyU+4LaHYG53JeCuniUtInbwkaivd+qoqwoeQhzigprwkIy2JS/wnoC68J+VtHInNVtqPfCdlI9FChZCFOK0p/Ceipo5MPCflbQ94YOH4YqMCjRCMvCesbwnw6Tgqatc8JCrlOCrjTx977+94KuQ77+9JeCrgjpn4KejwqVN8J+VtFDwn6qFCg1CCyoiwqRc4Z2wQVUnCg1CC9GoWzgnPCXwkYqLCiNCIe+5o/CQoJJcPCV24b+I4Kme4K+XJPCQnp7wkbS98Juyhwo4QjbgsKjwkK6aay7qpaNn8J+VtCbCpTxd8J+VtOGxgMOhImIk8JCkv2BQNOibpCrwnri78J65nUoKBUIDKmB2ChVCE/CRiYBqw5lyJe+/lS4l8JK/imgKKkIoPFjig61pSM6IYkY6YCQ/POKCvF9iXMK4PErwnou/8J+VtPCRj5PDtgoLCgRuYW1lEgNCAT0KDwoJbmFtZXNwYWNlEgIIBAqhCboBnQkKyAgKB2NvbHVtbnMSvAiyAbgICjhCNmBeREPwlqmn8JC2j2fguoFcOjxg8JitnSV9KjdC8J2Sr9GoQ/CvpKXwlqqG8J64guGzku+/vQocQhpyyLp6TUnwnpe/8J6xt/CRvrDwn5W0YHvDmwoRQg8iftGo4KmIYyolZ/CflbQKJ0IlXOChvfCWrZ9nceC1hsOuJkXhh4lcPzwq8JaEmCfwnLOOSOCvgQoUQhIn4aai6qmITCvwnri2YyU9X2AKGEIWOm5h4K6aP/CQpIUmYirgq6LRqDzCpQoVQhPwkbSIT9yS4K+577+94YyTwqUyCilCJ/CdkqZyIFxJ8J+VtCoi8J+VtGDwkYWWWfCQi6PwkLqxdOC1ljtcJQoLQgltIuC3slw8JicKC0IJwqw/8JCmm8KlCg9CDTpL4LWGKmQ1JCbhpbAKAkIACjBCLvCRqKHvv6rgqo/iia3wm4WQbCIg8Jatk/CRg4HwkY2y8JGPiirwn52QLnvvv70KPUI7wqUo8J6lkVpN8Ja/sPCfoZPCpTA877+9e/CetJZ0OkldLvCdiIDomo92Nm5lNvCRiLtp4LWbPvCdlYAKR0JF1o1c8Jattci62JvwkbWnSCY8Tz9SYPCdkrBpOl/wkISC8JCWjPCflbQiYCLwn5W0J/CWraTwnrqTwqXwkbWG0ajqoY9TCg9CDfCeuYdOTiYw77+9QyUKEEIOJPCdqZAiJ2Qs8JG/hlsKG0IZZfCRm6DwnoKPPTrwn4K0IkUmJ8i68J2duAozQjE6ejxt8J65lG3wn4miSUsq4KmZ4LOu8JC7g+C4l+CqrSpI4KiHLvCQkqTDmHDwkYOBCjdCNfCWqaZ3RHt58JC1r11pKjPwkbC46qySZ/CdvJ5OPC7wkayE4KuIKS7RqHrwn5W0O1Lvv716CgxCCjo2ddGoLjTgp4sKCEIG0ajwmr+3ChtCGeGyqy5g8JGYquCqpPCbgLYvOuqguM2+0agKGUIXXzTwnaqdLkB74Ka8YPCeuYfitq7CrEgKNkI0J19K0ag/bVxj4Lq9I2A8feqshTFc8J6AuH498J65qfCQkKQ5w5df8J6frlJg8J+VtDc0YApAQj5G0ag6LPCdi4nwnrikXELwnYuNeiRY8Ja1rfCflbQqJu+/vfCRqbUlS9Go4K6fUvCfhaEuJzzwkLqt8JGPhwoWQhQ6P1ngqq7IunAkLyQyPHbgu584Jwo6QjjwkZy+TMKgJC/gq6LDj/CeiqnqrItLJl/wkZmsUFNxJyJc8J+jgd2D4Kuo8JGvh18uPeC/kuK0ngo6Qjjwn5W0YD/itrElwqMk4KuL8JCequKvv/CflbQ8YCcvIi978JaiiC3hg40mPOCnnfCQlrvCpeGMhAo/Qj3wn6uhe/CeuaI78J2FlzTwkLynLuCrjSbwn6i68Ja+nlDqraE44a+Z8JGkgmDCpSJTa0Hvv709e/CQioAlCj8KBG5hbWUSN0I1Jicu0ajvv73hppPgtpJ5wrEpaMOILy4vJiImNuCzjFt74Z2s8JCjqVPwkY2y8JCmvfCRsoYKDwoJbmFtZXNwYWNlEgIIBAraAroB1gIKhQIKB2NvbHVtbnMS+QGyAfUBChFCD/CegavwkY6Aa8Kl4ra2YAoVQhMu8JCzgzY6IT/vv71J4b2b4KeMCgVCA++pggopQifwkKC88JCWtuqgsPCSjK4mPfCRr7ZOIl4m8K+ghMOy8JCBnXcyLi4KCEIG8JGCv1JgCjJCMOCmuW/CpcKl8JGkuFIq8J+DquGOhn7wkJaN8JGguCLgrYvwkb6w4L2nQsi6XOG9qQobQhlUJOK8t+CnqifbnyTwkYSue/CflbQ9d8KuCgpCCHHvv6bhpYlzCiZCJOKAh/CRpIloYH3CuiLCsO2fu8aV8J2qnkYuS/CeuapfLsOPJgoIQgbwkoyScEIKOwoEbmFtZRIzQjHwkZK3e+KCqvCflbQw8Jasu++/vWEuJDw64LGLYCUu8JCSo/CflbTwkYGwKjTgsokqCg8KCW5hbWVzcGFjZRICCAQKzAG6AcgBCk4KB2NvbHVtbnMSQ7IBQAo+QjxsPNGo0ah7w5tS8JuwjMOB8JCiqvCepZQ/4LuM8JiLgvCcvJrhrYlgOj0uPSLDpmAxyLpgPiXwkLy90agKPwoEbmFtZRI3QjXwn4yp8J+VtFx4TvCdkr8iYPCQpL/wkZmCYixwKvCek5dgJOqjklHqmr1RJsi64YuF8JCLpgo1CgluYW1lc3BhY2USKEImLmBUYC/wkbSIbinwkJ62Xk/wn6KQOfCWrZvwkLSKJPCRhIVge1cK6g+6AeYPCqAPCgdjb2x1bW5zEpQPsgGQDwpAQj4l6qmGwqU88JCBmfCeua3wnrii8J+VtE9gPyjgoo5q8J+hoO+/vSXigYlgOSZoTiXwkIeZJPCQqZDCv0HIugoJQgdWwqXvv5p7CixCKnXRqEFcZci6L8K94rWLMfCfqprRqDjvqYTDvXrCpfCQhLjwkYyuIuCrowoKQgjwnZGedci6SgojQiHwkbKB8JCkv+CxjFw8Iz1HYHjvv73wkI6wPOGNnylce0UKBUIDOFxTCkNCQeqnlmJsek/wn5W0fuCwlsKlMnHwkaSfL/CRjoB7Q/CflbThi4A08JuGgTNAw4/RqC8iPfCbgqjRqPCRtajwnbynCidCJcK44oCK4reM8JGFoyfwnriiM+CsoT7vrLzwkYyG8JG7ouCupG8KMUIv8JGPl/CQoKTCpT1cIeCyri8/fvCdvIHgu57wnLyx8JGDtfCepZ8o77+9VO+/vDQKAkIACiNCIeGwvvCeuZlU8JGOhuKEo/CRsJ8nfTQl35l0UWBi4bK2OgoLQgnwkYC78JC6nEUKLEIqQ+K1sCLwkJ2nIi/Iunhl8JG0ifCQuqvwkbWnKsKj8JGKjCdxyLrhiYtaCktCSULCpSc6wqXwkJK/8JCpvOCtofCcvYQ90ajgvIHhv7zCpfCRtpbgr5DgqLXhnIQuJOG9uG3vv7008JatpuCzgeGjhOC3lnA8KUEKMkIw6qqfWyphOvCQhILwkYOcPTw/Py7wkLqZPy9C4Z2rJ++/vWJf4oKO4K+LLvCRpLwvCgtCCeG9m03wnrmCPAooQiZe4Ki18JCgvDRYLzzqoLE9T/CRmoA68Ja+kyTCr3Ul8LG6vSrDkgodQhs/77+9LvCQuqN4LiclSj/DoDrvrL7gp5dWwqMKLkIsID3DtfCQq67wn4OG4LOye1wxKGDwkJSAfHzwka+4Q/CQgYLwnLyd8J2LiDIKF0IV8JGKiPCeuYki4aKRJPCQrpwkwqVUCjpCOPCeuZI98JCBkVxCcvCeuoPwnrmd6qeSPWXgoaY98JG0hHpj8JCgscebJjrDknxdXfCRjLNs4KewChNCEeGvh/CWqaQq8J2MjGrRqCoiCjRCMi/Iuu+/vXfIumbDmCQv8JCdofCdkqXwkbW3bU3wn5W04YKySPCRg5jgp4fwrY6m4oKNChZCFNGo4aiR4Ly3NuqgqmDwn5W077+9Ci9CLeGfpPCxrZ5xYPCeip5TKkwt8JKUq/CespQ6J8OSMeK0leC1hyVuPy9I8JGDsgojQiHhiZVLOvCQlrPgqrcu4oCESd+9TeC4lPCQg7k5ZkZ7XXUKFEISJ+K3kyx4aPCeuqYkyLoq77+9CjZCNPCflbTgsK0/RCRnPHtA8JC5qGI24KeIJOKAlVxe77+9IFc+RvCforXwnoqT0ajwn6uiPTwKBEICLjQKEUIP8Japoci6P/CQlo884K+pCkRCQvCWraZ48JC/plzwrqaAJmA8LvCQoIDgprA3JSou8J+pqF0n8J2Lg/CRg6Hwn5W0J++5sPCdhZ3wkYSF4K6j4K6KXwoqQijRqPCdlYZoZfCeip7vv70m4aCS4ra5LvCel7/wkZyp8JGMiX7wkbCDCi1CKy7wn5W0XMK+PDwmO/CRjI97NvCQlrsoLSfvv715Mj1QJPCRm4I6w4A0YzwKREJC8JuFlWkkXDo68J2XnPCQhqBzJOG9vSnwkKC377+98JCVvG1o8JSJozM38JCEgkDwkKCxwqXtn7Ml4LiMTi9nINu/ChFCD2BdIuCtgy/igobhjKciJQoYQhZl4aWxRs6exJY6Ly0hJfCfoZbvv71sCiNCIe2fu1Hvq5NgJsi68J64u2An4KeOdvCfiaPwn5W08J6Epgo/Qj1c76yWW8Oa8J65neCmkPCRtqB58JG9mPCRiIPvv5o8bPCRlownJ/CetIPvv73wr6Oj8J2SvT7vrK7hsL1sCh9CHT3Iuj0k0ahELsKl4bC8XDLwm7G4TvCdiLHhiq5xCkRCQu+/vT0q4LKz8JGcg3bqkqTvv73wn4mQeuCrrcKl8J6TkOCtnVU8TSLgp5cnL8KmcnM6Ju+/vfCflbQ8e9Go8J+CoQoTQhEkw4hb8JGWiyTwkYSWyLoiLgopQifjiIHqrILwkbWRJsKqJy/wn5W0Pzx7R+C6pXXgs6bguI45TvCRsbwKIEIeJ0nvv4fgqJAqVOCrreG8s3Xwn6K3Vj9Zw6rhjoY6Cj5CPOGnsDrgro/wkpGzP+K0reCgmMi6WyU/8J+gsXZUyLrgsZVhw6bwkZmSSSTwkbyySibwkaeeJPCepKk/TgosQirgvbTDuVMm8JCBkeCqkPCego8m4KKG4aWYOj0jbvCeipDgoZ46YOqtnWQKJkIkOzomIiXwkYqNT/CRoLHwnqC/4LeW4Y2pw4lg4LGL8JCtkcOrCgVCA+qjlwoSQhBgQis68JCpri8n77+9eS4lCjFCLyjqo5HiupPwkKqP4Ki5Ij/grbZ18JGakkdI8JGconJnLuKNpeC2ueCyrfCbhaV6ChpCGNez8J6yq3swIeGkoOGLhSYkw7o+bic8VwoXQhU94Yma4b+ePOCnh/CesoTRqPCeuZ8KMAoEbmFtZRIoQibgppDgqqbwkaa2PELwkLWLPE0mPyrhqonwm7KFyLrwkbWCIjVWIgoPCgluYW1lc3BhY2USAggECtoKugHWCgr8CQoHY29sdW1ucxLwCbIB7AkKCkIIzL9n8JORgywKBkIEdMKlJgoGQgTvtp88Ci5CLOCsl/CbsIx7YFc6PcOD8J+DhkDgs54s8J2SvuK2qiYmOkHgqq898J2UiC0nCi1CK2o/8J2NqfCeuq1wwqXwnZK7cPCQjpXCoC/wnqOIOPCQvJJg4Ki44rStyLoKEUIPJnkkJWsuXSNgYMi6P3xgCglCB+Cmribso7IKHEIaeF7wkaqOPz9c8JGyrFvhiY0qPeCvgsK+NTwKCkIIRzrwkauIYiIKCUIHQjMi8J2VhgpCQkDCpVw6YFdI8JGwu/CdvIc6e+CrkPCQiro88J6Lj/CRj4rgt65GwqVg8JGBruCmsPCRhYXwnZKw8J2IhUFk4Ki4Ci1CK+C6luChtj9oJOGdgeCxh00iSPCQv7Xvv71Y6qelP/CWrovwnou/L/CWtagKMkIwKfCRmK/vv5zch3tX8JGZoeCyiik/0ajgsZYn8JCzneC4kvCeuIxINydoXSZ7KzonCjJCMPCQpK57POCmt03wkZmVQjg+w5t70ag78J+VtE/jhLRx8J6KkVrwn4iWeuK3gC/RqAoVQhPDgEhFKmLwrraAUlnIuirwkZGfCjhCNil6L+GPuGA/acOa44aAP2HwkKi48J2UvOK6liLOjPCRgZjgpo9ga+Cqm1bwnrSxXHVXwqVvZQobQhk68JC/piZAw4/RqFzwkKOh8JG1ljrwkYiRCilCJz8rV+KPidGoOfCRj5jwkbSfJvCRjqZkIXtC8Jy8mXsl6p+ZIs6fYAoKQghTIPCcuaLOkAoYQhbhvJ0mUvCRhpIvWT97NPCWtZDwkY+XCkNCQfCflbTwnrirw7sn0ahd8J+Ct1vqrIRyTiB1X8K+8J+um+GMlGDIutaObvCcuqonamDwkaSJPPCRioAu8J+rlMKlCi9CLXDwn5W0PXt7Lnh7Rn424K2V4LCB8JGNsyXNvfCfiYRK8JarhmzIuvCRpL0lfgozQjHwkbyKZmRqbeGxgvCRr54lSSUn6qGqP24nP14lOzo9yLrgraolyLpW8J+Dr+K1ryp7CjpCOD090agjJeCskFHwnZWG4K2cIyTgrZ3wkYeiyLo8anslVDou8JCol05F8J+cv+CotnUnJnvwnri5CixCKknwnqWe8J2qouC3lDbRqOGdoTrhgYUnL+GqlSU68JGNkO2fsTxoYOKlkgoEQgJqIQoZQhfgqJAmb1DhvZZ877+iTfCRtIngsqNWJgo4QjYmIvCflbQ9eGE0OUJcMvCbsYZv4La4ZydvyLo88J+CqDDwnYCGYD3wm4WV8JG1ljjgurniuocKIUIfe27wkKS/J3lV8JGXjT9N4KWAP/CvpaDDhGDwn5W0PQoSQhBQ77+ueFV7wr/CpeqSvzNOCh5CHFLvubPhiZhr4K6TcH1FJkUl8JCSoT3wkK6aw5cKK0Ipe+G/suCzhyYuwqXwnZKpee+ok1M0asOu4LOGe2Bg4amKTlw/PXzgqokKHUIb77+9PcO7fe+/vWkq8J+VtC4qfiQmJ+qiuiEkChBCDickPW0/OkQke8Kl4KivCiBCHns/Il/wkY6UZ/CQlL4vaSfRqFritKMnV2bDh8OKUQo7QjnwkJKoSMKlwro8JcOeMF4u8JGktyI/4LKQ8KySkGBmwq9Gw6FldfCQrpzwn4erfPCflbTwkaal17AKKQoEbmFtZRIhQh818J+bsntD8JapiGzwkKOt8J2qofCdnrnqoLjwkL6ICioKCW5hbWVzcGFjZRIdQhsqdm9+6qyL4oKYbjp7KlXvv5Ng8JG1qOK0pyYK9w26AfMNCv4MCgdjb2x1bW5zEvIMsgHuDAoQQg7vv709Ktu/8J2UvOC3jwoaQhjihLRNPW7wkbSJ4LWd8JCkrfCSkqLgr7cKC0IJ4Yyr762B4bOzChBCDtuPYi/wkbykLzXwq56NCg5CDE9a6p+R44iO8JGMsAo+QjzCouqanPCdk4FFY3pS8J+JgS/wkJ2lwqU8JiLwnrmwJuG/tGAw8J6ji/CdqqnwnZKl8J6Km+CunCRyYiIKGkIYwqXgs6d7XOCvl2rgrJM3yLrhna87W8i6Ch1CG2XhnYJHJmEiYDs6S9GoXyXIui7go6VRJFx7XAo3QjUhJTbwnrin762BMPCeub7wlq2Re3M8e/CeuKTigKTCpSLwn664IXjirbbwnYiF4K6ZTycyXAo2QjThvYhTKnZdSPCcupLwkaWT8J65tVwl8J65pD3wn6ujLvCQnrRCPz0kLyolOsK8QkPwn6uaCg9CDSJ8Kmgu8J+VtPCRgpgKBEICIkEKMUIv8J+VtEs9Tci6eVk58J64pGDhpJjwlq2eJfCQlrPwnLykYCTqp5fwn4mG77+9PS4KC0IJ77+94aCT77iYCgNCASYKUUJP4Lqa8JaqnvCRtqjwkYSzMCTwn4GWNfCRlrMiTOGfslxV76yE4LWI4Ymd8JCBgeC7nnDwkJCY8JCRrDpgQeCxh/CflbQlVfCegYjwkK2eJQodQhvIumvgtqPgpq7wkJ6FPzThiosmZHUuw5JgWCcKJ0IleGDRqCQn4LqiwrjgtrXhvJxVyLok4YmRJy9eKmPigo5J8J2NgwoYQhZ5ZcOU0ajwn6uxPWTwn4mhLvCflbRXCjRCMvCflbR4Klgm8JGklci68J+tgjg68JGDgC3jhKbIujXwn5W0T+CstifitIVEYMi6Jy5SChlCF8i6P/CRjYzgoZ5z8K62k+qrgS8u76iqChFCD/CflbTwnZK08J65ieC9uQoxQi/wkbSDQfCeubvwn4CDMci64KqQSVnwnZKlKXst8Jq/uUFYL/CRjLbgro46JV8mJgoOQgw14b+D4Ku88J65vnwKF0IVyLpOUEoiJMi676y7Il7qp6jvv70qCiZCJOqfmfCQvbUk8JCNg/CQjJQx8JuKiPCQvbw5Z1Tqq6TwnouEUgoQQg4nIsi68JGOusKl4K+XKgo3QjVuMGAuYOGBv+C6hHtzO9GoW9Go8JGPhWE0bkLwnrmHaFDwkIqI4LuT8Ja1oMON8J64pzslWQouQixM8J6kkS7hs5jhvZtcJfCWraklTS/wm4OvXD7wkYyB8Ky5hDPwkY6GL+CstQoLQgkq8JCXpsi6WSkKNUIzL+GcnyfgrpkyPO+/vVlAIyXwm4SyYGU8772zwqUu4KyzItGo8J6Xs3xERUDit5wmXks8Ci1CK++/vS7gq4c9PdGoNOKujEIl8J2Souqgq0LwkZOXZO+5q1PwkLSnMfCeuLkKM0IxQ2gmQHbgqJM78J6jgPCfn4/wnp+y4Ke2OuCxnS7CpWBg8JChs/CRjLkq4Kup4K+yJgoTQhEi77+98J2ZmSJgN3YiyLo/IgorQik/77eF8JKGnfCeupfgp4vwkKiaejY+JvCRhaYiYPCQuq09VvCflr9LLgorQinwka+x8J66kV7wkKS5wrbRqPCQmo46em3Di+GlgErIuiQj8JCXqjVcLwoTQhHqn5w38JKQtGAtQD/wnrikPwowQi49M0s6avCWro8mT+OBleG/sz/js6/CpTo78J+VtPCdlY7wkJ6WJ0XRqPCfhLcnCgJCAAo5QjfgpJguw41cQfCfn5QuJ/CeuqI/yLrIujzwkLqt8J+rmeCzolx54Kmew5xqyLrwkI6oXOC6hD9bCixCKvCflbRg8J2Vg3fwkY+I4K6kUC3NvVQqw5HgrpzRqO+/vfCQgKBA8JGMiApCQkBgUXvwsLyzLvCflbQqPfCSjIbhqrRcPGrgp53gtoNg8J+qnfCRgK/CpTrvv73wnrm+PzXwnYad8JarlXDig5E/CiVCI/CYtIXtn5HwlqmoNiYkKnjwn4OG4K+HLiVX0ajwm7KdSVxQCkFCP0PwmJqFzbrqqYJcKm1wPPCQlrbwkbuwwqXOhj3CuPCfoYDiuL7wkKO84Z2CPlwu15pgaD/gp5fwkKa3e+GMtwolQiMk0ahv8JCWtEHgp7XgqofCpSVnXmNL8J+rtOCwtfCYpLE8JAoPQg1g4LSMVD3wn4W7Pz02CioKBG5hbWUSIkIgOuC8lElIw4xgPUQzXDddXPCQhpjDtSfgsZoiLmAvP2UKRAoJbmFtZXNwYWNlEjdCNS9X8J2bsz0lecKlYPCeuqLIujfwn4mlYPCRg4HwkIeV77+9cfCQoIjwn5W0Qz/igIDvtp4uCroGugG2BgrNBQoHY29sdW1ucxLBBbIBvQUKKUInP/CQgK7vv70k4L6BSCbgrKt70aghyLrgpr/vv7084bGGKci677+VCgdCBSTwkIyTChpCGOCrjS/wkbCY8JGTmMOuL1zvv70n8J+bowoKQgg16p+08JKRswpMQkp71o3wn4mR0ahmJSfwkJG1ZPCQurEs4K+NLiZg4LK3dvCfnr1rw7vgpovwn4CD8JC2j+KBtvCRqa0/4YuFYO+svvCRhaMv8J2UnAomQiRhW+Gkju2PuMi6ZTpp0ajCpXvwkYyDQeCqj0vwn5W0wqXhibsKNUIzwqbwkpGxfSokJ/CepZnCpfCflbTwnrqBeyUhcz85UHrhs7I/8J+Joisg4Y6MJCrwnYiGChhCFn5QKllcbnzqrZ0i8JGDqOGdovCepZ4KEEIO8JauhuCqsj0sIvCfq7UKJEIi4L+BRvCWv7B6IfCbsoDwkaS44b+8ZCV9ZuC7iFzwrK6kWAoXQhXhoZou0ajgqo3wkoWaXio+JfCRioUKLEIqUT94KCY68JGio9Go8J+VtDU98JChvTome1xk4KqIIj5LJvCQtLHwkbWHCiRCIiUi15MyJ8O8XtGoR+K3i9GoPCo68JGwmkBRKeG1kHrigIcKGEIW4KuITXJm4LqB8JC/gT8mJu+/veODmwpGQkR64Kqy8JatnPCflbRgUvCflbTDp9GoS++3gsKn77ue4ZywNyTCvifwkaSJ77+V44ev77mWTUMnYDpS8J2Uve+6v+K3rgpHQkXgtIxGyLoxIi3wnoCpzofgtYzwnZK7Knt7eD/vv73gqrLvuqHgsL7wnrqiSvCehYTwn6CEPCIq8JSUoW3jg6koL/CRtbIKM0IxSCXwkY6O8J64rMKp77ay6qe0yLrDkGde8JCKhy5iWPCRjLsmajo/STp1eU/wkLyHXAoFQgPhiZMKDEIKM/Cel6zIuuC9sgoxCgRuYW1lEilCJ0xodfCRtJTwn6K2P27wn6u08JGDtV17SC474KuD8JashH7wkauPcgoxCgluYW1lc3BhY2USJEIiKiQ4Pyo8eyo/Z/CQlrvfljzwnoCo8J64k+K0pzgkfeC3sgrGGLoBwhgK5RcKB2NvbHVtbnMS2ReyAdUXCiNCIW7CpSfwn5W08J+fqzJmP3zRqFbXofCeu7HwkY+Jw6ZJawotQitQ8JCrl09YXGDwn5W0ZD9gJFomPOG9kE5cYSfRqPCQlrhaKOCvjT824KqBCgNCAVwKPEI6alzwka+1RTd74LGW8JG1oPCRtaDgpqtpd/CSgb0k8JCrsifwnZOS3689Pzx7IjHhv7I6LU594bOSIgoGQgTwnoWBCjtCOSfRqD3hpqnwkYyC76yxLu2fs2Hwn4idPFzhvpwm8JCVnF3guqXwm4ugem7Yj/CflbQy4b2UKCpYdgoNQgvvv6090ahU4Y2qeQoCQgAKLkIs8J+CivCRhrYp8J+VtHtcPUA9Zih78J+jgH3wkJ6yRj0iP+CumTzwnZSu0agKOEI24YmwKvCflbTgr4IuL/Cdg6ZKVSLgobPwkYqGwqVQYPCQoLjwn6KWL/CQl6Jy4b2xUDp3OtGoCgZCBHHguowKC0IJQWDwrrGS4LOdCjZCNOGMk/Cdi6XgtIpPXfCRtIjvv709Jj/vv73wm4WS8J2Lky9vwqXwka+xOkPwnoqQ8J+VtC8KF0IVWvCRjoTwkKeOXPCQtLVccHw94oOsCh5CHOKItfCbspQlKuGKslVZJW8qXvCQiqwvPSJVw7oKPkI8S/Cfo4Ek4KmH8K2Uv+Cnl+C2vXlKJjwxw7tM86CFvuG9s/Cdhb/DhjHDl8KlOvCRvKQqa24q0ajwkYyQChFCDyfCruC3lvCav7sy8JC9lwoDQgEvCjxCOiHOhSYgIlbwkY67X8OWSGfwkK6G4LaC4K6jXOGKtTQn4KuM8JCGktGoZvCQhIF24reB4bGrVci6JWIKHkIce9GoKSLDnuC2tsKr4oGw8JC0tDxAPS86RTwmJAofQh3DqzTwn6mryLpr8JGyruCssiZ0SPCegJ1gJuCsswoUQhJc4b64YHzwnZGfP++/osOIcDAKJEIibycuYGvvv6NgJ+CquCciyLrwkYuN8JGbiGQmyLpK8JGPmAocQho06pOo4aa8LjrDrCYjyLpK8JGcsH3wkLW/IgoEQgLNvwo/Qj3wn5W04LSDIvCRh6InKmMmJequkO+/vW/gro7Nv/CbsoE8e8K0J+Gwuy7RqOCumVxg8JCkvzzwnouJ4bCSCitCKfCQo7VbOmBow6RmIMKlJ3sv8J2EpD0lJ/CQpJMvPF/wn4KhYfCRjaZfCh9CHV8i8JappuKApy/jhrvit55g8J+VtHvwm4WV4aiECg5CDMOnQCV76qeV8J65oQoyQjBEJEHwn668IvCvqJkvIfCbh5rqppvguoY8wqUx4aSmL8Kl4L+PMUHvv7028J65u3sKAkIAChtCGTPwkLuCTtGo4bCKwqU34Ky88J2CmvCQpJEKCkIILFzwn6OBUSoKRUJDw5Y9Isi68JKCjfCQp47Dvzw9TcOlYMOR4LGgwqXwm4ut4ra2POOBpeGKvvCQgZDwkZmT766N4Ymce1w48J28h+Gcqgo7Qjk04YyKOC89VvCQuqTqrIHgu5XvubNfKlLwnLezL/CWq5bIuvCRjo7wnrm+J/CWuaHwlquZPPCvp60KB0IFLybvrL4KGkIYKvCeuIrDpvCeuYvwkKykPPCRgZJSUXsvCg9CDdajQeCmsl9XJC7qo48KNUIzZnpgLsKlNj8k8J+VtMKlcvCdkqlu4LuZ8JCgtHs6MSjDvyV84LOLdfCQk5MuKnNXInskCjFCLypn8Jq/sj/wn6m18Ja5p+qphO+/veqvs3vigopPJDoqfcOw0ajwkK6I4YiqyLpyCgZCBG3vv70KMkIwwqU0Z/CbhLIy4K+6T8i676y64LuYyLrhpr1R8JCeqi/vv7194bGwUio/SicqL9GoChNCES/wkY6O8Ja9tfCehY53OmBJCgJCAAoOQgzwkbGBbD/vrYDvrLgKA0IBewonQiVU8JGWmtezLsKlyLrwnri5VPCRkr4/aOCqhvCeuYnwnrmb0agnChxCGntbwqXCpSbwkKiF8J+hhjzwkbCmKsi64Ku/CiRCIlXvv73vv71mP0LgrLLhiZjgtbBQMk1VXCUl8JG/nzplIj0KOkI44K6jP+G8nWVHJyBIIfCfh74v7Z+P8JGNsOC+mz/wn5W08J+JkEQycOC7gS5lJ++/jeqkluCroSQKJ0Il8K6yqybwkY688JGkvEMideKBsPCfqpxf77+a8J2VjSXwl567NAo9QjvwkL+CP3tq0ajIumBgOS/vv73wkpGy77qRJGrDkSPirZUqcCXgq4fCp8Kl8J+VtDxN8J65my/wkI21YgoWQhTwkYOZOuCmuPCeuLHhoJEvPCrDiwolQiMiXD5+LFfwn5W0JvCRipMnJybvvZ4tOvCyiKfwkKCA4KaqJgpIQkbwkY2XMMi6JC/wkaSJ4KuJesKlSjzwn5ujIvCQoL/wkY2CfUVyKPCQiqXwn4mjPeCoj/CflbTwn5W0R/CRm4InP8OcRdGoCiFCHy9CJuCovjrhpKXYj3XDlPCUlY7wn6OB4L2g0ajvv70KDEIKyLrvuJU94am1aAoEQgJkSgoCQgAKAkIACgJCAAomQiQi8J64t03wn5W04KuH4oWRKGXCpfCflbQ64Z+hYScmYvCdk4gKC0IJRnc6PXsm4aq8Ch1CG0RKJ/CQjK3wppiA4Z+0Wl7wkJSi8J2hiuCouAorQikkKvCfqoY/8JC6isi6R1ZcSDQl8JGQrz/wn5W0PDNNOm7CtsKlbVZGcgohQh8qJnvqrI3jhKTwnLyO8JCmtyl78J6jkCJL4Ym5bXI6CkNCQV4l8Jark3jwkL2YP/CRnKdtyLpn4K2dUkEi77+94K6eIfCfpIzwkY2iLlcuYVk9OuC3ivCeuZ1BcfCdvJLwkbuhCixCKvCflbR98JCbg/CeuZEi8J+vjm01VO+/omXwnrux4KmLPiU5biV+PSrDowoCQgAKLkIsR/Cbhabvv70l0ajDgvCdkqYqSvCfn5Tvtrkx4LGj4K6kJj8iRSRc8JGKqCcKLUIrJC/RqMKlMGvRv+CovE7groMnVyY88JGahV88SFU9wqXwkLqtW1JH8J65kgoCQgAKOEI24LC5QHdJJMONQFw7YPCRsbV6fS5O6p+Y8J2ZrSfwkJOhJljqmYPhirQk77+94LOG8JCAjTZgChpCGOCvi8i68J2amOOCuG/wkbCFXCJSIiE0JgoCQgAKB0IF8JCSqFgKPUI78JColijgrqNgNSbwkIWlL++/veC7jTvwmr+zWCLwkJKi8JGTkvCQvJM8w7fvv73RqOCukPCdqaPisqsKHUIb8JGOnCxZUz9caj/wnZWDw4Mn4r6K4K+A4redCgZCBPCfiaUKIUIfXC8k8JuyhPCRnJ3hpJhgYCYhcybCpfCQvJck8JKRswo6Qjg/8J+psOGKs2gq4K2WeickVyLwkZiMKjrwnZSaXmbRqOqti/CRtIk98J2UivCQmoTgsZYu54SiPwoKQgjDmz0i8J28hgodQhs88JGkuD/wlrWJ270gLjzIuj0sJu+/vSDhiZUKMEIu4K2gcyV7XOCvi2nRqMKl8JCGl2TwnoSVJNGo4LuGJyJ22I898J+VtCpd4oKlUQpFQkM28JatkT8/8JGxqe+/veGkuPCRtYDhvZXgtYgqe/CeuaTgp7k/8JColfCeuZ8j8J2VkCYvJPCQnYXgp47gpqDwkY6ACjdCNcOd8J+giuGtpPCRr4Fr8J+VtO+/vVx3MzbCpfCQoYEu8J2qqzRqZuGggT9gX2PwkL2ywqVgCgJCAAoFQgPgsqwKJkIkLjor4reaVyrgsqXCpTpcXPCeub7CtPCflbThq4pi8JGNgUkhCh9CHXs6JS898JCWifCbhLLgqrkrcPCRj4nguojwkJuiCiZCJDxgL9GoJeqtiTk94Kmc4LCw0ahg4bOT4amg6qmQXCYk8JG0vwo0QjLhu4xj4LOEXiLgqoEwyLrwlqqJ8J2Em2MnwqXIuk9F4K624YmMXCThi4Dgt7Lwn626PAodQhvgs55M8JGNsfCQi43wkISBYPCdkLYk8J2SqkQKLAoEbmFtZRIkQiLgrJDvv70kN3c/8JG1g/CepZY9J+GxtfCcuKvwkKiFwqUmCioKCW5hbWVzcGFjZRIdQhvCpSQ28JGNnScm8J+VtPCWrZ1PWOC1h+Criy4KuRW6AbUVCtgUCgdjb2x1bW5zEswUsgHIFAobQhk68JGNkOqmlfCQubvwkY2h8JCOkmDhjZhcCg9CDSI6akshJn7XmW7hqJoKFEIS77iM8JGOh3xiJDVs8J6Ajy5pCjpCOCVuWFXqoKguYMi6b2Av6qiQ6qyOVvCRsqvwkY6wLuGKs0TCszRfMeK4jibgsIAqM/CflbTgqZw9CihCJtGo8JuKvmHDvOG/pvCego8qViRxwqVGLOCwhSRu8J+VtPCQubpuCixCKirwnYWITS1cwr/grKDwlqyZ8J65viVAPFjvv73gqLw18JGqlzngspYqbgoUQhIl4LK/LfCQtaM64Ked8J2SpicKOEI2VjFYUvCRg4FgfEXhv57wnp+tJSU8LtGo8JCWkFzhj7hXwqUqUyxLwqVJbOGPu82/4aOb4Ka5ChRCEtGo4b6m4KmzIkUiIuGJqiRQLgo0QjIv0ajhnI/wnYarJ8K/4Z60w6pQefCQnrZcNUHwkK2EyLomJXngr6wm0ajwnoCn8J2qngohQh9Q8JCEgD3hsqPgtJRc8J+VtOqsqfCRkZ3grII66qyWCkhCRu+/rPCWv6PwkaSWfvCfoIok4LuK8J+fsPCRmZHwn5W0fWHwm4Sy8JGMrHNcIuCzqvCdi4HhpYB7UPCflbTwkY6OaHAvw64KCEIGPSbwnrqJCjhCNuKEl/Cei6dX4Z2LacKl8JCZuD/wnrq2w6Xwkbyh0ajwn6upIj7jgLIkI3oqLCfwkKO04KejLwoDQgF5ChdCFT/wkK6L0ajgroY/ajLita/wkKiWLwoGQgTgv4oyCglCB+K2q/CeuZEKD0IN0ajqqZNcJvCWq5dnMgoJQgdxcCU6d8O2CilCJ/Crm5XwkYygL3smInw4w65We+Gci++/vfCRtILhib/wkLqxYHA9PQo3QjVn8J28qfCdqp8rTyo96qW18J6KlvCQuq3wm4miU3AsZT9NKT170ajwkKuHKuGpuTrwlqO5JAowQi7qpanwnrmZfPCRjKrwkYyh0agiJ0Xwnrm8VibwkKC8JT3qoLF+4Z2z4LCQe9GoChNCES7wkKCCw7zigJ3wkaSD77+9ChNCEfCQqZQlTO+/vcKmUe+4oT0kCh5CHPCRrIA38J6LvzwiXG7ijYQkU0Xwn4Ooe1ouXEYKNEIyJn148JCUi3Zp76y0bC7CpT1DXD9xfi7wkYqIP8i6Yj/wlr2E8JGLt2J78JGPl/Cav7cKFkIUJmUlLsi68JCGl/CRoIlw8JGysS8KDUILPWvwnriJOuGwgm8KQ0JB8J+VtPCRj4rwkISnw7XwkbaTJy3wkJ6DUfCRsYM9PT/DoG3Do1ziuotZYHwq8J64t/CflbQi4KGeSGrwkby/YEgKREJCYDU6OS5tOl7gq7ngqo3wkLCn6pqwJXsi77+98J64tPCdkqbwkYC+QPCRtLw81JIj8J+JhOGzrvCehY7IuvCfrrQvCjlCN/CRjqt2J0vhpYBVcOCtiPCegKPwnoCb4LGMyLrvv70nZ2B3d/CRv4Fg4KOF0ajwnZOc77+9ez0KMkIw8J2Lsu+/vfCdgJ4n4b2dLvCdqp8i77+9flxFY8276p+aQ+GVh9Go8JGOvvCUkpJ7Ch1CG+C2nz7wkYa577+9JfCeo4dI77+9PVXwnZWBYAo8Qjon4Keo4KmZPy7hnbDgurNc8JCLoSooUOC7nzpczozvv73gqpVddnrwnZKi76ylPvCQjoQqXfCflbRVCiRCIiLwkJaqXOCykGAlwqMvUOCstyci77+9yLoufcO40ahceyUKJUIjXPCepZYh8JGkjeK2vmMiIy5cdDfvrLknLi/IuvCRhYY1JFkKEUIP8J65pz0/Z/CeubzDu19gChlCF2Lqn5EmeOC2guK2sWnwnp+t8JGMiHBcChxCGuGJmE4mwqXDqOKGg+K1r0fCpWbgqLzwkY2mCjxCOvCQuqLwn6mhOOCsj+qgs8OC8JauhDlg6q2P8Ji0g3t7WPCQjIwvXuqbsSUk4LeyXcKl0ag+8JG1pCQKD0INQnzwq52/0ajwm4WkewoCQgAKCEIGJOCointbCjBCLuOElSdz8J6kieKYoe+zuzXwn5W0b/CegKMvyLoqOiVaXTwxejXhm54q4KasXC0KQkJAVTrCpfCQrZDgt7RH8Jy8tOGds/CQlbx14Kmc77+9XGAj4Ka58JGHo+Css/Cfn6giLvCflbQq8JGFh+GDh+C7hgpIQkYv8JC6sWAy8JCWgcKl8J64uVxqJHdC8LGIpT3Ds1zwkYeCPOK0rfCfqqZ7JPCRtpDwkKCI8JGNq2J38J+preCznSTCpcOqCjVCM+C6iiXwkJOO8JGkosi6XCcn4LarImAn4aSgRfCfgJwqLtew8JGMuz0/4a+e8J66l+GLpAo2QjThu4kie/CesqdgQ/CQoLzqnLguPPCQpL/wnqSK8JGTlDrgvovwkpKgwqVT8JCohnh577+ECjFCLzzwkbyE6qyoUSR1LiXwn6mgP/CRnJ/wnoqTefCflbR08JG9hy9+Mj3wkY2Q6qC3CidCJXMkJiTCoCTDgD894K6OLjzwn5W0K/CRoLjwn5W0SV4/8JGbmWQKEEIO8J+JkHV7XC9MJfCRnJIKCkIIe/CeuYvirokKJUIj4Kak77+94YyzYC/vv71J0ahjJTkkJjrhn7nwkb6wXD1Q1a0KEEIOfT/wkYqo4KqQKC87WjwKMEIuZCZ68J2EnCZr8Ja1ivCfqpTCpShCKl8iXvCQgpskQMOYTidaVfCehYFB8JGNgQo0QjLwn5W00ag3eyZJwqjqn5PwkJWxe/CehL1gwqzwkYOzJFDwkY2HJmbhnbMkOmFBLuK0pwpKQkhYbiXwkYOzw6TwkYqB8JCWkPCRpL3wkbCZKvCRpqfIuvCdpqN70ahr4KiG8J65neqqpT3wnZilPfCQgINkXPCfg4dgJnjguL8KCUIHLiRb4LaCXwoXQhXRqPCeuLs6IvCQlrjwn5W08JC0sGAKMEIuZvCRr54pae+/vOClvC9c4b2bdi864LOtdidd0ajDp+K3jfCWqZ4uVO+/veqmigo3QjXwnZKmPsOBSnQi8J64tCU+aPCWqanRqOC7jfCQh6Y8PUrwn5W0wqwq8J6AgF8/yLrDq28kKgoTQhE94Z2D2JrwkYiP8JCBl+qsqAoMQgrhootg0ahr4LGGCgxCCvCWrZHwkICRyLoKAkIACiZCJG7wn6CW4YmMajzDqTovd+C6hMOSyLrwkJCxOvCRmacuJGEqYAojQiHwr6G2WiQq4KqyKuC7g0fwnYG0wqXwmr+6csOYJzThvbQKL0It8JCWm1Np0ajqrKXvv70qJzhT8JGFrvCQsKrwkLW98JGsgGBXT0ngsK7wkKiQCjxCOjo/4oKv357wn5W0PGA/XC7wkYWZ8JGyhz/wkIOCzoxC4KyBJPCWhIjgqIEk8JGDmydbKO+3j/CRtqkKNEIyPydSXy4swqUlTCfwkbCT8JGMiHtt8J+VtO+sq2RZ4Ky44KeN4KGj8J+VtMOVe2xByLoKRwoEbmFtZRI/Qj134K6PyLp78JuEsiHvrL4le3lxIuCotfCeuZss4LeW6p+cXOqnrvCRo5YqJz/guoRwO+KereqrsuCtiMi6Cg8KCW5hbWVzcGFjZRICCAQKswK6Aa8CCooCCgdjb2x1bW5zEv4BsgH6AQouQizgt5RB8J6LntaNTll78J+VtPCfiaAt8JCotfCeuYsk8J+VtO+4g3sz8JCnlgo0QjI9Kjw9JcKlI/CfiLE9ZWxFL2LhpZcr4YuA8J2Tg+C+pn3wkK6bWi7qpZHwnbyBViVvYAoDQgEqCjRCMtSC0ahgdEtV8JGbki5E8JGWuvCdvI884K6e8Jq/vvCTlbQq8J+VtOGdsPCdhIgm4KC8ChdCFVHwnoCfJPCcsaBbSPCQubzwn6+LeQo+QjxI8JGypO+/ve+/vTpS8Ja8kj4qOiY9IvCdhZI5wqXit4XgsZ1n4b2bRT3wkYuW8J+DgfCehIcmfCfhho8KDwoEbmFtZRIHQgUufHQuLwoPCgluYW1lc3BhY2USAggECs8ZugHLGQqSGQoHY29sdW1ucxKGGbIBghkKREJC4KKE4K+GP+GwvPCRj4dA8JCTje+/vfCen65oQmDCpWLDnsK68J2IqcOF4aCSJyco6pmpdyUvIuC1q2Iq4Z2v4b2dCgpCCOCovO+/vTo/Ch9CHSfRqPCRiqjwkJKYamtgLtGoPfCdlb5c6qmN77+9CiRCInvwkbS84Y6n77+98JGPhU82KvCRmaUiNe2fs11oQPCego8KOUI3JyRvcz8u1r0l4Ly+5qyS8JGmpsKlJiZc8J6AgiThgKV04KyQb+K2rVvgs4HwnY24JiXwkKuyPwoeQhxG4aCROSLvv73wnpe/ZMOZ8JGpv8i6Jj9gNCInCiJCIOC6hOCsrF7wkY6L4b+ba2Dgpod68JKFpvCeuLXwn4K9CjJCMPCRjJAneOCpru+/vfCQgKx7QuKCgOCytUPwnZSpwrRcV/CflbR84oCJd+G9ou+5qQpRQk9W8JG8jeCxmT0/8JGMuO+su8KhYMi68JGKpvCRiLXhs4PwkaqD4KyDwqUiwqU88JG9i/CRkKPCpeG+sPCflbRJPDXwnp+p8JCuq0Z14KmBChpCGDnDquCmhuC4lyfwkKiGXSJn4KOQJe+/vQoHQgXwnYynJworQingq6HhpbTwn5W0KtGoeeK1nUU/eOOPgfCfgpHRqPCeuaQm8Jatt8KleworQinCpT8/NlJb0ag94b+g4a6UVzpe44ap8JG2qPCQhpDwnY2KfWki8J64pAoqQijIuuGdqHInVPCRjbIqLz3RqCZgOvCWvbrCqC9ZOsKxLeGglDPwlqmLChtCGVLwn5+SwqXwnYupL/CRqbDIumdcPmDgrYgKGkIYREdgMD0r8JGMi/CfnZ488JGKiEoxLmBcCjlCNyTwkYK+ez884LWN77+98J64uS8i4b+y4LqB4KqB8JC9vSJAKjzvrKvCpT068JGxsOGwgvCQooAKJkIk77m3PPCen7Hhs4EmN1zhnbMkLuqnvD170ag46p+HJTzNu28iCilCJ/CQq6/gsZ1b4LWgYC4nfuKapkJDXDrwnripKMi66pKgY/CeuKc/RAo+Qjw/8JGkiTtpey4kOi7gtIzwrp6OIjIlPW9MYfCehLBiQWDCvuG/mOGdgfCTkZLwkJakcMKl6qKf8JCWgU8KIkIgIksnJFZeyLrwn5W0d+Css/CQo7TgqrY7L1dwTfCRm4YKLEIqyLovPyPwkYqgPHrgrpDhvZ3wn5W0dPCdhZfhi49bRGzvv71b8J2islxECgtCCXty8J+VtC86JQoKQggq8J65mci6JAo0QjI1PzAiI/CQoqnwkL+J8JGDm/CbhZXgs4Rs8Jatk1Yq37nvv71SUvCWvaA88J+ppTzCpQoEQgImMAoKQggiPWvRqMKlNwodQhtKPy9244SgeyI84am08J+VtMOi8JCrrDY/Z1kKJkIkQfCeuILRqCdh4bC+a+CqoFYiU8Kl4K2cYGEkJMKl4K6KfnBpCjtCOfCeuqFOTjY6Sy46a0Hwnrmp8JGjsfCRsIEu8J2QmeG9m/CfqofDkOKAoe+/vcKwe+CrkPCRsp9hPQoiQiBRbSLwkISCJCnwmKuZP/CflbTvuakl76q38J+hki/DvAo6QjjgrpDIuiI/KsKlWj0k8J65hydc4rqS8J65ji418J6AjiJ78J65l+K0p0fitrgnJXvwnrmdJeCungoLQglhe0/vv73RqGAKB0IFYPCRprYKB0IFXOG9ulEKGkIYS2Ym4LOidzrwn5W04r+yXCov4Kq44b2dCiVCI/CdkqUtInTwkJaf8LC0iiLwkI+KKvCflbThm4xcZvCRjJBsCh1CG++6hlwq8JGpmiLgtr3wnZSawqV5azLhi7AiXAoeQhwqOnvIuidyJNutOjok4KmI8J6Amy7wkbaXSDo9Cj1COzdJ8JCRjEnwkaWRWSbikYRy77+9wqXwn6u38JGrge+5mSZcyLosJ1nwn5+wQ/CRpJlNOuG9quK6pMOMCjxCOmB7RCo/cfCuuZHRqCTvv6Ewaci6Ind+POCqmOC/iGPgrr498JGkjOGoouK1sO+/vSJK8Ja1j/Cfn7AKF0IVXPCfg6Im76yWNzxgIu+/vS/wnLyWChpCGGlJUnvRqMOX8Jasuyd9eVzwnruwe++/vQoRQg8wZHjwkbag4YqL8JGMhSoKNkI0aWJUKyTwn4OI8JCdpHtg6q2Z4YOH8JGPoS/wnZOkeiJxL/CflbTwn5W0Oy9377+98J+VtAoYQhZiTmhYbiY/e/CeuoAu4KuAIuCph2YjCiVCIyRyWD/itKdBV9mm8JCKteC3tGc/LuChs8KlOGPhh4EkPGAkCiVCI/CdqqTwkIC877aq8JChgzw9XPCdkI978J6ToWJsflc/wqU8CixCKi7wkZKP8J+VtGA84LKOYSfwnpey4KeX8JGym+CxmeCxmfCeuaLwkIe0WwpKQkjwn6SrPPCflbTgqq5te+K1ryRJbyUq8LGXnkB2LmBK4aac4bW8JOGqg/CRtqnwn5W0JyfwnYuD8JCunPCbsobwlr+h8JGmoDsKE0IRXyQmJ0gkLmZaTfCfiJzCtSYKEEIOPC/wn6CFRDzgp4d3fXYKNkI0wqUvWvCQroDhhaBgduGltPCflbTwkYOW8JGHrzrwn6mnwqYmZ+K2hljCpSbvv709TCRZWAoHQgUv0agnPgoGQgRv6p+WCjBCLkPDofCRu7PwkbGZey/wn5W08JCBkuCnl21Xay5BME3wkJaVeC/hv5gqe+Cnjj4KLkIs8JapqcKz8JGMiPCdi4TgtacnS/CRjL9I4Z2uM3Uh77uw8JGNsi/Iuuqlq2gKDEIK8JCVinzhp5PDgAoYQhZCbNGoJXFheyThiaXwnri7LCfwn5W0CjtCOfCegJbwkJCf8JarnVxke++4kyUu8J6AgfCRqanikYZ7JPCfoqE8JO+lvPCRjZdg77+9PPCepZ44SgocQhouYC898JCnhGDwkbaNP0nitK1Bc0kuMiUnPwocQhrhu4Eu8JGvsmd08JGSmfCQo6kk8J6EvS9RewocQho577+98J+hoeGJi34gdOCygDcnTuGJkWXIugo3QjVt4rSnLnM0IvCflbROYOC7nPCepYjvv70/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+Cog9GowqXwkau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KJUIj8J2LsTldYFglXvCRpqVsKvCwqbvqraTDjSTqraZKPD88XCYKOkI4w4nCpS3hi4jwnrmfJzo08J+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+qpgkQ6KfCetKv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+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/CAQwKCkAwhkE3F0EQczwKIboBHgocCglUcmFuc2llbnQSD8IBDAoKJmKCUzMAVlQ5XAocugEZChcKBFVzZXISD8IBDAoKiIKHMkV0aGJAbAodugEaChgKBFVzZXISEMIBDQoLAUIWE1aERnRBhiwKIboBHgocCglUcmFuc2llbnQSD8IBDAoKIzlUklRkSUeHHAofugEcChoKBlN5c3RlbRIQwgENCgsBKJeUVwZSmRdDjAowugEtCisKGEludHJvc3BlY3Rpb25Tb3VyY2VJbmRleBIPwgEMCgpIIViEImkEIkEsCh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEEl3MwgGAFBpA8Cie6ASQKIgoEVXNlchIawgEXCgoWRVRlOSeFQUl8EP///////////wEKIboBHgocCglUcmFuc2llbnQSD8IBDAoKdYFylRVVZHk5HAoiugEfCh0KCVRyYW5zaWVudBIQwgENCgsBUliRdFg2d0V3bAopugEmCiQKBlN5c3RlbRIawgEXCgoVFDcpgTN4CHQsEP///////////wEKHboBGgoYCgRVc2VyEhDCAQ0KCwElcRhYQJCBhoFsCjC6AS0KKwoYSW50cm9zcGVjdGlvblNvdXJjZUluZGV4Eg/CAQwKChY4ZWZ2YEMkY0wKLLoBKQonCglUcmFuc2llbnQSGsIBFwoKBYVgkHA3dVFiTBD///////////8BCjC6AS0KKwoYSW50cm9zcGVjdGlvblNvdXJjZUluZGV4Eg/CAQwKCjaSaGeWQHQBUpwKCUIHRXhwbGFpbgowugEtCisKGEludHJvc3BlY3Rpb25Tb3VyY2VJbmRleBIPwgEMCgoIFmBiAnIhgZCcCiK6AR8KHQoJVHJhbnNpZW50EhDCAQ0KCwFzY4gTATdjmGOcCiK6AR8KHQoJVHJhbnNpZW50EhDCAQ0KCwFJBQGIdyR3d4A8CjG6AS4KLAoYSW50cm9zcGVjdGlvblNvdXJjZUluZGV4EhDCAQ0KCwFYJmBXgkhReJB8Ciy6ASkKJwoJVHJhbnNpZW50EhrCARcKCglwQ0QGc3ZgWEwQ////////////AQofugEcChoKBlN5c3RlbRIQwgENCgsBUFBzEzY3IgAIbAocugEZChcKBFVzZXISD8IBDAoKFCEkZoMgcRcyfAocugEZChcKBFVzZXISD8IBDAoKiRNQVEEAAZBlPAoiugEfCh0KCVRyYW5zaWVudBIQwgENCgsBQ3ZVJSkBJkcwbAowugEtCisKGEludHJvc3BlY3Rpb25Tb3VyY2VJbmRleBIPwgEMCgpREkIQcEKQKTUsCiG6AR4KHAoJVHJhbnNpZW50Eg/CAQwKCkCWhwOQOIaIgWwKIboBHgocCglUcmFuc2llbnQSD8IBDAoKGCNXJwhhWFdETAoJQgdFeHBsYWluChy6ARkKFwoEVXNlchIPwgEMCgqIR3BhhJJjMjJsCiK6AR8KHQoJVHJhbnNpZW50EhDCAQ0KCwFZEBUGI3eTUZg8CiG6AR4KHAoJVHJhbnNpZW50Eg/CAQwKCnFVhUUldYeIZzwKHroBGwoZCgZTeXN0ZW0SD8IBDAoKcVCDdQKFWVJEbAowugEtCisKGEludHJvc3BlY3Rpb25Tb3VyY2VJbmRleBIPwgEMCgohkAh4FRgFUwhcCh66ARsKGQoGU3lzdGVtEg/CAQwKCjdDYCV3IplgaEwKKboBJgokCgZTeXN0ZW0SGsIBFwoKFCSJhVAFEiUSbBD///////////8B +Cs8NCswNugHIDQpCCgNrZXkSO7oBOAo2CgJpZBIwugEtCisKGEludHJvc3BlY3Rpb25Tb3VyY2VJbmRleBIPwgEMCgoHUhVpBIVHApI8CiMKBGtpbmQSG0IZTWFpbnRhaW5lZFJlYWRSZXF1aXJlbWVudArcDAoFdmFsdWUS0gy6Ac4MCg4KCGZyb250aWVyEgIIBAq7DAoGaW5wdXRzErAMsgGsDAocugEZChcKBFVzZXISD8IBDAoKZWAVUUOGdkmIHAohugEeChwKCVRyYW5zaWVudBIPwgEMCgpolyiCEVMWkBOcCiG6AR4KHAoJVHJhbnNpZW50Eg/CAQwKChAVlTYnGZQIgxwKHLoBGQoXCgRVc2VyEg/CAQwKCimIA3VZApIDKCwKMboBLgosChhJbnRyb3NwZWN0aW9uU291cmNlSW5kZXgSEMIBDQoLAVNwZ0eWhnmGhYwKCUIHRXhwbGFpbgofugEcChoKBlN5c3RlbRIQwgENCgsBgHVWMWEERIYFfAoiugEfCh0KCVRyYW5zaWVudBIQwgENCgsBaWYhKVOANWWSTAocugEZChcKBFVzZXISD8IBDAoKOJVCWUkWNjczPAoxugEuCiwKGEludHJvc3BlY3Rpb25Tb3VyY2VJbmRleBIQwgENCgsBIHhjExFAl3EhfAoJQgdFeHBsYWluCiG6AR4KHAoJVHJhbnNpZW50Eg/CAQwKCgQyZ3FxgJkYiCwKIroBHwodCglUcmFuc2llbnQSEMIBDQoLAQkDGXd0IgNkI5wKCUIHRXhwbGFpbgofugEcChoKBlN5c3RlbRIQwgENCgsBMAiWF4QhBkUFfAoJQgdFeHBsYWluCie6ASQKIgoEVXNlchIawgEXCgoVdUMUZHAkIpRcEP///////////wEKMboBLgosChhJbnRyb3NwZWN0aW9uU291cmNlSW5kZXgSEMIBDQoLAYFTNpAZQoFIlTwKMLoBLQorChhJbnRyb3NwZWN0aW9uU291cmNlSW5kZXgSD8IBDAoKAxKFBCJwF0WUXAofugEcChoKBlN5c3RlbRIQwgENCgsBZjVCRogmN2JWTAoxugEuCiwKGEludHJvc3BlY3Rpb25Tb3VyY2VJbmRleBIQwgENCgsBeRgQUYBSEzQ3PAoJQgdFeHBsYWluCjC6AS0KKwoYSW50cm9zcGVjdGlvblNvdXJjZUluZGV4Eg/CAQwKCiMkEVmZdGAnhTwKIboBHgocCglUcmFuc2llbnQSD8IBDAoKCJIjAkVnMEM5TAohugEeChwKCVRyYW5zaWVudBIPwgEMCgpRWJEIlxgkUZR8Chy6ARkKFwoEVXNlchIPwgEMCgoRUXZQkzgzYAdcCh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEYORlyZHKXRUQ8Ch26ARoKGAoEVXNlchIQwgENCgsBRQQIlidBMGYnLAoeugEbChkKBlN5c3RlbRIPwgEMCgp0lwJGSXUiQSZ8CglCB0V4cGxhaW4KH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAXN4BGeBFgFUVkwKHboBGgoYCgRVc2VyEhDCAQ0KCwFWRXBAgRlmEjgsCh66ARsKGQoGU3lzdGVtEg/CAQwKCmCWYmSXKRl2ZiwKCUIHRXhwbGFpbgodugEaChgKBFVzZXISEMIBDQoLARR4YTBjVJUDhVwKMLoBLQorChhJbnRyb3NwZWN0aW9uU291cmNlSW5kZXgSD8IBDAoKWVF1QGSZByd1HAohugEeChwKCVRyYW5zaWVudBIPwgEMCgp4ORR4VVNAaGYsCiK6AR8KHQoJVHJhbnNpZW50EhDCAQ0KCwGBCEVHGDE4GECcCh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwElI4WRJkQjiDAsCh66ARsKGQoGU3lzdGVtEg/CAQwKCpljMncClnJwgRwKHboBGgoYCgRVc2VyEhDCAQ0KCwF3WEAoJWFZM3RMCjG6AS4KLAoYSW50cm9zcGVjdGlvblNvdXJjZUluZGV4EhDCAQ0KCwE0RJCSISGRATRMCjC6AS0KKwoYSW50cm9zcGVjdGlvblNvdXJjZUluZGV4Eg/CAQwKCiYGk2GAIlZQB5wKHLoBGQoXCgRVc2VyEg/CAQwKCnA2WJVTUDcRdWwKMLoBLQorChhJbnRyb3NwZWN0aW9uU291cmNlSW5kZXgSD8IBDAoKiYNlWIEWQVVxHAocugEZChcKBFVzZXISD8IBDAoKgxUShZcwQxSBfAonugEkCiIKBFVzZXISGsIBFwoKFDGESVgHQCiILBD///////////8B +Ct1CCtpCugHWQgoxCgNrZXkSKroBJwolCgJpZBIfugEcChoKBlN5c3RlbRIQwgENCgsBUVdJBjlodjCEnAoRCgRraW5kEglCB0NsdXN0ZXIKjUIKBXZhbHVlEoNCugH/QQpfCgZjb25maWcSVboBUgoWCgd2YXJpYW50EgtCCVVubWFuYWdlZAo4Cg53b3JrbG9hZF9jbGFzcxImQiRdee+/veqsjvCflbQnM1bCpcO3TydPTfCfiYMy4Kys8JGWqV0KFQoEbmFtZRINQgtBVOCoueGctCQvJQouCghvd25lcl9pZBIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKmRljIIWDKQYnHArUQAoKcHJpdmlsZWdlcxLFQLIBwUAKhgG6AYIBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAVM2KTByEBJFYZwKKAoHZ3JhbnRlZRIdugEaChgKBFVzZXISEMIBDQoLAWlyN1d5dyCUSCwKJwoHZ3JhbnRvchIcugEZChcKBFVzZXISD8IBDAoKSBJmYSaUlGBRnApzugFwCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAQhYGVckkTUmiGwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBUBF2J4ASZ2EYXAoTCgdncmFudG9yEghCBlB1YmxpYwpxugFuCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLARBhUGB5UAGXJRwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKKAoHZ3JhbnRvchIdugEaChgKBlN5c3RlbRIOwgELCgl3SIGXNxNwVpwKjwG6AYsBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAQhDY0kDRGkjMIwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBgVkCdmFBUzNUTAouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBMwYxFAY2l3F3PAqUAboBkAEKNwoIYWNsX21vZGUSK7oBKAomCghiaXRmbGFncxIawgEXCgoSMgRlKWJSU1AcEP///////////wEKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBVCFhmYZlEDkyPAopCgdncmFudG9yEh66ARsKGQoGU3lzdGVtEg/CAQwKCpl5VCgDJSkGOTwKjgG6AYoBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAYJRNVJXRJcTQhwKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAUVBc0aZIWd2hmwKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgpCeJkRcoJDeAlcCnK6AW8KLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpFiVloJGaCFiFcCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAXRnYRMSgIM0NUwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKnQG6AZkBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAUAVcoKAkHgkVCwKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLARYWkGM3IUB0EGwKOAoHZ3JhbnRvchItugEqCigKClByZWRlZmluZWQSGsIBFwoKBzGZhjViM5I3bBD///////////8BCpIBugGOAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwE3eId4iZkQNQE8Ci4KB2dyYW50ZWUSI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwEWKIKWB3EVlFCcCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCmRmNVcwSSQmElwKiQG6AYUBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLASFilSNBNhB1EXwKKQoHZ3JhbnRlZRIeugEbChkKBlN5c3RlbRIPwgEMCgqRkXAgGWBBkIQcCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKMJIFmYkyhSCUXAqFAboBgQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBRFcnYGggdiKJfAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgqIQ4gpg1JnZCZMCicKB2dyYW50b3ISHLoBGQoXCgRVc2VyEg/CAQwKCpQEURNpCRZTIYwKhgG6AYIBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKGFQhEiAQQgiRPAopCgdncmFudGVlEh66ARsKGQoGU3lzdGVtEg/CAQwKChWTkINDggFFhowKJwoHZ3JhbnRvchIcugEZChcKBFVzZXISD8IBDAoKaYFVdzmRUWkiHAqWAboBkgEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBU2eRIIhBd4mCnAoyCgdncmFudGVlEie6ASQKIgoEVXNlchIawgEXCgoXNyMGRQcFhwd8EP///////////wEKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKN1FwgFJFVIYVXApwugFtCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAVYABpRQRTc0iVwKJwoHZ3JhbnRlZRIcugEZChcKBFVzZXISD8IBDAoKJ3VySVkGV5YSfAoTCgdncmFudG9yEghCBlB1YmxpYwqZAboBlQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBKRZDchGQQ5AjTAoqCgdncmFudGVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwFWQyaRcBcAKGV8CjgKB2dyYW50b3ISLboBKgooCgpQcmVkZWZpbmVkEhrCARcKChFnlhNGYSGGYIwQ////////////AQp1ugFyCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKeBknIZI0Y5dEPAoTCgdncmFudGVlEghCBlB1YmxpYwotCgdncmFudG9yEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpXgwggEHMHZwIcCpcBugGTAQo3CghhY2xfbW9kZRIrugEoCiYKCGJpdGZsYWdzEhrCARcKCgUHc3dgJBY1KHwQ////////////AQotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgoVIZh5WAQYGWc8CikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKlkUlmAYgR0mDLAqIAboBhAEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgqYl0cSVWlDBgF8CikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKNwN0Z0QEOTQoHAopCgdncmFudG9yEh66ARsKGQoGU3lzdGVtEg/CAQwKCgZVIUGIEGQXJYwKcLoBbQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFAM4hyRyJoEGVMCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKCgcQAxkhAmRQWBwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKjAG6AYgBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAUA5JHElQSBQcCwKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLASeVdlcWFpMSRJwKJwoHZ3JhbnRvchIcugEZChcKBFVzZXISD8IBDAoKNSCVAJNQMZaSbAqGAboBggEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgokIVdAAWVCljksCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKCnWAcwGXcFV2cXwKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgoTMHgINwg3dGEsCpkBugGVAQo3CghhY2xfbW9kZRIrugEoCiYKCGJpdGZsYWdzEhrCARcKCgl1AxABeWRpI2wQ////////////AQouCgdncmFudGVlEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBKWCFcnZBhXU3bAoqCgdncmFudG9yEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwGBdBCFYlkCJmOMCooBugGGAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCicwZEUJYjA1BWwKJwoHZ3JhbnRlZRIcugEZChcKBFVzZXISD8IBDAoKJBhxVmWUOTZ0PAotCgdncmFudG9yEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpgE2NjQzUxMxVMCo4BugGKAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEkFZFlODGWYJEsCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKIwdikmY2BjlBHAouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBSEQgZ2kgIlUyTAqNAboBiQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBCQJzZXYJNIgBfAouCgdncmFudGVlEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBBZAgQ4cUN0MpXAooCgdncmFudG9yEh26ARoKGAoEVXNlchIQwgENCgsBSTYIR5JzWBJ4TAqNAboBiQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBMIFHOWWJJmVQLAooCgdncmFudGVlEh26ARoKGAoEVXNlchIQwgENCgsBACMBl5codFB0jAouCgdncmFudG9yEiO6ASAKHgoKUHJlZGVmaW5lZBIQwgENCgsBdYYoUUBWkylyfAqQAboBjAEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoheFkxSRQodFdsCi0KB2dyYW50ZWUSIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKClGRkQFENQWQYjwKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKJmGGhZgSEQFyTAqLAboBhwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBYQCTkYExhxiHXAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgqTZUWTA1UAclg8Ci0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKColUdCEyIHGDM3wKlQG6AZEBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKIASYVUECd3AELAoyCgdncmFudGVlEie6ASQKIgoEVXNlchIawgEXCgoRJ4AXl3RUl3KcEP///////////wEKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKkhlzkzIxKYKRXAqLAboBhwEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgozMYRBAXRCSBdcCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKClRyVDg1cjWCOWwKLgoHZ3JhbnRvchIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAQVJgSVnN4BicHwKc7oBcAotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEWApNGZkg3MSlcCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAXIHdYQhc3WQQHwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKkwG6AY8BCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAVaZQ3MzZVNRAJwKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAQd0c3JTKZWEOCwKLgoHZ3JhbnRvchIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAXIXmTQZUEiGR4wKcroBbwotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwF3VmSJUmeFCZN8CikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKMWYUJpSJSYVGTAoTCgdncmFudG9yEghCBlB1YmxpYwpzugFwCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAQhnUhCXIgVyWIwKKgoHZ3JhbnRlZRIfugEcChoKBlN5c3RlbRIQwgENCgsBcABhgYczdFk5HAoTCgdncmFudG9yEghCBlB1YmxpYwqJAboBhQEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBd0InIBVmEAWATAopCgdncmFudGVlEh66ARsKGQoGU3lzdGVtEg/CAQwKCokFdmFQlWAIVxwKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgpYGWOUJ0ACJoU8Cna6AXMKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBIlSSGAUjhINXTAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpRiCI5JoAJCDI8ChMKB2dyYW50b3ISCEIGUHVibGljCogBugGEAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFICCiZMHIwCWl8CigKB2dyYW50ZWUSHboBGgoYCgRVc2VyEhDCAQ0KCwFniUVINhQjJClcCikKB2dyYW50b3ISHroBGwoZCgZTeXN0ZW0SD8IBDAoKlwI3NzBTU5k3fAqQAboBjAEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoSRgYjOHIwEmM8Ci0KB2dyYW50ZWUSIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCokxVVR5VCFFNDwKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKFTaEUEIHdiZALAqLAboBhwEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBGAlId1JxM0l2nAotCgdncmFudGVlEiK6AR8KHQoKUHJlZGVmaW5lZBIPwgEMCgpUUBIRYJRiglJ8CicKB2dyYW50b3ISHLoBGQoXCgRVc2VyEg/CAQwKCgFRgCOYYYNJQWwKkAG6AYwBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAWgIdGd3hnRzQGwKMgoHZ3JhbnRlZRInugEkCiIKBFVzZXISGsIBFwoKBBcnMYSJOROIPBD///////////8BCicKB2dyYW50b3ISHLoBGQoXCgRVc2VyEg/CAQwKChWAlSBUiDNmhWwKd7oBdAotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFpZUVYCRllAZAsChMKB2dyYW50ZWUSCEIGUHVibGljCi4KB2dyYW50b3ISI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwExYmmJhiJzQ0gcCly6AVkKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBgBJoaCAYUXhpbAoTCgdncmFudGVlEghCBlB1YmxpYwoTCgdncmFudG9yEghCBlB1YmxpYwqOAboBigEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpQiGRxSHQAcZOcCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAREEBCIkdAGIkHwKLgoHZ3JhbnRvchIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAURHFJYogSQIgowKgAG6AX0KLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgoEQ5JnZ2eJRnR8CjgKB2dyYW50ZWUSLboBKgooCgpQcmVkZWZpbmVkEhrCARcKCgg3NwYChRUlaZwQ////////////AQoTCgdncmFudG9yEghCBlB1YmxpYwqQAboBjAEKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBMwRFdpSJlFZnLAoyCgdncmFudGVlEie6ASQKIgoEVXNlchIawgEXCgoSh3BhEClWEpl8EP///////////wEKJwoHZ3JhbnRvchIcugEZChcKBFVzZXISD8IBDAoKkyURYjiBBgR5XAqFAboBgQEKLAoIYWNsX21vZGUSILoBHQobCghiaXRmbGFncxIPwgEMCgpxeINGGGNWWRSMCicKB2dyYW50ZWUSHLoBGQoXCgRVc2VyEg/CAQwKCkJweVeHODIwkUwKKAoHZ3JhbnRvchIdugEaChgKBFVzZXISEMIBDQoLAVdjCWlFNAEWd4wKc7oBcAotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEhMBkjQFJmV5aMCioKB2dyYW50ZWUSH7oBHAoaCgZTeXN0ZW0SEMIBDQoLAWE0kSJ4aSlXgFwKEwoHZ3JhbnRvchIIQgZQdWJsaWMKlgG6AZIBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKGJeFUDNFlCUWHAoyCgdncmFudGVlEie6ASQKIgoEVXNlchIawgEXCgoXJZBCOUBRGZWcEP///////////wEKLgoHZ3JhbnRvchIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLATBCWSEmACBDZ0wKd7oBdAotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwEjBJNSiAEGZQJcChMKB2dyYW50ZWUSCEIGUHVibGljCi4KB2dyYW50b3ISI7oBIAoeCgpQcmVkZWZpbmVkEhDCAQ0KCwFUWJIRkAWHiCV8CpgBugGUAQotCghhY2xfbW9kZRIhugEeChwKCGJpdGZsYWdzEhDCAQ0KCwFUVzFSGXNIBwKcCjQKB2dyYW50ZWUSKboBJgokCgZTeXN0ZW0SGsIBFwoKGCEJIAV1MSUBHBD///////////8BCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCnBBMkZYZJR1lIwKjQG6AYkBCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKk1hRZHgYKSkRTAoqCgdncmFudGVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwFFWYeYMpRIFBIsCi0KB2dyYW50b3ISIroBHwodCgpQcmVkZWZpbmVkEg/CAQwKCnYiB4ZhFSRyKCwKlAG6AZABCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLARAZUpEmQBZkFpwKNAoHZ3JhbnRlZRIpugEmCiQKBlN5c3RlbRIawgEXCgoIIpGUUZWVgHRMEP///////////wEKKQoHZ3JhbnRvchIeugEbChkKBlN5c3RlbRIPwgEMCgoQIJFTSBlyNYhsCnO6AXAKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBY4UAcyVDN3FSTAoqCgdncmFudGVlEh+6ARwKGgoGU3lzdGVtEhDCAQ0KCwEQR1ACBkc3cQacChMKB2dyYW50b3ISCEIGUHVibGljCosBugGHAQosCghhY2xfbW9kZRIgugEdChsKCGJpdGZsYWdzEg/CAQwKCkAQCDQmGTUnlIwKKAoHZ3JhbnRlZRIdugEaChgKBFVzZXISEMIBDQoLATCZlgKXBJNVBHwKLQoHZ3JhbnRvchIiugEfCh0KClByZWRlZmluZWQSD8IBDAoKloNBI3ZpEFJZHApwugFtCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLAVKFAXkhMHeVCTwKEwoHZ3JhbnRlZRIIQgZQdWJsaWMKJwoHZ3JhbnRvchIcugEZChcKBFVzZXISD8IBDAoKRod5JVCYQAZWTAp8ugF5CjcKCGFjbF9tb2RlEiu6ASgKJgoIYml0ZmxhZ3MSGsIBFwoKAXk2BwBZCRFyHBD+//////////8BCikKB2dyYW50ZWUSHroBGwoZCgZTeXN0ZW0SD8IBDAoKhYAiAZgEZIgCbAoTCgdncmFudG9yEghCBlB1YmxpYwp3ugF0Ci0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLATaIV2dSk1FJMYwKLgoHZ3JhbnRlZRIjugEgCh4KClByZWRlZmluZWQSEMIBDQoLAQlZdYFyk1AIU1wKEwoHZ3JhbnRvchIIQgZQdWJsaWMKiQG6AYUBCi0KCGFjbF9tb2RlEiG6AR4KHAoIYml0ZmxhZ3MSEMIBDQoLARdZhEhRCDeJZFwKKAoHZ3JhbnRlZRIdugEaChgKBFVzZXISEMIBDQoLAQBSEnZgKTBnCBwKKgoHZ3JhbnRvchIfugEcChoKBlN5c3RlbRIQwgENCgsBI0CCd4dWczdoLApvugFsCiwKCGFjbF9tb2RlEiC6AR0KGwoIYml0ZmxhZ3MSD8IBDAoKiXdRE1MzdYlhLAonCgdncmFudGVlEhy6ARkKFwoEVXNlchIPwgEMCgoJkpiBQYUEOAJsChMKB2dyYW50b3ISCEIGUHVibGljCly6AVkKLQoIYWNsX21vZGUSIboBHgocCghiaXRmbGFncxIQwgENCgsBUBF1cXKQaTR1HAoTCgdncmFudGVlEghCBlB1YmxpYwoTCgdncmFudG9yEghCBlB1YmxpYwqSAboBjgEKNwoIYWNsX21vZGUSK7oBKAomCghiaXRmbGFncxIawgEXCgoWlFAJZ0FnSJVsEP///////////wEKKQoHZ3JhbnRlZRIeugEbChkKBlN5c3RlbRIPwgEMCgpYNGEBBTQCkQWcCigKB2dyYW50b3ISHboBGgoYCgRVc2VyEhDCAQ0KCwEwGZNpBAUlhkeM +Cm8KbboBagoqCgNrZXkSI7oBIAoeCgRuYW1lEhZCFPCRpJol8JarnMOgPWrgt60pcmN6ChEKBGtpbmQSCUIHSWRBbGxvYwopCgV2YWx1ZRIgugEdChsKB25leHRfaWQSEMIBDQoLAXgTcydZUWcxcWw= +CssBCsgBugHEAQpXCgNrZXkSULoBTQoeCgRuYW1lEhZCFC5w6qyS8JCujvCflbThv7vgr4EtCisKCnJlcGxpY2FfaWQSHboBGgoYCgRVc2VyEhDCAQ0KCwEHY0EkkYgDh1R8CiQKBGtpbmQSHEIaUmVwbGljYVN5c3RlbUNvbmZpZ3VyYXRpb24KQwoFdmFsdWUSOroBNwo1CgV2YWx1ZRIsQiok8J+VtGom8JGKhXgn8J6fquGon/CRpZXwnrmRNEov8J+VtOCvhvCegKE= +CtGnBQrNpwW6AcinBQo0CgNrZXkSLboBKgooCgZzb3VyY2USHroBGwoZCgZTeXN0ZW0SD8IBDAoKU1RTkRYlNmlgbAoaCgRraW5kEhJCEFNvdXJjZVJlZmVyZW5jZXMK8qYFCgV2YWx1ZRLnpgW6AeKmBQqvpgUKCnJlZmVyZW5jZXMSn6YFsgGapgUKwxC6Ab8QCvgPCgdjb2x1bW5zEuwPsgHoDwolQiPhgIQ8dUItPSThoItgJUTgpqrgp43gqIMuL/CQlI7grIkvJAonQiXhnbLwnZSNYOCtnCXwmr+94YmS3KdJJvCWroTwkbG1w5ffvsi6ChlCFz0k8JGxkvCQq7Jk4LueL8i6J/CQto4oCitCKTrjgZTgp50mZyQ6Li55NS7hvYrDuPCRioglPfCRg5LhirrwnqWQKjg8Cg1CCzA98J+roTUvSdGoCitCKT9b8J6jgCbgtpXwkZmiXGA6U/CRsZ5I4b+jSzoiXGI7IvCSkaUq4b6oCgdCBeGPukJUCg9CDcKl8J65vEXRqPCRnIkKCkIIPOCph/CeuZEKCkII8J+VtMOJJS8KPEI68JCHsyTwnrik77+i8JCGoEhcwqF74Kmu4KepOsKl8J28niQjw67RqHvwnYS7Kkp7P8Klez/gq4snQQozQjE0yLo/eyUmRiLgt4Mq77+9OjBf8JCwgVzvv5M6Lnoi4aSB6qyKN/CbsoInWXjwnrikCg9CDWAy77+9W+GPjPCcspkKMEIue/CRkLTCpXo6JvCdk4lBKyZWw6Ai6qeUQeC3nScv8JGkt/CSgKs/T/CRj4phTQoTQhEmSdGodSrwqq60bSfIuis6XgpCQkAiezonbvCdhJN6USLwn6Kj8JyxmfCdpIhqJuC0kPCRtIXhv4zvrKoi8J+VtPCRjZc9SfCQqLHIukPwkIWO6pqmCglCBz8oLTx3RD8KK0Ip6qyKQOCyjj3wkbakSNevYPCRmIgn4KmC4K6DPCxXYNGoJeGjlCIiUSUKBkIEWOGKtAoQQg7gu4bqrIZJIj3wnZKfLwodQhvwn5W04Ki88J6EtyYnOi9pJyvwkLShVPCQgI4KBkIEYEYiKgogQh7wkZq077WML0U98JCKl/CflbTwn5W0YEnwkL2LRiIKJ0IlJeKBkCfgrZ/Dvj/wn5W08J64gjzIuuqoiyYwbe+/jCV9J8KldgoLQgln8JapqeCsgSYKTEJK77+94bOCU+CzlvCQqJAj4K6c8J+VtPCflbRePOChnnXCpXd78JCkiVM6wqVWPfCQoLzhvrHwkbS9JVjwkbGs8JuFkeCpgfCeupIKOkI4Nci6LFxsUeC+sy8/YOG/uPCWrZjDv0978J+rpkzvv71geWBcP/CWv6HgsrU/8J+VtOCmsvCQkrIKGkIYeyTvrb/wnrm1PDnwkLWcIiLvv73wnrmJChdCFUQ/8JGXnWbCssi64Z+zJSo94Ke7fAo1QjMqKvCeuZc6KvCRhqvgq4le8J65vuqsiiYtfTzvv717MfCfqbpbSHUqYPCRqL/hvYngq5AKDkIM2JbWjtGo4LOq0ag5CjBCLvCRpro7UTzwnrinaeCvhybDgm0qP++/hPCRipBaJybhpbQlIvCQqI4kL/CeuYIKEkIQPGDwm7KWJMi6JeKCrTxwYAovQi3wkYqIM07wkYizMuGxpu+/vdW6OmAldOCmr1PIuk/gtYfwkaSO8JGciOK7jzwKO0I5a8Kl4riMW3LwkJqZNjzwkYqN8JGPgvCeuJQ8eywk77awTCIlOjU24Lql4KiP8JGDsFvwn5W07LOIChBCDvCQqLngp7AqKuqlsTpmCghCBiXCpeCukAo+Qjwu8JCwruKDmmtmX++krOCyqD0/e/CeuoPwkJ2g8JCKujzwkKitbuqmhCrwkI2c8J+VtEMsZ/CdkYBHWlwKPUI7wqXgsaAuw5lj4K2dJlMlwqUk0ajDrDpj4Y2EXFzIukVE8JCWiifgt5Twm7GTYybgr5c6LvCRmqrgqYgKKUIn6qOPLz3wn6K74KCwwqVcYPCeipnvrYFw4Ki88J67sPCeuLti0ah7Cg9CDVw/JybwkbWl0ah8JDkKGUIXe/Ccuq3wnZKXJfCQpJUmKT9kJfCdkrsKJkIk4K644YuC8JColUQuwqUn4LqESuCqiWDDqfCRm5k6KMO74LOVCkJCQMi6W+CqvGDvv70jXOCznfCQl4BZ4b2ddDLhrKxW0ahWYT3wnLye8JGQkipDJSfvv7wk0ajwn6ms8JCVllzgroIKGkIYLyR74aaaa8Ku8J2LgfCflbTwq7OSXtGoCjxCOu+suk0i77ePKOC6hCbIuj/wnrin8JCtnPCfooXgqLYq4K2HLljwn56bODXRqCdxYSnigLLvv73gq6AKE0IR8J6Ag++3jy5p8JatlFvRqCoKLEIq4K6eXHvqlYzvv73wnYukbGDCsXtX3Jbwm7G2MVwh8KyXqOCno1DwnrmfCitCKfCRjY098JGitWMl8JCuqnLwn4mj8J+VtCUvJnwqw7slOfCRhJc/4b6yCjRCMiJc4Y66wqU4S/CbhaQi8J6ln/CbsoTwkZuG4LqJ8J+VtCUnezbwkIagOjlvfT/wkY+UCgNCAWoKM0IxyLrgrp4mL+OCguCotVLgq7o9OvCfq6Q8KeGLjFts8JGMszsl4Kme4KyBQzxK8JGNpwoYQhYn4L6rwqVoJFxDe/CYo4NS8J66mFw/CilCJ/Cen67gsYZc8JimrvCRu6JtbvCav7Az8J+DsOKAiiUiY2XDpuCqrAo2QjRkJ++/vdGoPFtyJyZiJ/CetJ4oJSU+7Z+hPPCeu7Ekb/CfgqfgqLku4aaxXEPCtTDgt7MhCjFCL3vCs/CeuZIq8Ja+n+CtlvCRj4Lwlq20IsOYbkYq4b+5I+CunuCor0A14LGNPXB2Ch4KBG5hbWUSFkIU8JCOpzrwkY+FIDrwkaO/4LeKbToKIgoJbmFtZXNwYWNlEhVCE1fvuJHwkKO1bj/vv5pyU+CouC8K3BO6AdgTCogTCgdjb2x1bW5zEvwSsgH4EgoYQhY/V3DwnYaX8J+VtCNN77+9PS9L4K6VCh5CHMO8YDwkeWQ/LjzwkKGEw7FLPz8q4LqELyRGMiYKAkIACjxCOk3wn5W0J+Cph0LhpKAiPWfwkaWAKibwkIC9fcKlR0DwkZaSYCJ7JMKl8J2Fn2DwkYi98J6fqDzgs4gKBkIEPeC8vQo9Qjt78JGAmHtOPU7wn5W0cDzwkIqk4oKWeyLqp5g9KPCRtYXvv71NXCTwkKC4PfCQkqPvv70veScl4b64LQpCQkDhiZJMOl9K77+9S2zwlqum0ahm8JCAl9Go4Ki44byc4aa4e/CepZDwkY6mP005eSZlcfCQoLw/8J6frlw/77+9CiRCIuqsqiXit4rwkYqMPfCcvJciKuqgtuCunifIujzwkYaBPUEKNEIyK3Lvv70o77+9w7Yvdsi64KaI4KarXGDwkL+iL+CnomDwn6OAIiEiaPCfg4TRqFTgqogKEUIP6p+WPOqvgSrhrpHOjCAiCh9CHfCQtIQiLi/wlq2n1rLgsYvwn4mRV9Wu8JCtnylgCg1CC1xSw5Dwn5W04KuqChFCD8OtJ+qhke+4vfCRpqbDmwoPQg09P/CRj4XCsfCRjIUmChRCEmLvv73wkZGd8JC9uMKl77+9Ogo4Qjbwnrqp8J+VtMi6PD0oP3VMyLp+e3vgt4/gu43gsaYr8JCKvi9b8JG/iOGNqvCbspfwnZKqaUoKMkIwV/CRspjig6BgIvCQq4zwnrikXOK2pSRS77+98JCWmCJzOtGoOuCug/CeoIzhp6l+CgRCAlc8CjBCLu+6q2Aq4rqXPOGMqTrCpfCQhIBMSeGKtC/wkISY8J2Vims9JfCQhIApL/CRj4UKJEIiWvCeuZ/CpeCtneCvkPCRpJFNP/CesbHguoZCXzcq8J+VtAodQhtUKvCWrIgoPsOl77+94YuC4K+Iw5F7P1DRqCIKGEIW8JCGk+Cgu0zRqCLwkYG1PSbCvFc3JAoPQg06d8OI4oWAKyfqqYNJCgtCCfCRiqjwkL+kIAopQifCpS86YOKBsURQM/CWuYovyLrgrZ3it5Mk4aCM8JGMh1Y8P1k/XDoKHEIaIvCdlLw68JGvn9GoyLo8PcKl8JC5vPCQqIYKNEIy8Jq/ufCRj5Q9XkjqrKbwkYK+4L6f0ag/KvCegLRUwqU98JCyqGnvrJQw8J+VtGBgPVsKF0IV8JCemvCQqJngp40qOmUn8J2VjntnCgxCCj7IuuKAh+GdsDYKCEIGYCXwn5W0CixCKtGowqUqJfCeo5Dgr65c8JG0veK1sCp7JybgoZ4lQTzwkaC1WuCokO+/vQoOQgx8c/CeuZ1ue+CsuT0KEEIO4b+48J2IoTzIuingt5YKJ0IlP0TwnoKP4Lue8J2UmSQn8JGOi2k/8J+VtPCQhL3Dj8OBJVMqXwohQh9YYCZgwqVk8J6EscK4JMKmPz1d8JCtii5lP+CyuWs/Ci1CK/CRjrclJWQ/8JCAuPCeuKLwn5W0OfCQjKDwnaqtMCQsP/CQuoMvVC8kOmsKOUI3JS5344KrdSYi77myLmPwkYyqLlouXSbwn6+44aS7IiQhPWYuJOK0gUDwn6GV8JCksl7wkKOuPAo1QjMiNvCRj4IuPCrwkJWIwqUzKuC7hMKn8JGyjOC3svCdk4IgZlwiPXfwn5W08J+VtFwnKncKIUIfw4nqqZfwm4Sy77+9JOOChyVOUiAk8JCrpko98J2VgQpGQkRZKdGo4L6m8J6ymCJ7Jy8k4bOy8JCggeGftPCeuqsiJnon4LqEJfCQlrMn6qm7wqUuwqHwn5W0YOCti+G/h8OO8J+DpQobQhngrLZNJz/goLFmyLok8JG2ofCQlqDvrZUrCjRCMifhprcg8J+dvWgoQGrwkY6O4LeW4aqx8JGKjPCQioDgqIclLSYvJvCeuZRj8JCUlFw9CiJCINiw8JG1gCPwn5W04LK/OHPhqqTwnYWFTPCflbTwkY2XCgVCA1UlUQopQicv8J+VtCLCpWdMJGLhvZtT77+9XPCRlr0u8JCqmvCbspzhp7ZLPz8KNkI0PfCeuZTwm7KcP1LitKdX8JG/gjEl8JCLo/CflbTwlqy1MjZ78J+VtCLwnYWF8J6Xm+GyvgooQiYv8JCWqHsv8J+VtMKlMfCfo4Fa8JCmmeqjpPCRtLx74LKgKOGoqQoeQhxGSi17JcK/8J+rsuC2uMKlw6kmP1DwkI+TeW1GChFCD/CQhqAqIi7wn5+w8J+CiwoSQhA8JlB7Je+/vUhJ4Kiz4Ka8CiVCIyvgq4jguoLwkaSVb+K3kmLwlq2+4KqyVCU976qUcXYi4KayCj1CO/CeuZfgsa7IusOA8J64uVHwlqO/4b2d8Ja/sOG/rVzIusi6yLp94b2EIvCRmaLIuirRqMKlLC9de2xvCilCJ8i677+j4Ku/JPCfg5U8Sk58JTpJYCbwnZSo77+9Il7wnZWBwqDaqAooQibRqPCflbTgsqvgprLIuiQmTynwkZeFTWDgs4dmP/CdlYAqXmcqJwoYQhZ7LkfRqD/wkI6o8JG2kPCQlp3wlr2aCgdCBeqrnmgiCitCKXvwnp+k8J2UlvCQnoTwnYa98JCAnDxqT2XwnrmRwqU58J+DkSRgJmtqCjNCMS97w4pL8JGHsy7CpcKlyLrgsYJDU/CeuKHwkLqwYCjDvEXwkKKqRsi6JkUiVfCeuZsKJUIj8JGKgeqnq/CRj4g68J2DgnjwkY6O4K6u8J2SpuCpmi3gpqoKLEIqVvCRvJB78JuEslw1L10iUOqslifhnbA9ZOCyt+C0j2044aqFXXTwkZmWChdCFVzgrIPigbBT4LOIe/CflbRp8JG2hwoZQhfiurxvMy52XMi6Kkgle3zYiTrwkKOtKgoCQgAKGEIWIfCQioo9IizgqZ7Ctz5cyLo60ajDgQoeQhzwnZKf8JGlkiQ88JCtsFzvv70/4LGGQ2Q0ItGoCgJCAAoUQhJZ4Ki84LCoP/Cfgq1BOvCepLUKH0IdQ1fwm7GJ8JGYnGAkbvCRsIUuLu+3j++thHhdR2wKGkIYOi5bIlXDg+C9jcKl8JCeg1zhvZnwn5W0Ci1CK/CQoqvit4XwlrmqwqVDJ1BOe/CflbRIYHgo7Z+hLOCsqmE/YMKl8JCGoGAKPEI6e2AuJjrwkbSDOuC2vWDgu5DDiSLwn5W08J65nzrIum3CsC9gJeGwi+KCrOGctiU9e1oxYOCzouCqkQoWCgRuYW1lEg5CDDPskqUqKiJFKsOseQozCgluYW1lc3BhY2USJkIkKuCxiio04Y+o4Ka+auGpltS56peB8JCKiPCRvJfRqPCQnaYgCqoYugGmGArLFwoHY29sdW1ucxK/F7IBuxcKREJCQXtv8J2Sq0XhpLbwm7KAe/CQrJ3wkbaERSXwkYyCL9Go4YyTIuODofCRpIDgvZsuJeCummbCpfCrraUzIvCQqJsmCjNCMfCRpKEie2TgsYrgr5Dgrqnwn5W06quk44a68JGcjyDwlq2e4KaWPeK0uvCWrZXgob4KH0IdTOGJilxc4LGHYFVrIi/CpfCego8lXSU6fSTDoyQKF0IVLSck4KO/4KiwyLpLLMi6ej09XMi6Ci1CKzHhi4QuJz8hbOCzhsi64KaPfOCmvMO5P1kuwqVibCrwnrq70ag8YFzhqaYKQEI+8J6FhOqgsMi6K8Kl4KehXD0uOuC3sydcdy9f8J+VtPCQra7CpSrwkbWZ8JGcpGDwnrSMP/CRg4EzPCrvuakKOUI34LavZVjwn6uhdyE/NWLgrqPOkOC/lVzIulzhpLjwkbSIJl06SvCflbTIukFc4reb8JCojCdqewoOQgzvval6QGB3dvCbsbUKQkJA8Jy8pjTwnoCkdTg9bybwkJaV0ajRqO+9g/CRsbMmV+C1nVDwkI6BOj3wkJ2h26svPeqkv3vwn5W0yLpq8JGpggoGQgTwnrmZCiFCH3DwkJaM8J64nXp14KmeZ+qsrT3Om8i6yLok8J+VtEsKB0IFIsKlwqUKMkIww5sm8JCDq9GoL3oy0ahDPT8kXuK2sCjIuuKDq1LwkKS/4LGdLns34K+COirhiaQqCh9CHT094aeRPPCWraPwkKqLaljivYjwkZmm4KanPC9rChlCFyonez3qoopTSE0h8JCTj++/vSouPUp4CgRCAsK1CkBCPuKCkzwvOzolJs2+4Kys4YqeOvCego888J2EkvCRjZfwkJ2CLidRJCci8JGMtfCflbTwkJKnKuCxqirwkJaMCjtCOVc9InDwn5W08JCekW9r2Ixf8Ja9uiZgPPCQlq084K6V8J+VtGAuP1xg8JGOieCrvPCdvKjgpoVcQgo7Qjnwlr+iSj7wkIOVMvCQqIY88JCjr2LDtCone/CRjI/wnrmd8JGLi/CeuZnwkLOOLV0tYCXIuvCetIMKBEICJTwKLUIr8J+qqeKHqtGo4aeHw6VfLjBDTiXDp0IiZfCfoZXwn6ukLDzhvZlfOuGKtQo2QjTigpU9P82/Qk1uOC18KtWJ8J64iT5eYOGLgjjguKV7yLrwkbGZ0ag9JGhA8J+qq13gppAqCitCKeCmkEVgLyPgrYA7N++6iCRNKlpE4reZ8J67sWU8YHYqMTY8aPCbspImCihCJvCRtLpieOK8vngx0ahOO/Cfgq4/OfCeuYsv4razIPCRtahC4KijCjhCNjnDhOCnoWJQ8J+JkPCrnozwkLWe8JC6sPCWqahVJMi6deG7hu+/vfCRsKLwnouPPXDwnZSNOgofQh1E8J66tvCQiprwlqmP6pS0wqU5XFxY8JG0jeK3kQoIQgYnL1fgqLMKC0IJ8J65nScvfSZ0CiJCIPCQrolc4Kq5dDxw4q6TZuCogT9g8JCGoCUkOiXgpp8kChRCEnHvv73qpKLDiOGeuPCRnL7CuwoYQhbwkJOqNfCQlpk/w5fgrZZhPOqplGwvChtCGSV7PeK3mkoiyLp78JGTlSdw8JuCjy81T1sKNEIyXEFU4KODQ/CQoLjgvazOjNGoTF7wkIGdJVzCpTxUOj3wkY6LXGl7LTrgqIVZ3pLjhZYKOkI4PvCQqaw6bfCflbTCpPCegJ8nJiLwn4ml4oKOKvCRjot8e8i6XeGlheCvlz9SYPCRiqVK8JGPlyIKBEICYXUKKkIo8JC6sOGLke+/vWUn77+9w4FRT+Czsns9LmDqqZhI8JCKvOCuguGlnAorQinigLQ677+9JvCRm53RqCI8eCEm8JGMj/CRi5bhnaNwT8O84KmI4K+XJgo3QjV7LjXwnp+pLfCeubrguJxJMfCRtaIsJvCQkqEiaFJ+8JCdojxNKT1WJ/CWo6zwn5W08JGMuAokQiIvw5XDrPCfq6fvv73wn5W08JCjp07jgKrgt5PDmfCQraUuCjFCL+Gxm8i64LCVP/CRsq1rJ3UpRFHwkJeyLcKjKu+/vUxR8J6Xv/CflbQvXuqsgy5nCjxCOjwmfV3it5XDjPCfiaVeYOqhsPCdlKDgsJDIuj8uKiThvZlpXfCQqZXvv71tLvCQgLh08JGkluG5lXsKQEI+PFZqOvCxk67wkYe0JDfwn5W08Jicnk3Okmvwm4O0PfCfo4Dwn4K976y+J2Av8JarqHXgqKzwmKO5SD/qrIoKHUIb4LGn8JGkky7hrKE/yLomwqbwnLyh8JCPlWE8CgxCCioqYEDwnZ+vJCoKNEIyR/CRpLh7KOCunjsi4oCEUu+4kuCzq8KlP8OiLzrwlqmT8JCBkSfRqFbwn5W04LaCbCQKHEIa8J65jSRDcS4iN2Im0rJcLkIvJyo8J1nvv4MKJUIjctax4reARfCRhLbgu4HwnL6lOnvCpS4n4LOGVFo84LenJDoKJkIkXPCdmZ5hJirwnrm54oCaPeC9uPCRpIJWM/Cdg4p1XD9iRW0uCghCBn1g8JCWmgolQiPgtY09w6tyP/CQoqhRIvCSkqAn4b+MLyomP1wv8JCugsOQJgo5QjfwkLW+ffCTmbTwm4qcUVRl8JCSp1HDnS/wkYO34Ky38JuEsj3wkKiGJuODmiLitK3CpfCQv7UnChJCECjgu4bIui5eMiZ9w7pDPFYKNUIzWvCRnJPqpIrCpeCtlWhk8J+VtFIlJS9CwqXwn6CD8J+VtFYkNPCQoIMvT2BiTWBB4YyTCgJCAAoCQgAKIEIe8KyNuPCdkZQ68J+ggSpA4rO74byxLuGPueGfskQnCj9CPTAn4LKg4KqFXfCeuahh77+98JattC7iroPwn5u34LGgfiLwkIqXPiJQPWnhiZhyIkhc8KqrqeqluPCRjIsKDkIM76yAwqXwn5W04pGECjdCNci6YSTwn6u34Zys4YuKOi4hLTomwqUoJeC2guCtnfCdkrLegeKAn/Cfg7Q/X/Crnqnwn5+iCiVCI2DiuLdAOSdScC/goY5e0ajDj0Yh8J+bsCzwlqKGJyXwkaawChlCFyI/wrJ74LeoUCLguoHRqMK8Vy97OsKqCgpCCPCeuZlpRz5gChRCEuCzofCQsKE92olg8J6lnkg/NAo7QjlNwqXwkb+Z8J+VtPCRq5JkPj/wkbKcXCA6bDrwlqq08JOWnETwka+wPCnRqNGoUPCQqZI08JGFtioKOEI28Jy1msK4XeCynSAqU/CeuYfqn5XwnZWN8J+VtHvIuvCQvoTgqK97Ji8k4LeP4LKP8JGBmSVMCjRCMuK3mj806qmfIvCdqpsiLuqfvH3wsIOp8JGqiPCfm7lEYC/wkJ6CLiY24LOzXGrwn5W0CgdCBeCynmRcCkBCPk1gYEHgrJM9VE/wnrqoXCRTe+GhkvCQhpDwn5+gXOCrqjrwkaSWIuCosntpLu+/veGugeCupOGklntY4Z2vCglCB++/vfCflbQKO0I54LGabys/4K2cw4hcTiFbe35P8J6AnnZC4KiKSvCel6hb44aAXD3wkLm64LKsLcKl8J2HouqgtMi6CixCKiTRqPCflbTwsYCvPSXwlqmkLvCQhpfwnriRMSRcL1zwkY+C4b2LIm1SJgodQhvgs5XwnZKiIjXDsfCQmLNd4b+EwqXwn5W0Jz8KKEImJnvqoa3wkJ6A8J6ll2BmJi5a8JG2qCRgJ+CukOCzlfCSipTgr4cKHUIb8J+VtMKl8JGNlzpgPeCzlnt0PGAqLfCSkIpcCh9CHVbwkY+X8JG1gHJs4aeTbTbgso7vv71FUj/wkISCCixCKvCflbTwnrqz4a28yLrgtYZt77+98Ji0iOC0iSdrwqVFKl1i0ajqrIE9Ogo+QjzCpfCbgo888J65tid724nwn5GfXD7wlquw77+9T0rDvzwxwqU/8J+ItyTwkKi4a/CflbQl77+98JGwiD8KEEIOVjRf8JuxuT89Jz12w5wKRQoEbmFtZRI9Qjsv4K2V4LuD77+98J+VtOGDjT1Sbe+3j++/vSck8J+hlWrwn5W08J28huCxq9Go77ePL/CflbRV8JCdoQoPCgluYW1lc3BhY2USAggECooKugGGCgrPCQoHY29sdW1ucxLDCbIBvwkKOEI2O0PhvpUqYHrgqrjwkZOW0ah68JatnsKl4rqB0agv8J+iu/CfgqDIuiJOV8OXOvCfiLI6w5snCidCJSouOlcqKCsu8JC2j/Cdk4BN8JGRnsKlXEh7KuC6rfCRjZDIunkKHEIaJT3qpKkyP2Tgs506ZvCRgaFLYSoq8J+VtFwKMkIwYC9MJfCusp018JaqlCTwkY2q8JGIhWPwnZSS8J+VtCbwkbKF6qez0ajwkI2Ae8KlCgtCCeCtiCLwr6KBQAoeQhzgobnRqEtzJF3Iusi6LsOlPUvwnrqDMHwkJi08CgZCBPCRtZQKMkIw8JCsoeC6hD/Dj+CntfCei59PJvCQjaUuIuGMlOCzi3vgrKo98JGEuUo4M1rwkJWaCjdCNX7wkJWTP/CflbRWyLoyJ/CQla9+8JGOlmPwn5W0yLpaKjtv77+9XSTwnYiyPzpp4raz4b+eChpCGHd1VFzCpfCUlK4wRfCepZnwlr+x8JG1uwonQiXwkJWy8JGHgeC7hnrhiZPqpYrDpCdfPz3hlqPgprjwlJa8JEJgCkVCQ+Ktuj0i6p+aTiLhqrZ1M/Cen7DwkbaILuK3hUTgrLJv8JGgmDzwlqmdVe+uqy9KIibilITqmY7grpzwnritc+qnsnsKKEImJyU28J+gpV5KPeCxjN6lb0kl8JGNl1xVYvCflbRF4Yu3PCLguoQKNEIy8JyyuiIv8Ja1i0XIuvCQnrg6PcKlQTok4rqN6qeS0ag68J6Bl+Cstz1S4K2EIsi6LjoKQ0JBNz3gsaPCpTzwkY2XIvCQirpD4LucTu2etci60ajhnKrCpT1iZkvgqYjDsPCRpLHvrrzwkJa0w6Q6PNGo4LqWcEQKA0IBOQoDQgE8CipCKCfCoPCQsJYnXCrIuvCRrIUqIiXIusKiKFQlbCTwkKysVyVCJ1s/JjwKHUIbIsOjJHvwnriiKvCShIjhqonvv70l6q2KYcOIChhCFl3wnp+t77+98JG9lOGBo/CRsaovRnIKOEI2MEvwnoKPY/CRoK/IumDit4TqnJfwkZecP/CdkrtwJEfgtqQv0ajwkY2A6p+TJkDCpSdleyA5Cg9CDVln77mzfOCnt/CdvIYKB0IF8J65nWoKE0IRdO+/vfCdl77RqD15TCrvv70KG0IZa3siJETwkaWZwqXwnY2q8JGNlyJX4KisQgomQiTwm7KRODd58J65myp48JCEgfCQv4YvReGnpG8m4K2M8JCFjHkKJ0Il8JGiunsu8JGPhfCfn7Dwn5W03q/wnrineyXDgfCbsKbgrLZ0cAovQi3wkIqCUnk9XsKl4Y26MuqvmOC7hiQ4JDnwkbSY4KibwqVW8J6foeC3rsOPaDYKMkIw8J+VtFw1avCQgJguw7RSJSt74aG31YDigIUuKidaWibwkJCt77m0Je+umkjwkYy3CidCJfCWqbjwnZK28J+VtPCfrbdgW+CymnvwkbCD8J65tzxb77+9OyAKNkI08J6ln+KAh14mJO+3hUwjSDIu4LCuJz9c8JGbo0vjg7bwn5W0KiV+Jnvig6d777+9P+CviwopQifwkY6pQvCfgrwiLuqguDxS8JGPmPCSkbPwkL+14Z+wffCQhpnql6IKDwoEbmFtZRIHQgXwkKSCJQohCgluYW1lc3BhY2USFEISQDN7wqXwr6evPe+/vSfwkaq5CroXugG2FwqWFwoHY29sdW1ucxKKF7IBhhcKIEIeQeCzsuKOsUl+JvCflbTwnoqYPci64LGWeyRO4ampCkNCQfCcvITgqbDitpZpIiLCpfCdkqI98J+pquqfk3NgIuGDjSrwnZWGQeG+pHvwkbCHJPCei5jwn6KyYuGOkSfwkIGTCh1CG9Go4bK/dibDp8i64LK5YMOOOjzvv70n4aqnMAo1QjMqavCThoLgvp4u8J6kpXLDqmkyIvCdlI0mSCTjg5kmZ2Dwm7CY4b+5WPCRkYpDRPCvpJ0KMUIv8JCohSngtr3goZ7vub3IuiUqQVjwkb+sIuG/tCo28J2Us/CQrY/CpVvwnbypYCIKEEIO4raS4K6KTO+/vUfvvIwKCEIGw4vwkIGVCiJCIPCRspolT+C+s++3j2sm77+9fuCwpzpoL3ltePCflbQiCgVCA8OQZgooQiY/4LGIbXvwkY2mTDlx4Y+5XvCeuafwn5um4La7Py/CpXtc4LSzOgoQQg4ieFzwnoCDPyRxN+C3gwojQiHCp/CQoIhqUSXgrqow4aWyT++3uPCfq5Twr6O/8J67sCQKGkIY8JG1mX7wm4WVdvCbhZJCalPCpTzwkKy/CkJCQOCzgiXwn5W0XfCQi4PwkYSTQiV3PPCRjLJyPe+ptvCRsp0iwqV776y8eOCmoT1sOkrIuuCxjdWDKvCflbTgt6YKBEICTkgKBkIE8JGOjgpBQj/wn66RL+Ctgj9+L2Phgrw/8J+JpF/IuuCwjMO74LOeJFZvXFg88JeGn8O4LvCqvo7wm4WVTOCoueKugPCRtLoKREJCIi7wkJ2g6qyBfWbwra+/bcO+8J2FmuGKivCRh6LgtY5gJ/CRsIHigIBUNsi6JuCrqT868JCdpPCRjaEvevCRvrA/CgJCAAorQik68J+iscKxJj5cXO+2oT/CpTrhgpB5JvCflbRgXOCnoeConci68J2VhgogQh51JtGoyLrgoYwn8JG0v9Go8J+Cv3vgsKPgtIEvw78KGEIWLiZp4KmRQPCflbR04Z2y8JKTr1s6JgovQi1h77+9XiffmNGo8JarhGAiaFcvJSQ94LGa0ajhg7IvUvCRsZ9kVzrDmfCQlYYKDUIL4L6c8J65tVDCpSIKA0IBPQofQh0qXCbCpTF3OuCxnXc6Oirwnrm+I11M8J+VtOC1mAo+QjzwkYyn8JKTh/CRhYDwkbui8JGEgDIl4b2JOvCeuYnwkbaUXjVcJ/CQl6HIuvCRg7Yq8JCdhXZeN3BaLiQKKUInPER7wqUk4Z6Q8JCdiS8vXsi68J6AgTBc8JGHpsi6YHvqpbfqr7RcChlCFznhoIguRiJVJzPwkKmRLzBmYCJlUFcnCixCKlx1yLpm0agiLu+/vTx7wqXwkbWF4q6AKfCeuYJF0ahSbnIl86CGrFvCpQoNQgt0PHvigpYn4Z2uLgo2QjQ84LGZIi5GezRswqVm0ajhn7ReOkMuRuCoquqhr2Av8JCkn+CopCUi4KiY4LOsJfCbhZVgCghCBi7RqOOWmgoNQgtO8J2SpVYi8J6XvwoUQhLIujRb8JCkgWBSw5nwkauMOjwKHUIb8J2LjvCQuaPwkICrYC7vv73IuvCRmaXwkaKuChhCFuCnnWA2ViY8QzhN8JCAvHFgXn4/OlEKCkII4rqR0ajgu5kKAkIAChhCFkEvauuginskJVLhqrlw4LGdJ+Cnl0UKFUITfj0uQcOa4KqBOuKZtU7gpoJNMQoEQgI1QAoCQgAKJ0IlZOOIleK3lPCeuKLgupJGTV3qpbd6ezwiT0YtPzrwm4WVWj86awoLQglNJlRa8J6jh3UKJEIie9SxwqVfP/CRoqoyPTMiPPCQkoRNJ/CQjoI8OvCRspgkLwo/Qj1n4b2I4KmewqXWreG8nUngtpsm8J+hpuCziMK1V8OrTyvvv6xa8JGOvE7wnri5KuKBnSliPPCRv4AvPiY/ChdCFS7wnZOhLmlbJ/CqvKDwn66C8J+VtAoYQhZcP/CWrZNDfuCvgC/wqp+48JGRg108CiNCIVLwnruxLvCQp57RqCskIiIn4Ki+8J2SpT8/4L2/PFlLOgoFQgMkP0kKKUInJ3I8P3tAP8OlKlVcw6PwnrmXPjw9KnvwnrqiReCuo/CRsIZUL2pgCilCJyTCpfCQtLQzQ/CeuZ00Je+/vfCQvIIlJn3guY/wkYy1PSZOe+CnogoHQgUl8JGLkgoIQgYnYOqqt1AKHEIawqUkJ2ThvYrIuuCtoSdVJOCnjXxnOeCygkQKP0I9auGxgtGo8Jato8i677+WVDDikYds8JGxlz/RqCfCpSPwnZSyOu+/gvCRiJ97cvCcsrtiJWA8cTwm8JGckQozQjE8XC88Syc0Yu+/vfCQnqQ/PDrCpXh78JGMvfCbspzwkKiW8K62gCrwnLG24KiHJjFvCjlCN2Qi8JCeg8Kl77+98JG1pPCQkqXqoZ09XybDnsKld2E88J66jsOa8Ja/sMi68J65kuC7lHk/w4UKDEIK4Z2z6p+TcOCvjAoTQhE4KSQiS+CunD8kVvCRjJNALwokQiI98J64oVfihKPCpSUn8JGbovCRkLfwnp+idy7wkaSWw6kmCi1CK0IsPVzCpVk84LeFJFAkPOGLgvCfiaTwkKizee+5qGDhiZg88JCguOGKnScKAkIACgNCAUsKF0IVJUwu4KqCXOCmkD/wn6miQCI/TSV5CklCR/CQq6vvv71pQT3gq4fRqDzgtr3RqOCnhCYkMFzgqrbwnZmQ44ib44GCLsOSJeG4skvDmOCqoPCQv4jwn4K+ZeGfuMO84YuECgRCAj9WCgNCASQKBEICwqUKAkIACgRCAiZgCgRCAi54CgpCCHvgr6vhnbJfCglCBy8iLuKAvyoKLEIqyLoq4raw8J2ElPCflbQn8JCmmT7huo3ih7Vve/CQlpAk1Lzgt5g/dj8mCixCKuC6p+Cno/CQjLLigIl+8JGytiXgqY14Uu+4s9Go8JGKg1RGZPCRtJg9KQocQhrgq5BgwrrwnZKsPD1rJsO7PeOFm3498K+liQoXQhXCpeKDgOGLhfCflbTRqEQnLi/gsrkKA0IBXwo4QjbRqNGoKiA/6qOPLy7RqGgm8JSUny7wn6un4K2I4LeW77+9JfCRi7lrL/CQurEmyLrIuuCmjz0KJEIi8J+VtGDwn6qF8J65vMi64oCS8J65rvCegKRK8JCgguCgtgo4QjZpS/CdlIjwkZuDOuC7ksaLTCYz8J+VtCYmKvCRpZJc4b69KWTgsZ3wkaeI8JGjieCwvF7gt4oKEUIP4raqLsOBJyJi4Y+sXGA9CkRCQuCno1wmP2tXw5Iy2Izwmr+we+OIg++/vSR88J6Xvy4rVDRcOsi6w5zwkKOg4LeW77e58JCLqPCdkqLgp51g8J+hugotQivrn6TwkK6ZInt78J65pMi6auqTqOCsk/CflbTwnZSW6qyC4KmIKvCego8nChtCGeCwlMKl8Ja+mkHZsu+/veCmsuOGlfCRpJUKLkIsw7M84aSkR/CRp5/qrI3gvZwv8J2Mv3vwn6S28J2SnyXgq5DIumTwkKGYVUIKCkIIw5vRqPCWqaUKLkIs77+9yLrjhIwrZPCQvrXwkKKO8JGNqCE98J64uUbwn5W0ImfwnoCfL1DguY4KQEI+Pyjgt4Rd8J2EsjfwkKa38JCXqFPgrInhjaNWJjVgVjlw8JGWuC9+8JGxmipd8J+VtOCohvCRtaThnaLIulwKDUILTkAl8Juyk/CUlakKB0IF4Ki8wqkKR0JF4KiQ3anigZHwlqmi4LeW8JGvtuCmsiLCrvCRhaAi8J+rpTw6JtGo4YONaFxG4K2I8J+VtFwu4aSbwqYu4KyI4LGdXSZZCi9CLfCehJHwnZiEdGA98JCsjiffp1Twn4mkfcO8WVzwkKOjJDzwkZOZU8KlKihZNAoMQgrNvCcu1pNMZFckCgoKBG5hbWUSAkIACg8KCW5hbWVzcGFjZRICCAQK+he6AfYXCr8XCgdjb2x1bW5zErMXsgGvFwo7QjlK8JGsg3snXD9gw6nwnrmq4KqDTuCnh/CeuKHgqorCpfCQlrzDuuOEnXvwn6WqZCzgr43DheqrgXsKEUIPWuCxv/CRpIDwkZGf4LGdChpCGGBT8JCdlPCflbRcXuqnoEkl4r+H8J+YrQowQi7IulMi8JGsgO+/hilgOlvgo65bV1Mm8JivuOCxmCIwIs6Z8J65l3tcV/CRkpw6CgNCASQKGEIWKsKlXPCRjqThirrhnbPwnaqq8Ja5ogoYQhY78JCjtfCRjL1VP++/vPCQvbjwn4mRCgJCAAoNQgvhno7wnYWA0agkYwoEQgImPgodQhvwkYWa8JGpl33vv73hiYvIuuCgrX574KuBOS4KJEIi4L6t8JCSqfCRjbLgpJjitK0oYjFre1RZ4bqnKlzwnZO3MwoxQi/wkIygdS9vIPCRtaRMdsi677+98JGlkNaO77eP6ryKYC/wlq2+KyrwkY2Q4L6GOgoLQgnwnrm24K2HYCoKGkIY8JG2ovCfobfRqOu2o3TgvJjCpfCflbQ9Ci1CK/CQurEk6qC04LKQyLrwm7KQYvCbsLQmJPCQi7Lwn5+j8J64pyrwkYuwPzwKGEIWwql6yLol8J2AoCR7Ju+/gjvwn5ujSwocQhrjgap7PSYv8JGlhj0ma/CRvrAh4LW6QuGPvAoUQhLgsZbvrL4v8JGMg+CmkDolWy4KFEIS8J2Vg8i64LeR8JG2kShdeV9cCghCBsOnPMOQQwogQh5DPzrwkJOC8J+VtCRByLouL1wyWfCRu67wmr+yJiQKQkJA8JuAlj9eJcKu4K2N4K6cyLo8KvCen63wkbap8J66hD/Du+CxmXt2OvCRjKrgrY3gvZrgoZ4qL1Tvv73wkJaVJwoWQhQ8ceKtvSLwkK2Aw7QqxIjgro7CpQogQh5IPiRQ8Ja9nOCqt2E976y+8JGNtOCuo/CRtIjit5MKIUIf0agnJXtENjMnZj3RqOqnqPCRiqbwn6ulJHXCpeG9nQo8Qjo9fD908JuAo3DOl+C2jvCWraBb0aguI/CRnIPwnp+r4reY8J2UtCTwkJKxV8O+8J6AlX3wn6u4W31cCgJCAAooQiZJ8JCNqC8u8LGituCokOCrr/CRtpc1wqUwKlrwkKiGJzo6InNnJQoiQiAl4pGDXMKx4rakJ8KlLEXIuinRqO+5ke+ttfCQk6guXAoRQg9nffCflbRDJuqqufCRsIgKBkIEKcKlSgo7QjkoJ/CUlJrwkaSQ8JCol/CQlpTwn5W0JiYv4K211o/wkY+P1aXwkJCq4reYwqUu4K6S8JGsiT00JjoKR0JFU/Ceuanwnrqn8JCSpMi676y78J2LrHjhooU9JSbwlq6NPvCRk5RILvCeipViYOKAgi8pfVnvuavwkLqw4b2ZVfCehY9eCgtCCWY7YirhvZ1laAoUQhI8U23vv73guo7wkYy/4LeWw5YKIUIfc+CxmmA9YPCfhovwk7muejzwkKiQaHPhp5Dil7JdIgoIQgYz8JCTh00KCEIG77+94KyrCglCBy9c1o3gqKsKGkIYPcOW8J+Dqe+2tyXDlngne2NcIvCQqJNjCjpCOPCQi6Zg4YaT8Jy6oOGLhGsw8JyzliRSPSXCpdGo8JGIrVE6Lzrvv73vv73RqHHIusKl8JOPpyo7CjlCN+K4rPCdnZHwnZSm86CEh/CRqoUnyLrIuuCgosi64L+K4rmRZHYkOvCRpL3hiZzCvPCQs4lHOHwKJEIi4KyPwqc8eWlcP8i68J2Sr/CRjZfwkJWy8JG+sPCfgqUiXwo2QjTwqrS+YeC6gjjwkYWkw5gmIiTwkKGJ4YujPeCotsKl8JG/oeKDrPCcva/wkYOe0ajgqId5Ci9CLfCQlrh+77+9P+CquCMi4YiG8JCrjPCRtafig5pWNGAoe+GJqkAlJyfvuagmewofQh3wlqG+LUXCpcmcUmolw43wn5W0JtuzM/CQtLI6SwoYQhZvaPCSkbTwnrqxPPCQgYjhiYoudCpgChpCGFxuLyjhvZtgLzzwkbWAPPCWvLUn8JCuqQo5Qjd5YGJyWO+5lWBu4KiCffCRjZA/Zj/IuvCRqZbwn66nVz9N8J2Su3BCSO+/vWHwnoGkyLrvv70lCi5CLHvgo55Ee0s/JTpSOlHwkLuCwqVZ4KuQWC5c4KmL8JKRtHsnR+Cuji7gtr1iCgVCA0V2PgoVQhNpP/CRtYPjgYJ9J8Klalfwnri7CipCKPCRiJ/it50u77+98J+VtOqjmHc/OuCuku+/vVYuJkTwn5W04o+80agKFUITfvCQu4Twlq2bRT8jyLpK4aKRPAorQiklwqbwkYqRJci6W/CQnrLCpWgi77+kw5XikYMv8JC0t3wmPSXwkIWEYAoqQijwkLqeP/CeuIXitqQ9IeKFqmk04KaWJidoPT9yJuGErHY94K2g4b+LCgVCA1QmRQobQhnwm4iU8JGPmDzgq6DwkbC+LnTgtqEk4b2zCg5CDFTgoqdALjbwkYyqLgoIQgbgr5BELyQKGEIWPWRF8JGFk19J8J+VtOGmjE8nPC5FPQodQhvOjMKlJVtg77+9JvCcsaRxSjM9dDRcVfCeupoKFEISKWxIwrFtJy96KjzwkpGM4LuGChlCF21qbzk38JGQtMKlZvCQh6I+8J+hliM8CitCKVtde8KlIuCystWH4KyB8JGMuPCQoIjhv5/wkYy98JCgiDYoL3Lvv70lChlCF/CRtajhsIhB8JaslC/vv70mP/CdjY9hCkNCQVngu4bwkbux4Ki88J6fuz8nRTonJ1Lwm4WV4qm+8J2qnPCRi7VaJPCdjLl9PDEt8J2Son044aqi8JGZkCon4b2ZChVCE/CbsKUm8KughUvwkY2XPEYlcHoKBEICOXQKQkJAPy12I2Lgq6DhiotEPzzjiJXwn5W0TfCWq4hIzozwkYq58J2Lqu+9o0Yv44W/YCoq8J+nrfCen65oOu+/ve+/nAovQi3CqjzwkLqx4b2b8J65lyRT8JC2j+CrjVwuLC49bnbwnrmfUfCfoLrwn5W0NzoKC0IJ8Juwiu+6jyFWCgNCAVUKDEIKZGA6TO+/veC3kgotQivwnZWC4LaHOj3gqLbIul9SXCbwnoCJ8JCBmsOw8JCWozxcIiIke1VmwqUqCkpCSFzwkYy3JPCdkrhD8JGYt+GltOG/svCQtak9cHta8JCVsDNmJm/wkK2O4Yuc8J+pvO+sufCdqoXwlqmVWsOndmPhsL7gu4ZQKwoxQi8naT/hpo0qwqVAL/CfiYjCuci6ZmBM4LC18JCelCvqrIbqqZZZLvCflbTwkbWncQoEQgIvOQo3QjUkYFBgRGnjiIUl8J+JkfCRhIdsLmfgsZU9KnnwkbaWL+GPvPCRpKvwkaS48JGNqnJh8Jy+twopQifgprIkw6k8bO+qv2948JGOju+/vfCfqqczOvCQjK8nLTgmPSTRqC4KMEIuLOCtnOGytypM4LKBYDzhtrTgvZIvPHw+8JG1pDzwmr+68J+emi/hirMl4Ka5Kgo0QjJ64L+XZ+CpgGgl4YmY8JCUm3bwkLaEU+KCgsKlKPCeuKRoaVxcJfCQv6k9RW50UDopXQoIQgbqr7EqJSQKJUIjIiZd4rWwJ/CRjaNU77+9KlXwkaWZ76y577+8biTCpeGivy4KQUI/YD08VD8u8J6fpCdtIjHgsZjvv73hnJJwJfCfq7HRqOGwk/CforHwn5W0wqU68J6Fj/CRjZBqJC/dm13wlr2bChZCFPCbhZFZyLrwm7GSdipyJ1vhpaEnCgZCBPCRtL0KC0IJ8JGMvD3wkbSDCipCKOC7kWfhnI8l4YWH4Y6r8JG2kfCQnrZ44K2EXPCQhJbIuibwkKC8wqUKJUIj77+9diQqL1x7w6rwnZST8JCKuz3gq4lr8J66kuCouOG/tDoKLUIrP+GPtUXwkb+Ue/CRjbDwlqmmYMKpYEdCKvCflbTIulDwkL6x4La7LkprLgohCgRuYW1lEhlCF8i68J65gtWsL8OwYvCflbQiJ+CsnF0gCg8KCW5hbWVzcGFjZRICCAQKsxW6Aa8VCs8UCgdjb2x1bW5zEsMUsgG/FAoXQhUv8J+VtHvgraDwl52DbCTDlPCRjZAKPUI7b/CRg4LwkYa2OMi68J6yrsi6dyY9wqdt8J+ksG1j8J+VtPCRsohcwqXwnrmJOuC+jdGy4aSY4Ky2dnIKPkI8NfCfiJ3IuuCoim4ifCTkmYTwn4W58JGDslzwkr6zPT5cP/CfnbNwJ+qgkVrwkaO/JPCRqoA9e/CeuaR6CghCBi4qIuqbtAoVQhPwnrmNOuGjq1kqVFzgrqnwnpO5Cg5CDMi68JCNlmDDtkwzJQoLQgnhl5fwkY6ERUsKAkIACgtCCXolfGB7cSZ7YgoCQgAKFUITans74YOHcyLvv70mPNGo8J+VtAoOQgwv8JCKkVDCpSTirqAKCkIIP/Cdkr3irpIKB0IF8JGNjGAKQkJA8J2Soi/hpYAu8J+VtOKRgPCflbTwkK6aP+C3il0mey8iPe+/vSrwkbamP1xA8J2SqVzwkYet8J+CvfCSvqp2PApIQkbwnpe/8JGIrfCRj5gkefCdgK3wn4mia9aO8JGPhe+so2xr6qmFKCbgsL7RqOK3lOqsrXlgYPCWv6RD8JGco3vvvLc977+9CjVCM+C6peGdsnsv4b2d4YOH4K6q4b2dePCQu4Lgs7PDtuKAmDzCpWdZ77+oNkXIuuCjnCzCuAohQh8i76y78JCurnvwkZyhw7Pwn5+h8JG9kfCQqJPwnrmwChVCEz8v4LGL8J2Api/wkKSsJfCdlL0KGEIW8Jars2A6RuK2uj/wkKuuY+Cnh8KlLwofQh3CufCflbTDoCslby/CpTxcYvCQqLkm4Y6JJu+/vQo0QjJaLz02wqXwkbKu4b2Nb2h6PCLqkrdTPid+4KmZ4K2Vw5DwnoCk8J+bp1zwkZmoWjorYAo5QjfwlrWYXPCflbTgt4AkWTnCpXsiJcO877mwL0jRqC7IuiXhsr3hpITwkKGE0ahiyLrgtpDwkYqICjtCOdGoKnQh8J2EsO+/vSZ7dfCQqIYnwrfwlJC+Lu+us/CeuZsrJzrhnbMu8J2khPCWvKbwkbKfKiLCpQo1QjPwkKGM4KGeYFcn8JGEt/CRjIM1J1FNw7IzS/CQrZrCpfCflbREwqkk8JC6rdGoYPCdi7AKKkIo8J+VtMi60agl8JGcvSY6wqV7N/Cdhrpz4reWJiPwkKaU76yX2bTDqgoeQhzRqCIj8JGOofCQgIUx77+9VkvwkaSJ8JCepEEiCilCJ8OhIkEu8J+VtOC6iuCziPCRtoYu4aSSIzrwkY2BIuCtrDon4ZyUbQokQiLwkaS3J/CeuocvyLrwkIqHNVwnOidpReK1rzrwnL6OYNGoCjhCNvCfn4hI8JGxkkXwkaqQUfCbhZLCo/CRsIziro09ez3gtZ3CpXDvv7174KGZJVAx4oCY8J65ggo6QjjwkY+H4KiPwqXRqPCflbR7ei8i4LuE8J65i+C6mfCRoq5g4bO14Ku8L1U28JCGg/Cfq5XwnoKPXwodQhsu8Ji0hvCWvoHwkYyQLjzgq4nRqEInP/CQrq8KHUIb8JGyryXwnoGUXnvRqERc4ampavCQvYRuN8i6Ch1CG++/vTh5XFzgp6By8JCAi+Kttz9+OvCRjbDIugo2QjQsZPCRg7Higrfgu50qKPCQupvwkKS/ci4hyLrgv4Q2e2A8PPCRg4E8VfCeuYJYfUXwnrmkCgZCBD3itKcKGkIYJOC8lXIk4bKcYjrwnqON77+UWz/wn5W0CjdCNeC7lSZ78JCFpOGMlT3vv71lYtqZ4LOgWj/IuvCQp5o5IvCav7fvvpg0aOCrg9yiTFEmyLp4CjlCNyTgtaB11qjwkaeRay7wn6K6KuqsgfCQoIg6MsOZL3pI8J+VtE9J77+9XMO4JF1C8JC0uC7gu4YKQUI/JPCUlZRt8JCWm/CRlp7grpnwkY6O4LKFwqXwkLqlPVniuovCsyYkTD8nOiouYGDwkbShSPCeuLvwkIqCJ8i6CilCJ8OpLT/Iumxc4KqF8J+snuK2o1fwkYqDWSZ7LvCav7JPe3XwnrmHIAoYQhYkJyfRqEpgwqXzoIeAYPCQup4r4LGECihCJlkp77+98JGIhfCRh6VY8JGkie+/vU7vv71+wqLwnoSO8J+VtNGoCgJCAAoPQg3DreCiovCWvpnwkZKnChxCGiTwkISx8JGOoEXwkKeGSCpR8JG0ieC/mWUlCg9CDVwuaci68JC7g2bgqrMKHkIcL/CRjbE/YFcwJvCRnJFx4YOHw6PwnYWfJVxgPgoaQhh0QcKo8JCtoOK0pyZ7OeCxluCznvCQpr4KBkIEIibRqAoHQgVJ8J+VtAojQiE/Ju+/vcOk8J+jgfCRj4kmeyVwJ0fDkGZ3Xmo9UvCRqKAKHUIbazw8cCIk76qP8JCggu+/vSQlZvCfgrzvv71HChdCFfCeuZEl8Jq/tjwjTfCfm7DwkICPJAoWQhTwlqun4LuIJFjwkLWVLvCQgZBZMgoNQgtg8JGcmtGo8JCpkAoDQgFsCj5CPOK0hyoiPOG4gz4l8J+hpeKuklHCuPCRjIw48JGLjyc/wqV2w4Q60agiezxcLybRhuK0p/CRjInwn5W0Ogo4Qjbit45777+9aeCzsifqqqHgqLPwn5W04Kq1e+qsqPCfoJfvv70iVeCpqWLiupDwkKC4esOYL24KFkIUXy5TISU3bMi6eOK2vl13e/Cav7EKOEI2e/Ceo5NpWeGqk9Go8Ja5pfCflbTgp5w9QvCRiLPIunUv8JCMn/Ceo5R+4LKS154qYFdyYcOGCjVCM/CdvKY/8JChil0/6qaqdfCRpI4me+K2uGLCuOC7kzzwkLyIXC9o8J65vvCQp7Fo4Z2EQgpNQks9I1zgtoZiQuCni2XwkYuS8J66gvCQhIAnyLpre++/mlTzoIeh8J6frX3WjfCehJ3wkYiEP/Cfr4gq8JGPijvwn4OJ8JGRoWvhoKgKIUIfXEJEIFwn4rea8J2SqzzCpfCQi7vXlPCcvKxB8JGEmQowQi7wkaWTJ++/h+GxlfCdlJLwpZiE4K2HXOqplu+/vSQkwqXwkYaSez9gI0nwn5W0CgRCAjUlCgJCAAoyQjBK8J2LpD/vv6bwkIy4KuKBtWFTyLoy16bwnrmN8JGPmPCRjag9PfCehLsvP/CRpI8KC0IJXjdcP2rwkZicChVCE+Gfolk/NW1iWyPwlryv4bOyL2cKCkIIQfCdnKHvv70KNUIzXm0qJ+qvgD9ZUDrIusOrTPCbsK/hjo7gp5ci4b+IaU88e/CdlIciJ/CQo67DiuG8m0dbCkFCPz9g8JCuhDZP8J+ip0nwm7KULzrwnrqnK2VF77+9avCusZQqPVnwkKCF4Yyc8JCysj1D8JG2hiTvv70k4raVJgosQio2PPCflbRJefCRvrA+fXtc4YOHL8K3XCc4PeK6uyTgp7lk2JBRIdGoJTsKLkIsPOGppTrvubPwnrm8KuCrkGnwkbGgaycuSG/hp4M64YyFS/CQuacmVfCQnrIKSgoEbmFtZRJCQkBX4b2ZIlzvv73hjKXwnL2SKcK/POGBiCVn4aaTNj8qLifgp6Lwm4qBNW/vrYPwkpGwJDzwkKGJYcKl4KGR4KGCCg8KCW5hbWVzcGFjZRICCAQKowy6AZ8MCq4LCgdjb2x1bW5zEqILsgGeCwoLQgnwkYSfJi9TezgKA0IBLwoTQhE64Z+meyXwkIC9XMi68J2SqwoIQgbgpanCpXsKD0IN8J2ViiZ6VCxze+C6gQoqQihePPCQlZLRqMi68J+QoC4n8JCLqCpg44SU8JCArCVS8J+lq1xUYFxhCiNCIfCflbRrJOG8mmB76pKp8JCWnCcnw7TwnYuT8J+VtDgqPwoNQgtb8JG1qOCzqOK0rQopQifwkb+X0ag/JeCyjy7CpSMnXPCQkqlc8Jq/siI/8JarnS/vv73hvKcKMkIwPeGcgPCfl61S8JChlfCRmotgwqUmOWDDo3jwkJWX77+hduGkgyo+ffCeuLfwkKCIChFCDy7vv73wm4WR8J+rompELwo+QjzgrYtQXPCRj6LgtrTir7Bq27XvrJUo8J6ApjzgqLPRqNGoZOGoriXwn5W04L6jX3Il8JCeuWDhj7vgqrMKGkIY8JCFjuK1r0nwkYafJiTwn5W0yLrwlr2ICjxCOkzwkYyy8JGaiydJLyTCpWrjgLrgpoUl4b2K8J+VtOGmjuqdn3c6IsKlZfCcvoRXIvCflbTwkaa2LzoKIkIgwqUvPTpgJvCeuZLCpTx7YClcYXt7d+K2tiJcOifgt4AKC0IJPCrhp5nvv70qCiBCHi7vv70m4Kiq77+9QuKAhl7RqCLvuZbgrYtgQOGznAoFQgPhg4cKHEIa8JCGkntJ77+9V8i68JGEr++/vXs/8LGggTwKLUIrw7A04bysLu2fgmREYCY/yLrwkIWwfSrig5Hwn5W0LvCQhqBTw4Lwn6GgJAo9QjvwkZmn77+9e1xg4LSD8J+VtPCeiqDitJjRqCciOmcnSGDwkYiKRz3wkY658JipkOGkqPCRj4nDj++qpwoGQgTwkIevCiFCHzrgt5I8wqXwn4OHTljqr7AnLiU/Jifvv71FSnBgL3YKI0Ih8JuFlS86IkjwkI6RKOqfnPCfg6888JGXnCJYXD/RqNGoCihCJuKpleCniOGoj+CniE9jK1rIuvCRioLhqanwkbCeSO+4vjnCpSJcCi5CLCw877mpXPCei79F8J+VtCvwnoqYIkQuwqVoe2B0LiXgrpPDoD/ckz9+Pjl1CiFCH1Phqb/wn5W0P+K3gDPhnY3wnbyKLl5a44eDJ8KleHEKOEI2JO+/vSTit5rDsTzhvo97w4DIui7wkY+CP+KBm1zwkZeBL3vwn5W0ffCRmal7w6zwkbaiP1xsCk1CS/CRiYBm8JKRsPCfgKHita/wnouNwqU8yLrwlr2hJPCdlYYu8J64pO+6nSI8zqHgop7wkYyt4Ka54LGm4KGqYPCRu7PhnK8kPCdIJgocQhrwkKC477+90agmJMi68JCnljw8e/CflbR7KQotQivwnLm2JNGoJDzvv71CcyZcOuCmvTMlMfCdlYYi77+96qGn44W3wr/gr5B7CjJCMD0m8JGKojrCpeCxrsKl4K+XKibwkZaowq9oNNOvbeqfl1Z3ctGo8JCHk1hj0ajCpQoqQig94YmdXCJMQ8i64Kqq8JaqikZpJjx0wqVq8JG0hNGoVFPgsrbqp6kwCh9CHWE/LuqfkPCcvKk98JK+m/CeuaHqrKrOlyXgsqUwCgdCBXckXD1+CgJCAAo0QjLwn4K/w4BMMuG/qmB70ajwm4WkL8ObTirwloSTPjHwkKOs7Z+x77+98J65mTvwkJKdXAoGQgRD3IwtCkhCRsOa4La68J+VtO+krdGoYPCwuaRZXOqvtF7wkYyQLfCflbTwm4WV8JGDpSTwn5W0J/CdiKbwkYy54La6PS1gXyIncCXguKMKM0IxYPCQrpnvv708XUbwnZC4IuGLhDfIuuG+gfCdkrbwkZmRYC/RqPCegKTwkJ6S8JCouAoIQgYsY2DCpSkKEEIO4KuH8JGnn8Olc+G/siQKNwoEbmFtZRIvQi1g4YuzwqHwkJSG4Lq6eyw/PMK8JyI6JcKlIWDwn5W0P2/wkY2zKSUlJ3skKmMKMwoJbmFtZXNwYWNlEiZCJCUvOvCTtaPwloSuQibgp44/fElw8J+rju+/veC7jSom8J65qAq+AroBugIKmgIKB2NvbHVtbnMSjgKyAYoCCjlCN+Cov/CRgpg94Ki+LSfRqPCWrLsmYPCfq7NaJvCcvIHvv73itpM71o8nPybwm4WV8LGAn+CmnDwKOEI2YsKsWyrgtqE64KCgJVwoRS/hi4DguqXwkaS4djpmUuC3lkjwkKi4diLgsZl3Uikq8J+bosKyChxCGmM84KyPdU/hpKQ94K6V4LeKJjXwka+b4KuhCiJCICHwkJ2QImAn8J6TqTLwkICAZi4qJETwkKiX8Ku7qj8lChJCEGBgyLojw51C8JCAsPCei78KPUI7YCou4KKKJ1Xvv73wkY2zwqZTW++/ve+/veGht/CRpZQ88JarsfCflbQkLvCdlKMnafCdl4E84LeKwqUKCgoEbmFtZRICQgAKDwoJbmFtZXNwYWNlEgIIBApcugFZCg4KB2NvbHVtbnMSA7IBAAoZCgRuYW1lEhFCD/CRtpdVP/CRhKkuPcO1PwosCgluYW1lc3BhY2USH0IdwqXwn5W0L/CRoKdT4KqRKiTqk4LRqMOyeumskHAKohe6AZ4XCsoWCgdjb2x1bW5zEr4WsgG6FgpGQkTit5PgsJ174KuHw4wtS2/hiZh24aqmXOGGoMi68JGZpeCrpkd7POC6guGDjUPwnriB8J+VtDDwkJ6FXcOd8J2Soi8gYAocQhpsJCh9KeGpqvCQqKhc8JGPhyngtZQ9aj1yJAoaQhjwnZSTL1bwkKiDyLo1TWzgta/wkK6cXjoKCEIGw48rNVRECkVCQybqrJYl8Ja1oyRY8J2IieGqlO+/veK2tsKlLe+sll3wkKe+8J+VtPCfg7HCpfCQgZ0xT/CQoLjitKdV8KudiuGjkiIKKkIo0ahlSkbwkY+J76+C8J+bouGLuzhgPlR1OO+/vfCQlofgsqrwn46tYgoHQgXwkaSDPwpDQkHwrY+DUfCRsIhr77+9JXrCqvCRjYPgt4HRqNGoXO+/vSnwn5W08J+VtCfwnY2v8JSXsnvgsrk84Ki8YzpAS3s1SgoKQghX8JC8geCsqgoQQg7DokU94KeAyLrhvK7IugoRQg970ag2JOGck1AlO+C6gSQKDEIK8JapgvCRjIpMPAo9QjvhvJ3wn5W0IC7wkYG18J60n1nCpTzwkYqF8J6AqO+/l1xkMj/vv7098JGjsfCeuKTigbEn77+9TyRcLwoOQgzwkZuBbvCflbTgspAKIUIfWuCpstGoV+Cvgjjwn5W0JWgueyfqq7REPPCQpIdJQQohQh/qn7sk8JGSsOqnlmDwnLytfe+/vcOwLvCfq5pj4LS3CjRCMuK1nybitZZgT/CRtLrwnaqcP9Goe+GnlC460aha76SF8J2NpS89Li868JGKmz1QXCp+CgpCCEXvv6FsKMOCCg5CDOC8ny89UF/wkI6+JAouQiwvJ8i6NuC3hfCutohf4LqjQFXzoIaYeypE4Kada+KCqPCRhZFOe0rwkbS9PAo6QjgiVmRh4Kmmej/gvIRcJiZ7PvCeuqV8IO+/nPCut7k88JGHrPCdm7PCoPCQlrt78JeXniZ7WMKlPQobQhngpq1AXvCflbTgrZwuwrRj8J+VtPCQoLcyCkhCRuC3kDtg1JDit47CpSbwkZC74pGG8JKRsntcLvCfiYLwn5W0Vj3wnLOd6qmL8J+VtO+/guGJpFwvSC7CpeCokOGdo/CRjZcKLkIs8JCAvfCdlY0l1ZVoYC7RqEXRqPCQlpVxXCpcJPCflbQnSeCzlmImaCQnJ0YKDEIK8J65j1Fc8JCGkAoUQhJQ8J6AoypM8JGLsVXqq6rvv70KE0IRJi/wn4C9JuK3gD8v8JKRjXsKBEICPnoKKEIm0ajgsYh7V+Gdq+OHoWbhioU9WSvCpVV+LkgiaO+/vSXwmK6PYGAKPkI8cPCRtLwuIiLgs4JcJ9GoPfCdlLwvXDjwrqWj8J+VtPCfrrUn8JuFlV3hiqDIuu+tgPCepZFuLzY5R3slCj5CPOCvkO+/vE7grK57cHU+Jsi68Juxt/CflbTRqOC6vcK/PXvwkr+IJU/wkKmQ8JCohj3wn5W0LipGYHoiLAo7QjnDtyJuYPCfqaIuaPCRiozRqOqSs+KFocOEPT3wn4OKQfCQpKgl8Jq/tmDwkKiWamBHLiVcL3hpMiQKCkII8J65gvCRpZYKBEICOm8KH0IdZiI08J2Uvci677yc8JGZqzrwkYOB6qyK8Ja1tVIKGEIW8JGPjvCbsp/Du/CcsJDgoI4/8J+VtAoOQgzwkJaQ4LqKbuC2lGAKCkII8J6LiiThnbMKNkI0XMK+4b+0wqLwn5W0RvCav7I/8J+YofCdkrvgro7it57wkKKcUuC0gPCehYdW4KGAUuCzowoeQhzgrJ5IaiTIun57U2cn77+98J+bpeCnryo/6qylCgJCAAo3QjXDmlwqdHHwn5W06pKa8J65kfCQrZVg4Ymo8J+ghOG9nVE8UyNg8JGNh/CWrokoOiU6JNGobwoLQgk8wqXwkYS4e1EKAkIACj9CPSbIujrwlq2X4Y6O8J+VtCtmVsKlfvCRi7AgIyLwkI254YCH6qKB0ahv4LCPJT0vKvCfg7Ltn5dgPeC6ji4KKEImXeCouHzCqiYs8K+omyFNLibwkLm+4YuK8J+VtCQnw5IoMDk1YGUKGkIYwqXRqPCflbR54KiiJPCWqoU/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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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 017b3d38f0493a0aca04dca4984706ccd66f4dd5 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 02:56:50 +0000 Subject: [PATCH 28/41] adapter: Batch catalog read-protection publication Stage changing recovery requirements and compaction bounds as one catalog operation. This avoids rescanning accumulated transaction updates once per record while preserving final-state admission and committed implications. Exercise mixed publication batches, atomic rejection, speculative replay, and unchanged updates at the catalog boundary. Add a generated catalog microbenchmark comparing scalar and batched transactions across collection counts. Full publication, DDL latency, and retention measurement remain separate acceptance work. --- .../design/20260903_decoupled_coordination.md | 17 ++ .../20260903_decoupled_coordination_prompt.md | 11 +- src/adapter/benches/catalog.rs | 148 ++++++++++++++++- src/adapter/src/catalog/transact.rs | 153 ++++++++++++++++-- src/adapter/src/coord/ddl.rs | 1 + src/adapter/src/coord/read_protection.rs | 22 +-- src/catalog/src/durable/transaction.rs | 57 +++++-- 7 files changed, 360 insertions(+), 49 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 3d177de29eb88..539cee04dbee5 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -643,3 +643,20 @@ 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. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index af5269180cda8..f119cf7edf512 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -27,12 +27,11 @@ priorities, not new design requirements or a reason to reopen agreed decisions. 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. +- Publication scaling: measure publication cost at representative collection + counts, including DDL latency and retained history. Batched protection staging + removes the per-record pending-update scans, but does not establish end-to-end + scalability. 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. diff --git a/src/adapter/benches/catalog.rs b/src/adapter/benches/catalog.rs index 76bc3edcc19c5..80f4e2596e0c5 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,137 @@ 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)); + for batched in [false, true] { + let mode = if batched { "batch" } else { "scalar" }; + group.bench_with_input(BenchmarkId::new(mode, 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 = if batched { + vec![Op::SetReadProtection { + requirements, + bounds, + }] + } else { + requirements + .into_iter() + .map(|requirement| Op::SetMaintainedReadRequirement { + id: requirement.id, + inputs: requirement.inputs, + frontier: requirement.frontier, + }) + .chain(bounds.into_iter().map(|bound| { + Op::SetCollectionCompactionBound { + id: bound.id, + frontier: bound.frontier, + } + })) + .collect() + }; + 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/transact.rs b/src/adapter/src/catalog/transact.rs index c574dc09c94cf..32a16945bfeea 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}; @@ -285,6 +286,11 @@ pub enum Op { inputs: BTreeSet, frontier: Option, }, + /// Stages a publication's changed protection records as one operation. + SetReadProtection { + requirements: Vec, + bounds: Vec, + }, /// Injects audit events into the catalog. /// /// This is a nonstandard path used for manually appending audit events at the current time. @@ -3258,6 +3264,12 @@ impl Catalog { } => { tx.set_maintained_read_requirement(id, inputs, frontier)?; } + Op::SetReadProtection { + requirements, + bounds, + } => { + tx.set_read_protection(requirements, bounds)?; + } Op::InjectAuditEvents { events } => { for event in events { let id = tx.allocate_audit_log_id()?; @@ -4030,6 +4042,7 @@ mod tests { async fn test_read_protection_admission_and_committed_updates() { use std::collections::{BTreeMap, BTreeSet}; + use mz_catalog::durable::objects::{CollectionCompactionBound, MaintainedReadRequirement}; use mz_catalog::memory::objects::StateDiff; use mz_persist_client::ShardId; use mz_repr::{GlobalId, Timestamp}; @@ -4042,6 +4055,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 +4069,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) @@ -4133,30 +4158,136 @@ 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().storage_metadata().compaction_bounds, + before.storage_metadata().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.storage_metadata().compaction_bounds, + catalog.state().storage_metadata().compaction_bounds + ); + assert_eq!( + state.maintained_read_requirements(), + catalog.state().maintained_read_requirements() + ); + } + assert_eq!( + catalog.state().storage_metadata().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], 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; + 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); assert_eq!(catalog.ddl_revision(), ddl_revision); let interval = vars::CATALOG_READ_PROTECTION_PUBLISH_INTERVAL.name.as_str(); let default_interval = catalog diff --git a/src/adapter/src/coord/ddl.rs b/src/adapter/src/coord/ddl.rs index 0e946dcf18ae3..756b8e1141af3 100644 --- a/src/adapter/src/coord/ddl.rs +++ b/src/adapter/src/coord/ddl.rs @@ -1398,6 +1398,7 @@ impl Coordinator { | Op::UpdateScopedSystemParameters { .. } | Op::SetCollectionCompactionBound { .. } | Op::SetMaintainedReadRequirement { .. } + | Op::SetReadProtection { .. } | Op::Comment { .. } | Op::CheckClusterState { .. } | Op::InjectAuditEvents { .. } => {} diff --git a/src/adapter/src/coord/read_protection.rs b/src/adapter/src/coord/read_protection.rs index 5b5bffeb8774b..6359e1aefbd1c 100644 --- a/src/adapter/src/coord/read_protection.rs +++ b/src/adapter/src/coord/read_protection.rs @@ -61,22 +61,12 @@ impl Coordinator { 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?; tracing::info!( diff --git a/src/catalog/src/durable/transaction.rs b/src/catalog/src/durable/transaction.rs index f2fd8c32149d4..efe70daf9f080 100644 --- a/src/catalog/src/durable/transaction.rs +++ b/src/catalog/src/durable/transaction.rs @@ -52,18 +52,18 @@ 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, 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, @@ -2775,6 +2775,39 @@ impl<'a> Transaction<'a> { 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(()) + } + /// 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> { From 5f10cf9af99229a08dd0cc47e5dd2d45322ea21e Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 03:39:36 +0000 Subject: [PATCH 29/41] wip: Integrate maintained recovery protection Checkpoint source/sink catalog requirements, ingestion birth-floor delivery, and frozen diagnostic catalog reconstruction. Remove checker-driven DDL revision semantics and retain input execution protection for metric sinks. Index bound ownership and controller accounting are implemented, but production index publication, delivery, and installation authorization are not connected. Read-only fenced reconstruction beyond saved permission needs a boundary decision before that integration is completed. The extended source/sink production demonstration is not yet exercised. This checkpoint is intentionally local and is not milestone completion. --- .../design/20260903_decoupled_coordination.md | 22 + .../20260903_decoupled_coordination_prompt.md | 5 - src/adapter/src/catalog.rs | 120 +++- src/adapter/src/catalog/apply.rs | 27 +- src/adapter/src/catalog/open.rs | 3 +- src/adapter/src/catalog/state.rs | 84 ++- src/adapter/src/catalog/transact.rs | 388 +++++++++--- src/adapter/src/coord.rs | 10 +- src/adapter/src/coord/command_handler.rs | 4 +- src/adapter/src/coord/ddl.rs | 19 +- src/adapter/src/coord/read_protection.rs | 69 ++- src/adapter/src/coord/sequencer/inner.rs | 37 +- src/adapter/src/error.rs | 8 +- src/adapter/src/session.rs | 8 +- src/adapter/src/util.rs | 1 + src/catalog/src/durable.rs | 12 + src/catalog/src/durable/persist.rs | 43 +- src/catalog/src/durable/transaction.rs | 315 ++++++++-- src/catalog/tests/compute-read-protection.rs | 546 +++++++++++++++++ src/catalog/tests/read-write.rs | 144 +++++ src/compute-client/src/controller.rs | 99 ++- .../src/controller/compaction_tests.rs | 569 ++++++++++++++++++ src/compute-client/src/controller/error.rs | 14 + src/compute-client/src/controller/instance.rs | 111 +++- src/sql/src/session/vars/definitions.rs | 2 +- src/storage-client/src/client.rs | 5 + src/storage-client/src/storage_collections.rs | 14 + src/storage-controller/src/history.rs | 19 + src/storage-controller/src/instance.rs | 36 +- src/storage-controller/src/lib.rs | 18 +- 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 | 247 +++----- test/restart/mzcompose.py | 241 ++++++++ .../audit-log.td | 3 - test/testdrive/audit-log.td | 3 - 37 files changed, 3155 insertions(+), 525 deletions(-) create mode 100644 src/catalog/tests/compute-read-protection.rs create mode 100644 src/compute-client/src/controller/compaction_tests.rs diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 539cee04dbee5..9cb55879d3a46 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -660,3 +660,25 @@ 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. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index f119cf7edf512..3292a48ec3482 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -32,11 +32,6 @@ priorities, not new design requirements or a reason to reopen agreed decisions. removes the per-record pending-update scans, but does not establish end-to-end scalability. 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 diff --git a/src/adapter/src/catalog.rs b/src/adapter/src/catalog.rs index 04fb807a70d80..d98a457ab716c 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -145,7 +145,6 @@ 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 @@ -186,7 +185,6 @@ 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), } } @@ -365,14 +363,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 @@ -609,7 +599,11 @@ 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. Freeze a read-only handle before calling + /// this when reconstruction must exclude concurrent durable changes. + pub async fn open_debug_catalog_inner( persist_client: PersistClient, storage: Box, now: NowFn, @@ -2653,7 +2647,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 +2663,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 +2676,109 @@ 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 frozen_catalog_reconstruction_preserves_protection_snapshot() { + use mz_build_info::DUMMY_BUILD_INFO; + 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 + .unwrap() + .open(SYSTEM_TIME().into(), &bootstrap) + .await + .unwrap(); + let _ = seed.sync_to_current_updates().await.unwrap(); + let mut tx = seed.transaction().await.unwrap(); + tx.insert_collection_metadata(BTreeMap::from([ + (input, ShardId::new()), + (output, ShardId::new()), + ])) + .unwrap(); + tx.set_collection_compaction_bound(input, Some(Timestamp::from(10))) + .unwrap(); + tx.set_maintained_read_requirement( + output, + BTreeSet::from([input]), + Some(Timestamp::from(10)), + ) + .unwrap(); + let _ = tx.get_and_commit_op_updates(); + let ts = tx.upper(); + tx.commit(ts).await.unwrap(); + seed.expire().await; + + let mut writer = Catalog::open_debug_catalog(persist.clone(), organization, &bootstrap) + .await + .unwrap(); + let expected = writer.state().dump(None).unwrap(); + let environment_id = writer.state().config.environment_id.clone(); + let mut frozen = TestCatalogStateBuilder::new(persist.clone()) + .with_organization_id(organization) + .build() + .await + .unwrap() + .open_read_only(&bootstrap) + .await + .unwrap(); + frozen.freeze().await.unwrap(); + + 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 + .unwrap(); + assert_ne!(expected, writer.state().dump(None).unwrap()); + + let reconstructed = Catalog::open_debug_catalog_inner( + persist, + frozen, + SYSTEM_TIME.clone(), + Some(environment_id), + &DUMMY_BUILD_INFO, + BTreeMap::new(), + &bootstrap, + Some(false), + None, + ) + .await + .unwrap(); + assert_eq!(expected, reconstructed.state().dump(None).unwrap()); + reconstructed.expire().await; + 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..36068bef3234f 100644 --- a/src/adapter/src/catalog/apply.rs +++ b/src/adapter/src/catalog/apply.rs @@ -386,11 +386,12 @@ impl CatalogState { } StateUpdateKind::CollectionCompactionBound(bound) => { apply_inverted_lookup( - &mut Arc::make_mut(&mut self.storage_metadata).compaction_bounds, + Arc::make_mut(&mut self.collection_compaction_bounds), &bound.id, bound.frontier.into_iter().collect(), diff, ); + self.update_storage_compaction_bound(bound.id); } StateUpdateKind::MaintainedReadRequirement(requirement) => { let id = requirement.id; @@ -1416,6 +1417,30 @@ impl CatalogState { storage_collection_metadata.shard, diff, ); + 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/open.rs b/src/adapter/src/catalog/open.rs index 33bc0a3e34c9e..fe8c1da8d2e84 100644 --- a/src/adapter/src/catalog/open.rs +++ b/src/adapter/src/catalog/open.rs @@ -172,6 +172,7 @@ 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(), catalog_read_protection_enabled: false, temporary_namespaces: Default::default(), @@ -467,6 +468,7 @@ impl Catalog { enabled } }; + txn.initialize_index_compaction_bounds()?; let mz_authentication_mock_nonce = txn.get_authentication_mock_nonce().ok_or_else(|| { @@ -620,7 +622,6 @@ 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)), }; diff --git a/src/adapter/src/catalog/state.rs b/src/adapter/src/catalog/state.rs index 2b291db975c91..7aa4093bbb391 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,6 +156,8 @@ 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: Arc>>, #[serde(serialize_with = "serialize_maintained_read_requirements")] pub(super) maintained_read_requirements: Arc>, pub(super) catalog_read_protection_enabled: bool, @@ -483,6 +485,7 @@ 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(), catalog_read_protection_enabled: false, license_key: ValidatedLicenseKey::for_tests(), @@ -2901,6 +2904,11 @@ impl CatalogState { &self.storage_metadata } + /// Returns committed compaction permission for maintained storage and compute collections. + pub fn collection_compaction_bounds(&self) -> &BTreeMap> { + &self.collection_compaction_bounds + } + /// Returns the durable maintained read requirements, keyed by output ID. pub fn maintained_read_requirements(&self) -> &BTreeMap { &self.maintained_read_requirements @@ -3123,6 +3131,13 @@ impl Catalog { } } +fn serialize_collection_compaction_bounds( + bounds: &BTreeMap>, + serializer: S, +) -> Result { + mz_ore::serde::map_key_to_string(bounds, serializer) +} + fn serialize_maintained_read_requirements( requirements: &BTreeMap, serializer: S, @@ -3137,6 +3152,73 @@ 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_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::try_from(i).unwrap() + 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)))]) + ); + 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!( + snapshot + .storage_metadata() + .compaction_bounds + .contains_key(&storage) + ); + } + } + #[mz_ore::test(tokio::test)] async fn maintained_read_requirements_apply_snapshot_update_drop() { use mz_catalog::memory::objects::{StateDiff, StateUpdate, StateUpdateKind}; diff --git a/src/adapter/src/catalog/transact.rs b/src/adapter/src/catalog/transact.rs index 32a16945bfeea..9625f2c3d7ee4 100644 --- a/src/adapter/src/catalog/transact.rs +++ b/src/adapter/src/catalog/transact.rs @@ -69,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; @@ -456,7 +456,6 @@ pub struct TransactionResult { struct TransactInnerResult { state: CatalogState, planning_changed: bool, - ddl_changed: bool, } #[derive(Debug, Clone, Copy)] @@ -796,7 +795,6 @@ impl Catalog { if let Some(TransactInnerResult { state, planning_changed, - ddl_changed, }) = new_state { if planning_changed { @@ -807,9 +805,6 @@ impl Catalog { self.shared_transient_revision .store(self.transient_revision, atomic::Ordering::SeqCst); } - if ddl_changed { - self.ddl_revision += 1; - } self.state = state; } @@ -1081,28 +1076,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 @@ -1120,7 +1102,6 @@ impl Catalog { Cow::Owned(state) => Ok(Some(TransactInnerResult { state, planning_changed, - ddl_changed, })), Cow::Borrowed(_) => Ok(None), } @@ -1759,15 +1740,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()); } @@ -1804,9 +1792,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()); @@ -3076,6 +3067,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( @@ -3368,6 +3375,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, @@ -3394,23 +3411,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(|| { @@ -3766,9 +3814,11 @@ 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_catalog::durable::objects::MaintainedReadRequirement; 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; @@ -3802,11 +3852,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)"), @@ -3824,10 +3891,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, @@ -3855,23 +3928,182 @@ 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::SetCollectionCompactionBound { + id: global_id, + frontier: Some(permission), + }); + } + 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::SetMaintainedReadRequirement { + id: other.id, + inputs: other.inputs.clone(), + frontier: Some(frontier.step_forward()), + }); + } + } + invalid.push(Op::SetCollectionCompactionBound { + id: *input, + frontier: Some(frontier.step_forward()), + }); + 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::SetMaintainedReadRequirement { + id: global_id, + inputs, + frontier: Some(permission), + }); ops.push(Op::SetCollectionCompactionBound { id: global_id, frontier: Some(permission), }); - } 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::SetMaintainedReadRequirement { + id: export_gid, + inputs: BTreeSet::from([export_gid, source_gid]), + frontier: Some(progress), + }, + Op::SetCollectionCompactionBound { + id: export_gid, + frontier: Some(progress), + }, + ]); + 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::SetMaintainedReadRequirement { + id: sink_gid, + inputs: BTreeSet::from([sink_gid, identities["birth_table"].1]), + frontier: Some(progress), + }); + 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; } @@ -3879,8 +4111,9 @@ 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_catalog::durable::objects::MaintainedReadRequirement; use mz_persist_client::ShardId; use mz_proto::RustType; use mz_repr::{GlobalId, Timestamp}; @@ -3990,7 +4223,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; @@ -4001,10 +4234,17 @@ mod tests { None, oracle_write_ts, None, - vec![Op::SetCollectionCompactionBound { - id: source_id, - frontier: Some(fresh_bound), - }], + vec![ + Op::SetMaintainedReadRequirement { + id: source_id, + inputs: BTreeSet::from([source_id]), + frontier: Some(fresh_bound), + }, + Op::SetCollectionCompactionBound { + id: source_id, + frontier: Some(fresh_bound), + }, + ], ) .await .expect("dependency permission should advance during the DDL transaction"); @@ -4018,9 +4258,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; @@ -4105,9 +4353,8 @@ mod tests { 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 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)))]) @@ -4116,7 +4363,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 @@ -4138,11 +4385,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" ); @@ -4191,8 +4438,8 @@ mod tests { .is_err() ); assert_eq!( - catalog.state().storage_metadata().compaction_bounds, - before.storage_metadata().compaction_bounds, + catalog.state().collection_compaction_bounds(), + before.collection_compaction_bounds(), "an incompatible batch must not advance any bound" ); assert_eq!( @@ -4238,8 +4485,8 @@ mod tests { .expect("matching compaction bound and read requirement should commit"); for state in [&speculative, &replayed] { assert_eq!( - state.storage_metadata().compaction_bounds, - catalog.state().storage_metadata().compaction_bounds + state.collection_compaction_bounds(), + catalog.state().collection_compaction_bounds() ); assert_eq!( state.maintained_read_requirements(), @@ -4247,7 +4494,7 @@ mod tests { ); } assert_eq!( - catalog.state().storage_metadata().compaction_bounds[&completed_input], + catalog.state().collection_compaction_bounds()[&completed_input], Antichain::new() ); assert_eq!( @@ -4277,7 +4524,7 @@ mod tests { ); 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)) ); @@ -4288,7 +4535,6 @@ mod tests { .expect("unchanged publication should commit without implications"); assert!(unchanged.catalog_updates.is_empty()); assert_eq!(catalog.transient_revision(), revision); - assert_eq!(catalog.ddl_revision(), ddl_revision); let interval = vars::CATALOG_READ_PROTECTION_PUBLISH_INTERVAL.name.as_str(); let default_interval = catalog .system_config() @@ -4308,11 +4554,9 @@ mod tests { .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() @@ -4332,7 +4576,6 @@ mod tests { .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()); @@ -4374,7 +4617,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!( @@ -4391,11 +4633,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" ); @@ -5005,7 +5243,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"); @@ -5136,7 +5374,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/coord.rs b/src/adapter/src/coord.rs index 82a456443497d..a83bcada0c379 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -3488,6 +3488,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 +3509,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: (), diff --git a/src/adapter/src/coord/command_handler.rs b/src/adapter/src/coord/command_handler.rs index 0ac1328385067..cd8f4ef020dba 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 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 +1419,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/ddl.rs b/src/adapter/src/coord/ddl.rs index 756b8e1141af3..52f856dcbc9db 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 { diff --git a/src/adapter/src/coord/read_protection.rs b/src/adapter/src/coord/read_protection.rs index 6359e1aefbd1c..32e555427c45b 100644 --- a/src/adapter/src/coord/read_protection.rs +++ b/src/adapter/src/coord/read_protection.rs @@ -47,12 +47,15 @@ impl Coordinator { 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, + } }, ); let requirement_updates = candidates.requirements.len(); @@ -98,7 +101,7 @@ fn publication_candidates( bounds: &BTreeMap>, frontiers: &[CollectionFrontiers], compaction_frontiers: &BTreeMap>, - is_active_mv_writer: impl Fn(GlobalId) -> bool, + owns_durable_progress: impl Fn(GlobalId) -> bool, ) -> PublicationCandidates { let mut candidates = PublicationCandidates::default(); let installed: BTreeMap<_, _> = frontiers.iter().map(|f| (f.id, f)).collect(); @@ -106,7 +109,7 @@ fn publication_candidates( for requirement in requirements.values() { let mut frontier: Antichain<_> = requirement.frontier.into_iter().collect(); - if is_active_mv_writer(requirement.id) + if owns_durable_progress(requirement.id) && let Some(output) = installed.get(&requirement.id) { // An upper at a refresh timestamp does not complete that refresh. @@ -326,6 +329,56 @@ 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), + |_| 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); diff --git a/src/adapter/src/coord/sequencer/inner.rs b/src/adapter/src/coord/sequencer/inner.rs index 9b8122878b406..943be3607583f 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; @@ -2272,10 +2273,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 +3437,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 +3558,20 @@ 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::SetMaintainedReadRequirement { + id: global_id, + inputs: requirement.inputs.clone(), + frontier: frontier.as_option().copied(), + }); + } // Parse the `create_sql` so we can update it to the new sink definition. // @@ -3612,11 +3637,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/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/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/src/durable.rs b/src/catalog/src/durable.rs index 32fb47d562874..428cb54e61f18 100644 --- a/src/catalog/src/durable.rs +++ b/src/catalog/src/durable.rs @@ -294,6 +294,18 @@ pub trait DurableCatalogState: ReadOnlyDurableCatalogState { /// Returns true if the catalog is opened is savepoint mode, false otherwise. fn is_savepoint(&self) -> bool; + /// Synchronizes an opened read-only catalog, then freezes its cached state for diagnostics. + /// + /// Cached reads, including [`Self::snapshot`] and [`Self::transaction`], exclude subsequent + /// durable changes. Explicit snapshots supplied to dry-run transactions are unaffected. + /// Buffered updates remain available to drain, and `current_upper` stays at the frozen upper. + /// No migrations or ephemeral cleanup run. Nonempty commits still fail as read-only writes. + /// Audit-log reads are unsupported after freezing because they bypass the cache. + /// + /// Repeated calls are no-ops. Returns an error for writable or savepoint catalogs, or if + /// synchronization observes fencing. Once frozen, the handle no longer observes fencing. + async fn freeze(&mut self) -> Result<(), CatalogError>; + /// Marks the bootstrap process as complete. async fn mark_bootstrap_complete(&mut self); diff --git a/src/catalog/src/durable/persist.rs b/src/catalog/src/durable/persist.rs index 5b825fd3cb78b..c293c0b8ef603 100644 --- a/src/catalog/src/durable/persist.rs +++ b/src/catalog/src/durable/persist.rs @@ -94,11 +94,13 @@ 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. Readonly, + /// Read-only cached state frozen by [`DurableCatalogState::freeze`]. + FrozenReadonly, /// Mutable operations have an effect in-memory, but aren't persisted durably. Savepoint, /// Mutable operations have an effect in-memory and durably. @@ -339,7 +341,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, @@ -387,7 +389,7 @@ impl> PersistHandle { let upper = self.write_handle.fetch_recent_upper().await; antichain_to_timestamp(upper.clone()) } - Mode::Savepoint => self.upper, + Mode::Savepoint | Mode::FrozenReadonly => self.upper, } } @@ -545,6 +547,10 @@ impl> PersistHandle { async fn sync_inner(&mut self, target_upper: Timestamp) -> Result<(), FenceError> { self.fenceable_token.validate()?; + if self.mode == Mode::FrozenReadonly { + return Ok(()); + } + // Savepoint catalogs do not yet know how to update themselves in response to concurrent // writes from writer catalogs. if self.mode == Mode::Savepoint { @@ -1702,6 +1708,13 @@ impl ReadOnlyDurableCatalogState for PersistCatalogState { } async fn get_audit_logs(&mut self) -> Result, CatalogError> { + // A fresh persist reader may have a since beyond the frozen upper. + if self.mode == Mode::FrozenReadonly { + return Err(DurableCatalogError::Internal( + "audit-log reads are unsupported on a frozen catalog".into(), + ) + .into()); + } self.sync_to_current_upper().await?; let audit_logs: Vec<_> = self .persist_snapshot() @@ -1821,13 +1834,29 @@ impl ReadOnlyDurableCatalogState for PersistCatalogState { #[allow(mismatched_lifetime_syntaxes)] impl DurableCatalogState for PersistCatalogState { fn is_read_only(&self) -> bool { - matches!(self.mode, Mode::Readonly) + matches!(self.mode, Mode::Readonly | Mode::FrozenReadonly) } fn is_savepoint(&self) -> bool { matches!(self.mode, Mode::Savepoint) } + async fn freeze(&mut self) -> Result<(), CatalogError> { + match self.mode { + Mode::FrozenReadonly => return Ok(()), + Mode::Readonly => {} + Mode::Writable | Mode::Savepoint => { + return Err(DurableCatalogError::Internal( + "only a read-only catalog can be frozen".into(), + ) + .into()); + } + } + self.sync_to_current_upper().await?; + self.mode = Mode::FrozenReadonly; + Ok(()) + } + async fn mark_bootstrap_complete(&mut self) { self.bootstrap_complete = true; if matches!(self.mode, Mode::Writable) { @@ -1887,7 +1916,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 @@ -1942,7 +1971,7 @@ impl DurableCatalogState for PersistCatalogState { catalog.upper = commit_ts.step_forward(); catalog.upper } - Mode::Readonly => unreachable!("handled above"), + Mode::Readonly | Mode::FrozenReadonly => unreachable!("handled above"), }; Ok(next_upper) @@ -1971,7 +2000,7 @@ impl DurableCatalogState for PersistCatalogState { self.upper = new_upper; return Ok(()); } - Mode::Readonly => { + Mode::Readonly | Mode::FrozenReadonly => { return Err(DurableCatalogError::NotWritable( "cannot advance upper of a read-only catalog".into(), ) diff --git a/src/catalog/src/durable/transaction.rs b/src/catalog/src/durable/transaction.rs index efe70daf9f080..9e84124e5dece 100644 --- a/src/catalog/src/durable/transaction.rs +++ b/src/catalog/src/durable/transaction.rs @@ -567,6 +567,7 @@ impl<'a> Transaction<'a> { .zip_eq(oids) .map(|((builtin, item_id, index_id), oid)| (builtin, item_id, index_id, oid)) .collect(); + let mut index_ids = Vec::new(); for (builtin, item_id, index_id, oid) in introspection_source_indexes { let introspection_source_index = IntrospectionSourceIndex { cluster_id, @@ -579,7 +580,9 @@ impl<'a> Transaction<'a> { self.introspection_sources .insert(key, value, self.op_id) .expect("no uniqueness violation"); + index_ids.push(index_id); } + self.stage_index_compaction_bounds(index_ids)?; Ok(()) } @@ -725,6 +728,7 @@ impl<'a> Transaction<'a> { ), >, ) -> Result<(), CatalogError> { + let mut previous = BTreeMap::new(); for (cluster_id, updates) in mappings { for (name, item_id, index_id, oid) in updates { let introspection_source_index = IntrospectionSourceIndex { @@ -738,8 +742,13 @@ impl<'a> Transaction<'a> { let prev = self .introspection_sources - .set(key, Some(value), self.op_id)?; - if prev.is_none() { + .set(key.clone(), Some(value), self.op_id)?; + if let Some(prev) = prev { + // Repeated keys must compare the first ID with the final ID. + previous + .entry(key) + .or_insert(GlobalId::from(prev.global_id)); + } else { return Err(SqlCatalogError::FailedBuiltinSchemaMigration(format!( "{index_id}" )) @@ -747,7 +756,11 @@ impl<'a> Transaction<'a> { } } } - Ok(()) + let current = previous + .keys() + .map(|key| GlobalId::from(self.introspection_sources.get(key).unwrap().global_id)) + .collect::>(); + self.reconcile_index_compaction_bounds(previous.into_values().collect(), current) } pub fn insert_user_item( @@ -792,24 +805,27 @@ 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, + }; + let is_index = value.item_type() == CatalogItemType::Index; + self.items + .insert(ItemKey { id }, value, self.op_id) + .map_err(|_| { + CatalogError::from(SqlCatalogError::ItemAlreadyExists(id, item_name.to_owned())) + })?; + if is_index { + self.stage_index_compaction_bounds([global_id])?; } + Ok(()) } /// Removes every item owned by an ephemeral session from the transaction, @@ -1453,8 +1469,14 @@ impl<'a> Transaction<'a> { // storage layer. self.cluster_replicas .delete(|_k, v| clusters.contains(&v.cluster_id), self.op_id); - self.introspection_sources + let deleted = self + .introspection_sources .delete(|k, _v| clusters.contains(&k.cluster_id), self.op_id); + self.remove_index_compaction_bounds( + deleted + .into_iter() + .map(|(_, v)| GlobalId::from(v.global_id)), + ); Ok(()) } @@ -1514,7 +1536,10 @@ impl<'a> Transaction<'a> { /// DO NOT call this function in a loop, use [`Self::remove_items`] instead. pub fn remove_item(&mut self, id: CatalogItemId) -> Result<(), CatalogError> { let prev = self.items.set(ItemKey { id }, None, self.op_id)?; - if prev.is_some() { + if let Some(value) = prev { + if value.item_type() == CatalogItemType::Index { + self.remove_index_compaction_bounds([value.global_id]); + } Ok(()) } else { Err(SqlCatalogError::UnknownItem(id.to_string()).into()) @@ -1533,7 +1558,14 @@ 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(); + self.remove_index_compaction_bounds( + deleted + .into_iter() + .filter(|(_, v)| v.item_type() == CatalogItemType::Index) + .map(|(_, v)| v.global_id), + ); if n == ids.len() { Ok(()) } else { @@ -1566,7 +1598,14 @@ 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(); + self.remove_index_compaction_bounds( + deleted + .into_iter() + .filter(|(k, _)| k.object_type == CatalogItemType::Index) + .map(|(_, v)| GlobalId::from(v.global_id)), + ); if n == descriptions.len() { Ok(()) @@ -1610,10 +1649,13 @@ 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(); + self.remove_index_compaction_bounds( + deleted + .into_iter() + .map(|(_, v)| GlobalId::from(v.global_id)), + ); if n == introspection_source_indexes.len() { Ok(()) } else { @@ -1637,11 +1679,21 @@ 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 previous = self + .items + .get(&ItemKey { id }) + .filter(|v| v.item_type() == CatalogItemType::Index) + .map(|v| v.global_id); + let value = item.into_key_value().1; + let current = (value.item_type() == CatalogItemType::Index).then_some(value.global_id); + let updated = self + .items + .update_by_key(ItemKey { id }, value, self.op_id)?; if updated { - Ok(()) + self.reconcile_index_compaction_bounds( + previous.into_iter().collect(), + current.into_iter().collect(), + ) } else { Err(SqlCatalogError::UnknownItem(id.to_string()).into()) } @@ -1662,16 +1714,26 @@ impl<'a> Transaction<'a> { return Ok(()); } + let previous = items + .keys() + .filter_map(|id| self.items.get(&ItemKey { id: *id })) + .filter(|v| v.item_type() == CatalogItemType::Index) + .map(|v| v.global_id) + .collect::>(); 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(); + let current = kvs + .iter() + .filter(|(_, v)| v.item_type() == CatalogItemType::Index) + .map(|(_, v)| v.global_id) + .collect::>(); let n = self.items.update_by_keys(kvs, self.op_id)?; let n = usize::try_from(n.into_inner()).expect("Must be positive and fit in usize"); if n == update_ids.len() { - Ok(()) + self.reconcile_index_compaction_bounds(previous, current) } else { let item_ids: BTreeSet<_> = self.items.items().keys().map(|k| k.id).collect(); let mut unknown = update_ids.difference(&item_ids); @@ -1784,10 +1846,16 @@ impl<'a> Transaction<'a> { return Ok(()); } + let mut previous = BTreeSet::new(); + let mut current = BTreeSet::new(); let n = self.system_gid_mapping.update( - |_k, v| { + |k, v| { if let Some(mapping) = mappings.get(&CatalogItemId::from(v.catalog_id)) { let (_, new_value) = mapping.clone().into_key_value(); + if k.object_type == CatalogItemType::Index { + previous.insert(GlobalId::from(v.global_id)); + current.insert(GlobalId::from(new_value.global_id)); + } Some(new_value) } else { None @@ -1803,7 +1871,7 @@ impl<'a> Transaction<'a> { return Err(SqlCatalogError::FailedBuiltinSchemaMigration(id_str).into()); } - Ok(()) + self.reconcile_index_compaction_bounds(previous, current) } /// Updates cluster `id` in the transaction to `cluster`. @@ -2069,12 +2137,15 @@ impl<'a> Transaction<'a> { ) .collect(); + let mut index_ids = Vec::new(); for introspection_source_index in introspection_source_indexes { + let id = introspection_source_index.index_id; let (key, value) = introspection_source_index.into_key_value(); self.introspection_sources.insert(key, value, self.op_id)?; + index_ids.push(id); } - Ok(()) + self.stage_index_compaction_bounds(index_ids) } /// Set persisted system object mappings. @@ -2086,13 +2157,25 @@ 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))) .collect(); - self.system_gid_mapping.set_many(mappings, self.op_id)?; - Ok(()) + let current = mappings + .iter() + .filter(|(k, _)| k.object_type == CatalogItemType::Index) + .map(|(_, v)| GlobalId::from(v.as_ref().unwrap().global_id)) + .collect::>(); + let previous = self.system_gid_mapping.set_many(mappings, self.op_id)?; + self.reconcile_index_compaction_bounds( + previous + .into_iter() + .filter(|(k, _)| k.object_type == CatalogItemType::Index) + .filter_map(|(_, v)| v.map(|v| GlobalId::from(v.global_id))) + .collect(), + current, + ) } /// Set persisted replica. @@ -2738,11 +2821,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 bound must accompany collection birth. For storage, the caller must + /// secure actual readability at that bound, including when reusing a shard. + /// Validation checks committed permission and read requirements, not physical + /// frontiers. Index installation follows [`Self::initialize_index_compaction_bounds`]. /// `None` denotes the empty frontier, not an ungoverned collection. pub fn set_collection_compaction_bound( &mut self, @@ -2808,10 +2892,121 @@ impl<'a> Transaction<'a> { Ok(()) } - /// Validates the final storage-backed read protection state without committing it. + /// Withholds compaction for indices born in this transaction when protection is enabled. + /// + /// Call after latching `catalog_read_protection_enabled` during bootstrap, in the + /// same transaction as index insertion. Existing ungoverned IDs are not adopted. + /// MIN grants no compaction permission. The caller must durably authorize the + /// actual installation frontier before creating the index in compute. + pub fn initialize_index_compaction_bounds(&mut self) -> Result<(), CatalogError> { + let initial = self.index_ids(true); + let born = self + .index_ids(false) + .difference(&initial) + .copied() + .filter(|id| { + !self + .storage_collection_metadata + .initial + .contains_key(&StorageCollectionMetadataKey { id: *id }) + }) + .collect::>(); + self.stage_index_compaction_bounds(born) + } + + fn index_ids(&self, initial: bool) -> BTreeSet { + let items = if initial { + self.items.initial.iter().collect() + } else { + self.items.items() + }; + let mappings = if initial { + self.system_gid_mapping.initial.iter().collect() + } else { + self.system_gid_mapping.items() + }; + let introspection = if initial { + self.introspection_sources.initial.iter().collect() + } else { + self.introspection_sources.items() + }; + items + .into_values() + .filter(|v| v.item_type() == CatalogItemType::Index) + .map(|v| v.global_id) + .chain( + mappings + .into_iter() + .filter(|(k, _)| k.object_type == CatalogItemType::Index) + .map(|(_, v)| GlobalId::from(v.global_id)), + ) + .chain( + introspection + .into_values() + .map(|v| GlobalId::from(v.global_id)), + ) + .collect() + } + + fn stage_index_compaction_bounds( + &mut self, + ids: impl IntoIterator, + ) -> Result<(), CatalogError> { + if self.get_config("catalog_read_protection_enabled".to_string()) != Some(1) { + return Ok(()); + } + let bounds = ids + .into_iter() + .filter_map(|id| { + let key = CollectionCompactionBoundKey { id }; + self.collection_compaction_bounds + .get(&key) + .is_none() + .then_some(( + key, + Some(CollectionCompactionBoundValue { + frontier: Some(mz_repr::Timestamp::MIN), + }), + )) + }) + .collect(); + self.collection_compaction_bounds + .set_many(bounds, self.op_id)?; + Ok(()) + } + + fn remove_index_compaction_bounds(&mut self, ids: impl IntoIterator) { + // Storage protection belongs to storage metadata removal, not item cleanup. + let keys = ids + .into_iter() + .filter(|id| { + self.storage_collection_metadata + .get(&StorageCollectionMetadataKey { id: *id }) + .is_none() + }) + .map(|id| CollectionCompactionBoundKey { id }) + .collect::>(); + self.collection_compaction_bounds + .delete_by_keys(keys, self.op_id); + } + + fn reconcile_index_compaction_bounds( + &mut self, + previous: BTreeSet, + current: BTreeSet, + ) -> Result<(), CatalogError> { + // Reconcile the whole batch so swapping IDs does not reset their bounds. + self.remove_index_compaction_bounds(previous.difference(¤t).copied()); + self.stage_index_compaction_bounds(current.difference(&previous).copied()) + } + + /// Validates the final 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.items.pending.is_empty() + && self.system_gid_mapping.pending.is_empty() + && self.introspection_sources.pending.is_empty() && self.collection_compaction_bounds.pending.is_empty() && self.maintained_read_requirements.pending.is_empty() { @@ -2820,13 +3015,18 @@ impl<'a> Transaction<'a> { let invalid = |message| CatalogError::from(DurableCatalogError::InvalidReadProtection(message)); let storage = self.storage_collection_metadata.items(); + let indexes = self.index_ids(false); + let initial_indexes = self.index_ids(true); + let owns_bound = |id| { + storage.contains_key(&StorageCollectionMetadataKey { id }) || indexes.contains(&id) + }; 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 }) { + if !owns_bound(key.id) { continue; } let Some(bound) = bounds.get(key) else { @@ -2849,11 +3049,12 @@ impl<'a> Transaction<'a> { } 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 + // guarantee and cannot adopt a bound without conversion. + if (self .storage_collection_metadata .initial .contains_key(&StorageCollectionMetadataKey { id: key.id }) + || initial_indexes.contains(&key.id)) && !self.collection_compaction_bounds.initial.contains_key(key) { return Err(invalid(format!( @@ -2861,9 +3062,9 @@ impl<'a> Transaction<'a> { key.id ))); } - if !storage.contains_key(&StorageCollectionMetadataKey { id: key.id }) { + if !owns_bound(key.id) { return Err(invalid(format!( - "compaction bound owner {} has no storage metadata", + "compaction bound owner {} has neither storage metadata nor an index identity", key.id ))); } @@ -2879,10 +3080,12 @@ impl<'a> Transaction<'a> { continue; }; for input in &requirement.inputs { - let readable = bounds - .get(&CollectionCompactionBoundKey { id: *input }) - .and_then(|bound| bound.frontier) - .is_some_and(|bound| bound <= required); + // Index arrangements are not logical recovery boundaries. + let readable = storage.contains_key(&StorageCollectionMetadataKey { id: *input }) + && 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}", @@ -3759,9 +3962,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..4816926ba2a29 --- /dev/null +++ b/src/catalog/tests/compute-read-protection.rs @@ -0,0 +1,546 @@ +// 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<'_>) { + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); +} + +async fn reject(mut txn: Transaction<'_>, message: &str) { + 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.initialize_index_compaction_bounds().unwrap(); + for owner in OWNERS { + owner.insert(&mut txn, 1001); + } + let expected: BTreeMap<_, _> = OWNERS + .into_iter() + .flat_map(|owner| [1000, 1001].map(|n| (owner.id(n), Some(Timestamp::MIN)))) + .collect(); + assert_eq!(bounds(txn.current_snapshot()), expected); + 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_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(); + for owner in OWNERS { + let current = bounds(txn.current_snapshot()); + assert!(!current.contains_key(&owner.id(1001))); + assert_eq!(current[&owner.id(1002)], Some(Timestamp::MIN)); + } + 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); + 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); + reject(txn, "lost its compaction bound").await; + 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.initialize_index_compaction_bounds().unwrap(); + 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(); + + // The intermediate ID must neither acquire a bound nor mask a surviving ID. + 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(); + assert_eq!(bounds(txn.current_snapshot()), expected); + txn.validate_read_protection().unwrap(); + } + 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; + + let mut txn = state.transaction().await.unwrap(); + txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) + .unwrap(); + txn.initialize_index_compaction_bounds().unwrap(); + Owner::Builtin.insert(&mut txn, 1000); + assert!(bounds(txn.current_snapshot()).is_empty()); + commit(txn).await; + for owner in OWNERS { + for frontier in [Some(Timestamp::MIN), None] { + let mut txn = state.transaction().await.unwrap(); + txn.set_collection_compaction_bound(owner.id(1000), frontier) + .unwrap(); + reject(txn, "cannot introduce").await; + } + let mut txn = state.transaction().await.unwrap(); + owner.remove(&mut txn, 1000); + owner.insert(&mut txn, 1000); + 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); + } + 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(); + let mut item = txn.get_item(&Owner::Ordinary.item_id(1000)).unwrap(); + item.create_sql = sql.to_string(); + txn.update_item(item.id, item).unwrap(); + assert!(!bounds(txn.current_snapshot()).contains_key(&Owner::Ordinary.id(1000))); + txn.set_collection_compaction_bound(Owner::Ordinary.id(1000), None) + .unwrap(); + 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(); + 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(); + 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; +} diff --git a/src/catalog/tests/read-write.rs b/src/catalog/tests/read-write.rs index 07c36497bcf45..003394c4b4ee3 100644 --- a/src/catalog/tests/read-write.rs +++ b/src/catalog/tests/read-write.rs @@ -810,6 +810,9 @@ async fn test_non_writer_commits(state_builder: TestCatalogStateBuilder) { let _ = savepoint_state.sync_to_current_updates().await.unwrap(); let _ = reader_state.sync_to_current_updates().await.unwrap(); + assert!(writer_state.freeze().await.is_err()); + assert!(savepoint_state.freeze().await.is_err()); + // Commit write with writer. let role_name = "joe"; let role_id = { @@ -883,6 +886,147 @@ async fn test_non_writer_commits(state_builder: TestCatalogStateBuilder) { } } +#[mz_ore::test(tokio::test)] +#[cfg_attr(miri, ignore)] +async fn test_persist_freeze_read_only() { + 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 + .unwrap(); + let _ = writer.sync_to_current_updates().await.unwrap(); + let mut frozen = builder + .clone() + .unwrap_build() + .await + .open_read_only(&test_bootstrap_args()) + .await + .unwrap(); + let mut live = builder + .clone() + .unwrap_build() + .await + .open_read_only(&test_bootstrap_args()) + .await + .unwrap(); + + // Commit after opening the diagnostic handle so freezing must synchronize it. + 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; + frozen.freeze().await.unwrap(); + assert!(frozen.is_read_only()); + assert!(!frozen.is_savepoint()); + let frozen_upper = frozen.current_upper().await; + assert_eq!(frozen_upper, expected_upper); + assert_eq!(writer.current_upper().await, expected_upper); + assert_eq!(frozen.snapshot().await.unwrap(), expected); + let initial_updates = frozen.sync_to_current_updates().await.unwrap(); + assert!(!initial_updates.is_empty()); + assert_eq!( + initial_updates, + live.sync_to_current_updates().await.unwrap() + ); + let initial_id = frozen.get_next_user_item_id().await.unwrap(); + + 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]), Some(20.into())) + .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 > frozen_upper); + + frozen.freeze().await.unwrap(); + assert_eq!(frozen.snapshot().await.unwrap(), expected); + assert_eq!(frozen.get_next_user_item_id().await.unwrap(), initial_id); + assert!(frozen.sync_updates(later_upper).await.unwrap().is_empty()); + assert!(frozen.sync_to_current_updates().await.unwrap().is_empty()); + assert_eq!(frozen.current_upper().await, frozen_upper); + let txn = frozen.transaction().await.unwrap(); + assert_eq!(txn.current_snapshot(), expected); + txn.commit(frozen_upper).await.unwrap(); + + let mut txn = frozen.transaction().await.unwrap(); + txn.get_and_increment_id(USER_ITEM_ALLOC_KEY.into()) + .unwrap(); + let _ = txn.get_and_commit_op_updates(); + assert!(matches!( + txn.commit(frozen_upper).await, + Err(CatalogError::Durable(DurableCatalogError::NotWritable(_))) + )); + assert!(matches!( + frozen.advance_upper(later_upper).await, + Err(CatalogError::Durable(DurableCatalogError::NotWritable(_))) + )); + assert!(matches!( + frozen.get_audit_logs().await, + Err(CatalogError::Durable(DurableCatalogError::Internal(_))) + )); + assert_eq!(frozen.snapshot().await.unwrap(), expected); + assert_eq!(live.snapshot().await.unwrap(), changed); + assert!(!live.sync_to_current_updates().await.unwrap().is_empty()); + assert_eq!(live.get_next_user_item_id().await.unwrap(), initial_id + 1); + assert_eq!(writer.current_upper().await, later_upper); + + let next_writer = builder + .unwrap_build() + .await + .open(SYSTEM_TIME().into(), &test_bootstrap_args()) + .await + .unwrap(); + assert!(matches!( + live.snapshot().await, + Err(CatalogError::Durable(DurableCatalogError::Fence(_))) + )); + assert_eq!(frozen.snapshot().await.unwrap(), expected); + next_writer.expire().await; + frozen.expire().await; + live.expire().await; + writer.expire().await; +} + /// 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). diff --git a/src/compute-client/src/controller.rs b/src/compute-client/src/controller.rs index bfd41301888a8..7862f0c5a687e 100644 --- a/src/compute-client/src/controller.rs +++ b/src/compute-client/src/controller.rs @@ -71,9 +71,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 +181,8 @@ impl PeekNotification { /// A controller for the compute layer. pub struct ComputeController { instances: BTreeMap, + /// Committed permissions for collections not yet installed. + staged_compaction_bounds: BTreeMap>, /// 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`). @@ -302,6 +304,7 @@ impl ComputeController { Self { instances: BTreeMap::new(), + staged_compaction_bounds: BTreeMap::new(), instance_workload_classes, build_info, storage_collections, @@ -467,6 +470,7 @@ impl ComputeController { // Destructure `self` here so we don't forget to consider dumping newly added fields. let Self { instances, + staged_compaction_bounds, instance_workload_classes, build_info: _, storage_collections: _, @@ -503,6 +507,7 @@ 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, @@ -527,7 +532,7 @@ 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 collection = Collection::new_log(self.staged_compaction_bounds.remove(&id)); let shared = collection.shared.clone(); collections.insert(id, collection); logs.push((log, id, shared)); @@ -815,6 +820,10 @@ 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 must be at or beyond every index export's `as_of`. + /// Recovery requiring a later `as_of` must commit and apply permission before creation. + /// 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( @@ -846,6 +855,13 @@ impl ComputeController { if as_of.is_empty() && dataflow.copy_to_ids().next().is_some() { return Err(EmptyAsOfForCopyTo); } + for id in dataflow.exported_index_ids() { + if let Some(bound) = self.staged_compaction_bounds.get(&id) { + if !PartialOrder::less_equal(as_of, bound) { + return Err(CompactionBoundViolation(id)); + } + } + } // Validation: the dataflow exports something // @@ -909,13 +925,16 @@ 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 shared = + SharedCollectionState::new(as_of.clone(), if write_only { None } else { bound }); 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 +960,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 +1062,64 @@ 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> { + 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(()) + } + + /// Forget a bound staged for an object dropped before compute installation. + /// + /// Installed permissions are released only by dropping the collection or instance. + pub fn remove_staged_compaction_bound(&mut self, id: GlobalId) { + self.staged_compaction_bounds.remove(&id); + } + + /// Return compaction candidates for installed governed indexes, including logging 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. + pub fn compaction_bound_proposals(&self) -> BTreeMap> { + self.instances + .values() + .flat_map(|instance| &instance.collections) + .filter_map(|(&id, collection)| { + collection + .shared + .compaction_bound_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 @@ -1358,12 +1437,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..64bc8e2eb8603 --- /dev/null +++ b/src/compute-client/src/controller/compaction_tests.rs @@ -0,0 +1,569 @@ +// 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, + ®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.compaction_bound_proposals()[&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.compaction_bound_proposals()[&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.compaction_bound_proposals()[&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 + .compaction_bound_proposals() + .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.compaction_bound_proposals()[&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.compaction_bound_proposals()[&OUTPUT], + frontier(6) + ); + drop(reader); + controller.drop_instance(INSTANCE); + assert!(controller.compaction_bound_proposals().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.compaction_bound_proposals().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(); + let pending = GlobalId::User(2); + controller + .apply_compaction_bound(pending, frontier(3)) + .unwrap(); + controller.remove_staged_compaction_bound(pending); + 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.compaction_bound_proposals()[&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.compaction_bound_proposals()[&INPUT], + frontier(2000) + ); + 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 compaction_frontiers(&self) -> 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 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..60b3b21305f9a 100644 --- a/src/compute-client/src/controller/instance.rs +++ b/src/compute-client/src/controller/instance.rs @@ -1477,7 +1477,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 +1742,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(()) @@ -1937,19 +1939,41 @@ 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); + 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 +1997,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()) @@ -2696,23 +2721,26 @@ 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 { + if let Some(bound) = &permission { + assert!(PartialOrder::less_equal(&as_of, bound)); + } // 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,9 +2751,15 @@ 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, + })), write_frontier: Arc::new(Mutex::new(upper)), } } @@ -2735,7 +2769,23 @@ impl SharedCollectionState { F: FnOnce(&mut MutableAntichain) -> R, { let mut caps = self.read_capabilities.lock().expect("poisoned"); - f(&mut *caps) + f(&mut caps.holds) + } + + pub fn compaction_bound(&self) -> Option> { + self.read_capabilities + .lock() + .expect("poisoned") + .permission + .clone() + } + + pub fn compaction_bound_proposal(&self) -> Option> { + let caps = self.read_capabilities.lock().expect("poisoned"); + 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 +2797,31 @@ impl SharedCollectionState { } } +#[derive(Debug)] +struct ReadCapabilities { + holds: MutableAntichain, + permission: Option>, +} + +impl ReadCapabilities { + /// 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 +3504,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/sql/src/session/vars/definitions.rs b/src/sql/src/session/vars/definitions.rs index 407fe531dc17b..6f3e0e8abda66 100644 --- a/src/sql/src/session/vars/definitions.rs +++ b/src/sql/src/session/vars/definitions.rs @@ -962,7 +962,7 @@ pub static CATALOG_READ_PROTECTION_PUBLISH_INTERVAL: VarDefinition = VarDefiniti "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.", + tradeoff.", false, ); 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..ec33d6e755e23 100644 --- a/src/storage-client/src/storage_collections.rs +++ b/src/storage-client/src/storage_collections.rs @@ -319,6 +319,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( @@ -2587,6 +2591,14 @@ impl StorageCollections for StorageCollectionsImpl { 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, @@ -3734,6 +3746,7 @@ mod tests { controller .apply_compaction_bounds(BTreeMap::from([(id, frontier(10))])) .unwrap(); + assert_eq!(controller.compaction_bound(id).unwrap(), Some(frontier(10))); assert_eq!(controller.compaction_frontiers()[&id], frontier(5)); assert_eq!( controller @@ -4266,6 +4279,7 @@ mod tests { ) .await .unwrap(); + assert_eq!(controller.compaction_bound(id).unwrap(), bound); let expected = bound.unwrap_or_else(|| frontier(100)); assert_eq!( controller 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..23b6c9d3eb8fd 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); 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..14165c285506a 100644 --- a/src/testdrive/src/action/consistency.rs +++ b/src/testdrive/src/action/consistency.rs @@ -18,8 +18,12 @@ 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_catalog::durable::DurableCatalogState; +use mz_ore::now::SYSTEM_TIME; use mz_ore::retry::{Retry, RetryResult}; -use mz_persist_client::{PersistLocation, ShardId}; +use mz_persist_client::{PersistClient, PersistLocation, ShardId}; use mz_postgres_util::{query_one, sql}; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; @@ -99,7 +103,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 +/// Acquires a frozen durable handle between two identical full memory dumps. +/// Reconstruction must happen afterward so its cost does not widen the alignment interval. +async fn aligned_catalog_snapshot( + state: &State, +) -> Result)>, anyhow::Error> { + async fn memory_dump(state: &State) -> Result { + reqwest::get(format!( + "http://{}/api/catalog/dump", + state.materialize.internal_http_addr, )) - .await? + .await + .context("GET catalog")? .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; + .text() + .await + .context("deserialize catalog") } - 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? + loop { + let Some((persist_client, mut storage)) = state.open_catalog_copy().await? else { + return Ok(None); + }; + let capture = async { + let before = memory_dump(state).await?; + storage.freeze().await?; + let after = memory_dump(state).await?; + Ok::<_, anyhow::Error>((before == after).then_some(before)) + } + .await; + match capture { + Ok(Some(memory)) => return Ok(Some((memory, persist_client, storage))), + Ok(None) => { + storage.expire().await; + tracing::debug!("catalog changed during snapshot acquisition, retrying"); } - None => { - batch_execute( - &client, - sql!("ALTER SYSTEM RESET catalog_read_protection_publish_interval"), - ) - .await? + Err(error) => { + storage.expire().await; + return Err(error); } } - 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:#}" - )), } } @@ -313,32 +243,28 @@ 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() { + let Some((memory_catalog, persist_client, storage)) = aligned_catalog_snapshot(state) + .await + .context("acquiring aligned catalog snapshot")? + 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(()); - } - - // 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")?; + }; // 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 { + storage.expire().await; // 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!( @@ -351,39 +277,36 @@ async fn check_catalog_state(state: &State) -> Result<(), anyhow::Error> { .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() + // 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"); + catalog.expire().await; if disk_catalog != memory_catalog { // The state objects here are around 100k lines pretty printed, so find the diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index 7f0e8a3f6c4fb..43c336654e25e 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -1038,6 +1038,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", ), ): @@ -1461,6 +1462,246 @@ 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": {"a": 1}, "after": null} + {"before": null, "after": {"a": 2}} + $ kafka-ingest format=bytes topic=protected-source + three + > SELECT * FROM protected_late_export; + one + three + two + """) + await_state( + "source recovery requirements advancing and remap physically compacting", + 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 compacted_past(inspect(source), birth), + ) + 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 + td(""" + > SELECT * FROM protected_export; + one + three + two + > SELECT * FROM protected_late_export; + one + three + two + > 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 + > SELECT * FROM protected_export; + four + one + three + two + > SELECT * FROM protected_late_export; + four + one + three + two + """) + await_state( + "altered sink advancing after recovery", + lambda: storage_advanced(sink, [sink, sink_next], altered["frontier"]) + and inspect(sink_next)["since"][0] > altered["frontier"], + ) c.down(destroy_volumes=True) 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 1fa45704465593a3b99248cbe11040bd1d54074f Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 06:06:55 +0000 Subject: [PATCH 30/41] adapter: Apply catalog permission to maintained compute Publish compute bounds with storage recovery progress and deliver committed permission before installation and compaction. Read-only prewarming follows durable catalog bounds independently of its SQL savepoint, without granting permission through local reconstruction. Extend source/sink recovery evidence for the committed SNAPSHOT=false cutoff and add a configurable production publication measurement workflow. Milestone 1 remains under integration and verification. --- .../design/20260903_decoupled_coordination.md | 17 + src/adapter/src/coord.rs | 70 +++- src/adapter/src/coord/catalog_implications.rs | 17 +- src/adapter/src/coord/catalog_subscriber.rs | 192 +++++++++++ src/adapter/src/coord/read_protection.rs | 251 +++++++++++++- src/environmentd/src/lib.rs | 35 +- test/restart/mzcompose.py | 321 +++++++++++++++++- 7 files changed, 877 insertions(+), 26 deletions(-) create mode 100644 src/adapter/src/coord/catalog_subscriber.rs diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 9cb55879d3a46..ba0cd2d0009ec 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -682,3 +682,20 @@ 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. diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index a83bcada0c379..2a4cde8281bda 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}; @@ -201,11 +202,13 @@ use crate::coord::appends::{ BuiltinTableAppendCompletion, BuiltinTableAppendNotify, DeferredOp, GroupCommitPermit, PendingWriteTxn, }; +use crate::coord::catalog_subscriber::CatalogSubscriber; use crate::coord::caught_up::CaughtUpCheckContext; 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}; @@ -240,6 +243,7 @@ pub(crate) mod timeline; pub(crate) mod timestamp_selection; pub mod catalog_implications; +mod catalog_subscriber; mod caught_up; mod command_handler; mod ddl; @@ -1319,6 +1323,7 @@ pub struct Config { pub controller_config: ControllerConfig, pub controller_envd_epoch: NonZeroI64, pub storage: Box, + pub catalog_subscriber: Option>, pub timestamp_oracle_url: Option, pub unsafe_mode: bool, pub all_features: bool, @@ -2063,6 +2068,7 @@ 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, + catalog_subscriber: Option, /// A client for persist. Initially, this is only used for reading stashed /// peek responses out of batches. @@ -2651,6 +2657,12 @@ impl Coordinator { .catalog() .system_config() .enable_storage_introspection_logs(); + let missing = self.restore_compute_read_protection().await?; + if !missing.is_empty() { + return Err(AdapterError::Unstructured(anyhow::anyhow!( + "index recovery has no committed compaction permission: {missing:?}" + ))); + } for instance in self.catalog.clusters() { self.controller.create_cluster( instance.id, @@ -2735,7 +2747,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() @@ -4068,10 +4080,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(), @@ -4097,6 +4111,19 @@ impl Coordinator { } } + // Prefer already-authorized recovery while retaining the selector's hard + // readability constraints. A saved bound is not a historical read hold. + if let Some(subscriber) = &mut self.catalog_subscriber { + subscriber.sync().await?; + for (id, policy) in &mut read_policies { + if let Some(bound) = subscriber.bounds().get(id) { + *policy = ReadPolicy::Multiple(vec![ + policy.clone(), + ReadPolicy::ValidFrom(bound.clone()), + ]); + } + } + } let read_ts = self.get_local_read_ts().await; let read_holds = as_of_selection::run( &mut dataflows, @@ -4106,12 +4133,22 @@ impl Coordinator { self.controller.read_only(), ); + let installations = dataflows + .iter() + .flat_map(|dataflow| { + dataflow + .exported_index_ids() + .map(|id| (id, dataflow.as_of.clone().expect("as-of selected"))) + }) + .collect(); + self.authorize_index_installation(installations).await?; + let catalog = self.catalog_mut(); for (id, plan) in catalog_ids.into_iter().zip_eq(dataflows) { catalog.set_physical_plan(id, plan); } - read_holds + Ok(read_holds) } /// Serves the coordinator, receiving commands from users over `cmd_rx` @@ -4229,6 +4266,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 @@ -4249,6 +4288,15 @@ 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.catalog_subscriber.is_some() => { + if let Err(error) = self.restore_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. @@ -4458,6 +4506,9 @@ impl Coordinator { if let Some(sweep) = self.hydration_history_sweep.take() { sweep.abort_and_wait().await; } + if let Some(subscriber) = self.catalog_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. @@ -4695,7 +4746,14 @@ 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()); + let installations = df_desc + .exported_index_ids() + .map(|id| (id, since.clone())) + .collect(); + self.authorize_index_installation(installations) + .await + .unwrap_or_terminate("cannot authorize committed index installation"); self.ship_dataflow_and_notice_builtin_table_updates( df_desc, @@ -5109,6 +5167,7 @@ pub fn serve( controller_config, controller_envd_epoch, mut storage, + catalog_subscriber, timestamp_oracle_url, unsafe_mode, all_features, @@ -5521,6 +5580,7 @@ pub fn serve( let mut coord = Coordinator { controller, catalog, + catalog_subscriber: catalog_subscriber.map(CatalogSubscriber::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/catalog_subscriber.rs b/src/adapter/src/coord/catalog_subscriber.rs new file mode 100644 index 0000000000000..c129b452bbe7d --- /dev/null +++ b/src/adapter/src/coord/catalog_subscriber.rs @@ -0,0 +1,192 @@ +// 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 CatalogSubscriber { + catalog: Box, + bounds: BTreeMap>, +} + +impl CatalogSubscriber { + /// 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, including the initial durable state on the first call. + 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)); + for (_, diff, bound) in bounds { + match diff { + StateDiff::Retraction => { + self.bounds.remove(&bound.id); + } + StateDiff::Addition => { + self.bounds + .insert(bound.id, Antichain::from_iter(bound.frontier)); + } + } + } + Ok(()) + } + + /// Returns committed bounds, not permission to read or to drop a collection. + /// + /// Missing records grant no permission. The caller must match IDs to its frozen + /// catalog and handle collection lifetimes, including dropped bound owners. + 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 + .unwrap(); + let _ = writer.sync_to_current_updates().await.unwrap(); + + let id = GlobalId::User(1000); + let completed = GlobalId::User(1001); + let mut txn = writer.transaction().await.unwrap(); + txn.insert_collection_metadata(BTreeMap::from([ + (id, ShardId::new()), + (completed, ShardId::new()), + ])) + .unwrap(); + txn.set_collection_compaction_bound(id, Some(10.into())) + .unwrap(); + txn.set_collection_compaction_bound(completed, None) + .unwrap(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + + 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 frozen_snapshot = savepoint.snapshot().await.unwrap(); + let reader = builder + .unwrap_build() + .await + .open_read_only(&test_bootstrap_args()) + .await + .unwrap(); + let mut subscriber = CatalogSubscriber::new(reader); + assert!(subscriber.bounds().is_empty()); + subscriber.sync().await.unwrap(); + 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.unwrap(); + txn.set_collection_compaction_bound(id, Some(frontier.into())) + .unwrap(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + } + subscriber.sync().await.unwrap(); + assert_eq!(subscriber.bounds()[&id], Antichain::from_elem(30.into())); + + let mut txn = writer.transaction().await.unwrap(); + txn.set_collection_compaction_bound(id, Some(40.into())) + .unwrap(); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + let mut txn = writer.transaction().await.unwrap(); + txn.delete_collection_metadata(BTreeSet::from([id])); + let _ = txn.get_and_commit_op_updates(); + let ts = txn.upper(); + txn.commit(ts).await.unwrap(); + subscriber.sync().await.unwrap(); + assert_eq!( + subscriber.bounds(), + &BTreeMap::from([(completed, Antichain::new())]) + ); + subscriber.sync().await.unwrap(); + assert_eq!( + subscriber.bounds(), + &BTreeMap::from([(completed, Antichain::new())]) + ); + assert!( + savepoint + .sync_to_current_updates() + .await + .unwrap() + .is_empty() + ); + assert_eq!(savepoint.snapshot().await.unwrap(), frozen_snapshot); + + subscriber.expire().await; + savepoint.expire().await; + writer.expire().await; + } +} diff --git a/src/adapter/src/coord/read_protection.rs b/src/adapter/src/coord/read_protection.rs index 32e555427c45b..90d0adc9c9df0 100644 --- a/src/adapter/src/coord/read_protection.rs +++ b/src/adapter/src/coord/read_protection.rs @@ -9,21 +9,134 @@ //! 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}; use mz_catalog::memory::objects::CatalogItem; use mz_repr::{GlobalId, Timestamp}; use mz_storage_client::storage_collections::CollectionFrontiers; +use timely::PartialOrder; use timely::progress::Antichain; use crate::AdapterError; use crate::catalog::Op; use crate::coord::Coordinator; +pub(super) const CATALOG_SUBSCRIPTION_INTERVAL: Duration = Duration::from_secs(1); + impl Coordinator { + /// Applies permission for surviving compute lifetimes and returns missing bound owners. + pub(super) async fn restore_compute_read_protection( + &mut self, + ) -> Result, AdapterError> { + if !self.catalog().state().catalog_read_protection_enabled() { + return Ok(BTreeSet::new()); + } + if let Some(subscriber) = &mut self.catalog_subscriber { + subscriber.sync().await?; + } + let state = self.catalog().state(); + let storage = &state.storage_metadata().collection_metadata; + let mut missing = BTreeSet::new(); + let bounds = state + .collection_compaction_bounds() + .iter() + .filter(|(id, _)| !storage.contains_key(id)) + .filter_map(|(&id, bound)| { + let bound = match &self.catalog_subscriber { + Some(subscriber) => match subscriber.bounds().get(&id) { + Some(bound) => bound, + None => { + // A drop must not block delivery to surviving lifetimes, nor + // release an installed bound in the frozen reconstruction. + missing.insert(id); + return None; + } + }, + None => bound, + }; + Some((id, bound.clone())) + }) + .collect::>(); + for (id, bound) in bounds { + self.controller + .compute + .apply_compaction_bound(id, bound) + .map_err(|error| AdapterError::Unstructured(error.into()))?; + } + Ok(missing) + } + + /// Authorizes installation of absent indexes while the caller holds their inputs. + /// + /// Installed indexes advance through publication, which accounts for their readers. + /// Recovery must restore valid maintained requirements before calling this method. + pub(super) async fn authorize_index_installation( + &mut self, + installations: BTreeMap>, + ) -> Result<(), AdapterError> { + let bounds = self.catalog().state().collection_compaction_bounds(); + let installations = installations + .into_iter() + .filter(|(id, _)| bounds.contains_key(id)) + .collect::>(); + if installations.is_empty() { + return Ok(()); + } + + if self.controller.read_only() { + // A savepoint write grants no permission. Keep input holds alive while + // following the writer, including when it is retaining older reader needs. + loop { + let missing = self.restore_compute_read_protection().await?; + if installations.keys().any(|id| missing.contains(id)) { + return Err(AdapterError::Unstructured(anyhow::anyhow!( + "index installation has no committed compaction permission: {missing:?}" + ))); + } + let subscriber = self + .catalog_subscriber + .as_ref() + .ok_or(AdapterError::ReadOnly)?; + if installations.iter().all(|(id, as_of)| { + subscriber + .bounds() + .get(id) + .is_some_and(|bound| PartialOrder::less_equal(as_of, bound)) + }) { + return Ok(()); + } + tokio::time::sleep(CATALOG_SUBSCRIPTION_INTERVAL).await; + } + } + + let bounds = installations + .into_iter() + .filter_map(|(id, frontier)| { + PartialOrder::less_than(&bounds[&id], &frontier).then(|| { + CollectionCompactionBound { + id, + frontier: frontier.into_option(), + } + }) + }) + .collect::>(); + if !bounds.is_empty() { + let ops = vec![Op::SetReadProtection { + requirements: Vec::new(), + bounds, + }]; + // Installation also runs inside an unfinished implication batch. Commit + // and apply this protection-only diff without checking whole-controller + // consistency until the enclosing batch has installed its other objects. + let (_builtin_updates, updates) = self.catalog_transact_inner(None, ops).await?; + Box::pin(self.apply_catalog_implications(None, updates)).await?; + } + 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() @@ -38,11 +151,13 @@ impl Coordinator { .active_collection_frontiers(); // Coordinator handlers serialize new creation holds with this publication. let compaction_frontiers = self.controller.storage_collections.compaction_frontiers(); + let compute_proposals = self.controller.compute.compaction_bound_proposals(); 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; @@ -95,12 +210,13 @@ 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. fn publication_candidates( requirements: &BTreeMap, bounds: &BTreeMap>, frontiers: &[CollectionFrontiers], compaction_frontiers: &BTreeMap>, + compute_proposals: &BTreeMap>, owns_durable_progress: impl Fn(GlobalId) -> bool, ) -> PublicationCandidates { let mut candidates = PublicationCandidates::default(); @@ -142,19 +258,31 @@ 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 Some(old_bound) = bounds.get(&id) 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()); + let mut bound = proposal.clone(); + // Controller proposals exclude only catalog permission. Early creation and + // execution holds remain authoritative alongside durable input requirements. + bound.extend(input_limits.get(&id).copied()); bound.join_assign(old_bound); - if let Some(bound_ts) = bound.as_option() { - match collection.implied_capability.as_option() { + 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 @@ -165,7 +293,7 @@ fn publication_candidates( } if &bound != old_bound { candidates.bounds.push(CollectionCompactionBound { - id: collection.id, + id, frontier: bound.into_option(), }); } @@ -231,6 +359,7 @@ mod tests { &bounds, &frontiers, &policy_frontiers(&frontiers), + &BTreeMap::new(), |_| true, ); let expected = expected.map(Timestamp::from); @@ -267,6 +396,7 @@ mod tests { &bounds, &[collection(output, Some(20), Some(0))], &BTreeMap::new(), + &BTreeMap::new(), |_| true, ); assert!(candidates.requirements.is_empty(), "completion is final"); @@ -306,6 +436,7 @@ mod tests { &bounds, &frontiers, &policy_frontiers(&frontiers), + &BTreeMap::new(), |id| id != pending, ); assert_eq!( @@ -357,6 +488,7 @@ mod tests { &bounds, &frontiers, &policy_frontiers(&frontiers), + &BTreeMap::new(), |_| true, ); let mut expected = vec![ @@ -388,6 +520,7 @@ mod tests { &BTreeMap::from([(input, frontier(10))]), &frontiers, &BTreeMap::from([(input, frontier(20))]), + &BTreeMap::new(), |_| false, ); assert_eq!( @@ -399,6 +532,92 @@ mod tests { ); } + #[mz_ore::test] + fn compute_only_publication_requires_governance_and_advancement() { + let index = GlobalId::User(1); + let ungoverned = GlobalId::User(2); + 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()), + (ungoverned, frontier(20)), + ]); + 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 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); @@ -423,6 +642,7 @@ mod tests { &bounds, &frontiers, &policy_frontiers(&frontiers), + &BTreeMap::new(), |_| false, ); assert_eq!( @@ -441,6 +661,7 @@ mod tests { &bounds, &frontiers, &policy_frontiers(&frontiers), + &BTreeMap::new(), |_| false, ); assert!(candidates.requirements.is_empty()); diff --git a/src/environmentd/src/lib.rs b/src/environmentd/src/lib.rs index 1f0ab1fa41eea..94fdde5e49f2b 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 catalog_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, + catalog_subscriber, timestamp_oracle_url: config.timestamp_oracle_url, unsafe_mode: config.unsafe_mode, all_features: config.all_features, diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index 43c336654e25e..5a32227ed4ee7 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -17,8 +17,10 @@ import json import time from collections.abc import Callable +from concurrent.futures import ThreadPoolExecutor from datetime import datetime from textwrap import dedent +from threading import Event import requests from psycopg.errors import ( @@ -28,7 +30,11 @@ 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 @@ -1702,6 +1708,317 @@ def sink_pending() -> bool: 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": {"a": 10}, "after": null} + {"before": null, "after": {"a": 20}} + """) + 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") + parser.add_argument("--publication-rounds", type=int, default=3) + parser.add_argument("--timeout-seconds", type=int, default=120) + 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.timeout_seconds <= 0 + ): + parser.error("counts, rounds, and timeout must be positive") + + 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 snapshot(count: int) -> dict: + [(catalog_id,)] = query( + "SELECT id FROM mz_objects WHERE name = 'mz_catalog_raw'" + ) + catalog = query(f"INSPECT SHARD '{catalog_id}'")[0][0] + response = requests.get( + f"http://localhost:{c.port('materialized', 6878)}/metrics", timeout=10 + ) + response.raise_for_status() + + def metrics(shard: str) -> dict[str, float]: + return { + line.split("{", 1)[0]: float(line.rsplit(" ", 1)[1]) + for line in response.text.splitlines() + if f'shard="{shard}"' in line + and line.split("{", 1)[0] + in ( + "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", + ) + } + + counters = metrics(catalog["shard_id"]) + assert "mz_persist_shard_diff_size_bytes" in counters, counters + assert "mz_persist_shard_cmd_succeeded" in counters, counters + retained = [] + # INSPECT every shard would dominate the measurement at large counts. + for i in sorted({0, count // 2, count - 1}): + [(item_id,)] = query( + f"SELECT id FROM mz_tables WHERE name = 'publication_{i}'" + ) + shard = query(f"INSPECT SHARD '{item_id}'")[0][0] + batches = [*shard["batches"], *shard["hollow_batches"].values()] + retained.append( + { + "collection": f"publication_{i}", + "since": shard["since"], + "upper": shard["upper"], + "persist_upper_minus_since_ms": shard["upper"][0] + - shard["since"][0], + "updates": sum(batch["len"] for batch in batches), + "metrics": metrics(shard["shard_id"]), + } + ) + return { + "monotonic_seconds": time.monotonic(), + "catalog_seqno": catalog["seqno"], + "catalog_counters": counters, + "retained_history_samples": retained, + } + + 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] + 1), + "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)) + created = 0 + for count in counts: + setup_start = time.monotonic() + for i in range(created, count): + query( + f"CREATE TABLE publication_{i} (a int) WITH (RETAIN HISTORY = FOR '1s')" + ) + query(f"INSERT INTO publication_{i} VALUES (0)") + created = count + setup_seconds = time.monotonic() - setup_start + stop = Event() + + def concurrent_ddl() -> list[float]: + latencies = [] + with c.sql_connection( + port=6877, + user="mz_system", + startup_params={"statement_timeout": f"{args.timeout_seconds}s"}, + ) as conn: + conn.autocommit = True + while not stop.is_set(): + start = time.monotonic() + conn.execute(b"CREATE VIEW publication_ddl_probe AS SELECT 1") + conn.execute(b"DROP VIEW publication_ddl_probe") + latencies.append(time.monotonic() - start) + return latencies + + before = snapshot(count) + rounds = [] + with ThreadPoolExecutor(max_workers=1) as executor: + ddl = executor.submit(concurrent_ddl) + try: + for revision in range(1, args.publication_rounds + 1): + write_start = time.monotonic() + for i in range(count): + query(f"UPDATE publication_{i} SET a = a + 1") + [(timestamp,)] = query( + "EXPLAIN TIMESTAMP AS JSON FOR SELECT * FROM publication_0" + ) + target = int( + json.loads(timestamp)["determination"]["oracle_read_ts"] + ) + publication_start = time.monotonic() + c.testdrive( + service="testdrive_no_reset", + input=dedent(f""" + $ set-sql-timeout duration={args.timeout_seconds}s force=true + > SELECT count(*) = {count} AND + bool_and(coalesce((r.data->'value'->>'frontier')::numeric > {target}, false)) + FROM mz_internal.mz_catalog_raw r + JOIN mz_internal.mz_object_global_ids g + ON r.data->'key'->'id'->>'User' = substring(g.global_id, 2) + JOIN mz_tables t ON t.id = g.id + WHERE r.data->>'kind' = 'CollectionCompactionBound' + AND t.name LIKE 'publication_%'; + true + """), + ) + rounds.append( + { + "revision": revision, + "target_timestamp": target, + "write_seconds": publication_start - write_start, + "publication_wait_seconds": time.monotonic() + - publication_start, + } + ) + finally: + stop.set() + ddl_latencies = ddl.result() + after = snapshot(count) + elapsed = after["monotonic_seconds"] - before["monotonic_seconds"] + deltas = { + metric: after["catalog_counters"][metric] + - before["catalog_counters"][metric] + for metric in ( + "mz_persist_shard_diff_size_bytes", + "mz_persist_shard_cmd_succeeded", + ) + } + assert ddl_latencies, "no concurrent DDL completed" + assert all(delta >= 0 for delta in deltas.values()), deltas + # Totals include SQL writes, DDL, observers, and builtin maintenance. + # The wait includes the configured retention window and retry overhead. + print( + json.dumps( + { + "workflow": "catalog-publication-measurement", + "collection_count": count, + "setup_seconds": setup_seconds, + "retention_ms": 1000, + "rounds": rounds, + "ddl_create_drop_seconds": ddl_latencies, + "publication_sample_seconds": elapsed, + "catalog_counter_deltas": deltas, + "catalog_diff_bytes_per_second": deltas[ + "mz_persist_shard_diff_size_bytes" + ] + / elapsed, + "before": before, + "after": after, + }, + sort_keys=True, + ), + flush=True, + ) c.down(destroy_volumes=True) @@ -2583,7 +2900,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): From 919daa5051fc024a9c4f439b0a5c904ac34633c5 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 06:15:20 +0000 Subject: [PATCH 31/41] test: Exercise catalog-governed index recovery Cover publication pause and restart for maintained indexes and committed-bound following during read-only prewarming, including a dropped sibling index. Complete storage trait implementations in compute test fixtures, preserve the upstream catalog-validation skip, and fix the frozen-reader rustdoc link. --- src/catalog/src/durable.rs | 5 +- src/compute-client/src/as_of_selection.rs | 10 ++ .../src/controller/compaction_tests.rs | 4 + src/testdrive/src/action/consistency.rs | 5 + test/0dt/mzcompose.py | 114 ++++++++++++++++++ test/restart/mzcompose.py | 58 +++++++++ 6 files changed, 194 insertions(+), 2 deletions(-) diff --git a/src/catalog/src/durable.rs b/src/catalog/src/durable.rs index 428cb54e61f18..e05c7b2ba4bb9 100644 --- a/src/catalog/src/durable.rs +++ b/src/catalog/src/durable.rs @@ -296,8 +296,9 @@ pub trait DurableCatalogState: ReadOnlyDurableCatalogState { /// Synchronizes an opened read-only catalog, then freezes its cached state for diagnostics. /// - /// Cached reads, including [`Self::snapshot`] and [`Self::transaction`], exclude subsequent - /// durable changes. Explicit snapshots supplied to dry-run transactions are unaffected. + /// Cached reads, including [`ReadOnlyDurableCatalogState::snapshot`] and + /// [`Self::transaction`], exclude subsequent durable changes. Explicit snapshots + /// supplied to dry-run transactions are unaffected. /// Buffered updates remain available to drain, and `current_upper` stays at the frozen upper. /// No migrations or ephemeral cleanup run. Nonempty commits still fail as read-only writes. /// Audit-log reads are unsupported after freezing because they bypass the cache. diff --git a/src/compute-client/src/as_of_selection.rs b/src/compute-client/src/as_of_selection.rs index 6d5cbb516f056..5b966f0893ac9 100644 --- a/src/compute-client/src/as_of_selection.rs +++ b/src/compute-client/src/as_of_selection.rs @@ -1043,6 +1043,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, diff --git a/src/compute-client/src/controller/compaction_tests.rs b/src/compute-client/src/controller/compaction_tests.rs index 64bc8e2eb8603..1dac98166a7dc 100644 --- a/src/compute-client/src/controller/compaction_tests.rs +++ b/src/compute-client/src/controller/compaction_tests.rs @@ -548,6 +548,10 @@ impl StorageCollections for EmptyStorage { 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, diff --git a/src/testdrive/src/action/consistency.rs b/src/testdrive/src/action/consistency.rs index 14165c285506a..76123cc30133d 100644 --- a/src/testdrive/src/action/consistency.rs +++ b/src/testdrive/src/action/consistency.rs @@ -243,6 +243,11 @@ async fn check_catalog_state(state: &State) -> Result<(), anyhow::Error> { storage_metadata: Option, } + // Catalog comparison requires --validate-catalog-store. + if state.materialize.catalog_config.is_none() { + return Ok(()); + } + let Some((memory_catalog, persist_client, storage)) = aligned_catalog_snapshot(state) .await .context("acquiring aligned catalog snapshot")? diff --git a/test/0dt/mzcompose.py b/test/0dt/mzcompose.py index f20f6f1957bcc..274c7dd3d3a93 100644 --- a/test/0dt/mzcompose.py +++ b/test/0dt/mzcompose.py @@ -1030,6 +1030,120 @@ def workflow_basic(c: Composition) -> None: """)) +def workflow_index_read_protection(c: Composition) -> None: + """Follow committed index permissions beyond a readonly catalog savepoint.""" + 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") + c.testdrive(dedent(""" + > 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'); + """)) + ids = dict(query(""" + 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 ('permission_keep_idx', 'permission_drop_idx') + """)) + + def bound_sql(global_id: str) -> str: + key = json.dumps({"User": int(global_id[1:])}) + return ( + "SELECT (data->'value'->>'frontier')::numeric " + "FROM mz_internal.mz_catalog_raw " + "WHERE data->>'kind' = 'CollectionCompactionBound' " + f"AND data->'key'->'id' = '{key}'::jsonb" + ) + + # Freezing publication before boot makes the savepoint permission observable. + query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '0s'") + frozen = {name: query(bound_sql(gid))[0][0] for name, gid in ids.items()} + c.up("mz_new") + c.await_mz_deployment_status(DeploymentStatus.READY_TO_PROMOTE, "mz_new") + 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 + """)) + for name, gid in ids.items(): + assert query(bound_sql(gid), "mz_new") == [(frozen[name],)] + query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '1s'") + + # 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"] + baseline = query(bound_sql(keep))[0][0] + deadline = time.monotonic() + 300 + while (committed := query(bound_sql(keep))[0][0]) <= baseline: + assert time.monotonic() < deadline, (keep, baseline, committed) + time.sleep(0.5) + assert committed > frozen["permission_keep_idx"] + c.testdrive(dedent(f""" + > SELECT read_frontier::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))} + """)) + # Read the writer after the readonly frontier to avoid a publication race. + [(read,)] = query( + "SELECT read_frontier::numeric FROM mz_internal.mz_frontiers " + f"WHERE object_id = '{keep}'", + "mz_new", + ) + assert read <= query(bound_sql(keep))[0][0] + assert query(bound_sql(keep), "mz_new") == [ + (frozen["permission_keep_idx"],) + ] + plan = query("EXPLAIN SELECT * FROM permission_keep", "mz_new")[0][0] + assert "permission_keep_idx" in plan, plan + + 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 5a32227ed4ee7..9da28221f4739 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -1071,6 +1071,64 @@ 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); + > INSERT INTO protected_index_input VALUES (1); + > 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::numeric, write_frontier::numeric " + f"FROM mz_internal.mz_frontiers WHERE object_id = '{index}'" + ) + birth = record("CollectionCompactionBound", index)["frontier"] + td(f""" + > SELECT (v->>'frontier')::numeric > {birth} + FROM ({index_bound_sql}) r(v); + true + > SELECT read_frontier > {birth} FROM ({index_frontier_sql}) f; + true + """) + query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '0s'") + # Observe the cap after stopping publication, then drive several controller + # progress reports past it. No peek is kept open to retain index history. + cap = record("CollectionCompactionBound", index)["frontier"] + 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 + 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"] From 398be083c868e491136d0d8d87058707e965cbab Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 07:25:29 +0000 Subject: [PATCH 32/41] catalog: Capture diagnostic snapshots at committed prefixes Pair an immutable coordinator snapshot with a validated durable upper and freeze the independent catalog reader at exactly that prefix. Full consistency checks no longer need a publication-free interval, and ordinary planning snapshots stay on their existing path. Correct index fixture feature settings, timestamp casts, Kafka record ordering, and inherited catalog Clippy findings. Production recovery and scaling verification remain in progress. --- src/adapter/src/catalog.rs | 16 ++++++ src/adapter/src/client.rs | 14 +++-- src/adapter/src/command.rs | 5 ++ src/adapter/src/coord.rs | 1 + src/adapter/src/coord/command_handler.rs | 10 ++++ src/catalog/src/durable.rs | 11 ++++ src/catalog/src/durable/persist.rs | 56 +++++++++++++++++-- src/catalog/src/durable/transaction.rs | 19 ++++++- src/catalog/tests/read-write.rs | 71 ++++++++++++++++++++++++ src/environmentd/src/http/catalog.rs | 18 +++++- src/testdrive/src/action/consistency.rs | 38 +++++++++---- test/0dt/mzcompose.py | 47 +++++++++++----- test/restart/mzcompose.py | 7 ++- 13 files changed, 272 insertions(+), 41 deletions(-) diff --git a/src/adapter/src/catalog.rs b/src/adapter/src/catalog.rs index d98a457ab716c..79c37de9792db 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -1320,6 +1320,22 @@ impl Catalog { Ok(CatalogDump::new(self.state.dump(None)?)) } + /// Returns a durable exclusive upper for a diagnostic snapshot of the writer. + /// + /// The caller must serialize capture with catalog mutations, not call this on + /// an older shared snapshot. Read-only reconstructions have no durable certificate. + pub(crate) async fn dump_upper(&self) -> Result, AdapterError> { + let mut storage = self.storage().await; + if storage.is_savepoint() || storage.is_read_only() { + return Ok(None); + } + let upper = storage.current_upper().await; + // Empty upper advancement can run off-loop. Unapplied content or fencing + // must not certify the in-memory catalog as the state at that newer upper. + storage.ensure_not_out_of_sync(upper).await?; + Ok(Some(upper)) + } + /// Checks the [`Catalog`]s internal consistency. /// /// Returns a JSON object describing the inconsistencies, if there are any. diff --git a/src/adapter/src/client.rs b/src/adapter/src/client.rs index 8b86043a8b6c7..61c77fe4db65a 100644 --- a/src/adapter/src/client.rs +++ b/src/adapter/src/client.rs @@ -1118,14 +1118,17 @@ impl SessionClient { .enable_extended_protocol_implicit_transaction() } - /// Dumps the catalog to a JSON string. + /// Dumps the catalog to JSON with its corresponding durable exclusive upper, if any. /// /// 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 { - // 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) + pub async fn dump_catalog( + &mut self, + ) -> Result<(CatalogDump, Option), AdapterError> { + let (snapshot, upper) = self + .send_without_session(|tx| Command::CatalogDumpSnapshot { tx }) + .await?; + Ok((snapshot.catalog.dump()?, upper)) } /// Checks the catalog for internal consistency, returning a JSON object describing the @@ -1347,6 +1350,7 @@ impl SessionClient { | Command::AuthenticateVerifySASLProof { .. } | Command::CheckRoleCanLogin { .. } | Command::CatalogSnapshot { .. } + | Command::CatalogDumpSnapshot { .. } | Command::Commit { .. } | Command::CancelRequest { .. } | Command::PrivilegedCancelRequest { .. } diff --git a/src/adapter/src/command.rs b/src/adapter/src/command.rs index 0d43ae267ce2c..b2a758109252a 100644 --- a/src/adapter/src/command.rs +++ b/src/adapter/src/command.rs @@ -91,6 +91,9 @@ pub enum Command { CatalogSnapshot { tx: oneshot::Sender, }, + CatalogDumpSnapshot { + tx: oneshot::Sender), AdapterError>>, + }, Startup { tx: oneshot::Sender>, @@ -482,6 +485,7 @@ impl Command { | Command::AuthenticateVerifySASLProof { .. } | Command::CheckRoleCanLogin { .. } | Command::CatalogSnapshot { .. } + | Command::CatalogDumpSnapshot { .. } | Command::PrivilegedCancelRequest { .. } | Command::GetWebhook { .. } | Command::Terminate { .. } @@ -527,6 +531,7 @@ impl Command { | Command::AuthenticateVerifySASLProof { .. } | Command::CheckRoleCanLogin { .. } | Command::CatalogSnapshot { .. } + | Command::CatalogDumpSnapshot { .. } | Command::PrivilegedCancelRequest { .. } | Command::GetWebhook { .. } | Command::Terminate { .. } diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index 2a4cde8281bda..dffa50a02df1a 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -495,6 +495,7 @@ impl Message { match self { Message::Command(_, msg) => match msg { Command::CatalogSnapshot { .. } => "command-catalog_snapshot", + Command::CatalogDumpSnapshot { .. } => "command-catalog_dump_snapshot", Command::Startup { .. } => "command-startup", Command::Execute { .. } => "command-execute", Command::Commit { .. } => "command-commit", diff --git a/src/adapter/src/coord/command_handler.rs b/src/adapter/src/coord/command_handler.rs index cd8f4ef020dba..e91a4de9d652a 100644 --- a/src/adapter/src/coord/command_handler.rs +++ b/src/adapter/src/coord/command_handler.rs @@ -359,6 +359,16 @@ impl Coordinator { }); } + Command::CatalogDumpSnapshot { tx } => { + // Capture the prefix while catalog mutations are serialized here. + // The immutable memory snapshot can be serialized off the coordinator. + let upper = self.catalog().dump_upper().await; + let snapshot = CatalogSnapshot { + catalog: self.owned_catalog(), + }; + let _ = tx.send(upper.map(|upper| (snapshot, upper))); + } + Command::CheckConsistency { tx } => { let _ = tx.send(self.check_consistency()); } diff --git a/src/catalog/src/durable.rs b/src/catalog/src/durable.rs index e05c7b2ba4bb9..9739468a2ae36 100644 --- a/src/catalog/src/durable.rs +++ b/src/catalog/src/durable.rs @@ -307,6 +307,17 @@ pub trait DurableCatalogState: ReadOnlyDurableCatalogState { /// synchronization observes fencing. Once frozen, the handle no longer observes fencing. async fn freeze(&mut self) -> Result<(), CatalogError>; + /// Freezes diagnostic cached reads at exactly `exclusive_upper`, excluding updates at that + /// timestamp and later. Otherwise has the same read-only semantics as [`Self::freeze`]. + /// + /// Open the read-only handle before capturing the upper. The requested upper must be at least + /// the handle's already-applied upper. Returns an error for backwards requests, non-read-only + /// handles, or fencing observed in the prefix. Already-observed fencing is never cleared. + /// Once frozen, only the same upper may be requested again. Fences beyond the prefix are not + /// observed. Waits for durable progress to reach the requested upper. + /// If this future is cancelled, discard the handle rather than reusing it. + async fn freeze_at(&mut self, exclusive_upper: Timestamp) -> Result<(), CatalogError>; + /// Marks the bootstrap process as complete. async fn mark_bootstrap_complete(&mut self); diff --git a/src/catalog/src/durable/persist.rs b/src/catalog/src/durable/persist.rs index c293c0b8ef603..3a13e318ed93c 100644 --- a/src/catalog/src/durable/persist.rs +++ b/src/catalog/src/durable/persist.rs @@ -535,16 +535,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()?; if self.mode == Mode::FrozenReadonly { @@ -571,6 +582,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 @@ -591,10 +605,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 frozen handle must never follow it again. + if terminal && update.ts >= target_upper { + continue; + } updates.entry(update.ts).or_default().push(update); } } @@ -1842,8 +1864,25 @@ impl DurableCatalogState for PersistCatalogState { } async fn freeze(&mut self) -> Result<(), CatalogError> { + if self.mode == Mode::FrozenReadonly { + return Ok(()); + } + let upper = self.current_upper().await; + self.freeze_at(upper).await + } + + async fn freeze_at(&mut self, exclusive_upper: Timestamp) -> Result<(), CatalogError> { match self.mode { - Mode::FrozenReadonly => return Ok(()), + Mode::FrozenReadonly if exclusive_upper == self.upper => { + self.fenceable_token.validate()?; + return Ok(()); + } + Mode::FrozenReadonly => { + return Err(DurableCatalogError::Internal( + "cannot change a frozen catalog's upper".into(), + ) + .into()); + } Mode::Readonly => {} Mode::Writable | Mode::Savepoint => { return Err(DurableCatalogError::Internal( @@ -1852,7 +1891,14 @@ impl DurableCatalogState for PersistCatalogState { .into()); } } - self.sync_to_current_upper().await?; + if exclusive_upper < self.upper { + return Err(DurableCatalogError::Internal(format!( + "cannot freeze catalog backwards from {} to {exclusive_upper}", + self.upper + )) + .into()); + } + self.sync_with_limit(exclusive_upper, true).await?; self.mode = Mode::FrozenReadonly; Ok(()) } diff --git a/src/catalog/src/durable/transaction.rs b/src/catalog/src/durable/transaction.rs index 9e84124e5dece..0c04b4fb44f0a 100644 --- a/src/catalog/src/durable/transaction.rs +++ b/src/catalog/src/durable/transaction.rs @@ -747,7 +747,7 @@ impl<'a> Transaction<'a> { // Repeated keys must compare the first ID with the final ID. previous .entry(key) - .or_insert(GlobalId::from(prev.global_id)); + .or_insert_with(|| GlobalId::from(prev.global_id)); } else { return Err(SqlCatalogError::FailedBuiltinSchemaMigration(format!( "{index_id}" @@ -758,7 +758,14 @@ impl<'a> Transaction<'a> { } let current = previous .keys() - .map(|key| GlobalId::from(self.introspection_sources.get(key).unwrap().global_id)) + .map(|key| { + GlobalId::from( + self.introspection_sources + .get(key) + .expect("updated mapping exists") + .global_id, + ) + }) .collect::>(); self.reconcile_index_compaction_bounds(previous.into_values().collect(), current) } @@ -2165,7 +2172,13 @@ impl<'a> Transaction<'a> { let current = mappings .iter() .filter(|(k, _)| k.object_type == CatalogItemType::Index) - .map(|(_, v)| GlobalId::from(v.as_ref().unwrap().global_id)) + .map(|(_, v)| { + GlobalId::from( + v.as_ref() + .expect("only mapping insertions are staged") + .global_id, + ) + }) .collect::>(); let previous = self.system_gid_mapping.set_many(mappings, self.op_id)?; self.reconcile_index_compaction_bounds( diff --git a/src/catalog/tests/read-write.rs b/src/catalog/tests/read-write.rs index 003394c4b4ee3..fd6b81f0d9b96 100644 --- a/src/catalog/tests/read-write.rs +++ b/src/catalog/tests/read-write.rs @@ -812,6 +812,9 @@ async fn test_non_writer_commits(state_builder: TestCatalogStateBuilder) { assert!(writer_state.freeze().await.is_err()); assert!(savepoint_state.freeze().await.is_err()); + let upper = writer_state.current_upper().await; + assert!(writer_state.freeze_at(upper).await.is_err()); + assert!(savepoint_state.freeze_at(upper).await.is_err()); // Commit write with writer. let role_name = "joe"; @@ -913,6 +916,26 @@ async fn test_persist_freeze_read_only() { .open_read_only(&test_bootstrap_args()) .await .unwrap(); + let mut exact = builder + .clone() + .unwrap_build() + .await + .open_read_only(&test_bootstrap_args()) + .await + .unwrap(); + let mut before_fence = builder + .clone() + .unwrap_build() + .await + .open_read_only(&test_bootstrap_args()) + .await + .unwrap(); + let opened_upper = exact.current_upper().await; + let err = exact + .freeze_at(opened_upper.saturating_sub(1)) + .await + .unwrap_err(); + assert!(err.to_string().contains("backwards"), "{err}"); // Commit after opening the diagnostic handle so freezing must synchronize it. let input = GlobalId::User(1000); @@ -978,6 +1001,38 @@ async fn test_persist_freeze_read_only() { let later_upper = writer.current_upper().await; assert!(later_upper > frozen_upper); + // Both commits are durable before this reader synchronizes. The exclusive boundary must + // exclude the bound changes and deletion even if listen delivers them in the same batch. + exact.freeze_at(expected_upper).await.unwrap(); + assert!(exact.is_read_only()); + assert!(!exact.is_savepoint()); + assert_eq!(exact.current_upper().await, expected_upper); + assert_eq!(exact.snapshot().await.unwrap(), expected); + assert_eq!( + exact.sync_to_current_updates().await.unwrap(), + initial_updates + ); + assert!(exact.freeze_at(later_upper).await.is_err()); + assert!(exact.freeze_at(opened_upper).await.is_err()); + for _ in 0..2 { + exact.freeze_at(expected_upper).await.unwrap(); + exact.freeze().await.unwrap(); + assert_eq!(exact.snapshot().await.unwrap(), expected); + assert_eq!(exact.current_upper().await, expected_upper); + assert!(exact.sync_updates(later_upper).await.unwrap().is_empty()); + let txn = exact.transaction().await.unwrap(); + assert_eq!(txn.current_snapshot(), expected); + txn.commit(expected_upper).await.unwrap(); + } + let mut txn = exact.transaction().await.unwrap(); + txn.get_and_increment_id(USER_ITEM_ALLOC_KEY.into()) + .unwrap(); + let _ = txn.get_and_commit_op_updates(); + assert!(matches!( + txn.commit(expected_upper).await, + Err(CatalogError::Durable(DurableCatalogError::NotWritable(_))) + )); + frozen.freeze().await.unwrap(); assert_eq!(frozen.snapshot().await.unwrap(), expected); assert_eq!(frozen.get_next_user_item_id().await.unwrap(), initial_id); @@ -1016,12 +1071,28 @@ async fn test_persist_freeze_read_only() { .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await .unwrap(); + let fenced_upper = live.current_upper().await; + assert!(matches!( + live.freeze_at(fenced_upper).await, + Err(CatalogError::Durable(DurableCatalogError::Fence(_))) + )); assert!(matches!( live.snapshot().await, Err(CatalogError::Durable(DurableCatalogError::Fence(_))) )); + assert!(matches!( + live.freeze_at(fenced_upper).await, + Err(CatalogError::Durable(DurableCatalogError::Fence(_))) + )); + // A fence outside the captured prefix does not invalidate diagnostic data. + before_fence.freeze_at(expected_upper).await.unwrap(); + assert_eq!(before_fence.snapshot().await.unwrap(), expected); + assert_eq!(before_fence.current_upper().await, expected_upper); + assert_eq!(exact.snapshot().await.unwrap(), expected); assert_eq!(frozen.snapshot().await.unwrap(), expected); next_writer.expire().await; + before_fence.expire().await; + exact.expire().await; frozen.expire().await; live.expire().await; writer.expire().await; diff --git a/src/environmentd/src/http/catalog.rs b/src/environmentd/src/http/catalog.rs index b346a1cecb713..742134b54332f 100644 --- a/src/environmentd/src/http/catalog.rs +++ b/src/environmentd/src/http/catalog.rs @@ -19,8 +19,22 @@ 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, upper)) => { + let mut headers = http::HeaderMap::new(); + if let Some(upper) = upper { + headers.insert( + "mz-catalog-upper", + http::HeaderValue::from_str(&upper.to_string()) + .expect("timestamp is a valid header value"), + ); + } + Ok(( + TypedHeader(ContentType::json()), + headers, + dump.into_string(), + )) + } Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), } } diff --git a/src/testdrive/src/action/consistency.rs b/src/testdrive/src/action/consistency.rs index 76123cc30133d..05d92a2632a35 100644 --- a/src/testdrive/src/action/consistency.rs +++ b/src/testdrive/src/action/consistency.rs @@ -25,6 +25,7 @@ use mz_ore::now::SYSTEM_TIME; use mz_ore::retry::{Retry, RetryResult}; use mz_persist_client::{PersistClient, PersistLocation, ShardId}; use mz_postgres_util::{query_one, sql}; +use mz_repr::Timestamp; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; @@ -187,22 +188,32 @@ async fn check_coordinator(state: &State) -> Result<(), anyhow::Error> { } } -/// Acquires a frozen durable handle between two identical full memory dumps. -/// Reconstruction must happen afterward so its cost does not widen the alignment interval. +/// Acquires a frozen durable handle matching a full memory dump. async fn aligned_catalog_snapshot( state: &State, ) -> Result)>, anyhow::Error> { - async fn memory_dump(state: &State) -> Result { - reqwest::get(format!( + async fn memory_dump(state: &State) -> Result<(String, Option), anyhow::Error> { + let response = reqwest::get(format!( "http://{}/api/catalog/dump", state.materialize.internal_http_addr, )) .await .context("GET catalog")? - .error_for_status()? - .text() - .await - .context("deserialize catalog") + .error_for_status()?; + let upper = response + .headers() + .get("mz-catalog-upper") + .map(|value| { + value + .to_str() + .context("catalog upper header")? + .parse::() + .map(Timestamp::from) + .context("catalog upper timestamp") + }) + .transpose()?; + let dump = response.text().await.context("deserialize catalog")?; + Ok((dump, upper)) } loop { @@ -210,9 +221,16 @@ async fn aligned_catalog_snapshot( return Ok(None); }; let capture = async { - let before = memory_dump(state).await?; + let (before, upper) = memory_dump(state).await?; + if let Some(upper) = upper { + // The reader was opened before capture. Its continuous Listen preserves + // the prefix even if publication advances while the dump is serialized. + storage.freeze_at(upper).await?; + return Ok(Some(before)); + } + // Servers without a durable prefix certificate require a stable window. storage.freeze().await?; - let after = memory_dump(state).await?; + let (after, _) = memory_dump(state).await?; Ok::<_, anyhow::Error>((before == after).then_some(before)) } .await; diff --git a/test/0dt/mzcompose.py b/test/0dt/mzcompose.py index 274c7dd3d3a93..b5e22ed91c611 100644 --- a/test/0dt/mzcompose.py +++ b/test/0dt/mzcompose.py @@ -20,6 +20,7 @@ import psycopg import pymysql +import requests from psycopg.errors import OperationalError from psycopg.sql import SQL, Identifier @@ -1047,6 +1048,7 @@ def query(sql: str, service: str = "mz_old") -> list[tuple]: 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") c.testdrive(dedent(""" > CREATE TABLE permission_input (a int); > INSERT INTO permission_input VALUES (1); @@ -1074,9 +1076,23 @@ def bound_sql(global_id: str) -> str: # Freezing publication before boot makes the savepoint permission observable. query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '0s'") - frozen = {name: query(bound_sql(gid))[0][0] for name, gid in ids.items()} c.up("mz_new") c.await_mz_deployment_status(DeploymentStatus.READY_TO_PROMOTE, "mz_new") + + 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[gid]["elements"][0] for name, gid in ids.items()} + + frozen = frozen_bounds() with c.override( Testdrive( materialize_url="postgres://materialize@mz_new:6875", @@ -1105,8 +1121,6 @@ def bound_sql(global_id: str) -> str: > SELECT * FROM permission_drop; 3 """)) - for name, gid in ids.items(): - assert query(bound_sql(gid), "mz_new") == [(frozen[name],)] query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '1s'") # The subscriber follows permissions, not SQL schema changes. A removed @@ -1123,25 +1137,32 @@ def bound_sql(global_id: str) -> str: time.sleep(0.5) assert committed > frozen["permission_keep_idx"] c.testdrive(dedent(f""" - > SELECT read_frontier::numeric >= {committed} + > 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))} """)) - # Read the writer after the readonly frontier to avoid a publication race. - [(read,)] = query( - "SELECT read_frontier::numeric FROM mz_internal.mz_frontiers " + 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 = '0s'") + 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", ) - assert read <= query(bound_sql(keep))[0][0] - assert query(bound_sql(keep), "mz_new") == [ - (frozen["permission_keep_idx"],) - ] - plan = query("EXPLAIN SELECT * FROM permission_keep", "mz_new")[0][0] - assert "permission_keep_idx" in plan, plan + 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 + """)) def workflow_kafka_source_rehydration(c: Composition) -> None: diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index 9da28221f4739..3cf2991985fce 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -1036,6 +1036,7 @@ def inspect(global_id: str) -> dict: "enable_catalog_read_protection": "true", "enable_expression_cache": "false", "enable_logical_compaction_window": "true", + "enable_index_options": "true", # Small batches must exercise real blob compaction, not inline writes. "persist_inline_writes_single_max_bytes": "0", "persist_compaction_heuristic_min_inputs": "2", @@ -1080,7 +1081,7 @@ def inspect(global_id: str) -> dict: index = gid("protected_index") index_bound_sql = record_sql("CollectionCompactionBound", index) index_frontier_sql = ( - "SELECT read_frontier::numeric, write_frontier::numeric " + "SELECT read_frontier::text::numeric, write_frontier::text::numeric " f"FROM mz_internal.mz_frontiers WHERE object_id = '{index}'" ) birth = record("CollectionCompactionBound", index)["frontier"] @@ -1666,8 +1667,8 @@ def sink_pending() -> bool: two $ kafka-verify-data format=json sink=materialize.public.protected_sink key=false sort-messages=true {"before": null, "after": {"a": 1}} - {"before": {"a": 1}, "after": null} {"before": null, "after": {"a": 2}} + {"before": {"a": 1}, "after": null} $ kafka-ingest format=bytes topic=protected-source three > SELECT * FROM protected_late_export; @@ -1855,8 +1856,8 @@ def fresh_sink_during_lag() -> bool: 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": {"a": 10}, "after": null} {"before": null, "after": {"a": 20}} + {"before": {"a": 10}, "after": null} """) await_state( "fresh sink requirement advancing only after durable output", From 1f62dd498bf138f50c7636998490a9c4dc03fba1 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 08:56:37 +0000 Subject: [PATCH 33/41] test: Observe publication through committed catalog snapshots The scaling harness reads bounds from the catalog dump instead of a query over mz_catalog_raw, which measured that maintained collection rather than publication. Batch sweep statements over one connection, and give the recovery demo explicit reader lease expiry plus merge-driving remap traffic. Complete adapter test diagnostics flagged by Clippy. --- src/adapter/src/catalog.rs | 57 ++++--- src/adapter/src/catalog/state.rs | 3 +- src/adapter/src/client.rs | 2 + src/adapter/src/command.rs | 2 +- src/adapter/src/coord/catalog_subscriber.rs | 97 +++++++++--- test/restart/mzcompose.py | 161 ++++++++++++++------ 6 files changed, 233 insertions(+), 89 deletions(-) diff --git a/src/adapter/src/catalog.rs b/src/adapter/src/catalog.rs index 79c37de9792db..f0bbab8291fc5 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -2716,44 +2716,55 @@ mod tests { .with_default_deploy_generation() .build() .await - .unwrap() + .expect("failed to build seed catalog") .open(SYSTEM_TIME().into(), &bootstrap) .await - .unwrap(); - let _ = seed.sync_to_current_updates().await.unwrap(); - let mut tx = seed.transaction().await.unwrap(); + .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()), ])) - .unwrap(); + .expect("failed to insert seed collection metadata"); tx.set_collection_compaction_bound(input, Some(Timestamp::from(10))) - .unwrap(); + .expect("failed to set seed compaction bound"); tx.set_maintained_read_requirement( output, BTreeSet::from([input]), Some(Timestamp::from(10)), ) - .unwrap(); + .expect("failed to set seed read requirement"); let _ = tx.get_and_commit_op_updates(); let ts = tx.upper(); - tx.commit(ts).await.unwrap(); + 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 - .unwrap(); - let expected = writer.state().dump(None).unwrap(); + .expect("failed to open writer catalog"); + let expected = writer + .state() + .dump(None) + .expect("failed to dump initial writer catalog"); let environment_id = writer.state().config.environment_id.clone(); let mut frozen = TestCatalogStateBuilder::new(persist.clone()) .with_organization_id(organization) .build() .await - .unwrap() + .expect("failed to build readonly catalog") .open_read_only(&bootstrap) .await - .unwrap(); - frozen.freeze().await.unwrap(); + .expect("failed to open readonly catalog"); + frozen.freeze().await.expect("failed to freeze catalog"); let ts = writer.current_upper().await; writer @@ -2774,8 +2785,14 @@ mod tests { }], ) .await - .unwrap(); - assert_ne!(expected, writer.state().dump(None).unwrap()); + .expect("failed to update writer read protection"); + assert_ne!( + expected, + writer + .state() + .dump(None) + .expect("failed to dump updated writer catalog") + ); let reconstructed = Catalog::open_debug_catalog_inner( persist, @@ -2789,8 +2806,14 @@ mod tests { None, ) .await - .unwrap(); - assert_eq!(expected, reconstructed.state().dump(None).unwrap()); + .expect("failed to reconstruct frozen catalog"); + assert_eq!( + expected, + reconstructed + .state() + .dump(None) + .expect("failed to dump reconstructed catalog") + ); reconstructed.expire().await; writer.expire().await; } diff --git a/src/adapter/src/catalog/state.rs b/src/adapter/src/catalog/state.rs index 7aa4093bbb391..d2345bbd14a3b 100644 --- a/src/adapter/src/catalog/state.rs +++ b/src/adapter/src/catalog/state.rs @@ -3156,6 +3156,7 @@ mod tests { 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); @@ -3185,7 +3186,7 @@ mod tests { .apply_updates( vec![StateUpdate { kind, - ts: Timestamp::from(u64::try_from(i).unwrap() + 1), + ts: Timestamp::from(u64::cast_from(i) + 1), diff: StateDiff::Addition, }], &mut LocalExpressionCache::Closed, diff --git a/src/adapter/src/client.rs b/src/adapter/src/client.rs index 61c77fe4db65a..bbda37ec55c40 100644 --- a/src/adapter/src/client.rs +++ b/src/adapter/src/client.rs @@ -1120,6 +1120,8 @@ impl SessionClient { /// Dumps the catalog to JSON with its corresponding durable exclusive upper, if any. /// + /// The upper is absent for read-only catalog reconstructions. + /// /// No authorization is performed, so access to this function must be limited to internal /// servers or superusers. pub async fn dump_catalog( diff --git a/src/adapter/src/command.rs b/src/adapter/src/command.rs index b2a758109252a..e6c30c7b2427e 100644 --- a/src/adapter/src/command.rs +++ b/src/adapter/src/command.rs @@ -637,7 +637,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/catalog_subscriber.rs b/src/adapter/src/coord/catalog_subscriber.rs index c129b452bbe7d..7bfa55df9d23b 100644 --- a/src/adapter/src/coord/catalog_subscriber.rs +++ b/src/adapter/src/coord/catalog_subscriber.rs @@ -98,24 +98,32 @@ mod tests { .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); - let _ = writer.sync_to_current_updates().await.unwrap(); + .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.unwrap(); + 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()), ])) - .unwrap(); + .expect("failed to insert collection metadata"); txn.set_collection_compaction_bound(id, Some(10.into())) - .unwrap(); + .expect("failed to set initial compaction bound"); txn.set_collection_compaction_bound(completed, None) - .unwrap(); + .expect("failed to set completed compaction bound"); let _ = txn.get_and_commit_op_updates(); let ts = txn.upper(); - txn.commit(ts).await.unwrap(); + txn.commit(ts) + .await + .expect("failed to commit initial bounds transaction"); let mut savepoint = builder .clone() @@ -123,18 +131,27 @@ mod tests { .await .open_savepoint(SYSTEM_TIME().into(), &test_bootstrap_args()) .await - .unwrap(); - let _ = savepoint.sync_to_current_updates().await.unwrap(); - let frozen_snapshot = savepoint.snapshot().await.unwrap(); + .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 - .unwrap(); + .expect("failed to open subscriber catalog"); let mut subscriber = CatalogSubscriber::new(reader); assert!(subscriber.bounds().is_empty()); - subscriber.sync().await.unwrap(); + subscriber + .sync() + .await + .expect("failed to sync initial subscriber bounds"); assert_eq!( subscriber.bounds(), &BTreeMap::from([ @@ -145,33 +162,57 @@ mod tests { // Multiple replacements between syncs must not retract the final addition. for frontier in [20, 30] { - let mut txn = writer.transaction().await.unwrap(); + let mut txn = writer + .transaction() + .await + .expect("failed to start bound replacement transaction"); txn.set_collection_compaction_bound(id, Some(frontier.into())) - .unwrap(); + .expect("failed to replace compaction bound"); let _ = txn.get_and_commit_op_updates(); let ts = txn.upper(); - txn.commit(ts).await.unwrap(); + txn.commit(ts) + .await + .expect("failed to commit bound replacement transaction"); } - subscriber.sync().await.unwrap(); + subscriber + .sync() + .await + .expect("failed to sync replaced subscriber bounds"); assert_eq!(subscriber.bounds()[&id], Antichain::from_elem(30.into())); - let mut txn = writer.transaction().await.unwrap(); + let mut txn = writer + .transaction() + .await + .expect("failed to start pre-deletion bound transaction"); txn.set_collection_compaction_bound(id, Some(40.into())) - .unwrap(); + .expect("failed to advance compaction bound before deletion"); let _ = txn.get_and_commit_op_updates(); let ts = txn.upper(); - txn.commit(ts).await.unwrap(); - let mut txn = writer.transaction().await.unwrap(); + 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.unwrap(); - subscriber.sync().await.unwrap(); + txn.commit(ts) + .await + .expect("failed to commit collection deletion transaction"); + subscriber + .sync() + .await + .expect("failed to sync subscriber after collection deletion"); assert_eq!( subscriber.bounds(), &BTreeMap::from([(completed, Antichain::new())]) ); - subscriber.sync().await.unwrap(); + subscriber + .sync() + .await + .expect("failed to sync subscriber without new updates"); assert_eq!( subscriber.bounds(), &BTreeMap::from([(completed, Antichain::new())]) @@ -180,10 +221,16 @@ mod tests { savepoint .sync_to_current_updates() .await - .unwrap() + .expect("failed to sync savepoint after writer updates") .is_empty() ); - assert_eq!(savepoint.snapshot().await.unwrap(), frozen_snapshot); + assert_eq!( + savepoint + .snapshot() + .await + .expect("failed to snapshot savepoint after writer updates"), + frozen_snapshot + ); subscriber.expire().await; savepoint.expire().await; diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index 3cf2991985fce..d0a8ddcb2db11 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -1037,6 +1037,8 @@ def inspect(global_id: str) -> dict: "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", @@ -1192,10 +1194,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(): @@ -1203,6 +1210,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): @@ -1677,13 +1686,42 @@ def sink_pending() -> bool: two """) await_state( - "source recovery requirements advancing and remap physically compacting", + "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 compacted_past(inspect(source), 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) @@ -1737,31 +1775,16 @@ def sink_pending() -> bool: lambda: record(bound_kind, sink_input)["frontier"] > altered["frontier"], ) assert storage_requirement(sink, [sink, sink_next]) == altered + verify_source_rows() td(""" - > SELECT * FROM protected_export; - one - three - two - > SELECT * FROM protected_late_export; - one - three - two > 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 - > SELECT * FROM protected_export; - four - one - three - two - > SELECT * FROM protected_late_export; - four - one - three - two """) + source_rows.append("four") + verify_source_rows() await_state( "altered sink advancing after recovery", lambda: storage_advanced(sink, [sink, sink_next], altered["frontier"]) @@ -1875,6 +1898,7 @@ def workflow_catalog_publication_measurement( """Measure the production catalog path. Requires disposable composition volumes.""" parser.add_argument("--collection-counts", default="100,1000,10000") 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) args = parser.parse_args() try: @@ -1885,9 +1909,10 @@ def workflow_catalog_publication_measurement( not counts or counts[0] <= 0 or args.publication_rounds <= 0 + or args.publication_interval_ms <= 0 or args.timeout_seconds <= 0 ): - parser.error("counts, rounds, and timeout must be positive") + parser.error("counts, rounds, interval, and timeout must be positive") def query(sql: str) -> list[tuple]: with c.sql_connection( @@ -1898,6 +1923,18 @@ def query(sql: str) -> list[tuple]: 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 snapshot(count: int) -> dict: [(catalog_id,)] = query( "SELECT id FROM mz_objects WHERE name = 'mz_catalog_raw'" @@ -1952,6 +1989,14 @@ def metrics(shard: str) -> dict[str, float]: "retained_history_samples": retained, } + def catalog_bounds() -> dict: + response = requests.get( + f"http://localhost:{c.port('materialized', 6878)}/api/catalog/dump", + timeout=args.timeout_seconds, + ) + response.raise_for_status() + return response.json()["collection_compaction_bounds"] + c.down(destroy_volumes=True) with c.override( Materialized( @@ -1961,6 +2006,7 @@ def metrics(shard: str) -> dict[str, float]: "enable_logical_compaction_window": "true", "max_tables": str(counts[-1]), "max_objects_per_schema": str(counts[-1] + 1), + "catalog_read_protection_publish_interval": f"{args.publication_interval_ms}ms", "persist_inline_writes_single_max_bytes": "0", "persist_compaction_heuristic_min_inputs": "2", } @@ -1975,13 +2021,25 @@ def metrics(shard: str) -> dict[str, float]: created = 0 for count in counts: setup_start = time.monotonic() - for i in range(created, count): - query( - f"CREATE TABLE publication_{i} (a int) WITH (RETAIN HISTORY = FOR '1s')" - ) - query(f"INSERT INTO publication_{i} VALUES (0)") + 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)", + ) + ] + ) created = count setup_seconds = time.monotonic() - setup_start + collection_gids = [gid for (gid,) in query(""" + SELECT 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(collection_gids) == count, (len(collection_gids), count) stop = Event() def concurrent_ddl() -> list[float]: @@ -2006,8 +2064,12 @@ def concurrent_ddl() -> list[float]: try: for revision in range(1, args.publication_rounds + 1): write_start = time.monotonic() - for i in range(count): - query(f"UPDATE publication_{i} SET a = a + 1") + execute_each( + [ + f"UPDATE publication_{i} SET a = a + 1" + for i in range(count) + ] + ) [(timestamp,)] = query( "EXPLAIN TIMESTAMP AS JSON FOR SELECT * FROM publication_0" ) @@ -2015,21 +2077,29 @@ def concurrent_ddl() -> list[float]: json.loads(timestamp)["determination"]["oracle_read_ts"] ) publication_start = time.monotonic() - c.testdrive( - service="testdrive_no_reset", - input=dedent(f""" - $ set-sql-timeout duration={args.timeout_seconds}s force=true - > SELECT count(*) = {count} AND - bool_and(coalesce((r.data->'value'->>'frontier')::numeric > {target}, false)) - FROM mz_internal.mz_catalog_raw r - JOIN mz_internal.mz_object_global_ids g - ON r.data->'key'->'id'->>'User' = substring(g.global_id, 2) - JOIN mz_tables t ON t.id = g.id - WHERE r.data->>'kind' = 'CollectionCompactionBound' - AND t.name LIKE 'publication_%'; - true - """), - ) + # Observe the writer's own committed catalog snapshot. Querying + # mz_catalog_raw would measure that maintained collection's + # ingestion of catalog history instead of publication. + while True: + bounds = catalog_bounds() + published = [ + bounds[gid]["elements"] + for gid in collection_gids[:count] + ] + if all( + elements and elements[0] > target + for elements in published + ): + break + if ( + time.monotonic() - publication_start + > args.timeout_seconds + ): + raise UIError( + f"publication for {count} collections did not pass " + f"{target} within {args.timeout_seconds}s" + ) + time.sleep(0.5) rounds.append( { "revision": revision, @@ -2062,6 +2132,7 @@ def concurrent_ddl() -> list[float]: "workflow": "catalog-publication-measurement", "collection_count": count, "setup_seconds": setup_seconds, + "publication_interval_ms": args.publication_interval_ms, "retention_ms": 1000, "rounds": rounds, "ddl_create_drop_seconds": ddl_latencies, From 68937401cdf8590b64c48097530f96412d0b4e65 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 18:13:53 +0000 Subject: [PATCH 34/41] doc: Record maintained compute permission evidence and prewarming liveness question --- .../design/20260903_decoupled_coordination.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index ba0cd2d0009ec..f10aafefe3eab 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -699,3 +699,26 @@ 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. From 2e489e8fa64558e91818e6c13d07f53f7d700fdd Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 20:55:42 +0000 Subject: [PATCH 35/41] doc: Consolidate decoupled coordination guidance Separate the agreed design, selected implementation decisions, and historical handoffs so implementation sessions need not treat the log as a second specification. Preserve the existing log entries in a sibling file and keep workflow guidance in the prompt. Steer work toward prewarming progress, representative publication costs, and independent maintained lifecycle execution. Keep the affected cluster progress and promotion-readiness questions explicit. --- .../design/20260903_decoupled_coordination.md | 489 ++---------------- .../20260903_decoupled_coordination_log.md | 421 +++++++++++++++ .../20260903_decoupled_coordination_prompt.md | 88 ++-- 3 files changed, 516 insertions(+), 482 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 f10aafefe3eab..721198f606050 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, @@ -156,7 +159,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 +167,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 +179,33 @@ 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. 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. Any necessary advancement goes through catalog authorization, not a +startup-specific writer protocol or a reconstruction exemption. A local savepoint +write grants no compaction permission. + ## 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 @@ -298,427 +310,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. - -### 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. +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..8e2fbab983311 --- /dev/null +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -0,0 +1,421 @@ +# 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. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index 3292a48ec3482..6d643aab63282 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -7,55 +7,63 @@ 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: measure publication cost at representative collection - counts, including DDL latency and retained history. Batched protection staging - removes the per-record pending-update scans, but does not establish end-to-end - scalability. Do not infer scalability from the small recovery demonstration or - make a general catalog redesign a prerequisite. -- 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, but recovery liveness and performance acceptance remain +open. Bring catalog-following work forward where it resolves those gaps rather +than waiting for the milestone label to change. + +- Prewarming: a blocked cluster should not prevent unrelated, otherwise-ready + work from prewarming. Preserve strict permission and dependency ordering, and + handle concurrent drops during bootstrap. Bring the affected cluster's eventual + progress and promotion-readiness behavior to Aljoscha for a decision. Partial + prewarming alone does not settle those questions. +- Publication scaling: finish representative measurements using the existing + workflow, including DDL latency, subscriber lag, and retained history. Observing + the writer's committed snapshot isolates publication latency but does not remove + the cost to real catalog consumers. Distinguish publication overhead from workload + and observer costs. Do not make a general catalog redesign a prerequisite. +- Ownership transition: prioritize a production maintained-lifecycle subscriber + over more standalone APIs. The bound-only 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. 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 -rather than adding machinery to preserve them. +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. @@ -81,7 +89,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 @@ -101,3 +110,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 60707652ba43b841d4730df31b2ded3daabb06e1 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 20:59:48 +0000 Subject: [PATCH 36/41] doc: Settle permission as an installation constraint and steer cleanup Committed compaction permission is a hard upper constraint on installation as_of, applied in as-of selection, never a wait on the writer. This resolves the read-only prewarming liveness question without per-cluster isolation. Fold the compaction bounds chapter into Boundary contracts, cut duplicated cost and hold-change text, and state that a durable record must carry non-derivable information and that publication scales with changed records. Reorder the prompt's steering toward removing machinery that grew beyond the design: the wait loop, index birth records and whole-catalog scans, test-only catalog and coordinator surface, and O(catalog) publication. Add standing rules and record the review in the log. --- .../design/20260903_decoupled_coordination.md | 61 ++++++++------- .../20260903_decoupled_coordination_log.md | 20 +++++ .../20260903_decoupled_coordination_prompt.md | 76 ++++++++++++++----- 3 files changed, 106 insertions(+), 51 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination.md b/doc/developer/design/20260903_decoupled_coordination.md index 721198f606050..020d3f7ed1091 100644 --- a/doc/developer/design/20260903_decoupled_coordination.md +++ b/doc/developer/design/20260903_decoupled_coordination.md @@ -55,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 @@ -73,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 @@ -116,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 @@ -126,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 @@ -196,15 +195,16 @@ with input compaction permission. A saved index compaction bound is not a historical reconstruction guarantee. Recovery selects installation frontiers from actual readability and all valid read -requirements. Reconciliation that discards existing history requires committed -permission, even when performed as part of reconstruction. +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. Any necessary advancement goes through catalog authorization, not a -startup-specific writer protocol or a reconstruction exemption. A local savepoint -write grants no compaction permission. +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 @@ -278,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 diff --git a/doc/developer/design/20260903_decoupled_coordination_log.md b/doc/developer/design/20260903_decoupled_coordination_log.md index 8e2fbab983311..2dd212a6011ae 100644 --- a/doc/developer/design/20260903_decoupled_coordination_log.md +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -419,3 +419,23 @@ 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. diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index 6d643aab63282..a1def9b6c98be 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -22,30 +22,66 @@ 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, but recovery liveness and performance acceptance remain -open. Bring catalog-following work forward where it resolves those gaps rather -than waiting for the milestone label to change. - -- Prewarming: a blocked cluster should not prevent unrelated, otherwise-ready - work from prewarming. Preserve strict permission and dependency ordering, and - handle concurrent drops during bootstrap. Bring the affected cluster's eventual - progress and promotion-readiness behavior to Aljoscha for a decision. Partial - prewarming alone does not settle those questions. -- Publication scaling: finish representative measurements using the existing - workflow, including DDL latency, subscriber lag, and retained history. Observing - the writer's committed snapshot isolates publication latency but does not remove - the cost to real catalog consumers. Distinguish publication overhead from workload - and observer costs. Do not make a general catalog redesign a prerequisite. -- Ownership transition: prioritize a production maintained-lifecycle subscriber - over more standalone APIs. The bound-only 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. An MV's - requirement is a separate sequencer op today. These are transitional dependencies, - not evidence that the current single-owner path fails. +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. + +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. Cleanup: one `Op` for read protection instead of three, remove + `remove_staged_compaction_bound`, and add a targeted test for the switch from + `implied_capability` to `read_capabilities.frontier()` in dependency validation + and sink as-of selection, which also affects 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. +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 From 486ef1a1deccec8c12b289e8ba273e590524053b Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 21:13:51 +0000 Subject: [PATCH 37/41] catalog: Simplify read-protection interfaces Use SetReadProtection for creation, alteration, and publication without changing transaction operation ordering. Remove the unused staged-bound removal API and the obsolete scalar-operation benchmark comparison. Cover dependency and sink creation while execution holds preserve history older than policy permission, with and without catalog governance. Record the unresolved index installation feasibility and diagnostic-checker boundaries. Leave their production paths unchanged pending design guidance. --- .../20260903_decoupled_coordination_log.md | 15 ++ .../20260903_decoupled_coordination_prompt.md | 10 +- src/adapter/benches/catalog.rs | 212 ++++++++---------- src/adapter/src/catalog/transact.rs | 188 +++++++++------- src/adapter/src/coord/ddl.rs | 2 - src/adapter/src/coord/sequencer/inner.rs | 12 +- .../inner/create_materialized_view.rs | 12 +- src/compute-client/src/controller.rs | 7 - .../src/controller/compaction_tests.rs | 5 - src/storage-client/src/storage_collections.rs | 134 +++++++++++ 10 files changed, 376 insertions(+), 221 deletions(-) diff --git a/doc/developer/design/20260903_decoupled_coordination_log.md b/doc/developer/design/20260903_decoupled_coordination_log.md index 2dd212a6011ae..48037d57d1fbf 100644 --- a/doc/developer/design/20260903_decoupled_coordination_log.md +++ b/doc/developer/design/20260903_decoupled_coordination_log.md @@ -439,3 +439,18 @@ 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 a1def9b6c98be..0aac8d1301aa3 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -26,6 +26,10 @@ 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 @@ -52,10 +56,8 @@ code and should go first. 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. Cleanup: one `Op` for read protection instead of three, remove - `remove_staged_compaction_bound`, and add a targeted test for the switch from - `implied_capability` to `read_capabilities.frontier()` in dependency validation - and sink as-of selection, which also affects ungoverned collections. +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. diff --git a/src/adapter/benches/catalog.rs b/src/adapter/benches/catalog.rs index 80f4e2596e0c5..ef7372ce50ba9 100644 --- a/src/adapter/benches/catalog.rs +++ b/src/adapter/benches/catalog.rs @@ -59,130 +59,110 @@ 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)); - for batched in [false, true] { - let mode = if batched { "batch" } else { "scalar" }; - group.bench_with_input(BenchmarkId::new(mode, 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))) + 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 records = |frontier| { - let requirements = pairs - .iter() - .map(|&(input, output)| MaintainedReadRequirement { - id: output, - inputs: BTreeSet::from([input]), - frontier: Some(frontier), - }) - .collect(); - let bounds = pairs + 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| 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 = if batched { - vec![Op::SetReadProtection { - requirements, - bounds, - }] - } else { - requirements - .into_iter() - .map(|requirement| Op::SetMaintainedReadRequirement { - id: requirement.id, - inputs: requirement.inputs, - frontier: requirement.frontier, - }) - .chain(bounds.into_iter().map(|bound| { - Op::SetCollectionCompactionBound { - id: bound.id, - frontier: bound.frontier, - } - })) - .collect() - }; - 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()); + .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.storage_metadata().compaction_bounds.len(), - 2 * pairs.len() + state.maintained_read_requirements()[&output].frontier, + Some(Timestamp::new(frontier)) ); - for &(input, output) in &pairs { + for id in [input, output] { assert_eq!( - state.maintained_read_requirements()[&output].frontier, - Some(Timestamp::new(frontier)) + state.storage_metadata().compaction_bounds[&id], + Antichain::from_elem(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()); - }); - } + } + runtime.block_on(catalog.expire()); + }); } group.finish(); } diff --git a/src/adapter/src/catalog/transact.rs b/src/adapter/src/catalog/transact.rs index 9625f2c3d7ee4..bc8369c7b1735 100644 --- a/src/adapter/src/catalog/transact.rs +++ b/src/adapter/src/catalog/transact.rs @@ -277,16 +277,10 @@ pub enum Op { scoped: ScopedParameters, prune_scope: ScopedParametersScope, }, - SetCollectionCompactionBound { - id: GlobalId, - frontier: Option, - }, - SetMaintainedReadRequirement { - id: GlobalId, - inputs: BTreeSet, - frontier: Option, - }, - /// Stages a publication's changed protection records as one operation. + /// 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, @@ -3261,16 +3255,6 @@ 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::SetReadProtection { requirements, bounds, @@ -3794,6 +3778,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}; @@ -3816,7 +3801,6 @@ mod tests { async fn test_storage_birth_inherits_dependency_permission() { use std::collections::{BTreeMap, BTreeSet}; - use mz_catalog::durable::objects::MaintainedReadRequirement; use mz_proto::RustType; use mz_repr::{CatalogItemId, GlobalId}; use mz_storage_types::sources::SourceExportStatementDetails; @@ -3931,9 +3915,12 @@ mod tests { 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::SetCollectionCompactionBound { - id: global_id, - frontier: Some(permission), + ops.push(Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: global_id, + frontier: Some(permission), + }], + requirements: vec![], }); } continue; @@ -3966,16 +3953,22 @@ mod tests { let mut invalid = ops.clone(); for other in state.maintained_read_requirements().values() { if other.id != global_id { - invalid.push(Op::SetMaintainedReadRequirement { - id: other.id, - inputs: other.inputs.clone(), - frontier: Some(frontier.step_forward()), + invalid.push(Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: other.id, + inputs: other.inputs.clone(), + frontier: Some(frontier.step_forward()), + }], + bounds: vec![], }); } } - invalid.push(Op::SetCollectionCompactionBound { - id: *input, - frontier: Some(frontier.step_forward()), + 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) @@ -3992,14 +3985,20 @@ mod tests { state.collection_compaction_bounds()[&global_id], Antichain::from_elem(mz_repr::Timestamp::MIN), ); - ops.push(Op::SetMaintainedReadRequirement { - id: global_id, - inputs, - frontier: Some(permission), + ops.push(Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: global_id, + inputs, + frontier: Some(permission), + }], + bounds: vec![], }); - ops.push(Op::SetCollectionCompactionBound { - id: global_id, - frontier: Some(permission), + ops.push(Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: global_id, + frontier: Some(permission), + }], + requirements: vec![], }); } else { assert_eq!( @@ -4015,14 +4014,20 @@ mod tests { let source_gid = identities["birth_source"].1; let progress = permission.step_forward(); ops.extend([ - Op::SetMaintainedReadRequirement { - id: export_gid, - inputs: BTreeSet::from([export_gid, source_gid]), - frontier: Some(progress), + Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: export_gid, + inputs: BTreeSet::from([export_gid, source_gid]), + frontier: Some(progress), + }], + bounds: vec![], }, - Op::SetCollectionCompactionBound { - id: export_gid, - frontier: Some(progress), + 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}')"); @@ -4070,10 +4075,13 @@ mod tests { .expect_err("changing inputs cannot advance the committed recovery frontier"); assert!(err.to_string().contains("read requirement"), "{err}"); - ops.push(Op::SetMaintainedReadRequirement { - id: sink_gid, - inputs: BTreeSet::from([sink_gid, identities["birth_table"].1]), - frontier: Some(progress), + 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 @@ -4113,7 +4121,6 @@ mod tests { async fn test_ddl_replay_inherits_fresh_dependency_permission() { use std::collections::{BTreeMap, BTreeSet}; - use mz_catalog::durable::objects::MaintainedReadRequirement; use mz_persist_client::ShardId; use mz_proto::RustType; use mz_repr::{GlobalId, Timestamp}; @@ -4235,14 +4242,20 @@ mod tests { oracle_write_ts, None, vec![ - Op::SetMaintainedReadRequirement { - id: source_id, - inputs: BTreeSet::from([source_id]), - frontier: Some(fresh_bound), + Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: source_id, + inputs: BTreeSet::from([source_id]), + frontier: Some(fresh_bound), + }], + bounds: vec![], }, - Op::SetCollectionCompactionBound { - id: source_id, - frontier: Some(fresh_bound), + Op::SetReadProtection { + bounds: vec![CollectionCompactionBound { + id: source_id, + frontier: Some(fresh_bound), + }], + requirements: vec![], }, ], ) @@ -4290,7 +4303,6 @@ mod tests { async fn test_read_protection_admission_and_committed_updates() { use std::collections::{BTreeMap, BTreeSet}; - use mz_catalog::durable::objects::{CollectionCompactionBound, MaintainedReadRequirement}; use mz_catalog::memory::objects::StateDiff; use mz_persist_client::ShardId; use mz_repr::{GlobalId, Timestamp}; @@ -4343,14 +4355,20 @@ 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 snapshot = catalog.clone(); @@ -5218,14 +5236,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(); @@ -5252,10 +5276,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, @@ -5360,9 +5387,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, diff --git a/src/adapter/src/coord/ddl.rs b/src/adapter/src/coord/ddl.rs index 52f856dcbc9db..645e3e2bd1b4b 100644 --- a/src/adapter/src/coord/ddl.rs +++ b/src/adapter/src/coord/ddl.rs @@ -1401,8 +1401,6 @@ impl Coordinator { | Op::ResetSystemConfiguration { .. } | Op::ResetAllSystemConfiguration { .. } | Op::UpdateScopedSystemParameters { .. } - | Op::SetCollectionCompactionBound { .. } - | Op::SetMaintainedReadRequirement { .. } | Op::SetReadProtection { .. } | Op::Comment { .. } | Op::CheckClusterState { .. } diff --git a/src/adapter/src/coord/sequencer/inner.rs b/src/adapter/src/coord/sequencer/inner.rs index 943be3607583f..b561c333fb595 100644 --- a/src/adapter/src/coord/sequencer/inner.rs +++ b/src/adapter/src/coord/sequencer/inner.rs @@ -25,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, @@ -3566,10 +3567,13 @@ impl Coordinator { // 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::SetMaintainedReadRequirement { - id: global_id, - inputs: requirement.inputs.clone(), - frontier: frontier.as_option().copied(), + ops.push(catalog::Op::SetReadProtection { + requirements: vec![MaintainedReadRequirement { + id: global_id, + inputs: requirement.inputs.clone(), + frontier: frontier.as_option().copied(), + }], + bounds: vec![], }); } 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/compute-client/src/controller.rs b/src/compute-client/src/controller.rs index 7862f0c5a687e..f8e495b49a23e 100644 --- a/src/compute-client/src/controller.rs +++ b/src/compute-client/src/controller.rs @@ -1095,13 +1095,6 @@ impl ComputeController { Ok(()) } - /// Forget a bound staged for an object dropped before compute installation. - /// - /// Installed permissions are released only by dropping the collection or instance. - pub fn remove_staged_compaction_bound(&mut self, id: GlobalId) { - self.staged_compaction_bounds.remove(&id); - } - /// Return compaction candidates for installed governed indexes, including logging indexes. /// /// Each candidate excludes only the catalog permission contribution. Reader, policy, diff --git a/src/compute-client/src/controller/compaction_tests.rs b/src/compute-client/src/controller/compaction_tests.rs index 1dac98166a7dc..feecdcbd7c1ab 100644 --- a/src/compute-client/src/controller/compaction_tests.rs +++ b/src/compute-client/src/controller/compaction_tests.rs @@ -320,11 +320,6 @@ async fn compaction_bounds_are_staged_before_creation_and_never_adopt_live_expor Err(CompactionBoundError::WriteOnlyCollection(OUTPUT)) )); controller.drop_collections(INSTANCE, vec![OUTPUT]).unwrap(); - let pending = GlobalId::User(2); - controller - .apply_compaction_bound(pending, frontier(3)) - .unwrap(); - controller.remove_staged_compaction_bound(pending); assert!(controller.staged_compaction_bounds.is_empty()); controller.drop_instance(INSTANCE); } diff --git a/src/storage-client/src/storage_collections.rs b/src/storage-client/src/storage_collections.rs index ec33d6e755e23..102c3ac217771 100644 --- a/src/storage-client/src/storage_collections.rs +++ b/src/storage-client/src/storage_collections.rs @@ -3699,6 +3699,140 @@ 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_committed_compaction_bound_caps_progress() { let (controller, persist) = bound_test_controller().await; From 2015a7e390f22190f50f74a62b6e4a3fd5c22181 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 21:31:03 +0000 Subject: [PATCH 38/41] 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 4850e69cff9401e26939f4c575d4a548eb3fb213 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 21:48:46 +0000 Subject: [PATCH 39/41] catalog: Publish sparse index protection without blocking recovery Let index permission appear at first publication. Install from held readability without a catalog write, using saved permission when feasible and replacing at readability when it is not. Preserve execution holds and monotonic committed bounds. Track changed controller frontiers and maintained input requirements for publication. Validate affected durable records through derived lookups and retire sparse bounds from final lifetime changes instead of mutation-path birth bookkeeping. Reconstruct durable catalog state through the existing diagnostic endpoint using owned prefix input off the coordinator thread. Preserve the normal invariant check and legacy testdrive compatibility without a frozen catalog mode or a dedicated snapshot command. Require a positive publication cadence. Extend recovery and cost workflows for unpublished indexes, suspended clusters, sparse catalog changes, observer cost, and follower application. Production proof and representative measurements remain to be completed. --- Cargo.lock | 1 + .../design/20260903_decoupled_coordination.md | 18 +- src/adapter/src/catalog.rs | 146 +++-- src/adapter/src/catalog/apply.rs | 28 +- src/adapter/src/catalog/consistency.rs | 48 ++ src/adapter/src/catalog/open.rs | 207 ++++-- src/adapter/src/catalog/state.rs | 384 ++++++++++- src/adapter/src/catalog/transact.rs | 38 +- src/adapter/src/client.rs | 81 ++- src/adapter/src/command.rs | 8 +- src/adapter/src/coord.rs | 79 +-- src/adapter/src/coord/command_handler.rs | 21 +- ...iber.rs => compaction_bound_subscriber.rs} | 37 +- src/adapter/src/coord/info_metrics.rs | 7 +- src/adapter/src/coord/read_protection.rs | 305 +++++---- src/adapter/src/peek_client.rs | 7 +- src/catalog/Cargo.toml | 1 + src/catalog/src/durable.rs | 36 +- src/catalog/src/durable/objects.rs | 91 +++ src/catalog/src/durable/persist.rs | 159 +++-- src/catalog/src/durable/transaction.rs | 603 +++++++++--------- src/catalog/tests/compute-read-protection.rs | 248 ++++++- src/catalog/tests/open.rs | 1 + src/catalog/tests/read-write.rs | 242 +++---- src/compute-client/src/as_of_selection.rs | 67 +- src/compute-client/src/controller.rs | 101 ++- .../src/controller/compaction_tests.rs | 320 +++++++++- src/compute-client/src/controller/instance.rs | 53 +- src/controller/src/lib.rs | 2 + src/environmentd/src/http/catalog.rs | 35 +- src/environmentd/src/lib.rs | 4 +- src/sql/src/session/vars/definitions.rs | 7 +- src/storage-client/src/storage_collections.rs | 304 ++++++++- src/storage-controller/src/lib.rs | 293 +++++++++ src/testdrive/src/action/consistency.rs | 138 ++-- test/0dt/mzcompose.py | 99 ++- test/restart/mzcompose.py | 184 ++++-- 37 files changed, 3260 insertions(+), 1143 deletions(-) rename src/adapter/src/coord/{catalog_subscriber.rs => compaction_bound_subscriber.rs} (87%) 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/src/adapter/src/catalog.rs b/src/adapter/src/catalog.rs index f0bbab8291fc5..072b42b115936 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -145,6 +145,8 @@ pub struct Catalog { expr_cache_handle: Option, storage: Arc>>, transient_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 @@ -185,6 +187,7 @@ impl Clone for Catalog { expr_cache_handle: self.expr_cache_handle.clone(), storage: Arc::clone(&self.storage), transient_revision: self.transient_revision, + diagnostic_config: Arc::clone(&self.diagnostic_config), shared_transient_revision: Arc::clone(&self.shared_transient_revision), } } @@ -601,8 +604,7 @@ impl Catalog { /// Reconstructs a debug catalog from caller-prepared durable storage without reopening it. /// - /// Storage must retain its initial update stream. Freeze a read-only handle before calling - /// this when reconstruction must exclude concurrent durable changes. + /// Storage must retain its initial update stream. pub async fn open_debug_catalog_inner( persist_client: PersistClient, storage: Box, @@ -740,6 +742,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( @@ -1320,20 +1337,25 @@ impl Catalog { Ok(CatalogDump::new(self.state.dump(None)?)) } - /// Returns a durable exclusive upper for a diagnostic snapshot of the writer. - /// - /// The caller must serialize capture with catalog mutations, not call this on - /// an older shared snapshot. Read-only reconstructions have no durable certificate. - pub(crate) async fn dump_upper(&self) -> Result, AdapterError> { - let mut storage = self.storage().await; - if storage.is_savepoint() || storage.is_read_only() { - return Ok(None); - } - let upper = storage.current_upper().await; - // Empty upper advancement can run off-loop. Unapplied content or fencing - // must not certify the in-memory catalog as the state at that newer upper. - storage.ensure_not_out_of_sync(upper).await?; - Ok(Some(upper)) + 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. @@ -2698,8 +2720,53 @@ mod tests { #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] - async fn frozen_catalog_reconstruction_preserves_protection_snapshot() { - use mz_build_info::DUMMY_BUILD_INFO; + 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.unwrap()[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 + .unwrap(); + + let expected = catalog.state().dump(None).unwrap(); + let reader = catalog.open_diagnostic_reader().await.unwrap(); + let upper = catalog.current_upper().await; + let input = reader.into_snapshot_at(upper).await.unwrap(); + let reconstructed = catalog.reconstruct_state(input).await.unwrap(); + assert_eq!(expected, reconstructed.dump(None).unwrap()); + 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; @@ -2755,16 +2822,12 @@ mod tests { .state() .dump(None) .expect("failed to dump initial writer catalog"); - let environment_id = writer.state().config.environment_id.clone(); - let mut frozen = TestCatalogStateBuilder::new(persist.clone()) - .with_organization_id(organization) - .build() - .await - .expect("failed to build readonly catalog") - .open_read_only(&bootstrap) + let reader = writer + .open_diagnostic_reader() .await .expect("failed to open readonly catalog"); - frozen.freeze().await.expect("failed to freeze catalog"); + let upper = writer.current_upper().await; + let memory = writer.clone(); let ts = writer.current_upper().await; writer @@ -2794,27 +2857,16 @@ mod tests { .expect("failed to dump updated writer catalog") ); - let reconstructed = Catalog::open_debug_catalog_inner( - persist, - frozen, - SYSTEM_TIME.clone(), - Some(environment_id), - &DUMMY_BUILD_INFO, - BTreeMap::new(), - &bootstrap, - Some(false), - None, - ) - .await - .expect("failed to reconstruct frozen catalog"); - assert_eq!( - expected, - reconstructed - .state() - .dump(None) - .expect("failed to dump reconstructed catalog") - ); - reconstructed.expire().await; + 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).unwrap()); + drop(memory); writer.expire().await; } diff --git a/src/adapter/src/catalog/apply.rs b/src/adapter/src/catalog/apply.rs index 36068bef3234f..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,17 +389,36 @@ impl CatalogState { } StateUpdateKind::CollectionCompactionBound(bound) => { apply_inverted_lookup( - Arc::make_mut(&mut self.collection_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, @@ -1417,6 +1439,8 @@ 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); } diff --git a/src/adapter/src/catalog/consistency.rs b/src/adapter/src/catalog/consistency.rs index 2a02d09477641..5d7b685d5712a 100644 --- a/src/adapter/src/catalog/consistency.rs +++ b/src/adapter/src/catalog/consistency.rs @@ -58,6 +58,54 @@ 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 first = memory_lines + .iter() + .zip(&disk_lines) + .position(|(a, b)| a != b) + .unwrap_or(memory_lines.len().min(disk_lines.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 fe8c1da8d2e84..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), @@ -174,6 +259,8 @@ impl Catalog { 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(), @@ -201,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, )?; @@ -223,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, @@ -232,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(); @@ -454,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. @@ -468,7 +579,6 @@ impl Catalog { enabled } }; - txn.initialize_index_compaction_bounds()?; let mz_authentication_mock_nonce = txn.get_authentication_mock_nonce().ok_or_else(|| { @@ -481,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, @@ -544,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 @@ -566,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`. @@ -599,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, @@ -621,6 +736,7 @@ impl Catalog { let catalog = Catalog { state, expr_cache_handle, + diagnostic_config, transient_revision: 1, shared_transient_revision: Arc::new(AtomicU64::new(1)), storage: Arc::new(tokio::sync::Mutex::new(storage)), @@ -801,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 d2345bbd14a3b..8f2101417b8cd 100644 --- a/src/adapter/src/catalog/state.rs +++ b/src/adapter/src/catalog/state.rs @@ -157,9 +157,15 @@ pub struct CatalogState { pub(super) source_references: imbl::OrdMap, pub(super) storage_metadata: Arc, #[serde(serialize_with = "serialize_collection_compaction_bounds")] - pub(super) collection_compaction_bounds: Arc>>, + 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, @@ -487,6 +493,8 @@ impl CatalogState { 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(), @@ -2905,15 +2913,30 @@ impl CatalogState { } /// Returns committed compaction permission for maintained storage and compute collections. - pub fn collection_compaction_bounds(&self) -> &BTreeMap> { + 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 @@ -3126,20 +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: &BTreeMap>, + 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 @@ -3198,6 +3228,28 @@ mod tests { 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, + ) + .unwrap(), + serde_json::to_value(BTreeMap::from([ + ( + storage.to_string(), + Antichain::from_elem(Timestamp::from(10)) + ), + ( + compute.to_string(), + Antichain::from_elem(Timestamp::from(10)) + ), + ])) + .unwrap() + ); let snapshot = state.clone(); let _ = state .apply_updates( @@ -3211,12 +3263,70 @@ mod tests { .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]) + ); } } @@ -3252,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, + ) + .unwrap(), + serde_json::to_value(BTreeMap::from([( + initial.id.to_string(), + (&initial.inputs, initial.frontier), + )])) + .unwrap() ); + let serialized = serde_json::to_value(&state).unwrap(); + assert!(serialized.get("maintained_input_requirements").is_none()); + assert!(serialized.get("read_protection_changes").is_none()); let snapshot = state.clone(); let _ = state @@ -3269,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( @@ -3285,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, @@ -3331,6 +3635,68 @@ 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) + .unwrap(); + let (_, extra_id) = catalog.allocate_user_id_for_test().await.unwrap(); + 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 bc8369c7b1735..28a7f9b645803 100644 --- a/src/adapter/src/catalog/transact.rs +++ b/src/adapter/src/catalog/transact.rs @@ -1062,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 @@ -4559,7 +4560,7 @@ mod tests { .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, @@ -4570,16 +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!(!snapshot.transient_revision_is_current()); - let paused_snapshot = catalog.clone(); - assert!(paused_snapshot.transient_revision_is_current()); - 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 @@ -4592,9 +4610,9 @@ mod tests { }], ) .await - .expect("resuming publication should commit"); + .expect("resetting publication cadence should commit"); assert_eq!(catalog.transient_revision(), revision + 2); - 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!( diff --git a/src/adapter/src/client.rs b/src/adapter/src/client.rs index bbda37ec55c40..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,33 +1121,60 @@ impl SessionClient { .enable_extended_protocol_implicit_transaction() } - /// Dumps the catalog to JSON with its corresponding durable exclusive upper, if any. - /// - /// The upper is absent for read-only catalog reconstructions. - /// - /// 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<(CatalogDump, Option), AdapterError> { - let (snapshot, upper) = self - .send_without_session(|tx| Command::CatalogDumpSnapshot { tx }) - .await?; - Ok((snapshot.catalog.dump()?, upper)) + /// Dumps the catalog to JSON. Access must be limited to internal servers or superusers. + pub async fn dump_catalog(&mut self) -> Result { + 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. @@ -1352,7 +1382,6 @@ impl SessionClient { | Command::AuthenticateVerifySASLProof { .. } | Command::CheckRoleCanLogin { .. } | Command::CatalogSnapshot { .. } - | Command::CatalogDumpSnapshot { .. } | Command::Commit { .. } | Command::CancelRequest { .. } | Command::PrivilegedCancelRequest { .. } diff --git a/src/adapter/src/command.rs b/src/adapter/src/command.rs index e6c30c7b2427e..f7f5bf6ec001d 100644 --- a/src/adapter/src/command.rs +++ b/src/adapter/src/command.rs @@ -84,15 +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, - }, - CatalogDumpSnapshot { - tx: oneshot::Sender), AdapterError>>, + include_durable_upper: bool, }, Startup { @@ -485,7 +485,6 @@ impl Command { | Command::AuthenticateVerifySASLProof { .. } | Command::CheckRoleCanLogin { .. } | Command::CatalogSnapshot { .. } - | Command::CatalogDumpSnapshot { .. } | Command::PrivilegedCancelRequest { .. } | Command::GetWebhook { .. } | Command::Terminate { .. } @@ -531,7 +530,6 @@ impl Command { | Command::AuthenticateVerifySASLProof { .. } | Command::CheckRoleCanLogin { .. } | Command::CatalogSnapshot { .. } - | Command::CatalogDumpSnapshot { .. } | Command::PrivilegedCancelRequest { .. } | Command::GetWebhook { .. } | Command::Terminate { .. } diff --git a/src/adapter/src/coord.rs b/src/adapter/src/coord.rs index dffa50a02df1a..d56bbd6cdaf49 100644 --- a/src/adapter/src/coord.rs +++ b/src/adapter/src/coord.rs @@ -202,8 +202,8 @@ use crate::coord::appends::{ BuiltinTableAppendCompletion, BuiltinTableAppendNotify, DeferredOp, GroupCommitPermit, PendingWriteTxn, }; -use crate::coord::catalog_subscriber::CatalogSubscriber; 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}; @@ -243,9 +243,9 @@ pub(crate) mod timeline; pub(crate) mod timestamp_selection; pub mod catalog_implications; -mod catalog_subscriber; mod caught_up; mod command_handler; +mod compaction_bound_subscriber; mod ddl; pub(crate) mod group_sync; mod hydration_history; @@ -495,7 +495,6 @@ impl Message { match self { Message::Command(_, msg) => match msg { Command::CatalogSnapshot { .. } => "command-catalog_snapshot", - Command::CatalogDumpSnapshot { .. } => "command-catalog_dump_snapshot", Command::Startup { .. } => "command-startup", Command::Execute { .. } => "command-execute", Command::Commit { .. } => "command-commit", @@ -1324,7 +1323,7 @@ pub struct Config { pub controller_config: ControllerConfig, pub controller_envd_epoch: NonZeroI64, pub storage: Box, - pub catalog_subscriber: Option>, + pub compaction_bound_subscriber: Option>, pub timestamp_oracle_url: Option, pub unsafe_mode: bool, pub all_features: bool, @@ -2069,7 +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, - catalog_subscriber: Option, + 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. @@ -2658,12 +2659,7 @@ impl Coordinator { .catalog() .system_config() .enable_storage_introspection_logs(); - let missing = self.restore_compute_read_protection().await?; - if !missing.is_empty() { - return Err(AdapterError::Unstructured(anyhow::anyhow!( - "index recovery has no committed compaction permission: {missing:?}" - ))); - } + self.restore_compute_read_protection().await?; for instance in self.catalog.clusters() { self.controller.create_cluster( instance.id, @@ -4112,38 +4108,32 @@ impl Coordinator { } } - // Prefer already-authorized recovery while retaining the selector's hard - // readability constraints. A saved bound is not a historical read hold. - if let Some(subscriber) = &mut self.catalog_subscriber { - subscriber.sync().await?; - for (id, policy) in &mut read_policies { - if let Some(bound) = subscriber.bounds().get(id) { - *policy = ReadPolicy::Multiple(vec![ - policy.clone(), - ReadPolicy::ValidFrom(bound.clone()), - ]); - } - } + 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(), ); - let installations = dataflows - .iter() - .flat_map(|dataflow| { - dataflow - .exported_index_ids() - .map(|id| (id, dataflow.as_of.clone().expect("as-of selected"))) - }) - .collect(); - self.authorize_index_installation(installations).await?; - let catalog = self.catalog_mut(); for (id, plan) in catalog_ids.into_iter().zip_eq(dataflows) { catalog.set_physical_plan(id, plan); @@ -4291,8 +4281,9 @@ impl Coordinator { // 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.catalog_subscriber.is_some() => { - if let Err(error) = self.restore_compute_read_protection().await { + _ = 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)); @@ -4303,7 +4294,7 @@ impl Coordinator { // 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"); } @@ -4507,7 +4498,7 @@ impl Coordinator { if let Some(sweep) = self.hydration_history_sweep.take() { sweep.abort_and_wait().await; } - if let Some(subscriber) = self.catalog_subscriber.take() { + if let Some(subscriber) = self.compaction_bound_subscriber.take() { subscriber.expire().await; } @@ -4748,14 +4739,6 @@ impl Coordinator { let read_holds = self.acquire_read_holds(id_bundle); let since = read_holds.least_valid_read(); df_desc.set_as_of(since.clone()); - let installations = df_desc - .exported_index_ids() - .map(|id| (id, since.clone())) - .collect(); - self.authorize_index_installation(installations) - .await - .unwrap_or_terminate("cannot authorize committed index installation"); - self.ship_dataflow_and_notice_builtin_table_updates( df_desc, instance, @@ -5168,7 +5151,7 @@ pub fn serve( controller_config, controller_envd_epoch, mut storage, - catalog_subscriber, + compaction_bound_subscriber, timestamp_oracle_url, unsafe_mode, all_features, @@ -5581,7 +5564,9 @@ pub fn serve( let mut coord = Coordinator { controller, catalog, - catalog_subscriber: catalog_subscriber.map(CatalogSubscriber::new), + 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/command_handler.rs b/src/adapter/src/coord/command_handler.rs index e91a4de9d652a..da218c17d304d 100644 --- a/src/adapter/src/coord/command_handler.rs +++ b/src/adapter/src/coord/command_handler.rs @@ -353,22 +353,21 @@ 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, }); } - Command::CatalogDumpSnapshot { tx } => { - // Capture the prefix while catalog mutations are serialized here. - // The immutable memory snapshot can be serialized off the coordinator. - let upper = self.catalog().dump_upper().await; - let snapshot = CatalogSnapshot { - catalog: self.owned_catalog(), - }; - let _ = tx.send(upper.map(|upper| (snapshot, upper))); - } - Command::CheckConsistency { tx } => { let _ = tx.send(self.check_consistency()); } diff --git a/src/adapter/src/coord/catalog_subscriber.rs b/src/adapter/src/coord/compaction_bound_subscriber.rs similarity index 87% rename from src/adapter/src/coord/catalog_subscriber.rs rename to src/adapter/src/coord/compaction_bound_subscriber.rs index 7bfa55df9d23b..908b9db8e2021 100644 --- a/src/adapter/src/coord/catalog_subscriber.rs +++ b/src/adapter/src/coord/compaction_bound_subscriber.rs @@ -16,12 +16,12 @@ use timely::progress::Antichain; /// Tracks committed compaction bounds independently of the SQL catalog. #[derive(Debug)] -pub(super) struct CatalogSubscriber { +pub(super) struct CompactionBoundSubscriber { catalog: Box, bounds: BTreeMap>, } -impl CatalogSubscriber { +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()); @@ -31,8 +31,11 @@ impl CatalogSubscriber { } } - /// Consumes committed updates, including the initial durable state on the first call. - pub(super) async fn sync(&mut self) -> Result<(), CatalogError> { + /// 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?; @@ -48,24 +51,27 @@ impl CatalogSubscriber { // 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(()) + Ok(changed) } /// Returns committed bounds, not permission to read or to drop a collection. /// - /// Missing records grant no permission. The caller must match IDs to its frozen - /// catalog and handle collection lifetimes, including dropped bound owners. + /// 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 } @@ -146,12 +152,13 @@ mod tests { .open_read_only(&test_bootstrap_args()) .await .expect("failed to open subscriber catalog"); - let mut subscriber = CatalogSubscriber::new(reader); + let mut subscriber = CompactionBoundSubscriber::new(reader); assert!(subscriber.bounds().is_empty()); - subscriber + let changed = subscriber .sync() .await .expect("failed to sync initial subscriber bounds"); + assert_eq!(&changed, subscriber.bounds()); assert_eq!( subscriber.bounds(), &BTreeMap::from([ @@ -174,10 +181,14 @@ mod tests { .await .expect("failed to commit bound replacement transaction"); } - subscriber + 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 @@ -201,18 +212,20 @@ mod tests { txn.commit(ts) .await .expect("failed to commit collection deletion transaction"); - subscriber + 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())]) ); - subscriber + 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())]) 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 90d0adc9c9df0..921bb0d197dd0 100644 --- a/src/adapter/src/coord/read_protection.rs +++ b/src/adapter/src/coord/read_protection.rs @@ -17,7 +17,6 @@ use mz_catalog::durable::objects::{CollectionCompactionBound, MaintainedReadRequ use mz_catalog::memory::objects::CatalogItem; use mz_repr::{GlobalId, Timestamp}; use mz_storage_client::storage_collections::CollectionFrontiers; -use timely::PartialOrder; use timely::progress::Antichain; use crate::AdapterError; @@ -27,38 +26,18 @@ use crate::coord::Coordinator; pub(super) const CATALOG_SUBSCRIPTION_INTERVAL: Duration = Duration::from_secs(1); impl Coordinator { - /// Applies permission for surviving compute lifetimes and returns missing bound owners. - pub(super) async fn restore_compute_read_protection( - &mut self, - ) -> Result, AdapterError> { + /// 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(BTreeSet::new()); - } - if let Some(subscriber) = &mut self.catalog_subscriber { - subscriber.sync().await?; + return Ok(()); } let state = self.catalog().state(); let storage = &state.storage_metadata().collection_metadata; - let mut missing = BTreeSet::new(); let bounds = state .collection_compaction_bounds() .iter() .filter(|(id, _)| !storage.contains_key(id)) - .filter_map(|(&id, bound)| { - let bound = match &self.catalog_subscriber { - Some(subscriber) => match subscriber.bounds().get(&id) { - Some(bound) => bound, - None => { - // A drop must not block delivery to surviving lifetimes, nor - // release an installed bound in the frozen reconstruction. - missing.insert(id); - return None; - } - }, - None => bound, - }; - Some((id, bound.clone())) - }) + .map(|(&id, bound)| (id, bound.clone())) .collect::>(); for (id, bound) in bounds { self.controller @@ -66,73 +45,27 @@ impl Coordinator { .apply_compaction_bound(id, bound) .map_err(|error| AdapterError::Unstructured(error.into()))?; } - Ok(missing) + self.sync_compute_read_protection().await } - /// Authorizes installation of absent indexes while the caller holds their inputs. - /// - /// Installed indexes advance through publication, which accounts for their readers. - /// Recovery must restore valid maintained requirements before calling this method. - pub(super) async fn authorize_index_installation( - &mut self, - installations: BTreeMap>, - ) -> Result<(), AdapterError> { - let bounds = self.catalog().state().collection_compaction_bounds(); - let installations = installations - .into_iter() - .filter(|(id, _)| bounds.contains_key(id)) - .collect::>(); - if installations.is_empty() { + /// 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(()); - } - - if self.controller.read_only() { - // A savepoint write grants no permission. Keep input holds alive while - // following the writer, including when it is retaining older reader needs. - loop { - let missing = self.restore_compute_read_protection().await?; - if installations.keys().any(|id| missing.contains(id)) { - return Err(AdapterError::Unstructured(anyhow::anyhow!( - "index installation has no committed compaction permission: {missing:?}" - ))); - } - let subscriber = self - .catalog_subscriber - .as_ref() - .ok_or(AdapterError::ReadOnly)?; - if installations.iter().all(|(id, as_of)| { - subscriber - .bounds() - .get(id) - .is_some_and(|bound| PartialOrder::less_equal(as_of, bound)) - }) { - return Ok(()); - } - tokio::time::sleep(CATALOG_SUBSCRIPTION_INTERVAL).await; + }; + 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; } - } - - let bounds = installations - .into_iter() - .filter_map(|(id, frontier)| { - PartialOrder::less_than(&bounds[&id], &frontier).then(|| { - CollectionCompactionBound { - id, - frontier: frontier.into_option(), - } - }) - }) - .collect::>(); - if !bounds.is_empty() { - let ops = vec![Op::SetReadProtection { - requirements: Vec::new(), - bounds, - }]; - // Installation also runs inside an unfinished implication batch. Commit - // and apply this protection-only diff without checking whole-controller - // consistency until the enclosing batch has installed its other objects. - let (_builtin_updates, updates) = self.catalog_transact_inner(None, ops).await?; - Box::pin(self.apply_catalog_implications(None, updates)).await?; + self.controller + .compute + .apply_compaction_bound(id, bound) + .map_err(|error| AdapterError::Unstructured(error.into()))?; } Ok(()) } @@ -145,13 +78,63 @@ 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(); - let compute_proposals = self.controller.compute.compaction_bound_proposals(); + .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().collection_compaction_bounds(), @@ -172,10 +155,16 @@ impl Coordinator { _ => 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(()); } @@ -186,9 +175,10 @@ impl Coordinator { 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(), @@ -211,43 +201,46 @@ struct PublicationCandidates { } /// 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>, 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 owns_durable_progress(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 @@ -271,14 +264,18 @@ fn publication_candidates( .iter() .map(|(&id, proposal)| (id, proposal, None)); for (id, proposal, policy) in storage_proposals.chain(compute_proposals) { - let Some(old_bound) = bounds.get(&id) else { + let old_bound = bounds.get(&id); + if old_bound.is_none() && policy.is_some() { continue; - }; + } 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()); - bound.join_assign(old_bound); + if let Some(old_bound) = old_bound { + bound.join_assign(old_bound); + } if let Some(bound_ts) = bound.as_option() && let Some(policy) = policy { @@ -291,7 +288,7 @@ fn publication_candidates( None => candidates.unbounded_policy_lag += 1, } } - if &bound != old_bound { + if Some(&bound) != old_bound { candidates.bounds.push(CollectionCompactionBound { id, frontier: bound.into_option(), @@ -305,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)) } @@ -533,9 +563,8 @@ mod tests { } #[mz_ore::test] - fn compute_only_publication_requires_governance_and_advancement() { + fn compute_publication_preserves_existing_bounds() { let index = GlobalId::User(1); - let ungoverned = GlobalId::User(2); let uninstalled = GlobalId::User(3); for (old, proposal, expected) in [ (Some(10), Some(20), Some(Some(20))), @@ -548,10 +577,8 @@ mod tests { (index, old.map(Timestamp::from).into_iter().collect()), (uninstalled, frontier(10)), ]); - let proposals = BTreeMap::from([ - (index, proposal.map(Timestamp::from).into_iter().collect()), - (ungoverned, frontier(20)), - ]); + let proposals = + BTreeMap::from([(index, proposal.map(Timestamp::from).into_iter().collect())]); let candidates = publication_candidates( &BTreeMap::new(), &bounds, @@ -574,6 +601,26 @@ mod tests { } } + #[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); 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/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 9739468a2ae36..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)] @@ -294,30 +304,6 @@ pub trait DurableCatalogState: ReadOnlyDurableCatalogState { /// Returns true if the catalog is opened is savepoint mode, false otherwise. fn is_savepoint(&self) -> bool; - /// Synchronizes an opened read-only catalog, then freezes its cached state for diagnostics. - /// - /// Cached reads, including [`ReadOnlyDurableCatalogState::snapshot`] and - /// [`Self::transaction`], exclude subsequent durable changes. Explicit snapshots - /// supplied to dry-run transactions are unaffected. - /// Buffered updates remain available to drain, and `current_upper` stays at the frozen upper. - /// No migrations or ephemeral cleanup run. Nonempty commits still fail as read-only writes. - /// Audit-log reads are unsupported after freezing because they bypass the cache. - /// - /// Repeated calls are no-ops. Returns an error for writable or savepoint catalogs, or if - /// synchronization observes fencing. Once frozen, the handle no longer observes fencing. - async fn freeze(&mut self) -> Result<(), CatalogError>; - - /// Freezes diagnostic cached reads at exactly `exclusive_upper`, excluding updates at that - /// timestamp and later. Otherwise has the same read-only semantics as [`Self::freeze`]. - /// - /// Open the read-only handle before capturing the upper. The requested upper must be at least - /// the handle's already-applied upper. Returns an error for backwards requests, non-read-only - /// handles, or fencing observed in the prefix. Already-observed fencing is never cleared. - /// Once frozen, only the same upper may be requested again. Fences beyond the prefix are not - /// observed. Waits for durable progress to reach the requested upper. - /// If this future is cancelled, discard the handle rather than reusing it. - async fn freeze_at(&mut self, exclusive_upper: Timestamp) -> Result<(), CatalogError>; - /// Marks the bootstrap process as complete. async fn mark_bootstrap_complete(&mut self); 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/persist.rs b/src/catalog/src/durable/persist.rs index 3a13e318ed93c..29439e589a6fd 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::{ @@ -99,8 +99,6 @@ pub const _EXPRESSION_CACHE_SEED: usize = 4; pub(crate) enum Mode { /// Mutable operations are prohibited. Readonly, - /// Read-only cached state frozen by [`DurableCatalogState::freeze`]. - FrozenReadonly, /// Mutable operations have an effect in-memory, but aren't persisted durably. Savepoint, /// Mutable operations have an effect in-memory and durably. @@ -389,7 +387,7 @@ impl> PersistHandle { let upper = self.write_handle.fetch_recent_upper().await; antichain_to_timestamp(upper.clone()) } - Mode::Savepoint | Mode::FrozenReadonly => self.upper, + Mode::Savepoint => self.upper, } } @@ -558,10 +556,6 @@ impl> PersistHandle { ) -> Result<(), FenceError> { self.fenceable_token.validate()?; - if self.mode == Mode::FrozenReadonly { - return Ok(()); - } - // Savepoint catalogs do not yet know how to update themselves in response to concurrent // writes from writer catalogs. if self.mode == Mode::Savepoint { @@ -613,7 +607,7 @@ impl> PersistHandle { for update in batch_updates { let update: StateUpdate = update.into(); // Terminal synchronization discards the suffix of this continuous - // listen, since the frozen handle must never follow it again. + // listen, since the consumed handle must never follow it again. if terminal && update.ts >= target_upper { continue; } @@ -778,7 +772,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. @@ -786,6 +780,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, @@ -809,9 +808,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:?})"); @@ -911,9 +911,8 @@ impl> PersistHandle { } } } - f(snapshot) - }) - .await + Ok(snapshot) + } } /// Generates an iterator of [`StateUpdate`] that contain all updates to the catalog @@ -1173,7 +1172,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; @@ -1504,6 +1503,7 @@ impl OpenableDurableCatalogState for UnopenedPersistCatalogState { self.open_inner(Mode::Savepoint, initial_ts, bootstrap_args) .boxed() .await + .map(|state| state as Box) } #[mz_ore::instrument] @@ -1514,6 +1514,7 @@ impl OpenableDurableCatalogState for UnopenedPersistCatalogState { self.open_inner(Mode::Readonly, EpochMillis::MIN.into(), bootstrap_args) .boxed() .await + .map(|state| state as Box) } #[mz_ore::instrument] @@ -1525,6 +1526,7 @@ impl OpenableDurableCatalogState for UnopenedPersistCatalogState { self.open_inner(Mode::Writable, initial_ts, bootstrap_args) .boxed() .await + .map(|state| state as Box) } #[mz_ore::instrument(level = "debug")] @@ -1640,12 +1642,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(), } } } @@ -1657,6 +1661,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 @@ -1697,6 +1702,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> { @@ -1730,13 +1804,6 @@ impl ReadOnlyDurableCatalogState for PersistCatalogState { } async fn get_audit_logs(&mut self) -> Result, CatalogError> { - // A fresh persist reader may have a since beyond the frozen upper. - if self.mode == Mode::FrozenReadonly { - return Err(DurableCatalogError::Internal( - "audit-log reads are unsupported on a frozen catalog".into(), - ) - .into()); - } self.sync_to_current_upper().await?; let audit_logs: Vec<_> = self .persist_snapshot() @@ -1856,53 +1923,13 @@ impl ReadOnlyDurableCatalogState for PersistCatalogState { #[allow(mismatched_lifetime_syntaxes)] impl DurableCatalogState for PersistCatalogState { fn is_read_only(&self) -> bool { - matches!(self.mode, Mode::Readonly | Mode::FrozenReadonly) + matches!(self.mode, Mode::Readonly) } fn is_savepoint(&self) -> bool { matches!(self.mode, Mode::Savepoint) } - async fn freeze(&mut self) -> Result<(), CatalogError> { - if self.mode == Mode::FrozenReadonly { - return Ok(()); - } - let upper = self.current_upper().await; - self.freeze_at(upper).await - } - - async fn freeze_at(&mut self, exclusive_upper: Timestamp) -> Result<(), CatalogError> { - match self.mode { - Mode::FrozenReadonly if exclusive_upper == self.upper => { - self.fenceable_token.validate()?; - return Ok(()); - } - Mode::FrozenReadonly => { - return Err(DurableCatalogError::Internal( - "cannot change a frozen catalog's upper".into(), - ) - .into()); - } - Mode::Readonly => {} - Mode::Writable | Mode::Savepoint => { - return Err(DurableCatalogError::Internal( - "only a read-only catalog can be frozen".into(), - ) - .into()); - } - } - if exclusive_upper < self.upper { - return Err(DurableCatalogError::Internal(format!( - "cannot freeze catalog backwards from {} to {exclusive_upper}", - self.upper - )) - .into()); - } - self.sync_with_limit(exclusive_upper, true).await?; - self.mode = Mode::FrozenReadonly; - Ok(()) - } - async fn mark_bootstrap_complete(&mut self) { self.bootstrap_complete = true; if matches!(self.mode, Mode::Writable) { @@ -2017,7 +2044,7 @@ impl DurableCatalogState for PersistCatalogState { catalog.upper = commit_ts.step_forward(); catalog.upper } - Mode::Readonly | Mode::FrozenReadonly => unreachable!("handled above"), + Mode::Readonly => unreachable!("handled above"), }; Ok(next_upper) @@ -2046,7 +2073,7 @@ impl DurableCatalogState for PersistCatalogState { self.upper = new_upper; return Ok(()); } - Mode::Readonly | Mode::FrozenReadonly => { + Mode::Readonly => { return Err(DurableCatalogError::NotWritable( "cannot advance upper of a read-only catalog".into(), ) diff --git a/src/catalog/src/durable/transaction.rs b/src/catalog/src/durable/transaction.rs index 0c04b4fb44f0a..5aad91daf572f 100644 --- a/src/catalog/src/durable/transaction.rs +++ b/src/catalog/src/durable/transaction.rs @@ -58,12 +58,13 @@ use crate::durable::objects::{ DefaultPrivilegesValue, DurableType, GidMappingKey, GidMappingValue, IdAllocKey, IdAllocValue, IntrospectionSourceIndex, Item, ItemKey, ItemValue, MaintainedReadRequirement, 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, + 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, }) } @@ -567,7 +611,6 @@ impl<'a> Transaction<'a> { .zip_eq(oids) .map(|((builtin, item_id, index_id), oid)| (builtin, item_id, index_id, oid)) .collect(); - let mut index_ids = Vec::new(); for (builtin, item_id, index_id, oid) in introspection_source_indexes { let introspection_source_index = IntrospectionSourceIndex { cluster_id, @@ -580,9 +623,7 @@ impl<'a> Transaction<'a> { self.introspection_sources .insert(key, value, self.op_id) .expect("no uniqueness violation"); - index_ids.push(index_id); } - self.stage_index_compaction_bounds(index_ids)?; Ok(()) } @@ -728,7 +769,6 @@ impl<'a> Transaction<'a> { ), >, ) -> Result<(), CatalogError> { - let mut previous = BTreeMap::new(); for (cluster_id, updates) in mappings { for (name, item_id, index_id, oid) in updates { let introspection_source_index = IntrospectionSourceIndex { @@ -742,13 +782,8 @@ impl<'a> Transaction<'a> { let prev = self .introspection_sources - .set(key.clone(), Some(value), self.op_id)?; - if let Some(prev) = prev { - // Repeated keys must compare the first ID with the final ID. - previous - .entry(key) - .or_insert_with(|| GlobalId::from(prev.global_id)); - } else { + .set(key, Some(value), self.op_id)?; + if prev.is_none() { return Err(SqlCatalogError::FailedBuiltinSchemaMigration(format!( "{index_id}" )) @@ -756,18 +791,7 @@ impl<'a> Transaction<'a> { } } } - let current = previous - .keys() - .map(|key| { - GlobalId::from( - self.introspection_sources - .get(key) - .expect("updated mapping exists") - .global_id, - ) - }) - .collect::>(); - self.reconcile_index_compaction_bounds(previous.into_values().collect(), current) + Ok(()) } pub fn insert_user_item( @@ -823,15 +847,11 @@ impl<'a> Transaction<'a> { extra_versions, ephemeral_owner_session, }; - let is_index = value.item_type() == CatalogItemType::Index; self.items .insert(ItemKey { id }, value, self.op_id) .map_err(|_| { CatalogError::from(SqlCatalogError::ItemAlreadyExists(id, item_name.to_owned())) })?; - if is_index { - self.stage_index_compaction_bounds([global_id])?; - } Ok(()) } @@ -932,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" ); @@ -964,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 @@ -1213,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(), @@ -1476,14 +1497,8 @@ impl<'a> Transaction<'a> { // storage layer. self.cluster_replicas .delete(|_k, v| clusters.contains(&v.cluster_id), self.op_id); - let deleted = self - .introspection_sources + self.introspection_sources .delete(|k, _v| clusters.contains(&k.cluster_id), self.op_id); - self.remove_index_compaction_bounds( - deleted - .into_iter() - .map(|(_, v)| GlobalId::from(v.global_id)), - ); Ok(()) } @@ -1543,10 +1558,7 @@ impl<'a> Transaction<'a> { /// DO NOT call this function in a loop, use [`Self::remove_items`] instead. pub fn remove_item(&mut self, id: CatalogItemId) -> Result<(), CatalogError> { let prev = self.items.set(ItemKey { id }, None, self.op_id)?; - if let Some(value) = prev { - if value.item_type() == CatalogItemType::Index { - self.remove_index_compaction_bounds([value.global_id]); - } + if prev.is_some() { Ok(()) } else { Err(SqlCatalogError::UnknownItem(id.to_string()).into()) @@ -1567,12 +1579,6 @@ impl<'a> Transaction<'a> { let ks: Vec<_> = ids.clone().into_iter().map(|id| ItemKey { id }).collect(); let deleted = self.items.delete_by_keys(ks, self.op_id); let n = deleted.len(); - self.remove_index_compaction_bounds( - deleted - .into_iter() - .filter(|(_, v)| v.item_type() == CatalogItemType::Index) - .map(|(_, v)| v.global_id), - ); if n == ids.len() { Ok(()) } else { @@ -1607,12 +1613,6 @@ impl<'a> Transaction<'a> { .collect(); let deleted = self.system_gid_mapping.delete_by_keys(ks, self.op_id); let n = deleted.len(); - self.remove_index_compaction_bounds( - deleted - .into_iter() - .filter(|(k, _)| k.object_type == CatalogItemType::Index) - .map(|(_, v)| GlobalId::from(v.global_id)), - ); if n == descriptions.len() { Ok(()) @@ -1658,11 +1658,6 @@ impl<'a> Transaction<'a> { .collect(); let deleted = self.introspection_sources.delete_by_keys(ks, self.op_id); let n = deleted.len(); - self.remove_index_compaction_bounds( - deleted - .into_iter() - .map(|(_, v)| GlobalId::from(v.global_id)), - ); if n == introspection_source_indexes.len() { Ok(()) } else { @@ -1686,21 +1681,12 @@ 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 previous = self - .items - .get(&ItemKey { id }) - .filter(|v| v.item_type() == CatalogItemType::Index) - .map(|v| v.global_id); let value = item.into_key_value().1; - let current = (value.item_type() == CatalogItemType::Index).then_some(value.global_id); let updated = self .items .update_by_key(ItemKey { id }, value, self.op_id)?; if updated { - self.reconcile_index_compaction_bounds( - previous.into_iter().collect(), - current.into_iter().collect(), - ) + Ok(()) } else { Err(SqlCatalogError::UnknownItem(id.to_string()).into()) } @@ -1721,26 +1707,15 @@ impl<'a> Transaction<'a> { return Ok(()); } - let previous = items - .keys() - .filter_map(|id| self.items.get(&ItemKey { id: *id })) - .filter(|v| v.item_type() == CatalogItemType::Index) - .map(|v| v.global_id) - .collect::>(); let update_ids: BTreeSet<_> = items.keys().cloned().collect(); let kvs: Vec<_> = items .into_iter() .map(|(id, item)| (ItemKey { id }, item.into_key_value().1)) .collect(); - let current = kvs - .iter() - .filter(|(_, v)| v.item_type() == CatalogItemType::Index) - .map(|(_, v)| v.global_id) - .collect::>(); let n = self.items.update_by_keys(kvs, self.op_id)?; let n = usize::try_from(n.into_inner()).expect("Must be positive and fit in usize"); if n == update_ids.len() { - self.reconcile_index_compaction_bounds(previous, current) + Ok(()) } else { let item_ids: BTreeSet<_> = self.items.items().keys().map(|k| k.id).collect(); let mut unknown = update_ids.difference(&item_ids); @@ -1853,16 +1828,10 @@ impl<'a> Transaction<'a> { return Ok(()); } - let mut previous = BTreeSet::new(); - let mut current = BTreeSet::new(); let n = self.system_gid_mapping.update( - |k, v| { + |_k, v| { if let Some(mapping) = mappings.get(&CatalogItemId::from(v.catalog_id)) { let (_, new_value) = mapping.clone().into_key_value(); - if k.object_type == CatalogItemType::Index { - previous.insert(GlobalId::from(v.global_id)); - current.insert(GlobalId::from(new_value.global_id)); - } Some(new_value) } else { None @@ -1878,7 +1847,7 @@ impl<'a> Transaction<'a> { return Err(SqlCatalogError::FailedBuiltinSchemaMigration(id_str).into()); } - self.reconcile_index_compaction_bounds(previous, current) + Ok(()) } /// Updates cluster `id` in the transaction to `cluster`. @@ -2144,15 +2113,12 @@ impl<'a> Transaction<'a> { ) .collect(); - let mut index_ids = Vec::new(); for introspection_source_index in introspection_source_indexes { - let id = introspection_source_index.index_id; let (key, value) = introspection_source_index.into_key_value(); self.introspection_sources.insert(key, value, self.op_id)?; - index_ids.push(id); } - self.stage_index_compaction_bounds(index_ids) + Ok(()) } /// Set persisted system object mappings. @@ -2169,26 +2135,8 @@ impl<'a> Transaction<'a> { .map(DurableType::into_key_value) .map(|(k, v)| (k, Some(v))) .collect(); - let current = mappings - .iter() - .filter(|(k, _)| k.object_type == CatalogItemType::Index) - .map(|(_, v)| { - GlobalId::from( - v.as_ref() - .expect("only mapping insertions are staged") - .global_id, - ) - }) - .collect::>(); - let previous = self.system_gid_mapping.set_many(mappings, self.op_id)?; - self.reconcile_index_compaction_bounds( - previous - .into_iter() - .filter(|(k, _)| k.object_type == CatalogItemType::Index) - .filter_map(|(_, v)| v.map(|v| GlobalId::from(v.global_id))) - .collect(), - current, - ) + self.system_gid_mapping.set_many(mappings, self.op_id)?; + Ok(()) } /// Set persisted replica. @@ -2665,6 +2613,9 @@ impl<'a> Transaction<'a> { let Transaction { durable_catalog: _, + is_bootstrap_complete: _, + is_savepoint: _, + read_protection_index: _, databases, schemas, items, @@ -2819,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) { @@ -2836,10 +2787,10 @@ impl<'a> Transaction<'a> { /// Stages a storage collection's or index's compaction permission. /// - /// A first bound must accompany collection birth. For storage, the caller must + /// 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. Index installation follows [`Self::initialize_index_compaction_bounds`]. + /// 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, @@ -2905,205 +2856,225 @@ impl<'a> Transaction<'a> { Ok(()) } - /// Withholds compaction for indices born in this transaction when protection is enabled. - /// - /// Call after latching `catalog_read_protection_enabled` during bootstrap, in the - /// same transaction as index insertion. Existing ungoverned IDs are not adopted. - /// MIN grants no compaction permission. The caller must durably authorize the - /// actual installation frontier before creating the index in compute. - pub fn initialize_index_compaction_bounds(&mut self) -> Result<(), CatalogError> { - let initial = self.index_ids(true); - let born = self - .index_ids(false) - .difference(&initial) - .copied() - .filter(|id| { - !self - .storage_collection_metadata - .initial - .contains_key(&StorageCollectionMetadataKey { id: *id }) - }) - .collect::>(); - self.stage_index_compaction_bounds(born) - } - - fn index_ids(&self, initial: bool) -> BTreeSet { - let items = if initial { - self.items.initial.iter().collect() - } else { - self.items.items() - }; - let mappings = if initial { - self.system_gid_mapping.initial.iter().collect() - } else { - self.system_gid_mapping.items() - }; - let introspection = if initial { - self.introspection_sources.initial.iter().collect() - } else { - self.introspection_sources.items() - }; - items - .into_values() - .filter(|v| v.item_type() == CatalogItemType::Index) - .map(|v| v.global_id) - .chain( - mappings - .into_iter() - .filter(|(k, _)| k.object_type == CatalogItemType::Index) - .map(|(_, v)| GlobalId::from(v.global_id)), - ) - .chain( - introspection - .into_values() - .map(|v| GlobalId::from(v.global_id)), - ) - .collect() + 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 stage_index_compaction_bounds( - &mut self, - ids: impl IntoIterator, - ) -> Result<(), CatalogError> { - if self.get_config("catalog_read_protection_enabled".to_string()) != Some(1) { - return Ok(()); + 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); + } } - let bounds = ids - .into_iter() - .filter_map(|id| { - let key = CollectionCompactionBoundKey { id }; - self.collection_compaction_bounds - .get(&key) - .is_none() - .then_some(( - key, - Some(CollectionCompactionBoundValue { - frontier: Some(mz_repr::Timestamp::MIN), - }), - )) - }) - .collect(); - self.collection_compaction_bounds - .set_many(bounds, self.op_id)?; - Ok(()) + index } - fn remove_index_compaction_bounds(&mut self, ids: impl IntoIterator) { - // Storage protection belongs to storage metadata removal, not item cleanup. - let keys = ids + /// 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| { - self.storage_collection_metadata - .get(&StorageCollectionMetadataKey { id: *id }) - .is_none() + 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(keys, self.op_id); + .delete_by_keys(retired, self.op_id); } - fn reconcile_index_compaction_bounds( - &mut self, - previous: BTreeSet, - current: BTreeSet, - ) -> Result<(), CatalogError> { - // Reconcile the whole batch so swapping IDs does not reset their bounds. - self.remove_index_compaction_bounds(previous.difference(¤t).copied()); - self.stage_index_compaction_bounds(current.difference(&previous).copied()) - } - - /// Validates the final read protection state without committing it. - /// Call before entering a commit path that treats errors as fatal. + /// 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> { - if self.storage_collection_metadata.pending.is_empty() - && self.items.pending.is_empty() - && self.system_gid_mapping.pending.is_empty() - && self.introspection_sources.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 indexes = self.index_ids(false); - let initial_indexes = self.index_ids(true); - let owns_bound = |id| { - storage.contains_key(&StorageCollectionMetadataKey { id }) || indexes.contains(&id) + 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 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 !owns_bound(key.id) { - continue; + 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 cannot adopt a bound without conversion. - if (self - .storage_collection_metadata - .initial - .contains_key(&StorageCollectionMetadataKey { id: key.id }) - || initial_indexes.contains(&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 !owns_bound(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 neither storage metadata nor an index identity", - 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 { - // Index arrangements are not logical recovery boundaries. - let readable = storage.contains_key(&StorageCollectionMetadataKey { id: *input }) - && 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"), + )?; } } } @@ -3120,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 @@ -3167,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. @@ -3280,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(), @@ -3356,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( |( diff --git a/src/catalog/tests/compute-read-protection.rs b/src/catalog/tests/compute-read-protection.rs index 4816926ba2a29..f9b58c62fa8ea 100644 --- a/src/catalog/tests/compute-read-protection.rs +++ b/src/catalog/tests/compute-read-protection.rs @@ -131,12 +131,14 @@ fn bounds(snapshot: Snapshot) -> BTreeMap> { } 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}"); @@ -174,15 +176,11 @@ async fn index_birth_bootstrap_and_drop() { assert!(bounds(txn.current_snapshot()).is_empty()); txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) .unwrap(); - txn.initialize_index_compaction_bounds().unwrap(); + txn.finalize_index_compaction_bounds(); for owner in OWNERS { owner.insert(&mut txn, 1001); } - let expected: BTreeMap<_, _> = OWNERS - .into_iter() - .flat_map(|owner| [1000, 1001].map(|n| (owner.id(n), Some(Timestamp::MIN)))) - .collect(); - assert_eq!(bounds(txn.current_snapshot()), expected); + assert!(bounds(txn.current_snapshot()).is_empty()); commit(txn).await; Box::new(state).expire().await; @@ -193,6 +191,16 @@ async fn index_birth_bootstrap_and_drop() { .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(); @@ -212,10 +220,11 @@ async fn index_birth_bootstrap_and_drop() { )), ))) .unwrap(); + txn.finalize_index_compaction_bounds(); for owner in OWNERS { let current = bounds(txn.current_snapshot()); assert!(!current.contains_key(&owner.id(1001))); - assert_eq!(current[&owner.id(1002)], Some(Timestamp::MIN)); + assert!(!current.contains_key(&owner.id(1002))); } commit(txn).await; let mut txn = state.transaction().await.unwrap(); @@ -262,6 +271,8 @@ async fn surviving_index_permissions_are_monotonic() { 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(); @@ -269,7 +280,10 @@ async fn surviving_index_permissions_are_monotonic() { txn.set_config("catalog_read_protection_enabled".to_string(), Some(0)) .unwrap(); owner.insert(&mut txn, 1000); - reject(txn, "lost its compaction bound").await; + 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); } @@ -280,7 +294,7 @@ async fn surviving_index_permissions_are_monotonic() { .unwrap(); txn.update_system_object_mappings(BTreeMap::from([(Owner::Builtin.item_id(1000), mapping)])) .unwrap(); - txn.initialize_index_compaction_bounds().unwrap(); + txn.finalize_index_compaction_bounds(); assert_eq!(bounds(txn.current_snapshot()), expected); for owner in OWNERS { owner.remove(&mut txn, 1000); @@ -359,7 +373,15 @@ async fn index_id_swaps_preserve_bounds() { ]) .unwrap(); - // The intermediate ID must neither acquire a bound nor mask a surviving ID. + 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)] @@ -374,9 +396,23 @@ async fn index_id_swaps_preserve_bounds() { }), ))) .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; } @@ -397,23 +433,22 @@ async fn cannot_adopt_live_ungoverned_indices() { } commit(txn).await; - let mut txn = state.transaction().await.unwrap(); - txn.set_config("catalog_read_protection_enabled".to_string(), Some(1)) - .unwrap(); - txn.initialize_index_compaction_bounds().unwrap(); - Owner::Builtin.insert(&mut txn, 1000); - assert!(bounds(txn.current_snapshot()).is_empty()); - 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; @@ -435,6 +470,8 @@ async fn index_permission_is_not_a_recovery_boundary() { .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())])) @@ -463,12 +500,22 @@ async fn index_permission_is_not_a_recovery_boundary() { ("CREATE VIEW v AS SELECT 1", CatalogItemType::View), ] { let mut txn = state.transaction().await.unwrap(); - let mut item = txn.get_item(&Owner::Ordinary.item_id(1000)).unwrap(); - item.create_sql = sql.to_string(); - txn.update_item(item.id, item).unwrap(); - assert!(!bounds(txn.current_snapshot()).contains_key(&Owner::Ordinary.id(1000))); - txn.set_collection_compaction_bound(Owner::Ordinary.id(1000), None) + 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(); @@ -518,6 +565,10 @@ async fn cluster_and_ephemeral_cleanup() { 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(); @@ -535,6 +586,7 @@ async fn cluster_and_ephemeral_cleanup() { 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()))]) @@ -544,3 +596,155 @@ async fn cluster_and_ephemeral_cleanup() { 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 fd6b81f0d9b96..372f9085b34af 100644 --- a/src/catalog/tests/read-write.rs +++ b/src/catalog/tests/read-write.rs @@ -810,12 +810,6 @@ async fn test_non_writer_commits(state_builder: TestCatalogStateBuilder) { let _ = savepoint_state.sync_to_current_updates().await.unwrap(); let _ = reader_state.sync_to_current_updates().await.unwrap(); - assert!(writer_state.freeze().await.is_err()); - assert!(savepoint_state.freeze().await.is_err()); - let upper = writer_state.current_upper().await; - assert!(writer_state.freeze_at(upper).await.is_err()); - assert!(savepoint_state.freeze_at(upper).await.is_err()); - // Commit write with writer. let role_name = "joe"; let role_id = { @@ -891,8 +885,11 @@ async fn test_non_writer_commits(state_builder: TestCatalogStateBuilder) { #[mz_ore::test(tokio::test)] #[cfg_attr(miri, ignore)] -async fn test_persist_freeze_read_only() { - let builder = TestCatalogStateBuilder::new(PersistClient::new_for_tests().await) +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() @@ -902,42 +899,44 @@ async fn test_persist_freeze_read_only() { .await .unwrap(); let _ = writer.sync_to_current_updates().await.unwrap(); - let mut frozen = builder - .clone() - .unwrap_build() - .await - .open_read_only(&test_bootstrap_args()) - .await - .unwrap(); - let mut live = builder - .clone() - .unwrap_build() - .await - .open_read_only(&test_bootstrap_args()) - .await - .unwrap(); - let mut exact = builder - .clone() - .unwrap_build() - .await - .open_read_only(&test_bootstrap_args()) - .await - .unwrap(); - let mut before_fence = builder - .clone() - .unwrap_build() - .await - .open_read_only(&test_bootstrap_args()) - .await + 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(); - let opened_upper = exact.current_upper().await; - let err = exact - .freeze_at(opened_upper.saturating_sub(1)) + ( + 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_err(); - assert!(err.to_string().contains("backwards"), "{err}"); + .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() + ); - // Commit after opening the diagnostic handle so freezing must synchronize it. let input = GlobalId::User(1000); let output = GlobalId::User(1001); let mut txn = writer.transaction().await.unwrap(); @@ -971,25 +970,19 @@ async fn test_persist_freeze_read_only() { txn.commit(ts).await.unwrap(); let expected_upper = writer.current_upper().await; - frozen.freeze().await.unwrap(); - assert!(frozen.is_read_only()); - assert!(!frozen.is_savepoint()); - let frozen_upper = frozen.current_upper().await; - assert_eq!(frozen_upper, expected_upper); - assert_eq!(writer.current_upper().await, expected_upper); - assert_eq!(frozen.snapshot().await.unwrap(), expected); - let initial_updates = frozen.sync_to_current_updates().await.unwrap(); - assert!(!initial_updates.is_empty()); + let replayed = reader(persist.clone()).await; assert_eq!( - initial_updates, - live.sync_to_current_updates().await.unwrap() + replayed + .into_snapshot_at(expected_upper) + .await + .unwrap() + .snapshot, + expected ); - let initial_id = frozen.get_next_user_item_id().await.unwrap(); - 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]), Some(20.into())) + 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()) @@ -999,103 +992,72 @@ async fn test_persist_freeze_read_only() { let ts = txn.upper(); txn.commit(ts).await.unwrap(); let later_upper = writer.current_upper().await; - assert!(later_upper > frozen_upper); - - // Both commits are durable before this reader synchronizes. The exclusive boundary must - // exclude the bound changes and deletion even if listen delivers them in the same batch. - exact.freeze_at(expected_upper).await.unwrap(); - assert!(exact.is_read_only()); - assert!(!exact.is_savepoint()); - assert_eq!(exact.current_upper().await, expected_upper); - assert_eq!(exact.snapshot().await.unwrap(), expected); + 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!( - exact.sync_to_current_updates().await.unwrap(), - initial_updates + live.into_snapshot_at(later_upper).await.unwrap().snapshot, + changed ); - assert!(exact.freeze_at(later_upper).await.is_err()); - assert!(exact.freeze_at(opened_upper).await.is_err()); - for _ in 0..2 { - exact.freeze_at(expected_upper).await.unwrap(); - exact.freeze().await.unwrap(); - assert_eq!(exact.snapshot().await.unwrap(), expected); - assert_eq!(exact.current_upper().await, expected_upper); - assert!(exact.sync_updates(later_upper).await.unwrap().is_empty()); - let txn = exact.transaction().await.unwrap(); - assert_eq!(txn.current_snapshot(), expected); - txn.commit(expected_upper).await.unwrap(); - } - let mut txn = exact.transaction().await.unwrap(); - txn.get_and_increment_id(USER_ITEM_ALLOC_KEY.into()) - .unwrap(); - let _ = txn.get_and_commit_op_updates(); - assert!(matches!( - txn.commit(expected_upper).await, - Err(CatalogError::Durable(DurableCatalogError::NotWritable(_))) - )); - - frozen.freeze().await.unwrap(); - assert_eq!(frozen.snapshot().await.unwrap(), expected); - assert_eq!(frozen.get_next_user_item_id().await.unwrap(), initial_id); - assert!(frozen.sync_updates(later_upper).await.unwrap().is_empty()); - assert!(frozen.sync_to_current_updates().await.unwrap().is_empty()); - assert_eq!(frozen.current_upper().await, frozen_upper); - let txn = frozen.transaction().await.unwrap(); - assert_eq!(txn.current_snapshot(), expected); - txn.commit(frozen_upper).await.unwrap(); - - let mut txn = frozen.transaction().await.unwrap(); - txn.get_and_increment_id(USER_ITEM_ALLOC_KEY.into()) - .unwrap(); - let _ = txn.get_and_commit_op_updates(); - assert!(matches!( - txn.commit(frozen_upper).await, - Err(CatalogError::Durable(DurableCatalogError::NotWritable(_))) - )); - assert!(matches!( - frozen.advance_upper(later_upper).await, - Err(CatalogError::Durable(DurableCatalogError::NotWritable(_))) - )); - assert!(matches!( - frozen.get_audit_logs().await, - Err(CatalogError::Durable(DurableCatalogError::Internal(_))) - )); - assert_eq!(frozen.snapshot().await.unwrap(), expected); - assert_eq!(live.snapshot().await.unwrap(), changed); - assert!(!live.sync_to_current_updates().await.unwrap().is_empty()); - assert_eq!(live.get_next_user_item_id().await.unwrap(), initial_id + 1); - assert_eq!(writer.current_upper().await, later_upper); - - let next_writer = builder + let mut next_writer = builder + .clone() .unwrap_build() .await .open(SYSTEM_TIME().into(), &test_bootstrap_args()) .await .unwrap(); - let fenced_upper = live.current_upper().await; - assert!(matches!( - live.freeze_at(fenced_upper).await, - Err(CatalogError::Durable(DurableCatalogError::Fence(_))) - )); + let fenced_upper = next_writer.current_upper().await; assert!(matches!( - live.snapshot().await, + at_fence.into_snapshot_at(fenced_upper).await, Err(CatalogError::Durable(DurableCatalogError::Fence(_))) )); - assert!(matches!( - live.freeze_at(fenced_upper).await, - Err(CatalogError::Durable(DurableCatalogError::Fence(_))) - )); - // A fence outside the captured prefix does not invalidate diagnostic data. - before_fence.freeze_at(expected_upper).await.unwrap(); - assert_eq!(before_fence.snapshot().await.unwrap(), expected); - assert_eq!(before_fence.current_upper().await, expected_upper); - assert_eq!(exact.snapshot().await.unwrap(), expected); - assert_eq!(frozen.snapshot().await.unwrap(), expected); + 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; - before_fence.expire().await; - exact.expire().await; - frozen.expire().await; - live.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 diff --git a/src/compute-client/src/as_of_selection.rs b/src/compute-client/src/as_of_selection.rs index 5b966f0893ac9..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!() } @@ -1178,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] @@ -1210,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, @@ -1226,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 f8e495b49a23e..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; @@ -183,6 +184,7 @@ 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`). @@ -198,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. @@ -242,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, @@ -305,11 +312,13 @@ 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, @@ -471,11 +480,13 @@ impl ComputeController { 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, @@ -511,6 +522,7 @@ impl ComputeController { "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, })) @@ -532,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(self.staged_compaction_bounds.remove(&id)); + 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)); @@ -820,8 +839,9 @@ 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 must be at or beyond every index export's `as_of`. - /// Recovery requiring a later `as_of` must commit and apply permission before creation. + /// 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 @@ -855,13 +875,6 @@ impl ComputeController { if as_of.is_empty() && dataflow.copy_to_ids().next().is_some() { return Err(EmptyAsOfForCopyTo); } - for id in dataflow.exported_index_ids() { - if let Some(bound) = self.staged_compaction_bounds.get(&id) { - if !PartialOrder::less_equal(as_of, bound) { - return Err(CompactionBoundViolation(id)); - } - } - } // Validation: the dataflow exports something // @@ -909,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 @@ -931,8 +956,16 @@ impl ComputeController { for id in dataflow.export_ids() { let bound = staged_bounds.remove(&id); let write_only = dataflow.sink_exports.contains_key(&id); - let shared = - SharedCollectionState::new(as_of.clone(), if write_only { None } else { bound }); + 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, compute_dependencies: dataflow.imported_index_ids().collect(), @@ -1073,6 +1106,9 @@ impl ComputeController { 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 { @@ -1095,20 +1131,39 @@ impl ComputeController { Ok(()) } - /// Return compaction candidates for installed governed indexes, including logging indexes. + /// 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. - pub fn compaction_bound_proposals(&self) -> BTreeMap> { - self.instances - .values() - .flat_map(|instance| &instance.collections) - .filter_map(|(&id, collection)| { - collection - .shared - .compaction_bound_proposal() - .map(|bound| (id, bound)) + /// 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() } @@ -1376,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 diff --git a/src/compute-client/src/controller/compaction_tests.rs b/src/compute-client/src/controller/compaction_tests.rs index feecdcbd7c1ab..7875dfab24810 100644 --- a/src/compute-client/src/controller/compaction_tests.rs +++ b/src/compute-client/src/controller/compaction_tests.rs @@ -62,6 +62,7 @@ fn controller() -> ComputeController { &DUMMY_BUILD_INFO, Arc::new(EmptyStorage), false, + false, ®istry, PersistLocation::new_in_mem(), ControllerMetrics::new(®istry), @@ -206,7 +207,7 @@ async fn compaction_permission_caps_inputs_and_commands_without_overriding_holds .await; observe(&controller, frontier(5)).await; assert_eq!( - controller.compaction_bound_proposals()[&OUTPUT], + controller.take_compaction_bound_proposals(&BTreeSet::new())[&OUTPUT], frontier(15) ); let (_, mut changes) = storage_rx.try_recv().unwrap(); @@ -230,7 +231,7 @@ async fn compaction_permission_caps_inputs_and_commands_without_overriding_holds }) .await; assert_eq!( - controller.compaction_bound_proposals()[&OUTPUT], + controller.take_compaction_bound_proposals(&BTreeSet::new())[&OUTPUT], frontier(20) ); let mut reader = controller.acquire_read_hold(INSTANCE, OUTPUT).unwrap(); @@ -242,7 +243,7 @@ async fn compaction_permission_caps_inputs_and_commands_without_overriding_holds } observe(&controller, frontier(7)).await; assert_eq!( - controller.compaction_bound_proposals()[&OUTPUT], + controller.take_compaction_bound_proposals(&BTreeSet::new())[&OUTPUT], frontier(7) ); reader.release(); @@ -257,7 +258,7 @@ async fn compaction_permission_caps_inputs_and_commands_without_overriding_holds observe(&controller, frontier(20)).await; assert!( !controller - .compaction_bound_proposals() + .take_compaction_bound_proposals(&BTreeSet::from([OUTPUT])) .contains_key(&OUTPUT) ); drop(reader); @@ -278,7 +279,10 @@ async fn compaction_bounds_are_staged_before_creation_and_never_adopt_live_expor .unwrap(); } create_instance(&mut controller); - assert_eq!(controller.compaction_bound_proposals()[&INPUT], frontier(0)); + 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!( @@ -294,12 +298,16 @@ async fn compaction_bounds_are_staged_before_creation_and_never_adopt_live_expor .create_dataflow(INSTANCE, dataflow(6, false), None) .unwrap(); assert_eq!( - controller.compaction_bound_proposals()[&OUTPUT], + controller.take_compaction_bound_proposals(&BTreeSet::new())[&OUTPUT], frontier(6) ); drop(reader); controller.drop_instance(INSTANCE); - assert!(controller.compaction_bound_proposals().is_empty()); + assert!( + controller + .take_compaction_bound_proposals(&BTreeSet::new()) + .is_empty() + ); assert!(controller.staged_compaction_bounds.is_empty()); create_instance(&mut controller); @@ -313,7 +321,11 @@ async fn compaction_bounds_are_staged_before_creation_and_never_adopt_live_expor controller .create_dataflow(INSTANCE, dataflow(9, true), None) .unwrap(); - assert!(controller.compaction_bound_proposals().is_empty()); + 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)), @@ -356,7 +368,7 @@ async fn compaction_proposals_observe_permission_counts_atomically() { .unwrap(); drop(reader.clone()); assert_eq!( - controller.compaction_bound_proposals()[&INPUT], + controller.take_compaction_bound_proposals(&BTreeSet::from([INPUT]))[&INPUT], frontier(1000) ); } @@ -383,12 +395,295 @@ async fn compaction_proposals_observe_permission_counts_atomically() { .call_sync(drain) .await; assert_eq!( - controller.compaction_bound_proposals()[&INPUT], + 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 _ 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(proposals); + } + for (time, proposals) in (1..=100).zip(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; @@ -428,7 +723,10 @@ impl StorageCollections for EmptyStorage { unimplemented!("storage is not used by these compute tests") } - fn compaction_frontiers(&self) -> BTreeMap> { + fn take_read_protection_frontiers( + &self, + _additional_ids: &BTreeSet, + ) -> BTreeMap)> { unimplemented!("storage is not used by these compute tests") } diff --git a/src/compute-client/src/controller/instance.rs b/src/compute-client/src/controller/instance.rs index 60b3b21305f9a..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}; @@ -1874,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(()) @@ -1973,6 +1980,9 @@ impl Instance { // 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). @@ -2491,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 @@ -2626,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); }); @@ -2738,9 +2748,7 @@ pub(super) struct SharedCollectionState { impl SharedCollectionState { pub fn new(as_of: Antichain, permission: Option>) -> Self { - if let Some(bound) = &permission { - assert!(PartialOrder::less_equal(&as_of, bound)); - } + 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`. @@ -2759,17 +2767,36 @@ impl SharedCollectionState { 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.holds) + 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> { @@ -2780,8 +2807,13 @@ impl SharedCollectionState { .clone() } - pub fn compaction_bound_proposal(&self) -> Option> { + 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))); @@ -2801,9 +2833,16 @@ impl SharedCollectionState { 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( 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 742134b54332f..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; @@ -20,30 +21,30 @@ use crate::http::AuthedClient; pub async fn handle_catalog_dump(mut client: AuthedClient) -> impl IntoResponse { match client.client.dump_catalog().await { - Ok((dump, upper)) => { - let mut headers = http::HeaderMap::new(); - if let Some(upper) = upper { - headers.insert( - "mz-catalog-upper", - http::HeaderValue::from_str(&upper.to_string()) - .expect("timestamp is a valid header value"), - ); - } - Ok(( - TypedHeader(ContentType::json()), - headers, - dump.into_string(), - )) - } + 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 94fdde5e49f2b..a11d88e10c6fa 100644 --- a/src/environmentd/src/lib.rs +++ b/src/environmentd/src/lib.rs @@ -728,7 +728,7 @@ impl Listeners { adapter_storage }; - let catalog_subscriber = if read_only + let compaction_bound_subscriber = if read_only && adapter_storage .snapshot() .await? @@ -810,7 +810,7 @@ impl Listeners { controller_config: config.controller, controller_envd_epoch: envd_epoch, storage: adapter_storage, - catalog_subscriber, + 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 6f3e0e8abda66..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.", + 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/storage_collections.rs b/src/storage-client/src/storage_collections.rs index 102c3ac217771..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. @@ -360,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, @@ -409,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, @@ -518,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 = @@ -537,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(), @@ -568,6 +581,7 @@ impl StorageCollectionsImpl { finalizable_shards, finalized_shards, collections, + dirty_read_protection, txns_read, envd_epoch, read_only, @@ -960,6 +974,7 @@ impl StorageCollectionsImpl { StorageCollectionsImpl::update_read_capabilities_inner( &self.cmd_tx, + &self.dirty_read_protection, self_collections, &mut storage_read_updates, ); @@ -1181,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() { @@ -1192,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, ); @@ -1203,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>, ) { @@ -1258,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); @@ -1469,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() } @@ -2179,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(_) => { @@ -2416,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, ); @@ -2499,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 { @@ -2511,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, ); @@ -2587,7 +2651,12 @@ 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(()) } @@ -2637,6 +2706,7 @@ impl StorageCollections for StorageCollectionsImpl { StorageCollectionsImpl::update_read_capabilities_inner( &self.cmd_tx, + &self.dirty_read_protection, &mut collections, &mut updates, ); @@ -2670,6 +2740,7 @@ impl StorageCollections for StorageCollectionsImpl { finalizable_shards, finalized_shards, collections, + dirty_read_protection: _, txns_read: _, config, initial_txn_upper, @@ -2924,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, @@ -3177,6 +3249,7 @@ impl BackgroundTask { StorageCollectionsImpl::update_read_capabilities_inner( &self.cmds_tx, + &self.dirty_read_protection, &mut collections, &mut batched_changes, ); @@ -3205,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 @@ -3236,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, ); @@ -3833,6 +3913,187 @@ mod tests { } } + #[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; @@ -3881,7 +4142,10 @@ mod tests { .apply_compaction_bounds(BTreeMap::from([(id, frontier(10))])) .unwrap(); assert_eq!(controller.compaction_bound(id).unwrap(), Some(frontier(10))); - assert_eq!(controller.compaction_frontiers()[&id], frontier(5)); + assert_eq!( + controller.take_read_protection_frontiers(&BTreeSet::from([id]))[&id].1, + frontier(5) + ); assert_eq!( controller .collection_frontiers(id) @@ -3892,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)), @@ -4928,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/lib.rs b/src/storage-controller/src/lib.rs index 23b6c9d3eb8fd..1a8ca5396ce34 100644 --- a/src/storage-controller/src/lib.rs +++ b/src/storage-controller/src/lib.rs @@ -4158,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, + collections.clone(), + ) + .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/testdrive/src/action/consistency.rs b/src/testdrive/src/action/consistency.rs index 05d92a2632a35..8f72863cbf024 100644 --- a/src/testdrive/src/action/consistency.rs +++ b/src/testdrive/src/action/consistency.rs @@ -20,12 +20,10 @@ use crate::parser::{BuiltinCommand, LineReader, parse}; use anyhow::{Context, anyhow, bail}; use mz_adapter::catalog::{Catalog, DebugAwsContext}; use mz_adapter::session::Session; -use mz_catalog::durable::DurableCatalogState; use mz_ore::now::SYSTEM_TIME; use mz_ore::retry::{Retry, RetryResult}; -use mz_persist_client::{PersistClient, PersistLocation, ShardId}; +use mz_persist_client::{PersistLocation, ShardId}; use mz_postgres_util::{query_one, sql}; -use mz_repr::Timestamp; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; @@ -188,68 +186,53 @@ async fn check_coordinator(state: &State) -> Result<(), anyhow::Error> { } } -/// Acquires a frozen durable handle matching a full memory dump. -async fn aligned_catalog_snapshot( - state: &State, -) -> Result)>, anyhow::Error> { - async fn memory_dump(state: &State) -> Result<(String, Option), anyhow::Error> { - let response = reqwest::get(format!( - "http://{}/api/catalog/dump", - state.materialize.internal_http_addr, - )) - .await - .context("GET catalog")? - .error_for_status()?; - let upper = response - .headers() - .get("mz-catalog-upper") - .map(|value| { - value - .to_str() - .context("catalog upper header")? - .parse::() - .map(Timestamp::from) - .context("catalog upper timestamp") - }) - .transpose()?; - let dump = response.text().await.context("deserialize catalog")?; - Ok((dump, upper)) +/// 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 Ok(()); } - - loop { - let Some((persist_client, mut storage)) = state.open_catalog_copy().await? else { - return Ok(None); + 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 capture = async { - let (before, upper) = memory_dump(state).await?; - if let Some(upper) = upper { - // The reader was opened before capture. Its continuous Listen preserves - // the prefix even if publication advances while the dump is serialized. - storage.freeze_at(upper).await?; - return Ok(Some(before)); - } - // Servers without a durable prefix certificate require a stable window. - storage.freeze().await?; - let (after, _) = memory_dump(state).await?; - Ok::<_, anyhow::Error>((before == after).then_some(before)) - } - .await; - match capture { - Ok(Some(memory)) => return Ok(Some((memory, persist_client, storage))), - Ok(None) => { - storage.expire().await; - tracing::debug!("catalog changed during snapshot acquisition, retrying"); - } - Err(error) => { - storage.expire().await; - return Err(error); - } + } + 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 } + 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>, @@ -261,20 +244,11 @@ async fn check_catalog_state(state: &State) -> Result<(), anyhow::Error> { storage_metadata: Option, } - // Catalog comparison requires --validate-catalog-store. - if state.materialize.catalog_config.is_none() { - return Ok(()); - } - - let Some((memory_catalog, persist_client, storage)) = aligned_catalog_snapshot(state) - .await - .context("acquiring aligned catalog snapshot")? - 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 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. @@ -288,12 +262,7 @@ async fn check_catalog_state(state: &State) -> Result<(), anyhow::Error> { let Some(system_parameter_defaults) = dump.system_parameter_defaults else { storage.expire().await; - // 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(()); + bail!("missing system_parameter_defaults in memory catalog state"); }; let unfinalized_shards = dump @@ -321,16 +290,17 @@ async fn check_catalog_state(state: &State) -> Result<(), anyhow::Error> { let disk_catalog = catalog .for_session(&Session::dummy()) .state() - // 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. + // 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 // first lines that differs and show context around it. diff --git a/test/0dt/mzcompose.py b/test/0dt/mzcompose.py index b5e22ed91c611..edf22bb93c164 100644 --- a/test/0dt/mzcompose.py +++ b/test/0dt/mzcompose.py @@ -1049,7 +1049,12 @@ def query(sql: str, service: str = "mz_old") -> list[tuple]: query("ALTER SYSTEM SET enable_logical_compaction_window = true") query("ALTER SYSTEM SET enable_index_options = true") + 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; @@ -1058,27 +1063,18 @@ def query(sql: str, service: str = "mz_old") -> list[tuple]: 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'); """)) ids = dict(query(""" 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 ('permission_keep_idx', 'permission_drop_idx') + WHERE o.name IN ('permission_keep_idx', 'permission_drop_idx', + 'permission_suspended_idx', 'permission_bootstrap_drop_idx') """)) - def bound_sql(global_id: str) -> str: - key = json.dumps({"User": int(global_id[1:])}) - return ( - "SELECT (data->'value'->>'frontier')::numeric " - "FROM mz_internal.mz_catalog_raw " - "WHERE data->>'kind' = 'CollectionCompactionBound' " - f"AND data->'key'->'id' = '{key}'::jsonb" - ) - - # Freezing publication before boot makes the savepoint permission observable. - query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '0s'") - c.up("mz_new") - c.await_mz_deployment_status(DeploymentStatus.READY_TO_PROMOTE, "mz_new") - 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. @@ -1090,9 +1086,29 @@ def catalog_bounds(service: str) -> dict: def frozen_bounds() -> dict: bounds = catalog_bounds("mz_new") - return {name: bounds[gid]["elements"][0] for name, gid in ids.items()} + return {name: bounds.get(gid) for name, gid in ids.items()} + assert all(gid not in catalog_bounds("mz_old") 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") + assert all(gid not in catalog_bounds("mz_old") 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", @@ -1121,6 +1137,9 @@ def frozen_bounds() -> dict: > SELECT * FROM permission_drop; 3 """)) + assert frozen_bounds() == frozen + assert all(gid not in catalog_bounds("mz_old") for gid in ids.values()) + assert time.monotonic() - unpublished_start < 300 query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '1s'") # The subscriber follows permissions, not SQL schema changes. A removed @@ -1130,12 +1149,31 @@ def frozen_bounds() -> dict: query("DROP INDEX permission_drop_idx") query(f"INSERT INTO permission_input VALUES ({value})") keep = ids["permission_keep_idx"] - baseline = query(bound_sql(keep))[0][0] + baseline = catalog_bounds("mz_old").get(keep, {"elements": [0]})[ + "elements" + ][0] deadline = time.monotonic() + 300 - while (committed := query(bound_sql(keep))[0][0]) <= baseline: + publication_start = time.monotonic() + while ( + committed := catalog_bounds("mz_old").get(keep, {"elements": [0]})[ + "elements" + ][0] + ) <= baseline: assert time.monotonic() < deadline, (keep, baseline, committed) time.sleep(0.5) - assert committed > frozen["permission_keep_idx"] + committed_observed = time.monotonic() + assert committed > 0 + while True: + [(read,)] = query( + "SELECT read_frontier::text::numeric FROM mz_internal.mz_frontiers " + f"WHERE object_id = '{keep}'", + "mz_new", + ) + if read is not None and read >= committed: + break + assert time.monotonic() < deadline, (keep, committed, read) + time.sleep(0.5) + follower_observed = time.monotonic() c.testdrive(dedent(f""" > SELECT read_frontier::text::numeric >= {committed} FROM mz_internal.mz_frontiers WHERE object_id = '{keep}'; @@ -1144,11 +1182,30 @@ def frozen_bounds() -> dict: > SELECT count(*), sum(a) FROM permission_keep; {value} {sum(range(2, value + 2))} """)) + print( + json.dumps( + { + "workflow": "index-read-protection", + "publication_interval_ms": 1000, + "observer": "writer catalog dump and follower SQL frontier polling at 500ms", + "committed_permission": committed, + "writer_permission_observation_seconds": committed_observed + - publication_start, + "writer_observed_to_follower_frontier_seconds": follower_observed + - committed_observed, + "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 = '0s'") + 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})") @@ -1163,6 +1220,8 @@ def frozen_bounds() -> dict: 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 def workflow_kafka_source_rehydration(c: Composition) -> None: diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index d0a8ddcb2db11..6a01f7dd2585f 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -1075,8 +1075,19 @@ def inspect(global_id: str) -> dict: > CREATE TABLE protected_catalog_probe (a int); """) td(""" - > CREATE TABLE protected_index_input (a int); + > 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 + """) + 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'); """) @@ -1086,18 +1097,49 @@ def inspect(global_id: str) -> dict: "SELECT read_frontier::text::numeric, write_frontier::text::numeric " f"FROM mz_internal.mz_frontiers WHERE object_id = '{index}'" ) - birth = record("CollectionCompactionBound", index)["frontier"] + + 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 (v->>'frontier')::numeric > {birth} + > 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 > {birth} FROM ({index_frontier_sql}) f; + > SELECT read_frontier > 0 FROM ({index_frontier_sql}) f; true """) - query("ALTER SYSTEM SET catalog_read_protection_publish_interval = '0s'") - # Observe the cap after stopping publication, then drive several controller - # progress reports past it. No peek is kept open to retain index history. - cap = record("CollectionCompactionBound", index)["frontier"] + 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) @@ -1108,6 +1150,8 @@ def inspect(global_id: str) -> dict: 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} @@ -1896,10 +1940,21 @@ 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") + parser.add_argument( + "--collection-counts", + default="100,1000,10000", + help="Total generated objects per step, tables plus any noncollection filler", + ) 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 constant SQL views (not collections)", + ) + 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(","))) @@ -1911,8 +1966,15 @@ def workflow_catalog_publication_measurement( or args.publication_rounds <= 0 or args.publication_interval_ms <= 0 or args.timeout_seconds <= 0 + or args.observation_seconds <= 0 + or ( + args.active_collections is not None + and not 0 < args.active_collections <= counts[0] + ) ): - parser.error("counts, rounds, interval, and timeout must be positive") + parser.error( + "counts, rounds, durations must be positive, active collections must be in 1..min(counts)" + ) def query(sql: str) -> list[tuple]: with c.sql_connection( @@ -2020,14 +2082,21 @@ def catalog_bounds() -> dict: c.up("materialized", Service("testdrive_no_reset", idle=True)) created = 0 for count in counts: + active = args.active_collections or count setup_start = time.monotonic() + # Tables advance their uppers even without DML. Sparse runs therefore + # grow catalog size with noncollection views, not supposedly idle tables. 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)", + ( + 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 VIEW publication_filler_{i} AS SELECT {i} AS a",) ) ] ) @@ -2039,7 +2108,7 @@ def catalog_bounds() -> dict: WHERE t.name LIKE 'publication_%' ORDER BY substring(t.name, 13)::int """)] - assert len(collection_gids) == count, (len(collection_gids), count) + assert len(collection_gids) == active, (len(collection_gids), active) stop = Event() def concurrent_ddl() -> list[float]: @@ -2057,7 +2126,7 @@ def concurrent_ddl() -> list[float]: latencies.append(time.monotonic() - start) return latencies - before = snapshot(count) + before = snapshot(active) rounds = [] with ThreadPoolExecutor(max_workers=1) as executor: ddl = executor.submit(concurrent_ddl) @@ -2067,7 +2136,7 @@ def concurrent_ddl() -> list[float]: execute_each( [ f"UPDATE publication_{i} SET a = a + 1" - for i in range(count) + for i in range(active) ] ) [(timestamp,)] = query( @@ -2077,42 +2146,60 @@ def concurrent_ddl() -> list[float]: json.loads(timestamp)["determination"]["oracle_read_ts"] ) publication_start = time.monotonic() + observation_end = publication_start + args.observation_seconds + observed_publication_seconds = None + observer_samples = 0 # Observe the writer's own committed catalog snapshot. Querying # mz_catalog_raw would measure that maintained collection's # ingestion of catalog history instead of publication. - while True: - bounds = catalog_bounds() - published = [ - bounds[gid]["elements"] - for gid in collection_gids[:count] - ] - if all( - elements and elements[0] > target - for elements in published - ): - break - if ( - time.monotonic() - publication_start - > args.timeout_seconds - ): - raise UIError( - f"publication for {count} collections did not pass " - f"{target} within {args.timeout_seconds}s" - ) - time.sleep(0.5) + while time.monotonic() < observation_end: + if args.observer == "poll": + bounds = catalog_bounds() + observer_samples += 1 + if observed_publication_seconds is None and all( + bounds[gid]["elements"] + and bounds[gid]["elements"][0] > target + for gid in collection_gids + ): + observed_publication_seconds = ( + time.monotonic() - publication_start + ) + time.sleep( + max(0, min(0.5, observation_end - time.monotonic())) + ) rounds.append( { "revision": revision, "target_timestamp": target, "write_seconds": publication_start - write_start, - "publication_wait_seconds": time.monotonic() + "observation_seconds": time.monotonic() - publication_start, + "observed_publication_seconds": observed_publication_seconds, + "observer_samples": observer_samples, } ) finally: stop.set() ddl_latencies = ddl.result() - after = snapshot(count) + after = snapshot(active) + # Verify progress outside the cost window, including in no-poll runs. + # Passing the final target also covers every earlier round's target. + verification_start = time.monotonic() + target = max(round_["target_timestamp"] for round_ in rounds) + while True: + bounds = catalog_bounds() + if all( + bounds[gid]["elements"] and bounds[gid]["elements"][0] > target + for gid in collection_gids + ): + break + if time.monotonic() - verification_start > args.timeout_seconds: + raise UIError( + f"publication for {active} advancing collections did not pass " + f"{target} within {args.timeout_seconds}s after the cost window" + ) + time.sleep(0.5) + verification_seconds = time.monotonic() - verification_start elapsed = after["monotonic_seconds"] - before["monotonic_seconds"] deltas = { metric: after["catalog_counters"][metric] @@ -2125,17 +2212,36 @@ def concurrent_ddl() -> list[float]: assert ddl_latencies, "no concurrent DDL completed" assert all(delta >= 0 for delta in deltas.values()), deltas # Totals include SQL writes, DDL, observers, and builtin maintenance. - # The wait includes the configured retention window and retry overhead. + # Publication observations include the retention window and retry overhead. print( json.dumps( { "workflow": "catalog-publication-measurement", - "collection_count": count, + "catalog_object_count": count, + "collection_count": active, + "noncollection_constant_view_count": count - active, + "scenario": ( + "all-advancing-tables" + if active == count + else "sparse-tables-with-noncollection-filler" + ), + "observer": args.observer, + "common_observers": "INSPECT, SQL, metrics at sample boundaries, EXPLAIN TIMESTAMP per round", + "post_sample_publication_verification_seconds": verification_seconds, "setup_seconds": setup_seconds, "publication_interval_ms": args.publication_interval_ms, "retention_ms": 1000, "rounds": rounds, "ddl_create_drop_seconds": ddl_latencies, + "ddl_create_drop_percentiles_seconds": { + f"p{percentile}": sorted(ddl_latencies)[ + min( + len(ddl_latencies) - 1, + (len(ddl_latencies) * percentile + 99) // 100 - 1, + ) + ] + for percentile in (50, 95, 99) + }, "publication_sample_seconds": elapsed, "catalog_counter_deltas": deltas, "catalog_diff_bytes_per_second": deltas[ From de2cf50ab79955cdff8ddd790d36c69dd6ad976e Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 22:40:36 +0000 Subject: [PATCH 40/41] catalog: Preserve diagnostic identity and satisfy CI lint Use the durable organization in debug catalog identities so independent reconstruction opens the same catalog. Keep the regression fixtures on the ordinary debug construction path. Keep concurrent proposal expectations with their observed timestamps, use explicit Arc cloning, and remove the trailing blank line from AGENTS.md. --- AGENTS.md | 1 - src/adapter/src/catalog.rs | 18 +++++++++++++++--- .../src/controller/compaction_tests.rs | 6 +++--- src/storage-controller/src/lib.rs | 2 +- 4 files changed, 19 insertions(+), 8 deletions(-) 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/src/adapter/src/catalog.rs b/src/adapter/src/catalog.rs index 072b42b115936..93ba6fb502b5c 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -471,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() @@ -508,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() @@ -540,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() diff --git a/src/compute-client/src/controller/compaction_tests.rs b/src/compute-client/src/controller/compaction_tests.rs index 7875dfab24810..789a1406593d5 100644 --- a/src/compute-client/src/controller/compaction_tests.rs +++ b/src/compute-client/src/controller/compaction_tests.rs @@ -665,15 +665,15 @@ async fn compaction_proposals_do_not_lose_concurrent_mutations() { } }); let mut observed = Vec::new(); - for _ in 1..=100 { + 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(proposals); + observed.push((time, proposals)); } - for (time, proposals) in (1..=100).zip(observed) { + for (time, proposals) in observed { assert_eq!(proposals, BTreeMap::from([(INPUT, frontier(time))])); } assert!( diff --git a/src/storage-controller/src/lib.rs b/src/storage-controller/src/lib.rs index 1a8ca5396ce34..a784d826fac84 100644 --- a/src/storage-controller/src/lib.rs +++ b/src/storage-controller/src/lib.rs @@ -4269,7 +4269,7 @@ mod tests { ControllerMetrics::new(®istry), context, &txn, - collections.clone(), + Arc::::clone(&collections), ) .await; (controller, collections, persist) From 44a20e3716929b1a8fb14764158505f97e0828e8 Mon Sep 17 00:00:00 2001 From: Aljoscha Krettek Date: Wed, 9 Sep 2026 23:01:00 +0000 Subject: [PATCH 41/41] test: Run controlled catalog protection evidence in CI Schedule the read-only recovery proof and controlled publication sweeps on dedicated PR CI workers. Dispatch changes are temporary and will be removed after collecting the evidence. Measure fixed windows with controlled DDL offers, timed counter samples, required retention metrics, and stable index-bound populations. Parameterize follower cohorts and publication cadence. Keep production proofs off the VM. Use implicit trait-object coercions and informative test failures, preserving mismatch reporting for catalog dumps with unequal lengths. --- ci/test/pipeline.template.yml | 200 ++++++ .../20260903_decoupled_coordination_prompt.md | 59 +- src/adapter/src/catalog.rs | 38 +- src/adapter/src/catalog/consistency.rs | 9 +- src/adapter/src/catalog/state.rs | 17 +- src/catalog/src/durable/persist.rs | 18 +- test/0dt/mzcompose.py | 142 +++- test/restart/mzcompose.py | 673 ++++++++++++------ 8 files changed, 847 insertions(+), 309 deletions(-) diff --git a/ci/test/pipeline.template.yml b/ci/test/pipeline.template.yml index 30aaddf71db67..30557533d0fff 100644 --- a/ci/test/pipeline.template.yml +++ b/ci/test/pipeline.template.yml @@ -342,6 +342,206 @@ steps: agents: queue: hetzner-aarch64-8cpu-16gb + # TODO: Remove evidence-only dispatch after collecting milestone measurements. + - group: Catalog protection evidence + key: catalog-protection-evidence + steps: + - id: catalog-index-read-protection + label: Catalog index recovery and following + depends_on: build-x86_64 + inputs: ["*"] + timeout_in_minutes: 60 + coverage: skip + sanitizer: skip + agents: + queue: hetzner-x86-64-dedi-16cpu-64gb + plugins: + - ./ci/plugins/mzcompose: + composition: 0dt + run: index-read-protection + - id: catalog-index-following-100-1000 + label: Catalog following 100 indexes at 1000ms + depends_on: build-x86_64 + inputs: ["*"] + timeout_in_minutes: 60 + coverage: skip + sanitizer: skip + agents: + queue: hetzner-x86-64-dedi-16cpu-64gb + plugins: + - ./ci/plugins/mzcompose: + composition: 0dt + run: index-read-protection + args: + - --index-count=100 + - --publication-interval-ms=1000 + - id: catalog-index-following-1000-1000 + label: Catalog following 1000 indexes at 1000ms + depends_on: build-x86_64 + inputs: ["*"] + timeout_in_minutes: 60 + coverage: skip + sanitizer: skip + agents: + queue: hetzner-x86-64-dedi-16cpu-64gb + plugins: + - ./ci/plugins/mzcompose: + composition: 0dt + run: index-read-protection + args: + - --index-count=1000 + - --publication-interval-ms=1000 + - id: catalog-index-following-1000-10000 + label: Catalog following 1000 indexes at 10000ms + depends_on: build-x86_64 + inputs: ["*"] + timeout_in_minutes: 60 + coverage: skip + sanitizer: skip + agents: + queue: hetzner-x86-64-dedi-16cpu-64gb + plugins: + - ./ci/plugins/mzcompose: + composition: 0dt + run: index-read-protection + args: + - --index-count=1000 + - --publication-interval-ms=10000 + - id: catalog-publication-sparse-baseline + label: Catalog publication sparse-baseline + depends_on: build-x86_64 + inputs: ["*"] + timeout_in_minutes: 90 + coverage: skip + sanitizer: skip + agents: + queue: hetzner-x86-64-dedi-16cpu-64gb + plugins: + - ./ci/plugins/mzcompose: + composition: restart + run: catalog-publication-measurement + args: + - --collection-counts=100,1000,10000 + - --publication-rounds=3 + - --publication-interval-ms=1000 + - --observation-seconds=30 + - --timeout-seconds=300 + - --observer=none + - --ddl-rate-hz=0 + - --active-collections=10 + - --filler-kind=indexes + - id: catalog-publication-sparse-ddl + label: Catalog publication sparse-ddl + depends_on: build-x86_64 + inputs: ["*"] + timeout_in_minutes: 90 + coverage: skip + sanitizer: skip + agents: + queue: hetzner-x86-64-dedi-16cpu-64gb + plugins: + - ./ci/plugins/mzcompose: + composition: restart + run: catalog-publication-measurement + args: + - --collection-counts=100,1000,10000 + - --publication-rounds=3 + - --publication-interval-ms=1000 + - --observation-seconds=30 + - --timeout-seconds=300 + - --observer=none + - --ddl-rate-hz=2 + - --active-collections=10 + - --filler-kind=indexes + - id: catalog-publication-sparse-observer + label: Catalog publication sparse-observer + depends_on: build-x86_64 + inputs: ["*"] + timeout_in_minutes: 90 + coverage: skip + sanitizer: skip + agents: + queue: hetzner-x86-64-dedi-16cpu-64gb + plugins: + - ./ci/plugins/mzcompose: + composition: restart + run: catalog-publication-measurement + args: + - --collection-counts=100,1000,10000 + - --publication-rounds=3 + - --publication-interval-ms=1000 + - --observation-seconds=30 + - --timeout-seconds=300 + - --observer=poll + - --ddl-rate-hz=2 + - --active-collections=10 + - --filler-kind=indexes + - id: catalog-publication-sparse-slow-cadence + label: Catalog publication sparse-slow-cadence + depends_on: build-x86_64 + inputs: ["*"] + timeout_in_minutes: 90 + coverage: skip + sanitizer: skip + agents: + queue: hetzner-x86-64-dedi-16cpu-64gb + plugins: + - ./ci/plugins/mzcompose: + composition: restart + run: catalog-publication-measurement + args: + - --collection-counts=100,1000,10000 + - --publication-rounds=3 + - --publication-interval-ms=10000 + - --observation-seconds=30 + - --timeout-seconds=300 + - --observer=none + - --ddl-rate-hz=2 + - --active-collections=10 + - --filler-kind=indexes + - id: catalog-publication-active-ddl + label: Catalog publication active-ddl + depends_on: build-x86_64 + inputs: ["*"] + timeout_in_minutes: 90 + coverage: skip + sanitizer: skip + agents: + queue: hetzner-x86-64-dedi-16cpu-64gb + plugins: + - ./ci/plugins/mzcompose: + composition: restart + run: catalog-publication-measurement + args: + - --collection-counts=100,1000,10000 + - --publication-rounds=3 + - --publication-interval-ms=1000 + - --observation-seconds=30 + - --timeout-seconds=300 + - --observer=none + - --ddl-rate-hz=2 + - id: catalog-publication-active-slow-cadence + label: Catalog publication active-slow-cadence + depends_on: build-x86_64 + inputs: ["*"] + timeout_in_minutes: 90 + coverage: skip + sanitizer: skip + agents: + queue: hetzner-x86-64-dedi-16cpu-64gb + plugins: + - ./ci/plugins/mzcompose: + composition: restart + run: catalog-publication-measurement + args: + - --collection-counts=100,1000,10000 + - --publication-rounds=3 + - --publication-interval-ms=10000 + - --observation-seconds=30 + - --timeout-seconds=300 + - --observer=none + - --ddl-rate-hz=2 + - group: "MySQL" key: mysql-tests steps: diff --git a/doc/developer/design/20260903_decoupled_coordination_prompt.md b/doc/developer/design/20260903_decoupled_coordination_prompt.md index b3a23aa677541..c06e97a384eea 100644 --- a/doc/developer/design/20260903_decoupled_coordination_prompt.md +++ b/doc/developer/design/20260903_decoupled_coordination_prompt.md @@ -21,46 +21,25 @@ 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. +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 +89,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/src/catalog.rs b/src/adapter/src/catalog.rs index 93ba6fb502b5c..51028b35cf6de 100644 --- a/src/adapter/src/catalog.rs +++ b/src/adapter/src/catalog.rs @@ -2746,7 +2746,10 @@ mod tests { }; location.pending = true; let ts = catalog.current_upper().await; - let replica_id = catalog.allocate_user_replica_ids(1, ts).await.unwrap()[0]; + 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( @@ -2763,14 +2766,28 @@ mod tests { }], ) .await - .unwrap(); + .expect("can create pending replica"); - let expected = catalog.state().dump(None).unwrap(); - let reader = catalog.open_diagnostic_reader().await.unwrap(); + 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.unwrap(); - let reconstructed = catalog.reconstruct_state(input).await.unwrap(); - assert_eq!(expected, reconstructed.dump(None).unwrap()); + 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; @@ -2877,7 +2894,12 @@ mod tests { .reconstruct_state(input) .await .expect("failed to reconstruct catalog"); - assert_eq!(expected, reconstructed.dump(None).unwrap()); + assert_eq!( + expected, + reconstructed + .dump(None) + .expect("can dump reconstructed catalog state") + ); drop(memory); writer.expire().await; } diff --git a/src/adapter/src/catalog/consistency.rs b/src/adapter/src/catalog/consistency.rs index 5d7b685d5712a..7f0a103aba352 100644 --- a/src/adapter/src/catalog/consistency.rs +++ b/src/adapter/src/catalog/consistency.rs @@ -79,11 +79,10 @@ impl super::Catalog { if memory != disk { let memory_lines: Vec<_> = memory.lines().collect(); let disk_lines: Vec<_> = disk.lines().collect(); - let first = memory_lines - .iter() - .zip(&disk_lines) - .position(|(a, b)| a != b) - .unwrap_or(memory_lines.len().min(disk_lines.len())); + 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{}", diff --git a/src/adapter/src/catalog/state.rs b/src/adapter/src/catalog/state.rs index 8f2101417b8cd..7c1b6aeb615eb 100644 --- a/src/adapter/src/catalog/state.rs +++ b/src/adapter/src/catalog/state.rs @@ -3237,7 +3237,7 @@ mod tests { state.collection_compaction_bounds(), serde_json::value::Serializer, ) - .unwrap(), + .expect("can serialize collection compaction bounds"), serde_json::to_value(BTreeMap::from([ ( storage.to_string(), @@ -3248,7 +3248,7 @@ mod tests { Antichain::from_elem(Timestamp::from(10)) ), ])) - .unwrap() + .expect("can serialize expected collection compaction bounds") ); let snapshot = state.clone(); let _ = state @@ -3369,14 +3369,14 @@ mod tests { state.maintained_read_requirements(), serde_json::value::Serializer, ) - .unwrap(), + .expect("can serialize maintained read requirements"), serde_json::to_value(BTreeMap::from([( initial.id.to_string(), (&initial.inputs, initial.frontier), )])) - .unwrap() + .expect("can serialize expected maintained read requirements") ); - let serialized = serde_json::to_value(&state).unwrap(); + 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(); @@ -3652,8 +3652,11 @@ mod tests { let original = catalog .state .durable_item(catalog.state.get_entry_by_global_id(&id).entry) - .unwrap(); - let (_, extra_id) = catalog.allocate_user_id_for_test().await.unwrap(); + .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(); diff --git a/src/catalog/src/durable/persist.rs b/src/catalog/src/durable/persist.rs index 29439e589a6fd..b84322bf7ef84 100644 --- a/src/catalog/src/durable/persist.rs +++ b/src/catalog/src/durable/persist.rs @@ -1500,10 +1500,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 - .map(|state| state as Box) + .await?) } #[mz_ore::instrument] @@ -1511,10 +1511,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 - .map(|state| state as Box) + .await?) } #[mz_ore::instrument] @@ -1523,10 +1523,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 - .map(|state| state as Box) + .await?) } #[mz_ore::instrument(level = "debug")] diff --git a/test/0dt/mzcompose.py b/test/0dt/mzcompose.py index edf22bb93c164..4434287b27f90 100644 --- a/test/0dt/mzcompose.py +++ b/test/0dt/mzcompose.py @@ -26,7 +26,11 @@ 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, @@ -1031,8 +1035,20 @@ def workflow_basic(c: Composition) -> None: """)) -def workflow_index_read_protection(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) @@ -1049,6 +1065,7 @@ def query(sql: str, service: str = "mz_old") -> list[tuple]: 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(""" @@ -1068,12 +1085,31 @@ def query(sql: str, service: str = "mz_old") -> list[tuple]: > CREATE INDEX permission_bootstrap_drop_idx IN CLUSTER permission_suspended ON permission_suspended_input (a) WITH (RETAIN HISTORY = FOR '1s'); """)) - ids = dict(query(""" + 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 ('permission_keep_idx', 'permission_drop_idx', - 'permission_suspended_idx', 'permission_bootstrap_drop_idx') + 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, @@ -1088,13 +1124,15 @@ def frozen_bounds() -> dict: bounds = catalog_bounds("mz_new") return {name: bounds.get(gid) for name, gid in ids.items()} - assert all(gid not in catalog_bounds("mz_old") for gid in ids.values()) + 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") - assert all(gid not in catalog_bounds("mz_old") for gid in ids.values()) + 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 @@ -1138,9 +1176,13 @@ def frozen_bounds() -> dict: 3 """)) assert frozen_bounds() == frozen - assert all(gid not in catalog_bounds("mz_old") for gid in ids.values()) + 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 = '1s'") + 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. @@ -1149,31 +1191,55 @@ def frozen_bounds() -> dict: query("DROP INDEX permission_drop_idx") query(f"INSERT INTO permission_input VALUES ({value})") keep = ids["permission_keep_idx"] - baseline = catalog_bounds("mz_old").get(keep, {"elements": [0]})[ - "elements" - ][0] + 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 ( - committed := catalog_bounds("mz_old").get(keep, {"elements": [0]})[ - "elements" - ][0] - ) <= baseline: - assert time.monotonic() < deadline, (keep, baseline, committed) + 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_observed = time.monotonic() + 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: - [(read,)] = query( - "SELECT read_frontier::text::numeric FROM mz_internal.mz_frontiers " - f"WHERE object_id = '{keep}'", + 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", ) - if read is not None and read >= committed: + 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, (keep, committed, read) + assert time.monotonic() < deadline, ( + len(cohort), + observed_count, + targets, + ) time.sleep(0.5) - follower_observed = time.monotonic() c.testdrive(dedent(f""" > SELECT read_frontier::text::numeric >= {committed} FROM mz_internal.mz_frontiers WHERE object_id = '{keep}'; @@ -1186,13 +1252,26 @@ def frozen_bounds() -> dict: json.dumps( { "workflow": "index-read-protection", - "publication_interval_ms": 1000, + "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, @@ -1223,6 +1302,17 @@ def frozen_bounds() -> dict: 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""" diff --git a/test/restart/mzcompose.py b/test/restart/mzcompose.py index 6a01f7dd2585f..b2943703bdfa9 100644 --- a/test/restart/mzcompose.py +++ b/test/restart/mzcompose.py @@ -15,18 +15,20 @@ import copy import json +import math import time from collections.abc import Callable from concurrent.futures import ThreadPoolExecutor from datetime import datetime from textwrap import dedent -from threading import Event 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 @@ -980,6 +982,37 @@ 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 workflow_catalog_read_protection(c: Composition) -> None: """Exercise logical recovery protection and persist compaction without cached plans.""" c.down(destroy_volumes=True) @@ -1084,6 +1117,16 @@ def inspect(global_id: str) -> dict: 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() @@ -1289,51 +1332,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"], @@ -1359,11 +1396,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 @@ -1429,18 +1466,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"] ) @@ -1943,7 +1991,7 @@ def workflow_catalog_publication_measurement( parser.add_argument( "--collection-counts", default="100,1000,10000", - help="Total generated objects per step, tables plus any noncollection filler", + 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) @@ -1951,7 +1999,16 @@ def workflow_catalog_publication_measurement( parser.add_argument( "--active-collections", type=int, - help="Use this many advancing tables and fill the remaining count with constant SQL views (not collections)", + 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) @@ -1966,14 +2023,17 @@ def workflow_catalog_publication_measurement( 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, active collections must be in 1..min(counts)" + "counts, rounds, durations must be positive, DDL rate nonnegative, active collections in 1..min(counts)" ) def query(sql: str) -> list[tuple]: @@ -1997,68 +2057,154 @@ def execute_each(statements: list[str]) -> None: for statement in statements: conn.execute(statement.encode()) - def snapshot(count: int) -> dict: - [(catalog_id,)] = query( - "SELECT id FROM mz_objects WHERE name = 'mz_catalog_raw'" - ) - catalog = query(f"INSPECT SHARD '{catalog_id}'")[0][0] + def inspect(item_id: str) -> dict: + start = time.monotonic() + state = query(f"INSPECT SHARD '{item_id}'")[0][0] + end = time.monotonic() + batches = [*state["batches"], *state["hollow_batches"].values()] + return { + "start": start, + "end": end, + "state": { + key: state[key] for key in ("shard_id", "seqno", "since", "upper") + }, + "updates": sum(batch["len"] for batch in batches), + "batch_count": len(batches), + "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() - - def metrics(shard: str) -> dict[str, float]: - return { - line.split("{", 1)[0]: float(line.rsplit(" ", 1)[1]) - for line in response.text.splitlines() - if f'shard="{shard}"' in line - and line.split("{", 1)[0] - in ( - "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", - ) - } - - counters = metrics(catalog["shard_id"]) - assert "mz_persist_shard_diff_size_bytes" in counters, counters - assert "mz_persist_shard_cmd_succeeded" in counters, counters - retained = [] - # INSPECT every shard would dominate the measurement at large counts. - for i in sorted({0, count // 2, count - 1}): - [(item_id,)] = query( - f"SELECT id FROM mz_tables WHERE name = 'publication_{i}'" - ) - shard = query(f"INSPECT SHARD '{item_id}'")[0][0] - batches = [*shard["batches"], *shard["hollow_batches"].values()] - retained.append( - { - "collection": f"publication_{i}", - "since": shard["since"], - "upper": shard["upper"], - "persist_upper_minus_since_ms": shard["upper"][0] - - shard["since"][0], - "updates": sum(batch["len"] for batch in batches), - "metrics": metrics(shard["shard_id"]), - } - ) return { - "monotonic_seconds": time.monotonic(), - "catalog_seqno": catalog["seqno"], - "catalog_counters": counters, - "retained_history_samples": retained, + "start": start, + "end": end, + "shards": { + name: _catalog_protection_metrics(response.text, shard) + for name, shard in shards.items() + }, } - def catalog_bounds() -> dict: + 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=args.timeout_seconds, + 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( @@ -2067,7 +2213,7 @@ def catalog_bounds() -> dict: "enable_expression_cache": "false", "enable_logical_compaction_window": "true", "max_tables": str(counts[-1]), - "max_objects_per_schema": str(counts[-1] + 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", @@ -2080,12 +2226,21 @@ def catalog_bounds() -> dict: ), ): 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 their uppers even without DML. Sparse runs therefore - # grow catalog size with noncollection views, not supposedly idle tables. + # Tables advance without DML. Zero-replica indexes must demonstrate + # stable governed bounds, while views measure only catalog item size. execute_each( [ statement @@ -2096,160 +2251,246 @@ def catalog_bounds() -> dict: f"INSERT INTO publication_{i} VALUES (0)", ) if i < active - else (f"CREATE VIEW publication_filler_{i} AS SELECT {i} AS a",) + 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 - setup_seconds = time.monotonic() - setup_start - collection_gids = [gid for (gid,) in query(""" - SELECT 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(collection_gids) == active, (len(collection_gids), active) - stop = Event() - - def concurrent_ddl() -> list[float]: - latencies = [] - with c.sql_connection( - port=6877, - user="mz_system", - startup_params={"statement_timeout": f"{args.timeout_seconds}s"}, - ) as conn: - conn.autocommit = True - while not stop.is_set(): - start = time.monotonic() - conn.execute(b"CREATE VIEW publication_ddl_probe AS SELECT 1") - conn.execute(b"DROP VIEW publication_ddl_probe") - latencies.append(time.monotonic() - start) - return latencies - - before = snapshot(active) + 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 = [] - with ThreadPoolExecutor(max_workers=1) as executor: - ddl = executor.submit(concurrent_ddl) - try: - for revision in range(1, args.publication_rounds + 1): - write_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"] - ) - publication_start = time.monotonic() - observation_end = publication_start + args.observation_seconds - observed_publication_seconds = None - observer_samples = 0 - # Observe the writer's own committed catalog snapshot. Querying - # mz_catalog_raw would measure that maintained collection's - # ingestion of catalog history instead of publication. - while time.monotonic() < observation_end: - if args.observer == "poll": - bounds = catalog_bounds() - observer_samples += 1 - if observed_publication_seconds is None and all( - bounds[gid]["elements"] - and bounds[gid]["elements"][0] > target - for gid in collection_gids - ): - observed_publication_seconds = ( - time.monotonic() - publication_start - ) - time.sleep( - max(0, min(0.5, observation_end - time.monotonic())) - ) - rounds.append( - { - "revision": revision, - "target_timestamp": target, - "write_seconds": publication_start - write_start, - "observation_seconds": time.monotonic() - - publication_start, - "observed_publication_seconds": observed_publication_seconds, - "observer_samples": observer_samples, - } - ) - finally: - stop.set() - ddl_latencies = ddl.result() - after = snapshot(active) - # Verify progress outside the cost window, including in no-poll runs. - # Passing the final target also covers every earlier round's target. + 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"] + 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, + "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[gid]["elements"] and bounds[gid]["elements"][0] > target - for gid in collection_gids + 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} advancing collections did not pass " - f"{target} within {args.timeout_seconds}s after the cost window" + f"publication for {active} collections did not pass {target}" ) time.sleep(0.5) - verification_seconds = time.monotonic() - verification_start - elapsed = after["monotonic_seconds"] - before["monotonic_seconds"] - deltas = { - metric: after["catalog_counters"][metric] - - before["catalog_counters"][metric] - for metric in ( - "mz_persist_shard_diff_size_bytes", - "mz_persist_shard_cmd_succeeded", - ) + verification_end = time.monotonic() + history_after = { + name: inspect(item_id) for name, item_id in sampled_ids.items() } - assert ddl_latencies, "no concurrent DDL completed" - assert all(delta >= 0 for delta in deltas.values()), deltas - # Totals include SQL writes, DDL, observers, and builtin maintenance. - # Publication observations include the retention window and retry overhead. print( json.dumps( { "workflow": "catalog-publication-measurement", - "catalog_object_count": count, - "collection_count": active, - "noncollection_constant_view_count": count - active, - "scenario": ( - "all-advancing-tables" - if active == count - else "sparse-tables-with-noncollection-filler" - ), + "schema_version": 2, + "clock": "time.monotonic seconds", + "cost_scope": "combined raw catalog traffic, not isolated publication overhead", + "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, - "common_observers": "INSPECT, SQL, metrics at sample boundaries, EXPLAIN TIMESTAMP per round", - "post_sample_publication_verification_seconds": verification_seconds, - "setup_seconds": setup_seconds, + "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, - "ddl_create_drop_seconds": ddl_latencies, - "ddl_create_drop_percentiles_seconds": { - f"p{percentile}": sorted(ddl_latencies)[ - min( - len(ddl_latencies) - 1, - (len(ddl_latencies) * percentile + 99) // 100 - 1, - ) - ] - for percentile in (50, 95, 99) - }, - "publication_sample_seconds": elapsed, - "catalog_counter_deltas": deltas, - "catalog_diff_bytes_per_second": deltas[ - "mz_persist_shard_diff_size_bytes" - ] - / elapsed, - "before": before, - "after": after, }, sort_keys=True, ),