Files
comunidadhll/docs/player-search-read-model-plan.md
2026-06-09 10:31:21 +02:00

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_matches and rcon_match_player_stats

Stored Fields

Each row stores one player projection for one public search scope:

  • server_id
  • player_id
  • player_name
  • normalized_player_name
  • first_seen_at
  • last_seen_at
  • servers_seen
  • matches_current_year
  • kills_current_year
  • deaths_current_year
  • teamkills_current_year
  • updated_at

Current scope rows:

  • all-servers
  • comunidad-hispana-01
  • comunidad-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:

  1. use player_search_index when the requested scope has rows
  2. return read-model results directly, including empty query results when the index is populated but the query does not match
  3. 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-01 and comunidad-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