Game State
The caller's view of the game, plus server-side catch-up.
The poll is the hottest path in the product: every client in the room hits it every two seconds for the whole game. It must not take the row lock in the common case, and until 19 Aug it took it on every single call.
The bug was the predicate, not the intent. needs_work read round_phase != PHASE_COMPLETE, which is not "is there work to do", it is "is the game still running" — true for every poll of every live game. So N clients serialised on one lock, each holding a threadpool thread and a pooled DB connection while it waited. Latency compounds from there: slower responses leave more polls outstanding, more outstanding polls queue deeper on the lock. On gamma with three people it went 2.9s -> 32s -> 122s, drained the 50-connection pool, and took down everything that had nothing to do with games (Deepgram transcript writes, reminders, the room cleanup task) before /health stopped answering and kubelet killed the container.
So: run the catch-up speculatively on the UNLOCKED row. auto_advance reports whether it changed anything, and for a live game the answer is no on almost every poll — a turn timer has to expire, or a lap has to finish. Only then do we discard the speculative work, re-read FOR UPDATE and do it again for real. Asking the spec directly is what keeps this honest: a hand-written "is it due yet" predicate beside six formats' tick methods is exactly the thing that drifts back into locking on every call.
Path parameters
Headers
Response
Successful Response
Changes
No recorded changes to this endpoint across all 1 revision of this API.