Reuse leaderboard aggregates and add repeatable benchmarks
This commit is contained in:
+60
-5
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user