Repository navigation
sandboxd: evacuate a drained node so peers adopt its claims at once - #437
Merged
Merged
Conversation
CMGS
force-pushed
the
feat/drain-evacuate
branch
from
November 3, 2026 23:42
0d7be95 to
95e054b
Compare
A drain leaves claims serving until their leases end, so a node cannot be retired on demand. POST /v1/drain?evacuate=1 hibernates and archives each claim on the maintenance budget, releases its row and asks a peer to adopt it. The node drops a claim only after a peer answered, and takes the row back when none does. A repeat call resumes a hand-off an outage cut short. Queued claims answer 503 to a wake, archived ones move first, and the run takes the budget one batch at a time so crash recovery and reaps keep their turn. SDK Close follows a moved claim.
CMGS
force-pushed
the
feat/drain-evacuate
branch
from
November 4, 2026 00:24
95e054b to
0f966bc
Compare
This was referenced Nov 4, 2026
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.
What
POST /v1/drain?evacuate=1drains a node and moves its claims to peers with their state, so a node can be retired or serviced without waiting for leases to run out. It needsarchive_failover.POST /v1/draindoes.Release(id, expect, owner)moves the row this node owns at epochexpectto no owner at a new epoch, in one compare-and-set. The node then asks a peer to adopt it throughGET /v1/sandboxes/{id}/ownerwith the claim token, and forgets the claim only after a peer answered. With no adopter it takes the row back and keeps the claim./v1/infogetsevacuating(claims left) and/metricsgetssandboxd_evacuating. Onceevacuatingis gone,claimedcounts the claims that stayed, and the log names each and why.DELETE /v1/drainstops the run. A claim mid-archive finishes its archive and keeps its row.SDK
Closein Go and Python now follows a moved claim: a 404 runsLookupand releases at the owner it finds.Fencing and repair
Putis owner-guarded.Replace,Consumeand a secondReleaseneed the pre-release epoch.Lookupwith its token adopts it. A released row past its retention deadline is purged by the hourly sweep.Wake gate and queue order
Until its hand-off, a wake of a queued claim answers
503 sandbox moving to another node; retrywithRetry-After. The gate sits inbeginWake, so an explicit restart of a failed claim still works.Archived claims move first and running ones last, because a parked claim answers 503 until its turn while a running one keeps serving. The run takes one budget's worth at a time, so crash recovery and reaps of the claims that stay keep their FIFO turn.
These claims stay and keep serving: egress-lane claims, claims with volumes, failed claims, claims whose tenant was removed, claims archived before
archive_failoverwas on, claims whose step failed, and every claim of a node withoutarchive_failover.Lease across the move
A claim that never opted into archiving keeps its lease deadline. An opted-in claim keeps the later of its lease and
archive_delete_after_seconds, or no deadline when that is 0. Either deadline holds while the claim stays archived: its first wake on the adopter starts a fresh lease, like any wake of an archived claim. Sweep archives keep replacing the lease with retention, as before.Hot path
Nothing on the claim path. A wake of a running claim is unchanged. A wake of a hibernated or archived claim adds one map read under the lock
beginWakealready takes.Infois O(1)./metricsgains one line.Measurements
Two nodes, about 11 ms apart, S3 and PostgreSQL on one of them, on the binary of this commit:
A running claim answers 503 for about 1 s of its move. A first wake on the adopter over S3 takes 11-14 s.
/dev/shmand a spawned process intact; old owner answers 404;Lookupfinds the adopter;Closeon a stale handle releases theremax_runningNot run on hardware: the exact crash window between release and a peer's answer, the egress-lane skip and replicated claims. Those have unit tests only.
Known costs
Closeof a sandbox that is gone costs one lookup across the peers. ALookupthat cannot reach the adopter also answers nil, in Go, so that claim lives to its deadline, or to its retention once archived.Tests
archivestest(Mem and PostgreSQL): release, its fencing and take after release.store/peer: adoption across peers.Retry-Afteron HTTP and preview.Closeon a moved claim and on one no node holds, in Go and Python.