3.6 KiB
3.6 KiB
Player Search Read Model
Objective
Define the first dedicated read model for player search so /api/stats/players/search does not aggregate large RCON historical tables on every public request.
Table
Primary table:
player_search_index
Operational scope:
- PostgreSQL is the default operational store
- SQLite remains available only as explicit local compatibility for validation and isolated maintenance runs
Read-model nature:
- this table is regenerable
- it is not the canonical source of truth
- canonical historical data remains in
rcon_materialized_matchesandrcon_match_player_stats
Stored Fields
Each row stores one player projection for one public search scope:
server_idplayer_idplayer_namenormalized_player_namefirst_seen_atlast_seen_atservers_seenmatches_current_yearkills_current_yeardeaths_current_yearteamkills_current_yearupdated_at
Current scope rows:
all-serverscomunidad-hispana-01comunidad-hispana-02
Why server_id exists:
- the public search endpoint already supports
server_id - keeping one row per scope preserves the existing frontend contract without recalculating large runtime aggregates for server-filtered searches
Refresh Command
Manual command:
python -m app.rcon_historical_player_stats refresh-player-search-index
SQLite-only local override:
python -m app.rcon_historical_player_stats refresh-player-search-index --sqlite-path backend/data/hll_vietnam_dev.sqlite3
Refresh policy:
- rebuild from materialized RCON/AdminLog tables
- replace rows scope by scope
- aggregate only the current UTC year
- keep the latest current-year player name
- store accent-insensitive normalized names in Python
Public Read Path
Priority for /api/stats/players/search:
- use
player_search_indexwhen the requested scope has rows - return read-model results directly, including empty query results when the index is populated but the query does not match
- fall back to runtime aggregation only when:
- the read model table is unavailable
- the requested scope has no rows yet
- a controlled read error occurs
Returned compatibility:
- the payload still returns
player_id - the payload still returns
player_name - the payload still returns
matches_considered - the payload still returns
last_seen_at - the payload still returns
servers_seen
matches_considered remains compatible by mapping from matches_current_year.
PostgreSQL Notes
No extra PostgreSQL extensions are required.
Specifically:
- no
pg_trgm - no custom text-search extension
Search tolerance is implemented with:
- normalized lowercase names
- accent stripping in Python
- indexed scope + normalized-name reads
- runtime fallback preserved as a safety net
Current Limitations
- this read model is focused on player search only, not personal profile totals
- counts are current-year only by design
- historical players with no activity in the current UTC year are not intentionally prioritized in this first model
- profile and personal stats still use runtime aggregation over materialized tables
Production Validation
Recommended checks after refresh:
- run
python -m app.rcon_historical_player_stats refresh-player-search-index - confirm the command reports rows for
all-servers,comunidad-hispana-01andcomunidad-hispana-02 - call
/api/stats/players/search?q=<known-player>&limit=5 - verify response metadata reports
read_model=player-search-index - verify fallback metadata only appears when the read model is empty or unavailable