Architecture
AstralLeaderboards splits authority and presentation across two deployables that share the common module (models + wire packets). This page documents the write/read paths, the caches on each side, and the periodic safety nets that keep them converged.
Modules
Module | What it is | Owns |
|---|---|---|
| A Paper plugin, one instance per game server. | Command, menu, placeholders, built-in Bukkit trackers, a read-through Caffeine cache per player and per (leaderboard, period). |
| A standalone MageHic-runtime service, one instance for the whole network. | The MariaDB |
| Shared library, no |
|
Both paper and master connect independently to the same messaging broker (MessagingService, exchange leaderboards — constant LeaderboardConstants.MESSAGING_CHANNEL) and the same Redis (CacheService). Paper never talks to MariaDB directly; every write and every cold read goes through the master over messaging.
Period windows
Every leaderboard is tracked in three buckets simultaneously — the period is a read-time choice, not a per-leaderboard config value:
Period | Bucket key | Resets |
|---|---|---|
| constant | Never. |
| ISO week-based year+week, e.g. | When the ISO week rolls over (UTC). |
| Calendar year+month, e.g. | When the calendar month rolls over (UTC). |
LeaderboardPeriod.currentKeys(now) returns all three keys for "now"; every write fans out to all three buckets in one statement (ON DUPLICATE KEY UPDATE) so a single score lands in the all-time, weekly and monthly board at once. All keys are computed in UTC so master and every paper server agree without clock skew concerns beyond system-clock drift.
Write path
sort travels with every write packet — it is not looked up on the master. EntryService.ingest records whatever direction paper last sent and persists it to the leaderboards table only when it changes, so the master's own periodic rebuilds (which have no config file) can reproduce the same ranking after a restart.
Read path (per-player standing)
Read path (leaderboard top-N)
Every leaderboard × period combination is warmed on LeaderboardServiceImpl construction so the first render after startup doesn't block on a cache miss.
Redis layout
Key | Written by | Shape |
|---|---|---|
| master ( | ZSET, member = player UUID, score = value with a |
| master | HASH, member → exact original score (the ZSET score is nudged, so exact reads come from here). |
| master ( | HASH, field |
Periodic rebuilds (master safety nets)
Two runTaskTimerAsync loops, every 30 minutes (PERIODIC_REBUILD_MINUTES), independent of the live update path:
Task | What it does |
|---|---|
| Recomputes the stored |
| Re-derives the Redis top-N for every known leaderboard × every current period bucket from MariaDB (including leaderboards with zero current entries, so a freshly rolled-over weekly/monthly board is cleared instead of left stale), then deletes rows whose |
These exist to repair drift from missed/duplicate packets and to prune storage — deleteStaleBuckets means weekly/monthly rows older than the current window are never served again and are safe to delete.
Wire packets
Registered in LeaderboardPacketRegistry on both modules (opcodes must match on every node):
Opcode | Packet | Direction | Purpose |
|---|---|---|---|
|
| master → paper (broadcast) | "Refresh this player's cached standing." |
|
| master → paper (broadcast) | Emitted after the periodic index rebuild; paper reacts the same as |
|
| master → paper (broadcast) | "Refresh this leaderboard's cached top-N (all periods)." |
|
| paper → master (RPC) | Cold-cache pull of a player's standings. |
|
| master → paper (RPC reply) | Reply to |
|
| paper → master | Overwrite ( |
|
| paper → master | Add a delta (built-in trackers, |
Failure modes
Messaging broker down: writes and cold-cache pulls fail silently server-side (logged via
getSLF4JLogger); no in-game feedback is shown for/leaderboard set. Peer caches drift until the next successful broadcast or the master's 30-minute periodic rebuild.Master down: paper's warm Caffeine/Redis-hash caches keep serving stale data until they expire (5-minute refresh, 1-day TTL on the per-player hash); writes queue up as failed futures, not retried.
MariaDB down: the master's writes fail (logged); Redis continues serving whatever it last held.