adapter: skip shardless builtins in every migration plan, not only forced ones - #38917
Conversation
…rced ones Since MaterializeInc#38864 a 0dt upgrade from a catalog written before v26.41.0 to main crash-loops the new environmentd in read-only mode: cannot migrate builtin schemas from version 26.19.0 to version 26.43.0-dev.0: missing shard ID for builtin ... mz_replica_hydration_history MaterializeInc#38864 registers an Evolution step at 26.43.0-dev.0 for a table that first shipped in v26.41.0. A source catalog older than that has no shard for it. plan_migration keeps the step, migrate_evolve_one finds no shard, and its read-only branch bails; the leader branch returns Ok and lets bootstrap create the shard, which is why the in-place upgrade checks pass and only the 0dt-preflight jobs fail (six on nightly 18324). MaterializeInc#36668 fixed this exact bail for the forced dev-to-dev plan by filtering shardless objects out of plan_forced_migration: there is nothing to evolve or replace for an object that does not yet exist in persist. The versioned plan never got the filter, so any evolution step for a builtin younger than the source version reproduces it. Move the filter to the one place both plans pass through. run() now drops shardless objects from whichever plan it selected, and plan_forced_migration loses its private copy. The read-only bail in migrate_evolve_one stays as the fail-fast for anything that still reaches it. Adds test_evolution_skips_builtin_without_shard: one builtin with a shard and a stale fingerprint, one without a shard at the current fingerprint (the shape catalog open produces for a builtin new to the durable catalog), steps at 0.2.0 and 0.3.0 over a 0.1.0 source, run read-only and as leader. Fails on main with the message above. Fixes SQL-713. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The in-process test-isolation failure mentioned under Verification ( |
QA LLM Review (Post Merge)@bosconi — an automated review of commit 1. MEDIUM -- dropping shardless objects from the plan also drops them from the fingerprint update, turning a tolerated state into a panic
DetailsThe change is safe only under the unstated invariant no shard implies the durable fingerprint is already current. That holds for a builtin whose mapping this very open just created ( The new test cannot catch this: Suggested fix: keep the skipped objects in the set handed to - let plan = self.drop_shardless(plan);
+ let (plan, shardless) = self.split_shardless(plan);
...
let mut migrated_objects = BTreeSet::new();
migrated_objects.extend(plan.evolve);
migrated_objects.extend(plan.replace);
+ // Nothing to evolve or replace in persist for these, but the leader creates their shards
+ // at the target schema during bootstrap, so their fingerprints must still advance.
+ migrated_objects.extend(shardless); |
Motivation
Since #38864 every 0dt upgrade from a catalog written before v26.41.0 to
maincrash-loops the new environmentd in read-only mode (SQL-713):Six jobs on nightly#18324: all four
Postgres CDC (before source versioning, multi-version upgrade, ...)shards (from 0.147.0 and 26.19.0) and bothChecks Self-Managed earliest to latest direct upgradeshards (from 0.147.0). The same jobs passed on #18320, the previousmainnightly.#38864 registers
MigrationStep::evolution("26.43.0-dev.0", ..., "mz_replica_hydration_history"). That table first shipped in v26.41.0, so a source catalog older than that has no shard for it.plan_migrationkeeps every step above the source version, the plan reachesmigrate_evolve_one, the shard lookup finds nothing, and the read-only branch bails. In leader mode the same case returnsOk(()), which is why the in-placeChecks ... upgradescenarios pass on the same build and only the 0dt-preflight jobs fail.#36668 fixed this exact bail for the forced dev-to-dev plan by filtering shardless objects out of
plan_forced_migration: "there is nothing to evolve or replace for an object that does not yet exist in persist." The versioned plan never got the filter, so any evolution step for a builtin younger than the source version reproduces it. #38788's step formz_cluster_replica_metrics_historydid not, only because that table predates every supported source version.Description
Move the filter to the one place both plans pass through.
Migration::runnow callsdrop_shardlesson the selected plan, forced or versioned, before executing it;plan_forced_migrationloses its private copy of the same filter. A builtin with no registered shard does not exist in persist yet, whether it was added in the target version or the source version predates it entirely, and in both cases the leader creates it at the target schema during bootstrap. The read-only bail inmigrate_evolve_onestays as the fail-fast for anything that still reaches it.This keeps the semantics #36668 chose rather than adding a new one: the read-only race the bail guards against, a leader registering the shard while a read-only process evolves it, cannot involve a builtin the source version never had.
Verification
New turmoil test
test_evolution_skips_builtin_without_shard: one builtin table with a shard and a stale fingerprint (the pre-evolution schema), one without a shard at the current fingerprint (the shape catalog open produces for a builtin new to the durable catalog), steps at 0.2.0 and 0.3.0 over a 0.1.0 source, run once read-only and once as leader. Both runs must succeed, evolve the first table, and leave the second out ofnew_fingerprints.main'sbuiltin_schema_migration.rsthe read-only run fails withmissing shard ID for builtin SystemObjectDescription { schema_name: "schema", object_type: Table, object_name: "new_table" } (s2), the production message.test_builtin_schema_migrationharness passes.Each turmoil test was run in its own process (
cargo test -p mz-adapter --lib -- <full path> --exact), which is what nextest does in CI. Running the module's tests together in one process fails independently of this change:configure_tracing_for_turmoilinstalls a global subscriber under aOnce, and whenhydration_history_forced_migration_policy(#[mz_ore::test]) runs first the install fails and poisons theOncefor every turmoil test after it. Pre-existing, not addressed here.cargo fmtclean;cargo clippy -p mz-adapter --tests -D warningsclean.🤖 Generated with Claude Code