How to Build High‑Performance Game Leaderboards and Counters with Redis and Java

This article explains how to design ranking systems for games and social apps, compares database sorting and client‑side approaches, and provides step‑by‑step Java code for implementing Redis‑based counters, leaderboards, performance optimizations, and real‑world use cases.

dbaplus Community
dbaplus Community
dbaplus Community
How to Build High‑Performance Game Leaderboards and Counters with Redis and Java

Design Options

For small data sets a simple ORDER BY query with an index and the TOP clause can return rankings in under 100 ms. For friend‑based rankings the client can fetch the friend list, retrieve each friend's current score, and sort locally to reduce database load.

Redis Counter Implementation

What is a counter?

A counter records the number of occurrences of an event (e.g., article views, product purchases) and is useful for trend analysis.

Using INCR

import redis.clients.jedis.Jedis;

public class CounterExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        String articleId = "article:123";
        String viewsKey = "views:" + articleId;
        long views = jedis.incr(viewsKey);
        System.out.println("Article views: " + views);
        jedis.close();
    }
}

Using INCRBY

import redis.clients.jedis.Jedis;

public class CounterExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        String articleId = "article:123";
        String viewsKey = "views:" + articleId;
        long views = jedis.incrBy(viewsKey, 10); // increase by 10
        System.out.println("Article views: " + views);
        jedis.close();
    }
}

Redis Leaderboard Implementation

What is a leaderboard?

A leaderboard ranks items by a numeric score, commonly used in games, social media, and e‑commerce.

Using ZADD to add members

import redis.clients.jedis.Jedis;

public class LeaderboardExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        String leaderboardKey = "leaderboard";
        String player1 = "PlayerA";
        String player2 = "PlayerB";
        jedis.zadd(leaderboardKey, 1000, player1);
        jedis.zadd(leaderboardKey, 800, player2);
        jedis.close();
    }
}

Using ZINCRBY to update scores

import redis.clients.jedis.Jedis;

public class LeaderboardExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        String leaderboardKey = "leaderboard";
        String player1 = "PlayerA";
        jedis.zincrby(leaderboardKey, 200, player1); // add 200 points
        jedis.close();
    }
}

Performance Optimizations

Counter Optimizations

Redis Transactions : Wrap INCR in a MULTI/EXEC block to guarantee atomicity across multiple commands.

Distributed Locks : Use Redisson’s lock to serialize updates under high contention.

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisException;

public class CounterOptimizationExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        String counterKey = "view_count";
        try {
            Transaction tx = jedis.multi();
            tx.incr(counterKey);
            tx.exec();
        } catch (JedisException e) {
            e.printStackTrace();
        } finally {
            jedis.close();
        }
    }
}
import org.redisson.Redisson;
import org.redisson.api.RLock;

public class CounterOptimizationWithLockExample {
    public static void main(String[] args) {
        Redisson redisson = Redisson.create();
        RLock lock = redisson.getLock("counter_lock");
        try {
            lock.lock();
            // perform counter operation
        } finally {
            lock.unlock();
            redisson.shutdown();
        }
    }
}

Leaderboard Optimizations

Pagination : Use ZREVRANGEWITHSCORES with calculated start/end indexes to fetch a page of results.

Caching : Store a snapshot of the top N rankings in a separate key and refresh it periodically.

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.Set;

public class LeaderboardPaginationExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        String leaderboardKey = "leaderboard";
        int pageSize = 10;
        int pageIndex = 1;
        Set<Tuple> page = jedis.zrevrangeWithScores(leaderboardKey,
                (pageIndex - 1) * pageSize,
                pageIndex * pageSize - 1);
        for (Tuple t : page) {
            System.out.println("Member: " + t.getElement() + ", Score: " + t.getScore());
        }
        jedis.close();
    }
}
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.Set;

public class LeaderboardCachingExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);
        String leaderboardKey = "leaderboard";
        String cacheKey = "cached_leaderboard";
        int cacheExpiration = 300; // seconds
        Set<Tuple> cached = jedis.zrevrangeWithScores(cacheKey, 0, -1);
        if (cached.isEmpty()) {
            Set<Tuple> data = jedis.zrevrangeWithScores(leaderboardKey, 0, -1);
            jedis.zadd(cacheKey, data);
            jedis.expire(cacheKey, cacheExpiration);
            cached = data;
        }
        for (Tuple t : cached) {
            System.out.println("Member: " + t.getElement() + ", Score: " + t.getScore());
        }
        jedis.close();
    }
}

Real‑World Cases

Social Media Like System

Each content item has a Redis string counter for likes.

A sorted set stores content IDs with their like counts as scores.

import redis.clients.jedis.Jedis;

public class SocialMediaLikeSystem {
    private Jedis jedis = new Jedis("localhost", 6379);
    public void likeContent(String contentId) {
        jedis.incr("likes:" + contentId);
        jedis.zincrby("rankings", 1, contentId);
    }
    public long getLikes(String contentId) {
        return Long.parseLong(jedis.get("likes:" + contentId));
    }
    public void showRankings() {
        System.out.println("Top content rankings:");
        jedis.zrevrangeWithScores("rankings", 0, 4)
            .forEach(t -> System.out.println(t.getElement() + ": " + t.getScore()));
    }
    public static void main(String[] args) {
        SocialMediaLikeSystem s = new SocialMediaLikeSystem();
        s.likeContent("post123");
        s.likeContent("post456");
        s.likeContent("post123");
        System.out.println("Likes for post123: " + s.getLikes("post123"));
        System.out.println("Likes for post456: " + s.getLikes("post456"));
        s.showRankings();
    }
}

Game Player Leaderboard

Each player's score is stored in a sorted set. ZINCRBY updates scores; ZREVRANK retrieves rank.

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.Set;

public class GameLeaderboard {
    private Jedis jedis = new Jedis("localhost", 6379);
    public void updateScore(String playerId, double score) {
        jedis.zincrby("leaderboard", score, playerId);
    }
    public Long getPlayerRank(String playerId) {
        return jedis.zrevrank("leaderboard", playerId);
    }
    public Set<Tuple> getTopPlayers(int count) {
        return jedis.zrevrangeWithScores("leaderboard", 0, count - 1);
    }
    public static void main(String[] args) {
        GameLeaderboard gl = new GameLeaderboard();
        gl.updateScore("player123", 1500);
        gl.updateScore("player456", 1800);
        gl.updateScore("player789", 1600);
        Long rank = gl.getPlayerRank("player456");
        System.out.println("Rank of player456: " + (rank != null ? rank + 1 : "Not ranked"));
        gl.getTopPlayers(3).forEach(t -> System.out.println(t.getElement() + ": " + t.getScore()));
    }
}

Conclusion & Best Practices

Redis provides ultra‑low‑latency counters (String) and sorted‑set leaderboards that scale under high concurrency. Key practices include:

Choose the appropriate data structure: String for simple counters, Sorted Set for rankings.

Ensure atomicity with transactions or distributed locks.

Periodically clean up stale data (e.g., ZREMRANGEBYRANK).

Cache frequently accessed ranking slices while balancing freshness.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaperformanceredisleaderboardCounter
dbaplus Community
Written by

dbaplus Community

Enterprise-level professional community for Database, BigData, and AIOps. Daily original articles, weekly online tech talks, monthly offline salons, and quarterly XCOPS&DAMS conferences—delivered by industry experts.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.