The right way to enumerate keys in Redis is SCAN. The KEYS command works but is O(N) and blocks the server, which means KEYS * on a million-key database locks Redis for seconds and is the most common cause of “Redis is slow” incidents. SCAN is O(1) per call, uses a cursor, and lets the server keep serving traffic while the iteration runs. The short version: use KEYS for debug and small databases, use SCAN for production, and use an application-level index for hot enumeration paths. The reason “redis get all keys” is a top search is that the docs lead with KEYS, and the production cost of KEYS is invisible until the database is large enough to feel it.
This post is the three commands, the production cost of each, and the right pattern for the most common use case.
Table of contents
- The direct answer:
SCAN, notKEYS KEYS: the debug-only commandSCAN: the production-safe command- The application-level index: the right answer for hot paths
- The performance math: when
KEYSbecomes a problem - The migration path from
KEYStoSCAN - FAQ
The direct answer: SCAN, not KEYS
SCAN is the production-safe command for enumerating keys. KEYS is the debug command. The difference is the time complexity:
KEYS patternis O(N) where N is the total number of keys in the database. The server scans every key, builds the result, and returns it in one response. While the server is scanning, no other commands are processed.SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]is O(1) per call. The server returns a small batch of keys and a new cursor; the client iterates by callingSCANagain with the new cursor until the cursor is 0.
For a Redis with 100 keys, KEYS * and SCAN 0 are indistinguishable. For a Redis with 1 million keys, KEYS * is a multi-second block; SCAN is a few hundred small commands that each take microseconds.
The rule: KEYS is for redis-cli in a debug session. SCAN is for everything else. The exception is a tiny database (a few hundred keys), where the difference does not matter and KEYS is fine.
KEYS: the debug-only command
The KEYS command:
> KEYS *
1) "user:1"
2) "user:2"
3) "order:1"
4) "order:2"
5) "session:abc"
...
> KEYS user:*
1) "user:1"
2) "user:2"
The pattern is a glob-style match (* is anything, ? is one char, [abc] is one of a/b/c). The command returns all matching keys in one response.
The cost: the server scans every key in the keyspace, evaluates the pattern, and builds the response. For a 1M-key database, this is a few seconds of CPU on the Redis main thread, during which no other command is processed. Every connected client times out, every in-flight operation stalls, every health check fails.
The Redis docs are explicit: “KEYS is intended for debugging and special operations, such as changing your keyspace layout. Don’t use KEYS in your regular application code.” The constraint is not theoretical. The cost is real, and the cost scales with the database size.
The right places for KEYS:
- A
redis-clisession for debugging (“are there anyuser:*keys?”). - A small database (a few hundred keys) where the cost is negligible.
- A maintenance script that runs once, when you can afford to block the server.
- A test environment, where the production cost does not matter.
The wrong places for KEYS:
- An application code path that runs on every request.
- A health check.
- A monitoring agent that polls the keyspace.
- Anything that runs in production with a non-trivial database.
SCAN: the production-safe command
The SCAN command:
> SCAN 0
1) "0" # new cursor
2) 1) "user:1"
2) "user:2"
3) "order:1"
4) "order:2"
5) "session:abc"
> SCAN 0
1) "0" # cursor is 0, iteration is done
2) (empty array)
The cursor is a number; the client passes the cursor from the previous response to the next call. When the cursor returns to 0, the iteration is complete. The MATCH and COUNT options filter the pattern and limit the batch size:
> SCAN 0 MATCH user:* COUNT 100
1) "42" # new cursor
2) 1) "user:1"
2) "user:2"
3) "user:3"
> SCAN 42 MATCH user:* COUNT 100
1) "0" # iteration done
2) 1) "user:4"
2) "user:5"
The COUNT is a hint, not a hard limit. Redis may return more or fewer keys per call, but the total over a full iteration is the same as KEYS *. The difference is that no single call holds the Redis main thread for long.
The full iteration pattern in Python:
import redis
r = redis.Redis()
cursor = 0
all_keys = []
while True:
cursor, keys = r.scan(cursor=cursor, match="user:*", count=1000)
all_keys.extend(keys)
if cursor == 0:
break
The same shape in ioredis:
const Redis = require("ioredis");
const redis = new Redis();
let cursor = "0";
const allKeys = [];
do {
const [nextCursor, keys] = await redis.scan(cursor, "MATCH", "user:*", "COUNT", 1000);
cursor = nextCursor;
allKeys.push(...keys);
} while (cursor !== "0");
The pattern is the same in every client: iterate until the cursor is 0, accumulate the keys, and process the batch.
The SCAN guarantees: a key that exists at the start of the iteration and is not deleted during the iteration is returned exactly once. A key that is added during the iteration may or may not be returned. A key that is deleted during the iteration may or may not be returned. The iteration is not a snapshot; it is a consistent-enough cursor over a changing keyspace.
The application-level index: the right answer for hot paths
For an application that has to enumerate a key subset on a hot path (“all users”, “all active sessions”, “all orders for a customer”), the right answer is not SCAN or KEYS. The right answer is a separate Redis set that maintains the index as the application writes the data.
The pattern:
# When a user is created, add to the index
redis.sadd("users:all", user_id)
redis.hset(f"user:{user_id}", mapping=user_data)
# When a user is deleted, remove from the index
redis.srem("users:all", user_id)
redis.delete(f"user:{user_id}")
# To enumerate all users
user_ids = redis.smembers("users:all")
users = [redis.hgetall(f"user:{uid}") for uid in user_ids]
The index is a set that contains every key in the namespace. Enumerating the set is O(N) but is a single SMEMBERS call, which is fast (microseconds for a few thousand members, milliseconds for millions). The cost is consistent regardless of the database size, and the operation is O(1) per key, not O(N) over the whole keyspace.
The trade-off: the application has to maintain the index. Every write to a key also writes to the index. The index can drift from the actual data (a bug in the application writes a key but forgets to add to the index, or deletes a key but forgets to remove from the index). The drift is the cost of the application-level index; the benefit is the O(1) hot-path enumeration.
The right pattern for a namespace that is small enough to enumerate on the hot path: maintain the index. The right pattern for a namespace that has to be enumerated rarely (an admin tool, a cleanup job): use SCAN. The right pattern for a namespace that has to be enumerated never: do not maintain an index at all; query the keys you need by ID.
The performance math: when KEYS becomes a problem
The math, in concrete numbers:
- 100 keys:
KEYS *takes ~0.1ms.SCANtakes ~0.1ms. Indistinguishable. - 1,000 keys:
KEYS *takes ~1ms.SCANtakes ~0.1ms per call, 10 calls = 1ms. Indistinguishable. - 10,000 keys:
KEYS *takes ~10ms.SCANtakes 10-100 calls = 1-10ms. Noticeable. - 100,000 keys:
KEYS *takes ~100ms.SCANtakes 100-1000 calls = 10-100ms. The difference is the difference between “the server is briefly slow” and “the server is fast the whole time.” - 1,000,000 keys:
KEYS *takes ~1-5 seconds.SCANtakes 1000-10000 calls = 100-1000ms, with no single call holding the server. The difference is the difference between “every client times out” and “the server stays responsive.”
The rule of thumb: KEYS is fine up to about 10,000 keys. Above that, the cost is noticeable. Above 100,000 keys, the cost is dangerous. Above 1,000,000 keys, KEYS is a self-inflicted outage.
The fix is not “use a smaller database.” The fix is to use SCAN, which is the right answer for every database size, and an application-level index for the hot paths that need O(1) enumeration.
The migration path from KEYS to SCAN
The migration is mechanical: replace KEYS pattern with a SCAN loop that accumulates the result, and the rest of the code stays the same. The shape:
# Before
keys = redis.keys("user:*")
for key in keys:
process(key)
# After
keys = []
cursor = 0
while True:
cursor, batch = redis.scan(cursor=cursor, match="user:*", count=1000)
keys.extend(batch)
if cursor == 0:
break
for key in keys:
process(key)
The SCAN loop accumulates all keys in memory, which is the same memory cost as KEYS (the result is the same set of keys). The difference is the time profile: KEYS blocks the server, SCAN does not.
For a code path that does not need the full result in memory (a “process each key as it is returned” loop), the right pattern is to process the batch inside the loop:
cursor = 0
while True:
cursor, batch = redis.scan(cursor=cursor, match="user:*", count=1000)
for key in batch:
process(key)
if cursor == 0:
break
The pattern is the same, but the memory cost is the size of one batch, not the size of the full result. The right answer for a database with millions of keys.
For a one-off script, the in-memory version is fine. For a long-running service, the per-batch version is the right answer.
How this fits the rest of the stack
Redis is also a hosting cost — the instance size, the storage, the bandwidth, and the eviction policy each show up as a line item. The team’s mental model for the Redis cost is the sum of those numbers, and a SCAN loop that walks every key is a real load on the instance. The RunxBuild hosting calculator is the right place to model that — pick the Redis tier, the storage, the bandwidth, and the expected request rate, and the calculator shows what the Redis instance costs at the team’s actual usage.
Useful related references:
FAQ
How do I get all keys in Redis?
Use SCAN in production: SCAN 0 MATCH * COUNT 1000 returns a batch and a new cursor; iterate until the cursor is 0. Use KEYS * only in debug sessions or with small databases.
Is KEYS safe to use in production?
No. KEYS is O(N) over the entire keyspace and blocks the Redis main thread for the duration. For a database with more than ~10,000 keys, the cost is noticeable. For a database with more than ~100,000 keys, the cost is dangerous. Use SCAN instead.
How does SCAN work?
SCAN cursor [MATCH pattern] [COUNT count] [TYPE type] returns a small batch of keys and a new cursor. The client iterates by calling SCAN again with the new cursor until the cursor is 0. The server does not block; each call is O(1).
What is the difference between KEYS and SCAN?
KEYS is O(N) over the entire keyspace and blocks the server. SCAN is O(1) per call, uses a cursor, and lets the server keep serving traffic. Use SCAN for production, KEYS for debug.
How do I enumerate keys with a pattern?
SCAN 0 MATCH "user:*" COUNT 1000 iterates over keys matching the glob pattern user:*. The pattern is a glob (* for any, ? for one char, [abc] for one of a/b/c). The MATCH is evaluated per batch, not globally, so a key that matches the pattern may be missed if it is added during the iteration.
How do I enumerate all keys matching a pattern in an application code path?
Use a Redis set as an application-level index. When you create a key, also add it to the set. When you delete a key, also remove it from the set. To enumerate, call SMEMBERS on the set. The cost is O(N) over the set, not O(N) over the entire keyspace.