redis_keysFull keyspace scan — blocks all Redis operations while running
The KEYS command performs a full scan of the entire Redis keyspace. It is a single-threaded O(N) operation — while it runs, every other Redis command waits. On a database with millions of keys this can stall Redis for seconds, causing cascading timeouts across all clients sharing the instance.
Common Causes
- —Debugging sessions left in production code
- —Admin scripts using KEYS to enumerate or clean up keys
- —Pattern-matching deletions using KEYS + DEL in a loop
- —Cache invalidation logic that scans for prefix-matched keys
How to Fix
- 1.Replace KEYS with SCAN and a cursor — it iterates in small batches without blocking
- 2.Maintain a secondary index (e.g. a Redis Set) to track related keys by prefix
- 3.Use Redis key namespacing and maintain explicit lists of keys per namespace
- 4.Restrict KEYS via Redis ACLs so it cannot fire in production at all
Never use KEYS in production
KEYS is documented by Redis itself as not suitable for production use. There is no safe "small dataset" exception — even a brief KEYS call on a shared instance will block every other client for its entire duration.Example
typescript
// BAD — blocks the entire Redis instance
const keys = await redis.keys('session:*');
for (const key of keys) {
await redis.del(key);
}
// GOOD — cursor-based scan, non-blocking
let cursor = '0';
do {
const [nextCursor, keys] = await redis.scan(cursor, 'MATCH', 'session:*', 'COUNT', 100);
cursor = nextCursor;
if (keys.length > 0) {
await redis.del(...keys);
}
} while (cursor !== '0');
// GOOD — maintain an explicit Set of keys
// When you write: redis.set(`session:${id}`, data)
// Also track: redis.sadd('sessions:index', `session:${id}`)
// To enumerate: redis.smembers('sessions:index')Why SCAN is safe and KEYS is not
SCAN uses a cursor to return a small batch of keys per call. It shares processing time with other commands — each call releases the server after its batch. The full scan may take many round trips, but no single call monopolises the instance.
typescript
// Helper: collect all matching keys without blocking
async function scanKeys(redis: Redis, pattern: string): Promise<string[]> {
const result: string[] = [];
let cursor = '0';
do {
const [next, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 200);
cursor = next;
result.push(...keys);
} while (cursor !== '0');
return result;
}
const sessionKeys = await scanKeys(redis, 'session:*');Restricting via ACLs
bash
# In redis.conf — deny KEYS to the application user
ACL SETUSER appuser ~* +@all -keys -flushall -flushdb
# Verify
redis-cli ACL WHOAMI
redis-cli ACL LIST