Skip to content

perf: friends list reads in-match and live lineup state from the active matches - #638

Merged
lukepolo merged 3 commits into
mainfrom
perf/friends-live-state
Oct 16, 2026
Merged

lukepolo merged 3 commits into
mainfrom
perf/friends-live-state

Conversation

@lukepolo

@lukepolo lukepolo commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

The friends subscription is the biggest database cost on both 5stack.gg and cs.playhard.gg. For every friend, on every refresh, it walked that friend's entire match history twice: once in is_in_another_match, and once in the player_lineup(where: lineup.match.status = Live) filter.

  • is_in_another_match now starts from the panel's active matches and probes the friend's lineup on both columns of its unique key, so the cached generic plan can't fall back to the steam_id index and the friend's history. The answers are unchanged.
  • New partial index idx_matches_active_schedule (status, scheduled_at) serves the active statuses and the near-term Scheduled matches as two index ranges, so matches scheduled further out are never read.
  • Drops idx_match_lineup_players_steam_id. The planner prices it exactly like the unique key for a (match_lineup_id, steam_id) probe and breaks the tie by index age, so a REINDEX of it brought the whole-history plan back (1,008 extra buffers). idx_match_lineup_players_steam_id_lineup_id already serves every steam_id lookup; the friends subscription reads the same 9,075 buffers without it.
  • New players.live_lineup computed field (get_player_live_lineup) returns only lineup rows in Live, non-practice matches. The web keeps the same where/limit on it, so Hasura's checks run on at most one row.
  • Measured on the exact subscription SQL from Hasura v2.50.3, 90 friends, prepared like Hasura. The panel also has 6,000 one-off import matches and 200 matches a week out. The response is identical:
    • ~37 matches per friend: 23.7ms / 40,393 buffers → 15.8ms / 9,075
    • ~160 matches per friend: 56.8ms / 157,069 buffers → 15.5ms / 9,075
  • test/friends-live-state.spec.ts covers:
    • is_in_another_match by status, by lineup and with paused tournaments (these pass on main too)
    • live_lineup against the permission-filtered match join, including practice sessions
    • cost guards (generic plan): a 150-match veteran, history spread among one-off players (all indexes rebuilt first), and 400 extra finished / far-off matches on the panel
    • a catalog guard against a steam_id-only index
    • Main fails 7 of these; earlier commits on this PR fail the practice, spread-history, far-off Scheduled and index guards.
  • The web moves to live_lineup in web#858, which needs codegen after this deploys. player_lineup keeps working until then.

…ve matches

is_in_another_match and the friends subscription's player_lineup Live filter walked every friend's whole match history on each refresh. is_in_another_match now starts from the active matches (partial index widened to every active status), and a players.live_lineup computed field returns only lineup rows in Live matches so Hasura's checks run on at most one row.
…y panels

Review of the first commit found three gaps: on a panel of one-off players the generic plan probed lineups through the steam_id index and re-read the whole history per active match; matches scheduled far ahead were checked for every friend; and live_lineup returned practice sessions the matches permissions hide. Lineups are now probed on their unique key, a new (status, scheduled_at) partial index serves the active statuses and the Scheduled cut-off as two ranges (additive, the old index stays), and practice is excluded. Each gap has a cost or behavior test that fails on the first commit.
Re-review found the planner prices idx_match_lineup_players_steam_id exactly like the unique key for a (match_lineup_id, steam_id) probe and breaks the tie by index age, so a REINDEX of it brought back the read-the-whole-history plan (1008 extra buffers). idx_match_lineup_players_steam_id_lineup_id already serves every steam_id lookup. The spread-history test now rebuilds every index first, and a catalog test keeps a steam_id-only index from coming back.
@lukepolo
lukepolo force-pushed the perf/friends-live-state branch from 30eda0f to fc8c14e Compare October 16, 2026 16:07
@lukepolo lukepolo closed this Oct 16, 2026
@lukepolo lukepolo reopened this Oct 16, 2026
@lukepolo
lukepolo merged commit 672093a into main Oct 16, 2026
14 checks passed
@lukepolo
lukepolo deleted the perf/friends-live-state branch October 16, 2026 16:11
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