Databases 9 min read

Why the Redis KEYS Command Causes Slowdowns and How to Replace It with SCAN

This article explains why using Redis's KEYS command can block other operations, illustrates the underlying single‑threaded execution model, and shows how upgrading shiro‑redis to use the SCAN command avoids performance issues in distributed session management.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Why the Redis KEYS Command Causes Slowdowns and How to Replace It with SCAN

Problem Background

A production transaction in a company started timing out, even though the only Redis command used by the business logic was a simple SET . Investigation of the Redis slow‑log revealed that the heavy command was KEYS XX* , which was not called directly by the application code.

The application uses Apache Shiro for permission management and stores session data in Redis for a distributed environment. Because Shiro does not provide a native Redis session store, the open‑source shiro-redis library is used. This library implements SessionDAO#getActiveSessions by invoking KEYS to retrieve all session keys, which caused the slowdown.

Root Cause and Fix

The issue was resolved by upgrading shiro-redis to a newer version that replaces the KEYS call with SCAN . The original implementation looked like this:

public Set<byte[]> keys(byte[] pattern){
    checkAndInit();
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();
    try{
        keys = jedis.keys(pattern);
    } finally{
        jedis.close();
    }
    return keys;
}

The updated version uses the cursor‑based iterator:

public Set<byte[]> keys(byte[] pattern) {
    Set<byte[]> keys = null;
    Jedis jedis = jedisPool.getResource();
    try{
        keys = new HashSet<byte[]>();
        ScanParams params = new ScanParams();
        params.count(count);
        params.match(pattern);
        byte[] cursor = ScanParams.SCAN_POINTER_START_BINARY;
        ScanResult<byte[]> scanResult;
        do{
            scanResult = jedis.scan(cursor, params);
            keys.addAll(scanResult.getResult());
            cursor = scanResult.getCursorAsBytes();
        } while(scanResult.getStringCursor().compareTo(ScanParams.SCAN_POINTER_START) > 0);
    } finally{
        jedis.close();
    }
    return keys;
}

Redis Command Execution Principles

Redis processes commands in a single‑threaded manner. Although a client perceives three steps—send, execute, return—multiple clients can send commands simultaneously, which are queued internally. The real flow is: send, queue, execute, return.

Because the server handles one command at a time, a slow command blocks the queue, making other clients wait and giving the impression that Redis is blocked.

Why KEYS Is Slow

The KEYS command must scan the entire dictionary hash table ( ht[0] ) to find keys matching a pattern, resulting in O(N) time complexity where N is the total number of keys. With millions of keys, the command can take many seconds, as demonstrated by a local experiment that inserted 100,000 keys via a Lua script and then blocked for over ten seconds when running KEYS * .

eval "for i=1,100000 do redis.call('set',i,i+1) end" 0

How SCAN Works

SCAN uses a cursor‑based iterator. Each call returns a small batch of keys and a new cursor; the client repeats the call with the new cursor until the cursor returns to zero. This paginated approach still has O(N) complexity but avoids long blocking because only a subset of keys is processed per call.

Other incremental iteration commands include SSCAN (for sets), HSCAN (for hashes), and ZSCAN (for sorted sets). These commands mitigate the blocking risk of their full‑scan counterparts ( SMEMBERS , HGETALL , ZRANGE ).

Summary

Redis executes commands sequentially in a single thread, so any command that takes a long time—such as KEYS on a large dataset—will block subsequent commands. To prevent production‑level latency, avoid using KEYS , SMEMBERS , HGETALL , or ZRANGE in critical paths; instead, employ the cursor‑based SCAN family for incremental iteration.

performanceDatabaseRedisSession ManagementkeysSCAN
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

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.