Repository navigation
Design zero-downtime upgrades for a hosted Chopin - #791
Open
lavaman131 wants to merge 1 commit into
Open
lavaman131 wants to merge 1 commit into
lavaman131 wants to merge 1 commit into
Conversation
…o signed-out people, and no lost Planner work The new docs/zero-downtime-upgrades-design.md records what a restart does today, with a file and line citation for every claim. Nothing listens between the old process's shutdown and the new one's listen, because a second process exits on the held writer lease. Running Planner turns are aborted, and remote Planner sessions are forgotten. Hosted logins survive but every document's Planner ownership is cleared. A proxy's 4xx would stop open tabs and connectors from retrying. Old chunks are answered with index.html. Older binaries refuse both newer migrations and unknown sidecar keys, so a rollback means restoring the database. The design follows the maintainer's decisions. A deploy must be strictly invisible, so stage 1 overlaps two processes: a warm standby waits on the writer lease while the old process keeps serving. The old process then cuts over in a fixed order: readiness off for a fixed unready window that covers the proxy's next check, listener stopped, in-flight requests finished, lease released, sockets closed with 1012. A process cannot count the proxies that check it, so the window never waits for a check that may not come. The old process never refuses a routed request and answers nothing after the release. The budget counts the browser's probe-first reconnect and its 1.2 s rescue point, and the proxy's retry interval. Remote Planner sessions are persisted, and a login is still restored only from its browser cookie. A hold whose link the cut closed is shown attached for a hand-over window, and requests sent to it are accepted as rows, so members see no disconnected Planner when its client resumes in time. A restart-notice client rule bounds that resume. The cases that can still show a disconnected Planner are listed as bounded exceptions: an owner with no tab open in the browser holding the login, a client slower than the window, and a client without resumable sessions. An acknowledged Planner request is never silently dropped in hosted mode. Every acknowledged request that cannot start at once becomes a planner_deferred_turns row before its acknowledgement, in normal operation too. Five kinds are covered, found from every queue push and every Chat.instruct caller: Chat and invoke_planner messages, system invoke_planner calls, comment notes sent to Chopin in a root or a child, and task drafts. A row moves through waiting, started, ended, interrupted and cancelled, with a notice in Chat, the thread or Build plan for every failure. A later writer's request claims the document instead of waiting behind an absent claimant. A row left started by a dead process is reported as interrupted and never replayed. An owner-missing resume is answered unavailable with retry, because sign-in-required ends the link. The known hosted deployment runs Docker Compose with Caddy 2 on one VM. A reference section shows two slots with HANDOVER=wait and HANDOVER_UNREADY_MS, and Caddy's hold and active health checks with a 1 s health timeout. It replaces the deploy script's plain pull and up --detach, which recreate both slots at once, with per-slot steps. The design itself stays platform-neutral. Re-estimated sizes: stage 1 grows from 8–11 to 14–17.5 engineer-weeks, and stage 2 from 4–6 to 4.5–5.5. Stages 3 and 4 are unchanged. Open questions 1 to 4 are recorded as partly or fully decided. Questions 5 to 14 remain open, and question 15, on stale deferred turns, is new. Assistant-model: Claude Sonnet 5.5 Assistant-model: Claude Haiku 5.5 Assistant-model: Claude Opus 5.5 Assistant-workflow: goal (run 2fb55c4c-d75a-43ab-a116-6ee11a1285ac) Assistant-verification: bun run ci passed with exit 0 (dprint, oxlint 0 errors and 71 existing warnings, token and design checks) Assistant-verification: citation check passed: 399 path:line citations and 91 continuations in the design doc name existing files with in-range lines Assistant-verification: relative link check passed: 61 relative links and anchors resolve with exact casing Assistant-verification: Caddy 2.11.7 stand-in probe, 30 retained runs per order: the design's cut order gave 0 errors in 12,702 requests; stopping the listener first gave 9 failed POSTs in 12,953 requests over 9 of 30 runs Assistant-verification: caddy validate accepted the reference upstream block in a minimal site block Assistant-verification: Docker Compose 5.5.1 throwaway project: plain up --detach recreated both slots after an image change, and up --detach --no-deps recreated one Assistant-verification: privacy grep passed: no private project, organization or resource names in the design doc, PR body or commit message User-preference: Deploys must be strictly invisible to users, and Planner work must survive a server deploy Co-authored-by: Alex Lavaee <lavaman131@github.com> Assistant-verification: dprint check passed: docs/zero-downtime-upgrades-design.md after the final edits to the deferred-turn rules
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
docs/zero-downtime-upgrades-design.md, a design for shipping a hosted Chopin without signing anyone out, interrupting open tabs, or dropping Planner work. It records what a restart does today, with a file and line citation for each claim. It then proposes four stages that can each ship on their own. This PR changes documentation only, no code.Key points of stage 1:
Strictly invisible deploys. A warm standby waits on the writer lease while the old process keeps serving. The old process runs its cut in a fixed order:
HANDOVER_UNREADY_MS, the proxy's health interval plus a margin; a process cannot count its proxies, so it never waits for a check that may not come);So the old process never refuses a routed request, and never answers after the release.
The budget counts the browser's probe-first reconnect. On a loss, the browser sends a probe first and makes its attempt no later than
RESCUE_AT(1.2 s). Adoption therefore has to happen within about 1.2 s of the loss. The worst case is about 750 ms with the reference values. The upgrade burst at 1.2 s is the thin margin, and the load test must cover it.Remote Planners stay attached through a deploy. Each hold whose link the cut closes is persisted with a hand-over window (proposed: 10 s). Until the window ends, browsers see the Planner as attached, and requests sent to it are accepted as durable rows. A new
restart-noticeclient rule (retry aresumerefused withretryevery 250 ms) brings a client back in about 2 s when the owner has a tab open. If the window ends without a resume, members see what a lost connection shows today, and the miss is logged with its cause.No acknowledged Planner request is silently dropped in hosted mode. Every acknowledged request that cannot start at once becomes a
planner_deferred_turnsrow before its acknowledgement, in normal operation as well as during a drain. Five kinds are covered: Chat@chopinmessages,invoke_plannermessages, systeminvoke_plannercalls, comment notes sent to Chopin (in a root or a child), and task drafts from Build. The design lists every other queue item and why it needs no row: job turns are already durable, and server-authored turns were acknowledged to nobody.waiting,started,ended,interruptedandcancelled. Each failure has a notice: in Chat, and also in the thread ("Chopin couldn't reply") or on Build plan.Stages and sizes
Sizes are re-estimates, not measurements.
Stage 1 grew from an earlier 8–11 weeks, for these reasons:
An earlier draft of this revision said 12–15. Covering comment notes, task drafts and system calls, and adding the hand-over window, added 2–2.5 weeks. Stage 2's parts add to 4.5–5.5 weeks.
Decisions already made
resumable-sessions.docs/authentication.md:395-396is unchanged. An owner-missingresumegetsunavailablewithretry: true. The doc spells out exactly what a command-line-only owner sees: attached through the window, then disconnected, with requests waiting as rows until that browser returns.HANDOVER=waitandHANDOVER_UNREADY_MS.pullandup --detach --no-depssteps replace the script's plainpullandup --detach. The plainup --detachrecreates both slots at once after an image change.lb_try_interval,health_timeout 1sand a 5 s hold.Open questions for maintainers
health_timeout 1sagainsthealth_fails 2.Verification
bun run ciexits 0. It ran dprint, oxlint (0 errors, 71 existing warnings), and the token and design checks.path:linecitations and 91:linecontinuations name existing files with in-range lines. I reread the code behind each new citation. I found the kinds of queued Planner turn by reading everychat.waiting.pushsite and every caller ofChat.instruct.caddy:2(Caddy 2.11.7) in front of two Python backends standing in for Chopin, with six clients sending mixed GET and POST requests. Each order ran 30 times, and every run's output was kept.health_timeout 1s), 30 runs gave no error in 12,702 requests. Each window answered exactly one readiness check with 503.caddy validateaccepts the reference upstream block inside a minimal site block.sleepcontainers sharing one image tag:up --detachrecreated both slots;up --detach --no-deps chopin-brecreated only that slot.caddy validateon the operator's own site block;Assistant-model: Claude Sonnet 5.5
Assistant-model: Claude Haiku 5.5
Assistant-model: Claude Opus 5.5
Assistant-workflow: goal (run 2fb55c4c-d75a-43ab-a116-6ee11a1285ac)
Assistant-verification: bun run ci passed with exit 0 (dprint, oxlint 0 errors and 71 existing warnings, token and design checks)
Assistant-verification: citation check passed: 399 path:line citations and 91 continuations in the design doc name existing files with in-range lines
Assistant-verification: relative link check passed: 61 relative links and anchors resolve with exact casing
Assistant-verification: Caddy 2.11.7 stand-in probe, 30 retained runs per order: the design's cut order gave 0 errors in 12,702 requests; stopping the listener first gave 9 failed POSTs in 12,953 requests over 9 of 30 runs
Assistant-verification: caddy validate accepted the reference upstream block in a minimal site block
Assistant-verification: Docker Compose 5.5.1 throwaway project: plain up --detach recreated both slots after an image change, and up --detach --no-deps recreated one
Assistant-verification: privacy grep passed: no private project, organization or resource names in the design doc, PR body or commit message
User-preference: Deploys must be strictly invisible to users, and Planner work must survive a server deploy
Co-authored-by: Alex Lavaee lavaman131@github.com