Files
comunidadhll/ai/tasks/done/TASK-211-optimize-annual-ranking-read-path.md
2026-06-10 08:52:15 +02:00

9.4 KiB

id, title, status, type, team, supporting_teams, roadmap_item, priority
id title status type team supporting_teams roadmap_item priority
TASK-211 Optimize annual ranking read path done backend Backend Senior
Arquitecto Python
foundation medium

TASK-211 - Optimize annual ranking read path

Goal

Optimizar la ruta publica de lectura del ranking anual para que lea exclusivamente los snapshots anuales ya materializados, evitando inicializacion, migracion o setup de storage en cada request publico y reduciendo la latencia del cold/read path sin tocar frontend.

Context

El endpoint publico anual /api/ranking?timeframe=annual&metric=kills&limit=30&year=2026 ha llegado a tardar 24-42 segundos en produccion. Sin embargo, las mediciones disponibles descartan a PostgreSQL como cuello de botella:

  • EXPLAIN ANALYZE sobre rcon_annual_ranking_snapshots tarda ~0.073 ms
  • EXPLAIN ANALYZE sobre rcon_annual_ranking_snapshot_items tarda ~0.083 ms
  • cProfile directo posterior de build_global_ranking_payload(... annual ...) tarda ~0.244 s
  • weekly ranking tarda ~0.055 s
  • player search tarda ~0.069 s
  • player profile tarda ~0.136 s

El hallazgo actual es que backend/app/rcon_annual_rankings.py llama a initialize_rcon_materialized_storage(db_path=db_path) al inicio de get_annual_ranking_snapshot(), incluso cuando la request publica solo necesita leer snapshots anuales ya calculados. Esta task debe separar con claridad la ruta de generacion/escritura anual de la ruta publica de lectura anual.

Preserve the current product identity: Spanish-speaking HLL Vietnam community, military/Vietnam/tactical/sober visual direction and controlled repository evolution.

Steps

  1. Revisar primero la ruta actual de lectura anual en backend/app/rcon_annual_rankings.py y el ensamblado de payloads en backend/app/payloads.py.
  2. Identificar exactamente donde la lectura publica annual entra en setup o inicializacion innecesaria y separar esa responsabilidad de la generacion/escritura anual.
  3. Hacer que la lectura publica anual use solo conexion de lectura y consultas sobre:
    • rcon_annual_ranking_snapshots
    • rcon_annual_ranking_snapshot_items
  4. Evitar initialize_rcon_materialized_storage() dentro de get_annual_ranking_snapshot() salvo que exista un modo SQLite legacy explicito con --sqlite-path.
  5. Mantener PostgreSQL como ruta operativa por defecto y conservar compatibilidad con SQLite solo como modo legacy explicito, sin migracion/setup durante la lectura publica.
  6. Revisar build_annual_ranking_snapshot_payload() y build_global_ranking_payload() para asegurar que la lectura annual publica sigue devolviendo el contrato correcto sin fallback runtime.
  7. Ejecutar la validacion funcional y de tiempos pedida, documentar antes/despues y anadir un test pequeno si el stack lo permite para blindar que la ruta PostgreSQL de lectura no inicializa storage.

Files to Read First

  • AGENTS.md
  • ai/repo-context.md
  • ai/architecture-index.md
  • backend/app/rcon_annual_rankings.py
  • backend/app/payloads.py
  • ai/tasks/done/TASK-188-audit-ranking-and-stats-query-performance.md

Expected Files to Modify

  • ai/tasks/in-progress/TASK-211-optimize-annual-ranking-read-path.md
  • backend/app/rcon_annual_rankings.py
  • backend/app/payloads.py

Optional only if strictly necessary:

  • backend unit test file related to annual rankings or payloads

Constraints

  • Keep the change minimal.
  • Preserve HLL Vietnam project identity.
  • Do not introduce unnecessary frameworks or dependencies.
  • Do not overwrite repository-specific context with generic platform template text.
  • No ejecutar ai-platform run.
  • No tocar frontend/.
  • No tocar assets de armas ni SVGs.
  • No reactivar Elo/MMR.
  • No reintroducir Comunidad Hispana #03.
  • No consultar RCON, scoreboard publico ni recalcular ranking dentro de la lectura publica annual.
  • No escanear rcon_match_player_stats en la ruta publica annual.
  • No hacer N+1 por jugador en la ruta publica annual.
  • Los endpoints publicos deben leer read models propios en PostgreSQL.
  • La lectura publica annual debe leer exclusivamente rcon_annual_ranking_snapshots y rcon_annual_ranking_snapshot_items.
  • Mantener compatibilidad con --sqlite-path solo como modo legacy explicito.

Validation

Before completing the task ensure:

  • get_annual_ranking_snapshot() no ejecuta initialize_rcon_materialized_storage() en la ruta PostgreSQL de lectura publica
  • la generacion/escritura annual sigue pudiendo inicializar storage cuando sea necesario
  • la lectura publica annual no recalcula ranking ni cae a runtime aggregates
  • la respuesta annual mantiene:
    • read_model = rcon-annual-ranking-snapshot
    • snapshot_status = ready
    • fallback_used = false
    • items = 30
  • se ejecuta esta medicion directa 3 veces:
