Databases 9 min read

How to Efficiently Count 100 Million Redis Keys Without Crashing Your Cluster

This article explains why the KEYS command is unsafe for large Redis keyspaces, introduces the SCAN command and multithreaded scanning techniques, compares performance, and offers alternative counting strategies to reliably count hundreds of millions of keys.

macrozheng
macrozheng
macrozheng
How to Efficiently Count 100 Million Redis Keys Without Crashing Your Cluster

Introduction

Many engineers accidentally run

KEYS *

on a Redis cluster with millions of keys, causing the server to block and services to fail.

Why not use KEYS?

Redis is single‑threaded;

KEYS *

scans the entire keyspace (O(N)). While scanning, no other commands are processed. For 100 million keys, even a 0.1 µs lookup per key takes about 10 seconds, leading to memory blow‑up and possible OOM.

Fatal triple hit :

Time complexity: 100 M keys ≈ 10 s.

Memory storm: returning too many keys can exhaust client memory.

Cluster limitation:

KEYS

only sees keys on the local node.

SCAN command

The

SCAN

command iterates with a cursor, returning a small batch of keys each call, avoiding blocking.

Basic Java SCAN implementation

public long safeCount(Jedis jedis) {
    long total = 0;
    String cursor = "0";
    ScanParams params = new ScanParams().count(500);
    do {
        ScanResult<String> rs = jedis.scan(cursor, params);
        cursor = rs.getCursor();
        total += rs.getResult().size();
    } while (!"0".equals(cursor));
    return total;
}

Scanning 500 keys per batch means ~200 000 calls for 100 M keys, roughly 10 minutes.

Multithreaded concurrent SCAN

Using a thread pool and atomic counters to parallelize the scan reduces time dramatically.

public long parallelCount(JedisPool pool, int threads) throws Exception {
    ExecutorService executor = Executors.newFixedThreadPool(threads);
    AtomicLong total = new AtomicLong(0);
    List<String> cursors = new ArrayList<>();
    for (int i = 0; i < threads; i++) {
        cursors.add(String.valueOf(i));
    }
    CountDownLatch latch = new CountDownLatch(threads);
    for (String cursor : cursors) {
        executor.execute(() -> {
            try (Jedis jedis = pool.getResource()) {
                String cur = cursor;
                do {
                    ScanResult<String> rs = jedis.scan(cur, new ScanParams().count(500));
                    cur = rs.getCursor();
                    total.addAndGet(rs.getResult().size());
                } while (!"0".equals(cur));
                latch.countDown();
            }
        });
    }
    latch.await();
    executor.shutdown();
    return total.get();
}

On a 32‑core CPU, this approach finishes in about 18 seconds (CPU usage ≈ 800%).

Distributed cluster parallel count

When using Redis Cluster, each master node scans its own slots in parallel using

parallelStream

.

public long clusterCount(JedisCluster cluster) {
    Map<String, JedisPool> nodes = cluster.getClusterNodes();
    AtomicLong total = new AtomicLong(0);
    nodes.values().parallelStream().forEach(pool -> {
        try (Jedis jedis = pool.getResource()) {
            if (jedis.info("replication").contains("role:slave")) return;
            String cursor = "0";
            do {
                ScanResult<String> rs = jedis.scan(cursor, new ScanParams().count(500));
                total.addAndGet(rs.getResult().size());
                cursor = rs.getCursor();
            } while (!"0".equals(cursor));
        }
    });
    return total.get();
}

Alternative solutions

Built‑in counter :

INFO keyspace

gives an approximate count instantly (O(1)) but includes expired keys.

Real‑time incremental counting : Use keyspace notifications to maintain an exact counter, at the cost of extra memory, CPU and network traffic.

Cost and hardware guidelines

CPU‑bound: threads = cores × 1.5

IO‑bound: threads = cores × 3

Control batch size (COUNT) to stay within memory limits.

When to choose which method

Exact statistics → distributed parallel SCAN; real‑time queries → incremental counter; trend analysis → sampling; avoid full

KEYS

scans.

Understanding data characteristics is more important than brute‑force effort in the era of massive datasets.

javaPerformanceRedisDistributedSCANKey Counting
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login 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.