Skip to content

fix(deploy): let a catch-up migration outlive the 10-minute Job deadline - #270

Merged
yetone merged 1 commit into
mainfrom
fix/deploy-migration-deadline
Sep 10, 2026
Merged

yetone merged 1 commit into
mainfrom
fix/deploy-migration-deadline

Conversation

@yetone

@yetone yetone commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Deploy run 34529081984 failed with deploy-release: job_failed after 10m14s. The Job's own events say why:

Warning  DeadlineExceeded  job-controller  Job was active longer than specified deadline

activeDeadlineSeconds is 600, and the poller is 200 × 3s — also 600s — so both bounds land together and the failure arrives as a bare job_failed with the pod already deleted and its logs gone.

600s is the right number for an ordinary deploy, which applies at most one small migration. It is the wrong number for a catch-up deploy that has to apply every pending migration in a single Job, one of which is a CREATE INDEX CONCURRENTLY over a production-sized table.

What changes

  • migration_deadline_seconds becomes a workflow input, defaulting to the same 600. Ordinary deploys are unaffected.
  • It is validated as a plain integer in [60, 2700] in the resolve step. Without that check a non-numeric value reaches Number(...) in deploy-release.mjs, becomes NaN, and falls through to that script's own 600s default — silently discarding what the operator asked for.
  • Poll attempts are derived as deadline / 3 + 20, so the poller outlives the Job's deadline and reports the terminal condition Kubernetes actually recorded rather than job_timeout.
  • timeout-minutes 45 → 75, covering the 2700s ceiling plus the 600s rollout and both smokes.

Why the default stays at 600: a deadline sized for the common case is what makes DeadlineExceeded mean stuck rather than busy. Raising it globally would remove that signal from every deploy to serve a one-time backfill.

On safety of the kill that prompted this: migration 0002 is transactional: true, so the killed pod rolled back rather than leaving a partial detach; 0005 and 0006 are transactional: false but use ensureConcurrentIndex, which repairs an INVALID index left by an interrupted build. The Deployment was never patched — the migration is the step before it. Production is on its previous image and the schema is intact and rerunnable, which is the behaviour docs/RELEASE.md already promises.

The residual Job is terminal (Failed=True, active unset) and carries no active writer, so listResidualMigrationJobs will not block the retry.

The candidate migration Job runs with activeDeadlineSeconds=600 and a poller
of 200 attempts x 3s, both hardcoded. That is sized for an ordinary deploy,
which applies at most one small migration. A catch-up deploy is a different
shape: with many migrations pending it must apply all of them in one Job,
including a CREATE INDEX CONCURRENTLY over a production-sized table, and 10
minutes is not enough. Kubernetes then kills the pod with DeadlineExceeded,
which surfaces as a bare job_failed, and the pod is deleted with its logs.

The deadline is now a workflow input defaulting to the same 600s, validated
as an integer in [60, 2700] before it can reach activeDeadlineSeconds -- a
non-numeric value would otherwise fall through to the script's own 600s
default and silently discard what the operator asked for. Poll attempts are
derived from it with a 20-attempt margin so the poller outlives the Job and
reports the real terminal condition instead of job_timeout. The job budget
goes to 75 minutes to cover the new ceiling plus rollout and both smokes.

The default is deliberately unchanged: a tight deadline on a normal deploy is
what makes DeadlineExceeded mean 'stuck' rather than 'busy'.
@yetone
yetone merged commit e11b024 into main Sep 10, 2026
9 checks passed
@yetone
yetone deleted the fix/deploy-migration-deadline branch September 10, 2026 21:23
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.

1 participant