Skip to content

environmentd: Let bootstrap finish before DDL-triggered restarts - #39151

Merged
aljoscha merged 6 commits into
mainfrom
aljoscha/0dt-bootstrap-cleanup
Sep 29, 2026
Merged

aljoscha merged 6 commits into
mainfrom
aljoscha/0dt-bootstrap-cleanup

Conversation

@aljoscha

@aljoscha aljoscha commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Closes: SQL-729

Continuous DDL can repeatedly restart a read-only deployment before it cleans up orphaned replicas, leaving them running and retaining resources.

Description

Preflight selects the boot mode and starts the catch-up task. Once adapter bootstrap succeeds, the task receives an ID baseline captured from the same catalog savepoint, and only then do DDL checks and the catch-up timeout start. An administrative skip is accepted throughout and promotes without waiting for bootstrap.

Orphan cleanup waits for queued delete requests to be processed before returning. It does not wait for pods to terminate.

Verification

Unit tests cover deferred catch-up startup, skipping before bootstrap, an early caught-up notification, and a bootstrap baseline that excludes subsequent catalog changes without consuming pending bootstrap updates.

@linear-code

linear-code Bot commented Sep 25, 2026

Copy link
Copy Markdown

SQL-729

@aljoscha
aljoscha added this pull request to stack #39156 September 25, 2026 12:46
@aljoscha
aljoscha marked this pull request as ready for review September 25, 2026 12:59
@aljoscha
aljoscha requested a review from a team as a code owner September 25, 2026 12:59
@aljoscha
aljoscha requested a review from ggevay September 25, 2026 13:02

