6.4 KiB
Public Snapshot Refresh Schedule
Goal
Public pages must read persisted snapshots and read models only. No public GET endpoint should regenerate ranking or historical snapshots at request time.
Current Scheduler Owner
The internal historical-runner owns public refresh scheduling. Host cron is not required.
Implemented Cadence
Local timezone for scheduled jobs: Europe/Madrid.
Heavy daily window:
- full public refresh:
06:00- rebuilds full historical snapshots, weekly/monthly ranking snapshots, annual ranking snapshots, player search index and player period stats
- runs under
public-full-refresh
Ranking page:
- annual ranking:
- rebuilt inside the daily full refresh at
06:00 - generated sequentially per scope and metric
- rebuilt inside the daily full refresh at
- monthly ranking:
07:00and19:00- generated sequentially per scope and metric
- runs under
public-ranking-monthly-refresh
- weekly ranking:
- every hour at minute
10 - generated sequentially per scope and metric
- runs under
public-ranking-weekly-refresh
- every hour at minute
Historical page:
- weekly historical leaderboard subset:
- every hour at minute
25 - scopes:
all-serverscomunidad-hispana-01comunidad-hispana-02
- metrics:
killsdeathsmatches_over_100_killssupport
- runs under
public-historical-weekly-refresh
- every hour at minute
- monthly historical UI subset:
- every
2hours at minute40 - same scopes as weekly historical
- leaderboard metrics:
killsdeathsmatches_over_100_killssupport
- also refreshes:
monthly-mvpmonthly-mvp-v2
- runs under
public-historical-monthly-refresh
- every
Recent matches:
- interval polling fallback remains enabled through
HLL_PUBLIC_RECENT_MATCHES_REFRESH_INTERVAL_SECONDS - default:
60seconds - immediate refresh still happens when the RCON capture loop materializes a finished match
Why Historical Weekly And Monthly Were Missing
The previous short cadence only refreshed:
ranking_snapshotsfor/api/rankingrecent-matchessnapshots
It did not refresh the broader historical snapshot subset consumed by historico.html. That left weekly and monthly historical leaderboard payloads in snapshot_status=missing until the next daily full refresh.
Locking And Overlap Policy
The scheduler uses in-process locks and controlled skips. It does not enqueue backlog jobs.
Heavy jobs:
public-full-refreshpublic-ranking-annual-refreshpublic-ranking-monthly-refresh
Policy:
- heavy jobs do not overlap with other heavy jobs
- hourly and bi-hourly jobs skip when a conflicting heavy job is in progress
- if a heavy job already ran in the same scheduler tick, lower-priority jobs skip and retry on the next tick
- weekly and monthly historical subset jobs also avoid overlapping with each other
This keeps the implementation simple and avoids CPU spikes without reintroducing request-time fallback work.
Manual Job Hardening
Manual public jobs executed with python -m app.historical_runner --public-job ... now follow the same storage and error-handling rules as scheduled runs:
- CLI JSON output serializes
datetimeanddatevalues safely - ranking snapshot rows are deduplicated by
player_idbefore insertingranking_snapshot_items - PostgreSQL snapshot storage initialization runs once at the start of the heavy job, then substeps reuse non-DDL connections
- missing
player_event_raw_ledgerno longer abortshistorical-monthly; the monthly MVP V2 slice degrades to an empty payload withevent_coverage.ready = false
This keeps manual validation commands useful without hiding partial failures inside the job payload.
Last Update Exposure
Historical and ranking payloads continue to expose persisted generation metadata from the snapshot records:
snapshot_statusgenerated_atsource_range_startsource_range_endis_stale
historico.js already renders generated_at as the visible "Actualizado" label, so no frontend contract change was required for this task.
Environment Variables
Existing variables kept:
HLL_PUBLIC_FULL_REFRESH_ENABLEDHLL_PUBLIC_FULL_REFRESH_TIMEHLL_PUBLIC_FULL_REFRESH_TIMEZONEHLL_PUBLIC_RECENT_MATCHES_REFRESH_INTERVAL_SECONDS
Retained for compatibility:
HLL_PUBLIC_RANKING_REFRESH_INTERVAL_SECONDS- no longer drives the hourly/slot-based ranking scheduler
- still participates in runner tick resolution and legacy compatibility paths
New scheduler variables:
HLL_PUBLIC_RANKING_WEEKLY_REFRESH_MINUTE- default
10
- default
HLL_PUBLIC_RANKING_MONTHLY_REFRESH_TIMES- default
07:00,19:00
- default
HLL_PUBLIC_HISTORICAL_WEEKLY_REFRESH_MINUTE- default
25
- default
HLL_PUBLIC_HISTORICAL_MONTHLY_REFRESH_MINUTE- default
40
- default
HLL_PUBLIC_HISTORICAL_MONTHLY_REFRESH_HOUR_INTERVAL- default
2
- default
Manual Validation Commands
One-off public jobs through the runner:
docker compose exec historical-runner python -m app.historical_runner --public-job ranking-weekly
docker compose exec historical-runner python -m app.historical_runner --public-job ranking-monthly
docker compose exec historical-runner python -m app.historical_runner --public-job historical-weekly
docker compose exec historical-runner python -m app.historical_runner --public-job historical-monthly
docker compose exec historical-runner python -m app.historical_runner --public-job public-full
Direct ranking matrix commands:
docker compose exec historical-runner python -m app.rcon_historical_leaderboards refresh-ranking-snapshots --timeframe weekly --limit 30
docker compose exec historical-runner python -m app.rcon_historical_leaderboards refresh-ranking-snapshots --timeframe monthly --limit 30
Operational checks:
docker logs --tail=300 hll-vietnam-historical-runner-1
docker exec hll-vietnam-historical-runner-1 sh -lc 'env | sort | grep HLL_PUBLIC'
python .\scripts\audit_public_requests.py --base-url https://comunidadhll.devzamode.es --timeout 30 --filter servers --output tmp\task240_servers_after.json
python .\scripts\audit_public_requests.py --base-url https://comunidadhll.devzamode.es --timeout 30 --output tmp\task240_full_audit_after.json
UI checks:
- verify
historico.htmlweekly rankings render withoutsnapshot_status=missing - verify
historico.htmlmonthly rankings render withoutsnapshot_status=missing - verify the visible "Actualizado" label changes after the corresponding runner job