Problem: Redis does not support deleting keys by wildcard directly with DEL or UNLINK.
In this tutorial, we'll look at how to find and delete Redis keys matching a pattern, and why you should use SCAN instead of KEYS on production servers.
Sometimes you may need to purge a group of related Redis keys. For example, your application might store cached user data using keys such as:
cache:user:1001
cache:user:1002
cache:user:1003
and you want to remove everything matching:
cache:user:*
The obvious solution may look like this:
redis-cli KEYS "cache:user:*"
and then delete the returned keys.
There is a problem, though: using **KEYS** on a production Redis server can be dangerous.
Why you should avoid KEYS in production
The Redis KEYS command searches the entire keyspace in one operation.
For a small development database this may not matter. On a production Redis instance containing hundreds of thousands or millions of keys, however, scanning the entire keyspace at once can block Redis while the command is running.
Instead, Redis provides the cursor-based SCAN command, which incrementally iterates through the keyspace.
For example:
redis-cli --scan --pattern "cache:user:*"
This returns keys matching the pattern without performing one large blocking operation.
Before deleting anything, it is a good idea to run this command by itself and verify that the pattern matches only the keys you actually want to remove.
Delete keys matching a pattern using SCAN
Once you have verified the pattern, you can pipe the results to redis-cli:
redis-cli --scan --pattern "cache:user:*" |
xargs -r redis-cli DEL
The process is simple:
--scaniterates through the Redis keyspace usingSCAN.--patternlimits the results to matching keys.xargspasses those keys toredis-cli DEL.- Redis deletes the matching keys.
This is considerably safer than the old approach:
redis-cli KEYS "cache:user:*" | xargs redis-cli DEL
While you may still see this command in older tutorials and Stack Overflow answers, avoid using it against a busy production Redis instance.
Use UNLINK instead of DEL for large values
There is another improvement we can make.
Redis provides the UNLINK command, which removes keys from the keyspace immediately but performs the actual memory reclamation asynchronously.
For large objects, this can reduce the amount of work performed synchronously by Redis.
The command becomes:
redis-cli --scan --pattern "cache:user:*" |
xargs -r redis-cli UNLINK
For production cleanup jobs, SCAN combined with UNLINK is often a better choice than KEYS combined with DEL.
For small keys or development environments, DEL remains perfectly reasonable.
Working with a different Redis database
Redis uses database 0 by default.
If your keys are stored in another logical database, specify it using the -n option.
For example, to inspect matching keys in database 2:
redis-cli -n 2 --scan --pattern "cache:user:*"
And to delete them:
redis-cli -n 2 --scan --pattern "cache:user:*" |
xargs -r redis-cli -n 2 UNLINK
Notice that the database is specified for both Redis connections.
Connecting to a remote Redis server
You can also specify the Redis host and port:
redis-cli -h redis.example.com -p 6379 \
--scan --pattern "cache:user:*"
The same options need to be supplied to the deletion command:
redis-cli -h redis.example.com -p 6379 \
--scan --pattern "cache:user:*" |
xargs -r redis-cli -h redis.example.com -p 6379 UNLINK
If your Redis server requires authentication or TLS, configure redis-cli appropriately for your environment. Avoid putting passwords directly into shell commands or scripts where they may be exposed through shell history, process listings, logs, or source control.
Control the SCAN batch size
SCAN supports a COUNT option that provides Redis with a hint about how many elements to examine during each iteration.
With redis-cli, you can use:
redis-cli --scan --pattern "cache:user:*" --count 1000
A larger value can make the scan complete faster, but it also means more work during each iteration.
COUNT is a hint, not a guarantee that Redis will return exactly that many keys.
For most occasional maintenance tasks, the default behavior is fine. If you are cleaning a very large keyspace, experiment carefully rather than blindly setting a very large value.
Be careful with xargs and very large deletions
For a modest number of keys, piping the results through xargs is convenient:
redis-cli --scan --pattern "cache:user:*" |
xargs -r redis-cli UNLINK
For very large production datasets, consider processing keys in controlled batches instead.
Deleting hundreds of thousands or millions of keys is operational work, not just a convenient one-liner. Even when SCAN and UNLINK are used, the operation consumes CPU, network bandwidth, memory-management resources, and replication bandwidth.
Run large cleanup operations during lower-traffic periods and monitor your Redis instance while they execute.
What about Redis Cluster?
Redis Cluster requires additional consideration.
Keys are distributed between multiple Redis nodes according to hash slots. A scan against one node does not necessarily represent the entire cluster keyspace.
If you need to remove matching keys across a Redis Cluster, make sure your cleanup process scans the appropriate primary nodes rather than assuming a single redis-cli --scan operation covers the entire cluster.
Also remember that multi-key commands in Redis Cluster are subject to hash-slot restrictions.
For large or important cluster cleanup operations, an application-level script using a Redis client library is generally preferable to a shell one-liner.
SCAN is not a snapshot
There is one more subtle detail worth knowing.
SCAN incrementally walks through a keyspace that may be changing while you scan it. It should therefore not be treated as a point-in-time snapshot of your Redis database.
For an occasional cache cleanup this normally isn't a problem. If your application is continuously creating keys matching the same pattern while your cleanup is running, however, you need to design the operation accordingly.
A safer workflow
When deleting Redis keys by pattern on a production system, I normally recommend doing it in two steps.
First, inspect what will be deleted:
redis-cli --scan --pattern "cache:user:*"
Check the output carefully.
Then perform the deletion:
redis-cli --scan --pattern "cache:user:*" |
xargs -r redis-cli UNLINK
For another database:
redis-cli -n 2 --scan --pattern "cache:user:*" |
xargs -r redis-cli -n 2 UNLINK
SCAN vs KEYS: quick comparison
| Command | Production use | Behaviour |
|---|---|---|
KEYS pattern | Avoid for keyspace iteration | Searches the entire keyspace in one operation |
SCAN | Preferred | Incrementally iterates through the keyspace |
DEL | Fine for normal deletion | Removes keys and reclaims memory synchronously |
UNLINK | Useful for large values | Removes keys while reclaiming memory asynchronously |
The important rule is simple:
Use
**SCAN**to find matching keys on production Redis servers. Use**KEYS**primarily for debugging or small, controlled datasets.
And whenever you are deleting keys by wildcard, inspect the matches before piping them into DEL or UNLINK. One overly broad * can make your Redis cleanup much more exciting than intended.
This approach is quite dangerous as KEYS can (and will) result in a DoS. While this may be a no biggie in dev/test env, production is another matter... Redis' SCAN is a better way to do it.
Hi Itamar, Thanks for your comment. I wouldn't use "and will" because it's too strong. It would depend on the number of keys in the database. I've used it with hundreds and thousands keys with no problem. Anyway, I would appreciate if you give me your example.
Hi Stan, Sorry for the use of strong language, but KEYS is evil :) Its use discouraged in Redis' own documentation (see http://redis.io/commands/keys) and I've seen many users get burnt by misusing/misunderstanding its purpose (i.e. development only). As bash is your poison of choice (;P), please see https://gist.github.com/itamarhaber/11126830 for a crude, but working, example of using SCAN to delete keys politely. Cheers, Itamar P.S. looks like there's a small typo in your example above - should be 'port' instead of 'post'
Hi Itamar, I've corrected the typo. Thanks for your feedback!. ;) Also updated the article and put the link to the solution that you referred to. Much appreciated!