@ggevay ggevay left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments.
(We'll soon be able to run CI)

Comment thread src/environmentd/src/lib.rs Resolved
@ggevay

ggevay commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

The GHCR issue should be resolved now. I've clicked rebuild on all those PRs of the stack that needed it.

@aljoscha
aljoscha requested review from a team as code owners September 28, 2026 10:30
@aljoscha
aljoscha requested a review from jubrad September 28, 2026 10:30
Comment thread src/controller/src/clusters.rs Resolved
Comment thread src/orchestrator/src/lib.rs Resolved
@def-

def- commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

QA LLM Review

1. MEDIUM -- Force-promote and ImmediatelyPromoteCausingDowntime rollouts now block for the whole read-only bootstrap

src/environmentd/src/lib.rs:824

set_catching_up() now runs only inside spawn_catchup, after mz_adapter::serve returns. For the whole read-only adapter bootstrap the state stays Initializing, so /api/leader/skip-catchup returns 400. That removes the escape hatch the previous head kept on purpose, and it makes every ImmediatelyPromoteCausingDowntime rollout wait for a full read-only bootstrap while the old generation is already torn down.

Details

For ImmediatelyPromoteCausingDowntime, should_force_promote() is always true (src/cloud-resources/src/crd/materialize.rs:607). orchestratord tears down the active generation first. It then calls skip-catchup as soon as the new pod passes its TCP probe, which happens early because listeners bind before serve. The 400 becomes Err("failed to skip catchup") (src/orchestratord/src/controller/materialize/generation.rs:263). The reconcile records FailedDeploy and retries until bootstrap finishes.

Before this change, and at 5b27cef, skip was accepted a few seconds after preflight. The process then promoted, halted, and came back as leader. The outage now also includes one full read-only bootstrap, plus whatever reconcile backoff is left when that bootstrap ends. A read-only bootstrap that stalls can no longer be forced past at all. The same reconcile error already shows up today in the brief window before set_catching_up (already tracked in MaterializeInc/database-issues#9884). This change stretches that window to the whole bootstrap.

Skip does not need this gate for the orphan-cleanup fix. A skip leads to promotion, and the restart after promotion boots as leader, which runs remove_orphaned_replicas itself. One fix is to call set_catching_up() before mz_adapter::serve and pass the skip future into spawn_catchup. A skip during bootstrap would then go straight to set_ready_to_promote, while the timeout and the DDL checks still start after bootstrap.

@aljoscha

Copy link
Copy Markdown
Contributor Author

@ggevay I reworked this, could you please take another look! 🙏

@aljoscha
aljoscha requested a review from ggevay September 28, 2026 14:21

@ggevay ggevay left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but I'm not totally sure whether we want to accept the issue that the QA bot pointed out. See some more details below.

Comment on lines +166 to +177
// NOTE: There _is_ a window where DDL can happen in the old
// environment, between checking above, us announcing as ready to
// promote, and cloud giving us the go-ahead signal. Its size
// depends on how quickly cloud will trigger promotion once we
// report as ready.
//
// We could add another check here, right before cutting over, but I
// think this requires changes in Cloud: with this additional check,
// it can now happen that cloud gives us the promote signal but we
// then notice there were changes and restart. Could would have to
// notice this and give us the promote signal again, once we're
// ready again.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit on the moved NOTE: the second paragraph is outdated, since orchestratord keeps re-sending promote until it sees IsLeader. Maybe:

Suggested change
// NOTE: There _is_ a window where DDL can happen in the old
// environment, between checking above, us announcing as ready to
// promote, and cloud giving us the go-ahead signal. Its size
// depends on how quickly cloud will trigger promotion once we
// report as ready.
//
// We could add another check here, right before cutting over, but I
// think this requires changes in Cloud: with this additional check,
// it can now happen that cloud gives us the promote signal but we
// then notice there were changes and restart. Could would have to
// notice this and give us the promote signal again, once we're
// ready again.
// NOTE: DDL the old leader commits between the final check above and
// the fencing `open` below only costs hydration progress: we reboot as
// leader from the full catalog, and those objects have hydrated on the
// old leader for at most that window. Don't re-check here: a restart
// after the promote signal can, under continuous DDL, hold the rollout
// in orchestratord's `Promoting` state, which never times out and
// cannot be cancelled.

aljoscha and others added 5 commits September 29, 2026 12:54
Let orphaned replica cleanup finish before preflight can restart the
read-only generation, including on the catch-up timeout path. Preserve
the administrative skip-catchup escape hatch.

Addresses SQL-729.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0d85f-8cd1-7078-afa3-21a9cce44857
Co-authored-by: Amp <[email protected]>
The catch-up task starts right after preflight again, so an administrator
can force-promote a read-only deployment whose bootstrap is stuck. The ID
baseline reaches the task over a oneshot once adapter bootstrap succeeds,
and only then do the DDL checks and the catch-up timeout start.
@aljoscha
aljoscha force-pushed the aljoscha/0dt-bootstrap-cleanup branch from 7a204c1 to 89d301e Compare September 29, 2026 12:55
)
.await
.expect("incompatible catalog/persist version");
/// Returns the next user item and replica IDs based on existing catalog objects.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the check_ddl_changes doc above (outside this diff) still says it looks for IDs "allocated after the preflight check began". Since the baseline now comes from this snapshot, maybe: "We do this by checking whether items or replicas with IDs above the highest ones in the catalog snapshot this deployment bootstrapped from were committed."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d6d46cc.

Comment on lines +424 to +425
tokio::time::advance(2 * caught_up_max_wait).await;
tokio::task::yield_now().await;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: advance plus one yield_now only gives the spawned task a bounded number of polls, so it's not certain a wrong implementation would reach its first catalog transaction before the asserts. A paused-clock sleep resolves only once the runtime is idle, which makes the check unambiguous:

Suggested change
tokio::time::advance(2 * caught_up_max_wait).await;
tokio::task::yield_now().await;
tokio::time::sleep(2 * caught_up_max_wait).await;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d6d46cc.

Describe the DDL check against the bootstrap snapshot baseline, and let
the paused clock idle the runtime before asserting that catch-up waits
for bootstrap.
@aljoscha
aljoscha merged commit c56cae1 into main Sep 29, 2026
86 checks passed
@aljoscha
aljoscha deleted the aljoscha/0dt-bootstrap-cleanup branch September 29, 2026 15:13
@bosconi bosconi mentioned this pull request Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants