Reuse leaderboard aggregates and add repeatable benchmarks

This commit is contained in:
2026-10-01 12:33:11 -04:00
parent f10345ae79
commit 221c4c10cf
5 changed files with 403 additions and 38 deletions
+60 -5
View File
@@ -46,7 +46,7 @@ Highest-round, best-game-kill and highest-score records link to the completed
match that earned them on the leaderboard and in lifetime/per-map profile stats.
Each source shows its map, game, completion time, server, and whether the match
start was recorded. **Run details** shows when the player was first observed:
at the recorded start, at a later round, or unknown for older stored records.
at the recorded start, at a reported round, or unknown for older stored records.
Presence is not proof of continuous survival. Equal personal bests link to the
earliest recorded completion, then the lowest match ID when times tie.
@@ -64,8 +64,10 @@ required. The leaderboard and profiles identify a player by **GUID**, displaying
the latest reported name across both games, regardless of game or map filters.
Different names for the same GUID do not split their leaderboard entry or
profile; identical names with different GUIDs remain separate. Stored player
records remain keyed by **game + GUID** for telemetry ingestion; the combined
leaderboard and profiles require no storage migration. GUIDs stay strings.
records remain keyed by **game + GUID** for telemetry ingestion; combining their
identity requires no rekeying. The record-source upgrade automatically adds
nullable participation-start fields; existing rows keep an unknown start instead
of inferring one. GUIDs stay strings.
## Recorded statistics
@@ -152,11 +154,64 @@ key returned in `maps`), `metric` (default `highestRound`), `q` (name/GUID searc
available `maps`, the matching player `total`, pagination values, and `summary`.
Each entry represents one GUID and combines its qualifying matches within the
game/map filters. Entries have no game field; map options retain their game
identity. Both `total` and `summary.players` count unique GUIDs; only `total` is
limited by the player search.
identity. Both `total` and `summary.players` count unique GUIDs; `total` applies
the selected metric's qualification rules and player search, while the summary
covers all completed qualifying participation in the game/map scope.
Metric keys are `highestRound`, `kills`, `roundsSurvived`, `headshots`, `revives`,
`downs`, `gamesPlayed`, `gamesCompleted`, `bestKills`, `bestScore`, `headshotRate`,
`killsPerDown`, `averageKills`, `averageRounds`, and `completionRate`.
Efficiency values in entries are unrounded ratios (for example, `0.5` means 50%)
or `null` when their denominator is zero. UI percentages multiply by 100.
Entries also include `records`, keyed by `highestRound`, `bestKills`, and
`bestScore`, with the completed match ID, value, map/game/server, timing and
observed participation behind each personal best.
## Query performance
Leaderboard reads materialize the selected completed participation once, reuse
one player aggregate for ranking and summary totals, and reuse the ranked/search
result for pagination and its matching count. Empty and out-of-range pages still
return accurate totals. Record evidence is fetched only for players on the page.
Exact player-rank reads share the ranking rules but skip unused fleet summaries
and map lists. No response cache delays visibility of a newly completed game.
Run the deterministic, isolated benchmark with:
```sh
node scripts/benchmark-leaderboard.mjs --scenario all --baseline-ref eac9ad4 --runs 3 --json /tmp/leaderboard-benchmark.json
```
The script creates temporary SQLite databases, loads the actual query modules,
checks complete-response parity against the selected Git revision, and reports
timings, source hashes and query plans. It never opens the application database.
The small fixture has 2,000 matches and 8,000 participation rows; the large fixture
has 50,000 matches and 200,000 participation rows. Both include completed,
active, interrupted and nonqualifying records.
On the local Intel i5-8279U / Node 25.9.0 / SQLite 3.49.2 test environment, three
measured runs after warmup gave these median query times on the large fixture:
| Request | Before | After |
| --- | ---: | ---: |
| Highest round, all games | 1,717 ms | 829 ms |
| Total kills, all games | 1,555 ms | 858 ms |
| One game | 899 ms | 491 ms |
| One map | 345 ms | 224 ms |
| Player search | 1,515 ms | 757 ms |
| Deep page | 1,421 ms | 752 ms |
| Headshot percentage | 1,444 ms | 760 ms |
| Exact player rank | 1,524 ms | 554 ms |
The small fixture's unfiltered request fell from 41 to 30 ms. All 150 additional
small-fixture parity cases and 22 timed small/large request cases matched the
baseline's complete responses, including ratios and record evidence. Timings
include SQL preparation and evidence lookup, exclude HTTP/browser rendering, and
use a warm filesystem cache with alternating implementation order. These are
local synthetic measurements, not production latency guarantees. Queries still
run synchronously, so larger histories remain a capacity consideration.
Validation for this change passed 1,019 unit tests, Svelte checking with no errors
or warnings, and a production build. Isolated Firefox walkthroughs at 1440px and
390px checked completed-only totals, record links, efficiency qualification,
scoped ranks/history, preserved navigation, empty scopes and mobile expansion.