fix(schedule): warm followers before leadership handoff - #13913
ChiragAgg5k wants to merge 1 commit into
Conversation
A replacement scheduler waited for leadership before loading its source, so a slow initial load stalled dispatch during a surge rollout. Every replica now reconciles before electing, exposes isReady(), and rebases discovery coverage on the predecessor's committed source view at takeover so delivered changes are not replayed. Ported from utopia-php/monorepo#312. Co-authored-by: loks0n <[email protected]>
✨ Benchmark resultsComparing
Per-scenario breakdown & investigation detailsMetrics below reflect the current branch (after). Δ P95 compares against the base.
Top API waits (after)
|
|
| if ($syncedUntil !== null) { | ||
| $seen = $this->moment($syncedUntil); | ||
| foreach ($this->entries as &$entry) { | ||
| $entry['coverFrom'] = $this->coverFrom($entry['activeFrom'], $seen, $entry['replaced']); |
There was a problem hiding this comment.
Committed replacements replay on takeover When a warm follower has loaded a replacement, its
replaced flag stays true until it leads and commits. This rebase then starts coverage at the replacement’s activeFrom, even if the previous leader already delivered and committed that version. The new leader can deliver those occurrences again.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/schedule/src/Scheduler.php
Line: 777
Comment:
**Committed replacements replay on takeover** When a warm follower has loaded a replacement, its `replaced` flag stays true until it leads and commits. This rebase then starts coverage at the replacement’s `activeFrom`, even if the previous leader already delivered and committed that version. The new leader can deliver those occurrences again.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| continue; | ||
| } | ||
|
|
||
| $this->syncIfDue(); |
There was a problem hiding this comment.
Takeover uses an older view If the leader loads a new or updated schedule after the follower’s last read, then stops before the follower’s next sync,
syncIfDue() does not refresh the follower at takeover. The follower can commit a window without the new schedule or dispatch its old definition. A later refresh cannot undo that dispatch.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/schedule/src/Scheduler.php
Line: 683
Comment:
**Takeover uses an older view** If the leader loads a new or updated schedule after the follower’s last read, then stops before the follower’s next sync, `syncIfDue()` does not refresh the follower at takeover. The follower can commit a window without the new schedule or dispatch its old definition. A later refresh cannot undo that dispatch.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| $b->resume(); | ||
| $b->resume(); |
There was a problem hiding this comment.
Handoff test mirrors internal yields These fixed
resume() calls assume exactly when the follower’s make() callback and run loop suspend. A harmless change to that sequence can break the test without changing delivery behavior. The repository requires tests to assert observable behavior rather than mirror implementation steps; this requirement must be satisfied before merging.
Context Used: Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/schedule/tests/E2E/RedisStoreTest.php
Line: 349-350
Comment:
**Handoff test mirrors internal yields** These fixed `resume()` calls assume exactly when the follower’s `make()` callback and run loop suspend. A harmless change to that sequence can break the test without changing delivery behavior. The repository requires tests to assert observable behavior rather than mirror implementation steps; this requirement must be satisfied before merging.
**Context Used:** Call out and harshly judge implementation-coupled tests. We don't mirror source code, configuration, or version pins in assertions. We test observable behavior; use linters for syntax and schema checks. ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Every scheduler replica now reconciles its source before electing, so a replacement loads its schedules while the old leader keeps dispatching. Adds
Scheduler::isReady()and rebases a follower's discovery coverage on the predecessor's committed source view at takeover, so a handoff neither stalls dispatch nor replays delivered changes.Ported from utopia-php/monorepo#312 after schedule moved to packages/schedule (#13887).
bin/monorepo check schedule(Pint, PHPStan, Rector)bin/monorepo test scheduleunitbin/monorepo test schedulee2e (Redis)bin/monorepo validategit diff --check🤖 Generated with Claude Code