python - <<'PY'
import time
from app.payloads import build_global_ranking_payload

for i in range(3):
    start = time.perf_counter()
    payload = build_global_ranking_payload(
        timeframe="annual",
        metric="kills",
        limit=30,
        year=2026,
        server_id="all",
    )
    elapsed = time.perf_counter() - start
    data = payload.get("data", payload)
    print({
        "attempt": i + 1,
        "seconds": round(elapsed, 3),
        "items": len(data.get("items") or []),
        "snapshot_status": data.get("snapshot_status"),
        "read_model": (data.get("source") or {}).get("read_model"),
    })
PY
  • se ejecuta esta medicion HTTP interna 3 veces:
python - <<'PY'
import json
import time
from urllib.request import urlopen

url = "http://127.0.0.1:8000/api/ranking?timeframe=annual&metric=kills&limit=30&year=2026"

for i in range(3):
    start = time.perf_counter()
    with urlopen(url, timeout=30) as response:
        body = response.read()
    elapsed = time.perf_counter() - start
    payload = json.loads(body.decode("utf-8"))
    data = payload.get("data", {})
    print({
        "attempt": i + 1,
        "seconds": round(elapsed, 3),
        "http": response.status,
        "items": len(data.get("items") or []),
        "snapshot_status": data.get("snapshot_status"),
        "read_model": (data.get("source") or {}).get("read_model"),
    })
PY
  • annual ranking queda claramente por debajo de 1 segundo en caliente
  • idealmente annual ranking queda por debajo de 300 ms
  • se ejecutan tests existentes relacionados si los hay
  • si no hay tests suficientes, se anade un test unitario pequeno para verificar con monkeypatch/mock que get_annual_ranking_snapshot() no invoca initialize_rcon_materialized_storage() en la ruta PostgreSQL de lectura
  • git diff --name-only matches the expected scope
  • no unrelated files were modified

Outcome

Documentar:

  • causa exacta del problema de latencia en la ruta annual/cold path
  • que separacion se hizo entre generacion annual y lectura publica annual
  • como queda tratada la compatibilidad PostgreSQL por defecto y SQLite legacy explicito
  • antes/despues de tiempos en la medicion directa y en la medicion HTTP
  • test ejecutados o test anadido
  • confirmacion explicita de que no se tocaron frontend, assets de armas, SVGs, Elo/MMR ni Comunidad Hispana #03

Result:

  • Updated backend/app/rcon_annual_rankings.py to split annual snapshot reads from annual snapshot generation/storage initialization.
  • Public annual reads now open PostgreSQL directly for snapshot queries and no longer call initialize_rcon_materialized_storage() on the PostgreSQL path.
  • Legacy SQLite remains available only when an explicit db_path/--sqlite-path is used; the read path now resolves the file path without running initialization or migrations.
  • Added backend/tests/test_annual_ranking_payload.py with a regression test asserting PostgreSQL annual reads do not invoke storage initialization.

Cause fixed:

  • The annual public read path entered initialize_rcon_materialized_storage() before deciding between PostgreSQL and SQLite.
  • In PostgreSQL mode that introduced avoidable cold-path schema/setup work in a request that only needed to read precomputed annual snapshots.
  • The fix moves annual public reads to a read-only connection path that consults only:
    • rcon_annual_ranking_snapshots
    • rcon_annual_ranking_snapshot_items

Validation performed:

  • PASS: python -m compileall backend\\app\\rcon_annual_rankings.py backend\\tests\\test_annual_ranking_payload.py
  • PASS: direct unit test with stdlib unittest:
    • AnnualRankingPayloadTests.test_get_annual_ranking_snapshot_skips_storage_init_on_postgres_read
  • PASS: direct in-process timing:
    • attempt 1: 0.010 s
    • attempt 2: 0.001 s
    • attempt 3: 0.001 s
    • items = 30
    • snapshot_status = ready
    • read_model = rcon-annual-ranking-snapshot
  • INFO: HTTP timing probe against http://127.0.0.1:8000/api/ranking?... could not run because the local server was not listening (ConnectionRefusedError).

Before/after timing summary:

  • Before, per task diagnosis:
    • public production annual endpoint observed around 24-42 s
    • direct in-process profiling around 0.244 s
  • After this change in local direct payload validation:
    • first call 0.010 s
    • warm calls 0.001 s

Scope confirmation:

  • No frontend file was touched.
  • No weapon asset, SVG or physical image was touched.
  • No Elo/MMR code was reactivated.
  • Comunidad Hispana #03 was not reintroduced.
  • ai/system-metrics.md was not touched by this task.
  • No push was made.

Change Budget

  • Prefer fewer than 5 modified files.
  • Prefer changes under 200 lines when feasible.
  • Split the work into follow-up tasks if limits are exceeded.