Any path that is not a probe or /registry/* is an instance request. Pinboard reads the namespace and id from the path, finds or picks the one worker that owns the id, and proxies the request to it.
https://pinboard.example.com/threads/t_8f2a/stream?x=1
└──┬───┘└─┬──┘└──┬──┘└─┬┘
namespace id rest query
→ {advertiseUrl}/threads/t_8f2a/stream?x=1Addressing
| Part | Rule |
|---|---|
| Namespace | / plus the first path segment. Must match a namespace some worker registered. |
| Id | The second segment. 1–256 characters of [A-Za-z0-9._~-]. Not decoded: % is rejected. |
| Rest | Everything after the id, forwarded as-is. |
| Query | Forwarded as-is. |
Ids are names the client chooses. The first request for an id places it; later requests reach the same worker until the placement ends.
Resolution
Each request runs one atomic resolve against Redis:
- Placed and owner live: forward to the owner.
- No worker ever registered the namespace:
404 unknown namespace. - Bucket in cold start:
503 registry cold start, retry. A bucket is the id's Redis shard; it refuses new placements for one lease (15 s by default) after first use, so a wiped Redis cannot double-place ids held in memory. - Pick a worker among the namespace's live workers that are not draining, not degraded, and have at least
min(5 s, lease / 3)of lease left.- A caller instance prefers its own worker; otherwise the client IP (or a custom
affinity.key) prefers one worker by rendezvous hash. - The preference holds while that worker's
headroomis at least the mean divided bybalanceFactor(1.25). - Otherwise two random candidates; the higher
headroomwins. - None placeable:
503 no live backends for namespace.
- A caller instance prefers its own worker; otherwise the client IP (or a custom
- Mint a placement epoch and write the row.
A placement ends when its owner's lease lapses, the owner releases it, a wind-down deadline passes, or the worker refuses the activation.
Placement epoch
Every placement row carries an epoch: a decimal string, increasing per bucket, minted whenever the id is placed. It is sent on every forward as Pinboard-Epoch. Workers use it to fence writes: an activation holding an older epoch than the stored one has lost the id.
| Where | Use |
|---|---|
Pinboard-Epoch request header | The epoch the forward resolved against |
Release epoch | Compare-and-delete: frees the row only at that epoch |
Tag epoch | Compare-and-set of the owner tag |
Retries Pinboard makes
| Situation | Pinboard does |
|---|---|
| More than 5 s passed between resolve and send | Re-resolves, at most twice |
Worker answers with Pinned-Refusal: lease-unhealthy | Releases the placement and, for requests without a body and for WebSocket upgrades, re-places once on another worker. Requests with a body get 503 worker refused activation, retry. |
| A new placement's worker is unreachable (TCP connect failed) | Releases the placement so the client's retry re-places. Answers 502. |
Pinboard never replays a request body. Every other failure goes back to the client.
Hostname-aware mode
With PINBOARD_HOSTNAME_AWARE=1 one fleet serves several hostnames, each with its own workers and data.
- The
Hostheader (lowercased, port stripped) names the hostname. Missing:400 missing Host header. - Workers register a
hostnameand anenvper namespace. The(hostname, namespace)binding names the env; placements live per(env, namespace). - An id placed by a worker on another hostname of the same env follows the env's
takeover:wind_down: the old owner gets a wind-down for one lease; requests answer503 takeover in progress, retryuntil it lapses.deny:503 placement held by another hostname.
- After a higher-epoch registration rebinds
(hostname, namespace)to a new env, new ids wait one lease:503 hostname binding displaced, retry.
Pages
- Proxy a request: the endpoint, headers, WebSockets, errors.
- Call from an instance: attested instance-to-instance requests.