IodidDOCUMENTATION

formulas/leaderboard.mdx

/doc/formulas/leaderboard

PUBLIC DOCUMENTATION

Leaderboard

High-level overview — competitive collection, MMR ordering, page composition and contextual ranking.

Leaderboard — overview

Note: this document is a high-level summary. The complete deep dive on indexing, contextual ZSETs and rebuilds is in persistence/leaderboard-ranking (collections, indexes, locking, TTL, warmup).

Sources: src/main/java/com/safjnest/lol/service/LeaderboardService.java · CompetitiveService.java:104-182 · src/main/java/com/safjnest/nosql/MongoDB.java:1559-1705,2159 · TierDivisionUtils.java

1. competitive collection — source of truth

competitive is the derived projection from Summoner.ranks + ProfileStatistics:

  • _id = competitiveId(puuid, queue) — first 16 bytes of SHA-256(puuid:queue) (MongoDB.competitiveId:3218).
  • Fields: puuid, region (LeagueShard), queue (GameQueueType canonical), tier (TierDivisionType), mmr (TierDivisionUtils.getMmr), primary (LaneType most played), otpChampionId, lastUpdate.

Written only via CompetitiveService:

java
// CompetitiveService.java:111-119
Rank rank = ranks.get(canonicalQueue);
long mmr = TierDivisionUtils.getMmr(rank.tier(), rank.lp());
if (mmr < 0) return null; // UNRANKED excluded
return new CompetitiveEntry(puuid, shard, canonicalQueue, rank.tier(), mmr, primary, otpChampionId, now);
  • updateFromRanks / updateFromStatistics — called by RankService and ProfileService after each refresh.
  • write → MongoDB.upsertCompetitive (:2159 replaceOne upsert) + LeaderboardService.updateIndex for Redis delta.

Indexes (docs/mongo/13-contextual-ranking.md + MongoDB.java:35-48):

js
db.competitive.createIndex({ queue:1, region:1, tier:1, mmr:-1, _id:-1 }, { name: "competitive_contextual_global" })
db.competitive.createIndex({ queue:1, tier:1, mmr:-1, _id:-1 })

Cover filter queue + region + tier + role/otp with sort mmr DESC, _id DESC.

2. MMR ordering

Leaderboard sort is always mmr DESC, _id DESC (MongoDB.findLeaderboardPage:1559):

java
Bson filter = competitiveFilter(rank, queue, region, role, otpChampionId);
// rank → MmrRange → { mmr: { $gte: min, $lt: max } }  (CHALLENGER: $gte: 30000)
FindIterable<Document> q = competitive().find(filter)
    .sort(Sorts.orderBy(Sorts.descending("mmr"), Sorts.descending("_id")))
    .skip(offset).limit(limit);
  • competitiveFilter (MongoDB.java:3545) translates TierType rank into an MMR range via TierDivisionUtils.getMmrRange — so rank=DIAMOND returns all mmr ∈ [2400,10000).
  • mmr is already base + lp so a Master 0 LP (10000) sorts above a Diamond I 100 LP (2800) without special cases.
  • Tie on identical mmr → _id DESC ensures stable pagination.

3. Page composition

LeaderboardService.getLeaderboard (:52-128):

text
GET /api/lol/{shard}/leaderboard?queue=RANKED_SOLO_5X5&rank=DIAMOND&region=EUW1&role=MID&otp=157&page=1&limit=20
  → LolApiParameters validates rank/queue/region/role/otp/page/limit (1..50)
  → Redis LEADERBOARD_PAGE hit? → return
  → Mongo findLeaderboardPage(offset, limit)  // competitive
  → Mongo findLeaderboardCount (with cache LEADERBOARD_COUNT + lock)
  → batch ProfileStatistics per puuid/shard (ProfileService.getStatistics, Redis→Mongo→ComputeScheduler)
  → resolveRankings(subjects) → enrich Rank with global/regional ranking
  → map Summoner → SummonerView → SummonerLeaderboard(position = offset+index+1)
  → cache LEADERBOARD_PAGE only if all statistics are present

Pagination: offset = (page-1)*limit, pages = ceil(total/limit), LeaderboardPage(page, limit, total, pages, entries).

Combinable filters:

Filtercompetitive fieldNotes
rankmmr ∈ MmrRangeoptional, null = all
queuequeuecanonical (TEAM_BUILDER_RANKED_SOLO → RANKED_SOLO_5X5)
regionregionGLOBAL = all
roleprimaryTOP/JUNGLE/MID/BOT/UTILITY
otpotpChampionIdchampion id >0

4. resolveRankings — contextual ranking

LeaderboardService.resolveRankings (:206-263):

java
public static Map<String, Map<GameQueueType, Rank>> resolveRankings(List<RankedSummoner> subjects) {
    for (RankedSummoner s : subjects) {
        String gKey = CONTEXTUAL_LEADERBOARD_SEGMENT.of(queue, "GLOBAL", tier.name());
        String rKey = CONTEXTUAL_LEADERBOARD_SEGMENT.of(queue, region, tier.name());
        keys.add(gKey); members.add(competitiveId(puuid, queue));
        keys.add(rKey); members.add(competitiveId(puuid, queue));
    }
    ensureIndexesAsync(segments); // async rebuild if ZSET missing
    List<Long> positions = RedisClient.reverseRanks(keys, members); // ZREVRANK
    // for each target:
    rank.withRankings(
        absoluteRanking(globalCounts, tier, positions[i]),
        absoluteRanking(regionalCounts, tier, positions[i+1])
    );
}
  • ZSET per segment — leaderboard:{queue}:{scope}:{tier} (scope=GLOBAL|region), member competitiveId (16-byte SHA-256), score mmr.

  • Counts hash — leaderboard:counts:{queue}:{scope} → tier → count. Populated by MongoDB.findCompetitiveRankCounts and used by absoluteRanking (:460-468):

    java
    static Long absoluteRanking(Map<TierDivisionType,Long> counts, TierDivisionType tier, Long withinDivision) {
        if (counts==null || withinDivision==null || !counts.containsKey(tier)) return null;
        long higher = 0;
        for (TierDivisionType d : descendingRankedDivisions()) { if (d==tier) break; higher += counts.getOrDefault(d,0L); }
        return higher + withinDivision + 1;
    }
    

    Absolute ranking = sum of higher tiers + intra-tier position + 1. Example: Master rank 5 with 100 Challengers → absolute rank 105.

  • ensureIndexesAsync / rebuildIndex (:387-437) — if ZSET missing, enqueues a ComputeScheduler job PROFILE that does forEachCompetitiveRankingSegment(queue,tier,region) → streaming → buildSortedSet with temp key → RENAME + SADD CONTEXTUAL_LEADERBOARD_SEGMENTS. Apex (isHighElo) has TTL 0 (persistent), otherwise CONTEXTUAL_LEADERBOARD_SEGMENT.ttlSeconds().

  • Warmup — warmupIndex (:277-287) pre-builds all Master+ segments (global + per-region) + counts at boot / after rebuild.

Deep dive: for details on locking (LEADERBOARD_COUNT_LOCK), distribution (LeaderboardDistribution), findLeaderboardAggregateCount and indexStatus diagnostics, see persistence/leaderboard-ranking.