Problem
The Chess.com client's admission control — the min-request-delay pacer (lastRequestRef), the permit gate/Semaphore, the failure-window throttle-down, EMA spacing — is entirely in-process (Ref / Semaphore), created fresh per ChessComClient.live. There is no shared/DB-backed coordination.
So N processes = N independent rate limiters. Two CcasServer instances (or a server + a heavy CLI run) on the same machine/NAT share one egress IP but each enforces its own budget → combined load ≈ N× the ~13.3 req/s the single-process pacer was tuned to hold. The whole client (75ms min-request-delay floor; CF-403 count = 0 across ~524k requests / 32 days) was tuned assuming one limiter per IP. Doubling it risks 429 / Cloudflare-403 storms.
Evidence
ChessComClient throttle state (ThrottleState ref, active-count ref, gate Semaphore, lastRequestRef) is all in-memory, instantiated per layer; emaDelay / recordOutcome / throttleDown touch only local refs, no DB.
- Only DB interaction is stats flushing, not live admission state.
- Surfaced by the multi-instance concurrency audit (2026-06-23).
Relief path
Bearer-token OAuth on /pub requests bypasses the per-IP rate limit (noted previously), which dissolves the shared-IP contention for the real dev-laptop-vs-prod case. That's part of #60 / auth work. A DB/Redis-backed global token bucket would be the alternative if unauthenticated multi-process ever needs to coexist.
Status
Single server per DB is the documented supported model; CLI-alongside-server shares this egress concern but is bursty/tiny. Latent for multi-server hosting (#60). Capturing so it isn't a surprise when concurrency becomes real.
Problem
The Chess.com client's admission control — the
min-request-delaypacer (lastRequestRef), the permit gate/Semaphore, the failure-window throttle-down, EMA spacing — is entirely in-process (Ref/Semaphore), created fresh perChessComClient.live. There is no shared/DB-backed coordination.So N processes = N independent rate limiters. Two
CcasServerinstances (or a server + a heavy CLI run) on the same machine/NAT share one egress IP but each enforces its own budget → combined load ≈ N× the ~13.3 req/s the single-process pacer was tuned to hold. The whole client (75ms min-request-delay floor; CF-403 count = 0 across ~524k requests / 32 days) was tuned assuming one limiter per IP. Doubling it risks 429 / Cloudflare-403 storms.Evidence
ChessComClientthrottle state (ThrottleStateref, active-count ref, gateSemaphore,lastRequestRef) is all in-memory, instantiated per layer;emaDelay/recordOutcome/throttleDowntouch only local refs, no DB.Relief path
Bearer-token OAuth on
/pubrequests bypasses the per-IP rate limit (noted previously), which dissolves the shared-IP contention for the real dev-laptop-vs-prod case. That's part of #60 / auth work. A DB/Redis-backed global token bucket would be the alternative if unauthenticated multi-process ever needs to coexist.Status
Single server per DB is the documented supported model; CLI-alongside-server shares this egress concern but is bursty/tiny. Latent for multi-server hosting (#60). Capturing so it isn't a surprise when concurrency becomes real.