Repository navigation
perf(db): R.2 — the home asks for four rounds, not for all of them - #316
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing |
This was referenced Sep 18, 2026
Willi363363
force-pushed
the
perf/home-history-limit
branch
from
September 18, 2026 11:33
3d45c6f to
63f1462
Compare
`selectGameHistory` had no `LIMIT`. The home took four with `.slice()`, in
Node, after every participation the account had ever had crossed the wire —
and the sort can use no index either, since the predicate is
`participant.user_id` and the order is `game.started_at` on the joined table.
`09-query-debt.md` recorded the shape on 2026-09-17 and said it had no number,
because the machine that would produce one had four rounds in it. Seeded:
2 000 rounds for one player among 22 000, four in five finished.
before median 8.76 ms 2000 rows 601.7 KiB quicksort of 2000
after median 4.17 ms 4 rows 1.2 KiB top-N heapsort of 4
**The plan is barely the point.** Both shapes still hash-join a sequential
scan; the limit buys a top-N heapsort and four milliseconds. What it buys that
matters is six hundred kilobytes that stop crossing the wire per home page
load, and a cost that stops growing with the account.
**A limit alone would have been a regression**, which the register did not say.
The home was also filtering on `ended_at` after the fact: four abandoned rounds
at the top fill the budget and leave a player who has played all week looking
at an empty list. So `HistoryWindow` carries both fields, and both default to
off — `exportAccount` passes no window and still gets every row, unfinished
ones included, which is what an export is.
Each new case was checked against the absence of the piece it covers: dropping
the predicate fails the first, dropping the limit fails the second, and
filtering on `participant.submitted_at` instead of `game.ended_at` fails two —
somebody who left a round that ran to the end still played it.
`turbo run test --force --concurrency=1`: 10 of 10 tasks, 3 868 passed, 0
skipped. Typecheck and lint green over all 22 tasks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsMRxyowr2Ch5RwiAa3t9z
Willi363363
force-pushed
the
perf/home-history-limit
branch
from
September 18, 2026 11:48
63f1462 to
ceeb121
Compare
This branch was successfully deployed
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.
Second step of track R, rebased on
stagingnow that #314 is merged andcarrying its own
R.2 ⬜ → ✅tick. Independent of #315.selectGameHistoryhad noLIMIT.home.tstook four rows with.slice(0, RECENT_ROUNDS), in Node, after every participation the account hadever had crossed the wire — and no index serves both halves, since the predicate
is
participant.user_idand the order isgame.started_aton the joinedtable.
The number the register asked for and did not have
09-query-debt.mdrecorded the shape on 2026-09-17 and said so plainly: "notmeasured, because the machine that would produce it has four rounds in it".
Seeded — 2 000 rounds for one player among 22 000, four in five of them
finished — and measured from Node, median of 20 runs after 3 warm-ups:
The plan is barely the point, and that is the honest finding. Both shapes
still hash-join a sequential scan; the limit buys a top-N heapsort instead of a
full one, and about four milliseconds. What it buys that matters is six
hundred kilobytes that stop crossing the wire on every home page load —
invisible on a loopback, the payload between a Vercel function and Neon, and the
only half that grows with the account.
The trap, which the register did not name
A limit alone would have been a regression. The home was also filtering, on
ended_at, after the slice. Four abandoned rounds at the top of a history fillthe budget, and a player who has played all week is shown an empty list.
So
HistoryWindowcarrieslimitandfinishedOnly, both defaulting tooff:
exportAccountpasses no window and still gets every row, unfinishedincluded, which is what an export is.
finishedOnlyreadsgame.ended_at, notparticipant.submitted_at— somebodywho walked out of a round that ran to the end still played it.
Tests, checked against the absence of the piece each covers
Four new cases in
history.test.ts. Not assumed — each run with its own pieceremoved:
ended_at is not nullpredicatelimitgame.ended_atswapped forparticipant.submitted_atChecks
turbo run test --force --concurrency=1: 10 of 10 tasks, 3 868 passed, 0 skipped, against a local Postgres and Redis.turbo run typecheck lint --force: 22 of 22.pnpm format:check: green.09-query-debt.mdloses the entry in this PR, and the file is back at exactly 200 lines.🤖 Generated with Claude Code
https://claude.ai/code/session_01DsMRxyowr2Ch5RwiAa3t9z