Skip to content

perf(bracket): drop per-click PUT /v1/picks save#285

Merged
0800tim merged 1 commit into
mainfrom
perf/drop-per-match-pick-save
Jun 4, 2026
Merged

perf(bracket): drop per-click PUT /v1/picks save#285
0800tim merged 1 commit into
mainfrom
perf/drop-per-match-pick-save

Conversation

@0800tim

@0800tim 0800tim commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Tim noticed every flag tap was firing a PUT /v1/picks/<user>/<match> in DevTools. Under TV-spike load (1000+ concurrent users × 5 picks/sec) that would saturate the single-process game-service event loop, even though the request is fire-and-forget client-side.

Removed both per-match call sites in BracketBuilder (onChangeMatch + onChangeKnockout) plus the persistPickToServer callback and the savePerMatchPick import. Durability now flows through:

  1. update() writes the full bracket to localStorage on every pick (no change).
  2. BracketAutoSave's 30s interval bulk-saves when dirty (PR feat(bracket): dirty-detect + floating save + 30s autosave #282).
  3. The explicit Save button at the end of the bracket page.

The autosave lastSavedSig was never bumped by the per-match endpoint, so dropping it actually fixes the dirty-detect — the floating Save button will now reliably appear after the first edit instead of being competed-with by the silent per-match path.

Load profile change (rough numbers):

Before After
RPS per 1k users @ 5 picks/sec 5,000 33
Headroom @ same game-service capacity ~1k concurrent ~150k concurrent

🤖 Generated with Claude Code

Tim noticed every flag tap was firing a PUT /v1/picks/<user>/<match>
in DevTools. Under TV-spike load (1000+ concurrent users rapidly
clicking 5 picks/sec each = 5000 RPS) that would saturate the
single-process game-service event loop, even though each request is
fire-and-forget client-side.

Removed:
  - persistPickToServer() callback (was wrapping savePerMatchPick)
  - Both call sites: onChangeMatch + onChangeKnockout
  - savePerMatchPick import (now dead from this file)

Durability now flows through three paths, all of which were already
in place:
  1. update() writes the full bracket to localStorage on every pick
  2. BracketAutoSave's 30s interval bulk-saves when dirty (PR #282)
  3. The explicit Save button at the end of the bracket page

The autosave's lastSavedSig wasn't being bumped by the per-match
endpoint anyway, so the floating Save button now reliably reflects
unsaved state on every pick instead of always-clean.

Load profile change (rough):
  Before: 1000 users × 5 picks/sec  = 5,000 RPS sustained.
  After:  1000 users × 1/30 per save = 33  RPS sustained.

150x reduction in pick-driven traffic. Headroom for ~150k concurrent
users at the same game-service capacity, vs ~1k before.

Tim 2026-06-05.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Signed-off-by: Tim Thomas <0800tim@gmail.com>
@0800tim 0800tim merged commit aa10532 into main Jun 4, 2026
11 of 12 checks passed
0800tim added a commit that referenced this pull request Jun 4, 2026
Brings perf/drop-per-match-pick-save across so the dev surface
matches what Tim verifies before prod sign-off.
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

DRY-RUN — this verdict is informational; CI is not blocked.

Auto-triage: GREEN — auto-triage clear

Risk score: 0/100

Metric Value
Files changed 1
Lines added 23
Lines removed 36
Apps touched apps/web
New dependencies 0
New 3rd-party hosts 0

No flags raised by the automated scanners. A human reviewer will still take a look.

Labels applied: area:web, auto-triage:green

Posted by @vtorn/pr-triage-bot. How this works: docs/security/01-pr-triage-process.md. Disagree with the verdict? Comment /triage override <reason> and a maintainer will re-review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant