12 KiB
id, title, status, type, team, supporting_teams, roadmap_item, priority
| id | title | status | type | team | supporting_teams | roadmap_item | priority | |
|---|---|---|---|---|---|---|---|---|
| TASK-215 | Optimize weekly and monthly ranking read path | done | backend | Backend Senior |
|
foundation | high |
TASK-215 - Optimize weekly and monthly ranking read path
Goal
Optimizar la ruta publica de lectura de los rankings weekly y monthly para que lean exclusivamente snapshots ya materializados, evitando inicializacion, migracion o setup de storage en cada request publica y reduciendo la latencia del read path sin tocar frontend, assets ni Elo/MMR.
Context
Tras TASK-211 se corrigio la ruta anual para separar lectura publica de generacion/escritura. Ahora se ha detectado el mismo patron en weekly/monthly.
Evidencia de produccion:
- la API publica devuelve correctamente:
snapshot_status = readyread_model = ranking-snapshotfallback_used = falseitems = 20
- el perfilado directo de:
build_global_ranking_payload(
timeframe="weekly",
metric="kills",
limit=20,
server_id="all",
)
ha tardado 101.33 segundos.
cProfile indica:
payloads.py:787 build_global_ranking_payload -> 101.330srcon_historical_leaderboards.py:315 get_latest_ranking_snapshot -> 101.330srcon_historical_leaderboards.py:98 initialize_ranking_snapshot_storage -> 101.306srcon_admin_log_materialization.py:24 initialize_rcon_materialized_storage -> 101.294spostgres_rcon_storage.py:383 initialize_postgres_rcon_storage -> 101.315s
La causa actual es que get_latest_ranking_snapshot() llama a initialize_ranking_snapshot_storage() dentro de una ruta publica de solo lectura. Esa funcion inicializa o migra storage y no debe ejecutarse durante una request publica que solo lee snapshots ya materializados.
Regla arquitectonica:
- los endpoints publicos de ranking weekly/monthly deben leer unicamente:
ranking_snapshotsranking_snapshot_items
- no deben:
- consultar RCON
- consultar scoreboard publico
- recalcular rankings en runtime
- escanear
rcon_match_player_stats - inicializar o migrar storage en request publico
- hacer N+1 por jugador
- preparar fallback runtime salvo modo interno explicito
Preserve the current product identity: Spanish-speaking HLL Vietnam community, military/Vietnam/tactical/sober visual direction and controlled repository evolution.
Steps
- Revisar primero la implementacion actual en
backend/app/rcon_historical_leaderboards.pyy el ensamblado de payloads enbackend/app/payloads.py. - Tomar como referencia conceptual la separacion aplicada en
TASK-211para annual. - Separar con claridad:
- inicializacion, generacion o escritura de snapshots
- lectura publica de snapshots
- Mantener
initialize_ranking_snapshot_storage()disponible para:- generacion
- refresh
- CLI
- procesos internos
- SQLite legacy si realmente lo requiere
- Evitar
initialize_ranking_snapshot_storage()enget_latest_ranking_snapshot()cuando se use PostgreSQL por defecto. - En la ruta PostgreSQL publica:
- abrir conexion directa con
connect_postgres_compat() - buscar snapshot en
ranking_snapshots - contar o listar
ranking_snapshot_items - devolver payload sin inicializar storage
- abrir conexion directa con
- Mantener compatibilidad SQLite legacy explicita:
- si
db_pathse pasa explicitamente, resolver path de solo lectura sin migrar o inicializar si el diseno actual lo permite - no romper tests existentes
- si
- Mantener el contrato de respuesta:
read_model = ranking-snapshotsnapshot_status = readyfallback_used = falseitemssegunlimit- weekly/monthly siguen funcionando para
all,comunidad-hispana-01ycomunidad-hispana-02 - las metricas existentes siguen funcionando
- Anadir un test unitario con monkeypatch o mock para verificar que
get_latest_ranking_snapshot()no invocainitialize_ranking_snapshot_storage()en la ruta PostgreSQL de lectura publica, tomando como referencia el test creado enTASK-211.
Files to Read First
AGENTS.mdai/repo-context.mdai/architecture-index.mdbackend/app/rcon_historical_leaderboards.pybackend/app/payloads.pyai/tasks/done/TASK-211-optimize-annual-ranking-read-path.md
Expected Files to Modify
ai/tasks/in-progress/TASK-215-optimize-weekly-monthly-ranking-read-path.mdbackend/app/rcon_historical_leaderboards.pybackend/app/payloads.py
Optional only if strictly necessary:
- backend unit test file related to ranking snapshots 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.
- No tocar SVGs.
- No modificar imagenes fisicas.
- No reactivar Elo/MMR.
- No reintroducir Comunidad Hispana #03.
- No tocar
ai/system-metrics.md. - No hacer push.
- No consultar RCON, scoreboard publico ni recalcular ranking dentro de la lectura publica weekly/monthly.
- No escanear
rcon_match_player_statsen la ruta publica weekly/monthly. - No hacer N+1 por jugador en la ruta publica weekly/monthly.
- No inicializar ni migrar storage en request publico weekly/monthly.
- Los endpoints publicos deben leer read models propios en PostgreSQL.
- La lectura publica weekly/monthly debe leer exclusivamente
ranking_snapshotsyranking_snapshot_items. - Mantener compatibilidad con SQLite legacy solo como modo explicito si ya existe soporte real para ello.
Validation
Before completing the task ensure:
get_latest_ranking_snapshot()no invocainitialize_ranking_snapshot_storage()en la ruta PostgreSQL de lectura publica- no aparece
initialize_ranking_snapshot_storageen el perfil de la ruta publica - no aparece
initialize_rcon_materialized_storageen el perfil de la ruta publica - no aparece
initialize_postgres_rcon_storageen el perfil de la ruta publica - la generacion o escritura de snapshots sigue pudiendo inicializar storage cuando sea necesario
- la lectura publica weekly/monthly no recalcula ranking ni cae a runtime aggregates
- la respuesta mantiene:
read_model = ranking-snapshotsnapshot_status = readyfallback_used = falseitemssegunlimit
- se ejecuta
compileall:
python -m compileall backend\app\rcon_historical_leaderboards.py backend\tests\<test_file>.py
- se ejecuta el test unitario anadido
- se ejecuta esta medicion directa:
python - <<'PY'
import time
from app.payloads import build_global_ranking_payload
tests = [
{"timeframe": "weekly", "metric": "kills", "limit": 20, "server_id": "all"},
{"timeframe": "weekly", "metric": "kills", "limit": 30, "server_id": "all"},
{"timeframe": "monthly", "metric": "kills", "limit": 20, "server_id": "all"},
]
for params in tests:
for i in range(3):
start = time.perf_counter()
payload = build_global_ranking_payload(**params)
elapsed = time.perf_counter() - start
data = payload.get("data", payload)
print({
"params": params,
"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"),
"fallback_used": data.get("fallback_used"),
})
PY
- si hay backend local accesible, se ejecuta esta medicion HTTP interna:
python - <<'PY'
import json
import time
from urllib.request import urlopen
urls = [
"http://127.0.0.1:8000/api/ranking?timeframe=weekly&metric=kills&limit=20",
"http://127.0.0.1:8000/api/ranking?timeframe=weekly&metric=kills&limit=30",
"http://127.0.0.1:8000/api/ranking?timeframe=monthly&metric=kills&limit=20",
]
for url in urls:
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({
"url": url,
"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"),
"fallback_used": data.get("fallback_used"),
})
PY
- weekly top 20 queda por debajo de
1 segundo - idealmente weekly top 20 queda por debajo de
300 ms - weekly/monthly siguen funcionando para
all,comunidad-hispana-01ycomunidad-hispana-02 - las metricas existentes siguen funcionando
git diff --name-onlymatches the expected scope- no unrelated files were modified
Outcome
Result:
- Updated
backend/app/rcon_historical_leaderboards.pyto split public weekly/monthly snapshot reads from storage initialization and snapshot generation. - Public reads now use a dedicated read-only connection helper:
- PostgreSQL default path opens a direct compat read connection without
initialize_ranking_snapshot_storage() - explicit SQLite legacy path resolves the file in read-only mode without migrations or setup
- PostgreSQL default path opens a direct compat read connection without
- Kept
initialize_ranking_snapshot_storage()for generation, refresh, CLI and internal write flows. - Tightened public ranking behavior so runtime fallback is no longer enabled by default; public requests only fall back when
HLL_BACKEND_RANKING_RUNTIME_FALLBACK_ENABLEDis explicitly enabled. - Added
backend/tests/test_ranking_snapshot_payload.pywith a regression test asserting PostgreSQL public reads do not invoke ranking snapshot storage initialization.
Modified files:
ai/tasks/done/TASK-215-optimize-weekly-monthly-ranking-read-path.mdbackend/app/rcon_historical_leaderboards.pybackend/tests/test_ranking_snapshot_payload.py
Cause fixed:
get_latest_ranking_snapshot()initialized ranking snapshot storage before deciding the read path.- On the PostgreSQL public read path that pulled in setup or migration work through:
initialize_ranking_snapshot_storage()initialize_rcon_materialized_storage()initialize_postgres_rcon_storage()
- The fix separates public snapshot reads from generation and prevents those initializers from appearing in the default public weekly/monthly read path.
Validation performed:
- PASS:
python -m compileall backend\app\rcon_historical_leaderboards.py backend\tests\test_ranking_snapshot_payload.py - PASS:
python -m unittest backend.tests.test_ranking_snapshot_payload - PASS: direct timing probe through
build_global_ranking_payload(...) - PASS: targeted
cProfileprobe confirms the profiled public read path now shows:build_global_ranking_payloadget_latest_ranking_snapshot- and does not show:
initialize_ranking_snapshot_storageinitialize_rcon_materialized_storageinitialize_postgres_rcon_storage
- INFO: HTTP probe against
http://127.0.0.1:8000could not run because the local backend was not listening (ConnectionRefusedError). - INFO: local SQLite had no persisted weekly/monthly snapshots available:
ranking_snapshots = 0ranking_snapshot_items = 0- because of that, local payload validation exercised the fast missing-snapshot public path, not a ready snapshot payload with items.
Before/after timing summary:
- Before, per production evidence:
build_global_ranking_payload(timeframe="weekly", metric="kills", limit=20, server_id="all")took101.33 s
- After, local direct payload timing:
- weekly limit 20:
0.002 s,0.001 s,0.001 s - weekly limit 30:
0.001 s,0.001 s,0.001 s - monthly limit 20:
0.001 s,0.001 s,0.001 s
- weekly limit 20:
- Local response contract after the change in this environment:
snapshot_status = missingread_model = ranking-snapshotfallback_used = falseitems = 0
- Note: the
ready/items>0contract could not be revalidated locally because no ranking snapshots existed in local storage.
Scope confirmation:
- No frontend file was touched.
- No weapon asset was touched.
- No SVG was touched.
- No physical image was modified.
- Elo/MMR was not reactivated.
- Comunidad Hispana #03 was not reintroduced.
ai/system-metrics.mdwas not touched.- 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.