Split The code is public; the app answers only to the crew.
The first paint groups every match ever played: `standings` costs 42 ms at 50k rows and 171 ms at 200k #218
Check · Review · Land · Live done — Landed and live in production.
`standings` was a `GROUP BY` over the whole `match` table, paid on every `/`, every `POST /api/match`, and every terminal frame — 171ms at 200k rows and growing without bound.
shipped merged
Sign in to join this conversation.
standingswas aGROUP BYover the wholematchtable, paid on every/, everyPOST /api/match, and every terminal frame — 171ms at 200k rows and growing without bound.What changed: a
standingtable, one row per identity, kept current byrecomputeStanding— called fromsaveMatchanddeleteMatch— which recomputes just that one identity's row from its own matches (WHERE identity = ?1, using thematch_by_identityindex), never the whole table.standings()is now a plain sorted read of that table, backed by a(kills DESC, damage DESC)index. I chose recompute-from-source over delta-tracking: the SQL is a direct copy of the old GROUP BY narrowed to one identity, so it can't drift from whatsaveMatch'sMAX(...)conflict logic actually stored — no separate delta math to get wrong on the late-post races the issue calls out. BothsaveMatch(periodic + final + late-periodic posts) anddeleteMatch(the terminal'sx) recompute; the aggregate lives in the same SQLite file both the server andsrc/tty.tsread, so there's no in-process cache to go stale between the two.Migration:
openDatabasecreatesstandingonce, populated with the old GROUP BY query, alongside the existingidentity/walked_out/mapmigrations — a database from before this change works on first open, paying the O(n) cost exactly once.Proof: the same
db.test.tscase for "standings group by identity, not name" now reads fromstanding. A new test seeds 20,000 matches throughsaveMatchand assertsstandings()answers in under 10ms; run against the pre-change code it takes ~53ms, so it's an honest regression test. A second migration test covers an old database with a fullmatchtable and nostandingtable yet.Left out:
matchCount(a plainCOUNT(*)) isn't in the issue's scope and I left it alone. No SQL moved out ofsrc/db.ts; no caller (game.ts,tty.ts,server.ts) changed.bun run check(types + full suite, 773 tests) passes.Closes #184