Preserve workflow-to-CHASM backfill migration safety - #43
chaptersix wants to merge 1 commit into
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad21de785c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if needsCallbacks { | ||
| backfiller = newBackfillerWithStateWithoutTask(ctx, backfillerState) |
There was a problem hiding this comment.
Do not persist taskless backfillers during mixed-version rollout
During a rolling upgrade or shard failover, a new server can take this branch and persist migrated backfillers without tasks, after which the SchedulerCallbacksTask may execute on an older server whose handler only rearms the invoker and generator. Once that handler marks every imported start as having a callback, the callback task becomes invalid, leaving these backfillers permanently present but never executable. The taskless representation must not be written until every possible task processor knows how to arm it, or the deferred state must be recoverable independently of the new callback-handler behavior.
Useful? React with 👍 / 👎.
| BackfillId: backfillID, | ||
| LastProcessedTime: lastProcessedTime, | ||
| Attempt: 0, | ||
| HasRecordedProgress: lastProcessedTime != nil, |
There was a problem hiding this comment.
Stage epoch-progress semantics before writing the new bit
When an epoch cursor is migrated during a rolling upgrade or later handled after rollback, an older BackfillerTaskHandler ignores this unknown proto bit and still interprets the (0,0) LastProcessedTime as an unset watermark. It consequently subtracts one millisecond from the request start and emits the occurrence at the Unix epoch again. Writing state whose correctness depends on this field therefore needs a staged/version-gated rollout so old task readers cannot process it.
Useful? React with 👍 / 👎.
Problem
Workflow-to-CHASM migration can replay an ongoing backfill boundary. Independent review also exposed two related transfer hazards: a valid Unix-epoch cursor is indistinguishable from an unset watermark, and migrated backfillers can process imported buffered starts before stale running-workflow entries are reconciled.
Evidence: #42.
Violated invariants
(exclusive cursor, inclusive end].Before / after
Before:
11:59, end12:00becomes a fresh CHASM range and emits[11:59, 12:00].Attempt=1to represent progress would strand the initialTaskStamp=1task.SKIPbefore callback reconciliation.After:
StartTimeis cloned intoLastProcessedTime,Attemptremains zero, and an explicit progress bit represents the Unix epoch without changing legacy nonzero-watermark behavior.Implementation
convertBackfillsLegacyToCHASMpreserves the exclusive cursor and explicit progress state.BackfillerTaskHandlerretains backward-compatible nonzero-watermark inference and records progress after committed partial work.CreateSchedulerFromMigrationandSchedulerCallbacksTaskHandlerdefer and idempotently start migrated backfillers around callback reconciliation.Compatibility and operations
The new proto bit is additive. New binaries infer progress from older nonzero watermarks; fresh and capacity-stalled old state stays fresh. The migration attempt remains zero so the initial task satisfies
TaskStamp > Attempt. Deploy readers/writers before relying on the epoch distinction during rolling upgrades.The change adds no ordinary scheduler hot-path read or write. At 10x migration load it copies one timestamp/bit per ongoing backfill and reuses the already-required callback transition. Crash retries reconstruct the same cursor; callback retries do not duplicate the initial task because a nonzero task stamp is retained.
Validation
Fuzz seed corpus:
(0,0),(-60,999000000),(86400,1); final bounded run executed 144,707 inputs and passed. Proto regeneration was clean and lint reported zero issues.Stack
migration-backfill/evidenceRendered failure/sequence artifacts are attached below.