How to atomically delete keys matching a pattern using Redis

Redis

Redis Problem Overview


In my Redis DB I have a number of prefix:<numeric_id> hashes.

Sometimes I want to purge them all atomically. How do I do this without using some distributed locking mechanism?

Redis Solutions


Solution 1 - Redis

Execute in bash:

redis-cli KEYS "prefix:*" | xargs redis-cli DEL

UPDATE

Ok, i understood. What about this way: store current additional incremental prefix and add it to all your keys. For example:

You have values like this:

prefix_prefix_actuall = 2
prefix:2:1 = 4
prefix:2:2 = 10

When you need to purge data, you change prefix_actuall first (for example set prefix_prefix_actuall = 3), so your application will write new data to keys prefix:3:1 and prefix:3:2. Then you can safely take old values from prefix:2:1 and prefix:2:2 and purge old keys.

Solution 2 - Redis

Starting with redis 2.6.0, you can run lua scripts, which execute atomically. I have never written one, but I think it would look something like this

EVAL "return redis.call('del', unpack(redis.call('keys', ARGV[1])))" 0 prefix:[YOUR_PREFIX e.g delete_me_*]

> Warning: As the Redis document says, because of performance maters, keys > command should not use for regular operations in production, this > command is intended for debugging and special operations. read > more

See the EVAL documentation.

Solution 3 - Redis

Here's a completely working and atomic version of a wildcard delete implemented in Lua. It'll run much faster than the xargs version due to much less network back-and-forth, and it's completely atomic, blocking any other requests against redis until it finishes. If you want to atomically delete keys on Redis 2.6.0 or greater, this is definitely the way to go:

redis-cli -n [some_db] -h [some_host_name] EVAL "return redis.call('DEL', unpack(redis.call('KEYS', ARGV[1] .. '*')))" 0 prefix:

This is a working version of @mcdizzle's idea in his answer to this question. Credit for the idea 100% goes to him.

EDIT: Per Kikito's comment below, if you have more keys to delete than free memory in your Redis server, you'll run into the "too many elements to unpack" error. In that case, do:

for _,k in ipairs(redis.call('keys', ARGV[1])) do 
    redis.call('del', k) 
end

As Kikito suggested.

Solution 4 - Redis

Disclaimer: the following solution doesn't provide atomicity.

Starting with v2.8 you really want to use the http://redis.io/commands/SCAN">SCAN</a> command instead of KEYS[1]. The following Bash script demonstrates deletion of keys by pattern:

#!/bin/bash
 
if [ $# -ne 3 ] 
then
  echo "Delete keys from Redis matching a pattern using SCAN & DEL"
  echo "Usage: $0 <host> <port> <pattern>"
  exit 1
fi

cursor=-1
keys=""

while [ $cursor -ne 0 ]; do
  if [ $cursor -eq -1 ]
  then
    cursor=0
  fi

  reply=`redis-cli -h $1 -p $2 SCAN $cursor MATCH $3`
  cursor=`expr "$reply" : '\([0-9]*[0-9 ]\)'`
  keys=${reply##[0-9]*[0-9 ]}
  redis-cli -h $1 -p $2 DEL $keys
done

[1] http://redis.io/commands/keys">KEYS</a> is a dangerous command that can potentially result in a DoS. The following is a quote from its documentation page:

> Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don't use KEYS in your regular application code. If you're looking for a way to find keys in a subset of your keyspace, consider using sets.

UPDATE: a one liner for the same basic effect -

$ redis-cli --scan --pattern "*:foo:bar:*" | xargs -L 100 redis-cli DEL

Solution 5 - Redis

For those who were having trouble parsing other answers:

eval "for _,k in ipairs(redis.call('keys','key:*:pattern')) do redis.call('del',k) end" 0

Replace key:*:pattern with your own pattern and enter this into redis-cli and you are good to go.

Credit lisco from: http://redis.io/commands/del

Solution 6 - Redis

I am using below command in redis 3.2.8

redis-cli KEYS *YOUR_KEY_PREFIX* | xargs redis-cli DEL

You can get more help related to keys pattern search from here :- https://redis.io/commands/keys. Use your convenient glob-style pattern as per your requirement like *YOUR_KEY_PREFIX* or YOUR_KEY_PREFIX?? or any other.

And if any of you have integrated Redis PHP library than below function will help you.

flushRedisMultipleHashKeyUsingPattern("*YOUR_KEY_PATTERN*"); //function call
    
function flushRedisMultipleHashKeyUsingPattern($pattern='')
        {
            if($pattern==''){
                return true;
            }
            
            $redisObj = $this->redis;
            $getHashes = $redisObj->keys($pattern);
            if(!empty($getHashes)){
                $response = call_user_func_array(array(&$redisObj, 'del'), $getHashes); //setting all keys as parameter of "del" function. Using this we can achieve $redisObj->del("key1","key2);
            }
        }

Thank you :)

Solution 7 - Redis

You can also use this command to delete the keys:-

Suppose there are many types of keys in your redis like-

  1. 'xyz_category_fpc_12'
  2. 'xyz_category_fpc_245'
  3. 'xyz_category_fpc_321'
  4. 'xyz_product_fpc_876'
  5. 'xyz_product_fpc_302'
  6. 'xyz_product_fpc_01232'

Ex- 'xyz_category_fpc' here xyz is a sitename, and these keys are related to products and categories of a E-Commerce site and generated by FPC.

If you use this command as below-

redis-cli --scan --pattern 'key*' | xargs redis-cli del

OR

redis-cli --scan --pattern 'xyz_category_fpc*' | xargs redis-cli del

It deletes all the keys like 'xyz_category_fpc' (delete 1, 2 and 3 keys). For delete other 4, 5 and 6 number keys use 'xyz_product_fpc' in above command.

If you want to Delete Everything in Redis, then follow these Commands-

With redis-cli:

  1. FLUSHDB - Removes data from your connection's CURRENT database.
  2. FLUSHALL - Removes data from ALL databases.

For Example:- in your shell:

redis-cli flushall
redis-cli flushdb

Solution 8 - Redis

@mcdizle's solution is not working it works only for one entry.

This one works for all keys with same prefix

EVAL "for i, name in ipairs(redis.call('KEYS', ARGV[1])) do redis.call('DEL', name); end" 0 prefix*

Note: You should replace 'prefix' with your key prefix...

Solution 9 - Redis

If you have space in the name of the keys, you can use this in bash:

redis-cli keys "pattern: *" | xargs -L1 -I '$' echo '"$"' | xargs redis-cli del

Solution 10 - Redis

@itamar's answer is great, but the parsing of the reply wasn't working for me, esp. in the case where there are no keys found in a given scan. A possibly simpler solution, directly from the console:

redis-cli -h HOST -p PORT  --scan --pattern "prefix:*" | xargs -n 100 redis-cli DEL

This also uses SCAN, which is preferable to KEYS in production, but is not atomic.

Solution 11 - Redis

Other answers may not work if your key contains special chars - Guide$CLASSMETADATA][1] for instance. Wrapping each key into quotes will ensure they get properly deleted:

redis-cli --scan --pattern sf_* | awk '{print $1}' | sed "s/^/'/;s/$/'/" | xargs redis-cli del

Solution 12 - Redis

I just had the same problem. I stored session data for a user in the format:

session:sessionid:key-x - value of x
session:sessionid:key-y - value of y
session:sessionid:key-z - value of z

So, each entry was a seperate key-value pair. When the session is destroyed, I wanted to remove all session data by deleting keys with the pattern session:sessionid:* - but redis does not have such a function.

What I did: store the session data within a hash. I just create a hash with the hash id of session:sessionid and then I push key-x, key-y, key-z in that hash (order did not matter to me) and if I dont need that hash anymore I just do a DEL session:sessionid and all data associated with that hash id is gone. DEL is atomic and accessing data/writing data to the hash is O(1).

Solution 13 - Redis

// TODO

You think it's command not make sense bu some times Redis command like DEL not working correct and comes to the rescue of this

redis-cli KEYS "*" | xargs -i redis-cli EXPIRE {} 1 it's life hack

Solution 14 - Redis

A version using SCAN rather than KEYS (as recommended for production servers) and --pipe rather than xargs.

I prefer pipe over xargs because it's more efficient and works when your keys contain quotes or other special characters that your shell with try and interpret. The regex substitution in this example wraps the key in double quotes, and escapes any double quotes inside.

export REDIS_HOST=your.hostname.com
redis-cli -h "$REDIS_HOST" --scan --pattern "YourPattern*" > /tmp/keys
time cat /tmp/keys | perl -pe 's/"/\\"/g;s/^/DEL "/;s/$/"/;'  | redis-cli -h "$REDIS_HOST" --pipe

Solution 15 - Redis

I think what might help you is the MULTI/EXEC/DISCARD. While not 100% equivalent of transactions, you should be able to isolate the deletes from other updates.

Solution 16 - Redis

FYI.

  • only using bash and redis-cli
  • not using keys (this uses scan)
  • works well in cluster mode
  • not atomic

Maybe you only need to modify capital characters.

scan-match.sh

#!/bin/bash
rcli="/YOUR_PATH/redis-cli" 
default_server="YOUR_SERVER"
default_port="YOUR_PORT"
servers=`$rcli -h $default_server -p $default_port cluster nodes | grep master | awk '{print $2}' | sed 's/:.*//'`
if [ x"$1" == "x" ]; then 
    startswith="DEFAULT_PATTERN"
else
    startswith="$1"
fi
MAX_BUFFER_SIZE=1000
for server in $servers; do 
    cursor=0
    while 
        r=`$rcli -h $server -p $default_port scan $cursor match "$startswith*" count $MAX_BUFFER_SIZE `
        cursor=`echo $r | cut -f 1 -d' '`
        nf=`echo $r | awk '{print NF}'`
        if [ $nf -gt 1 ]; then
            for x in `echo $r | cut -f 1 -d' ' --complement`; do 
                echo $x
            done
        fi
        (( cursor != 0 ))
    do
        :
    done
done

clear-redis-key.sh

#!/bin/bash
STARTSWITH="$1"

RCLI=YOUR_PATH/redis-cli
HOST=YOUR_HOST
PORT=6379
RCMD="$RCLI -h $HOST -p $PORT -c "

./scan-match.sh $STARTSWITH | while read -r KEY ; do
    $RCMD del $KEY 
done

Run at bash prompt

$ ./clear-redis-key.sh key_head_pattern

Solution 17 - Redis

Please use this command and try :

redis-cli --raw keys "$PATTERN" | xargs redis-cli del

Solution 18 - Redis

This is not direct answer to the question, but since I got here when searching for my own answers, I'll share this here.

If you have tens or hundreds of millions of keys you have to match, the answers given here will cause Redis to be non responsive for significant amount of time (minutes?), and potentially crash because of memory consumption (be sure, background save will kick in in the middle of your operation).

The following approach is undeniably ugly, but I didn't find a better one. Atomicity is out of question here, in this case main goal is to keep Redis up and responsive 100% of the time. It will work perfectly if you have all your keys in one of databases and you don't need to match any pattern, but cannot use http://redis.io/commands/FLUSHDB because of it's blocking nature.

Idea is simple: write a script that runs in a loop and uses O(1) operation like http://redis.io/commands/SCAN or http://redis.io/commands/RANDOMKEY to get keys, checks if they match the pattern (if you need it) and http://redis.io/commands/DEL them one by one.

If there is a better way to do it, please let me know, I'll update the answer.

Example implementation with randomkey in Ruby, as a rake task, a non blocking substitute of something like redis-cli -n 3 flushdb:

desc 'Cleanup redis'
task cleanup_redis: :environment do
  redis = Redis.new(...) # connection to target database number which needs to be wiped out
  counter = 0
  while key = redis.randomkey               
    puts "Deleting #{counter}: #{key}"
    redis.del(key)
    counter += 1
  end
end

Solution 19 - Redis

I tried most of methods mentioned above but they didn't work for me, after some searches I found these points:

  • if you have more than one db on redis you should determine the database using -n [number]
  • if you have a few keys use del but if there are thousands or millions of keys it's better to use unlink because unlink is non-blocking while del is blocking, for more information visit this page unlink vs del
  • also keys are like del and is blocking

so I used this code to delete keys by pattern:

 redis-cli -n 2 --scan --pattern '[your pattern]' | xargs redis-cli -n 2 unlink 

Solution 20 - Redis

I succeeded this with the simplest variant of EVAL command:

EVAL "return redis.call('del', unpack(redis.call('keys', my_pattern_here*)))" 0

where I replaced my_pattern_here with my value.

Solution 21 - Redis

Adding to this answer:

To find first 1000 keys:

EVAL "return redis.call('scan', 0, 'COUNT', 1000, 'MATCH', ARGV[1])" 0 find_me_*

To delete them:

EVAL "return redis.call('del', unpack(redis.call('SCAN', 0, 'COUNT', 1000, 'MATCH', ARGV[1])[2]))" 0 delete_me_*

Solution 22 - Redis

Below command worked for me.

redis-cli -h redis_host_url KEYS "*abcd*" | xargs redis-cli -h redis_host_url DEL

Solution 23 - Redis

If you have spaces in your key names, this will work with MacOS

redis-cli --scan --pattern "myprefix:*" | tr \\n \\0 | xargs -0 redis-cli unlink

Solution 24 - Redis

This one worked for me but may not be atomic:

redis-cli keys "stats.*" | cut -d ' ' -f2 | xargs -d '\n' redis-cli DEL

Solution 25 - Redis

poor man's atomic mass-delete?

maybe you could set them all to EXPIREAT the same second - like a few minutes in the future - and then wait until that time and see them all "self-destruct" at the same time.

but I am not really sure how atomic that would be.

Solution 26 - Redis

I support all answers related to having some tool or execute Lua expression.

One more option from my side:

In our production and pre-production databases there are thousands of keys. Time to time we need to delete some keys (by some mask), modify by some criteria etc. Of course, there is no way to do it manually from CLI, especially having sharding (512 logical dbs in each physical).

For this purpose I write java client tool that does all this work. In case of keys deletion the utility can be very simple, only one class there:

public class DataCleaner {

    public static void main(String args[]) {
        String keyPattern = args[0];
        String host = args[1];
        int port = Integer.valueOf(args[2]);
        int dbIndex = Integer.valueOf(args[3]);

        Jedis jedis = new Jedis(host, port);

        int deletedKeysNumber = 0;
        if(dbIndex >= 0){
            deletedKeysNumber += deleteDataFromDB(jedis, keyPattern, dbIndex);
        } else {
            int dbSize = Integer.valueOf(jedis.configGet("databases").get(1));
            for(int i = 0; i < dbSize; i++){
                deletedKeysNumber += deleteDataFromDB(jedis, keyPattern, i);
            }
        }

        if(deletedKeysNumber == 0) {
            System.out.println("There is no keys with key pattern: " + keyPattern + " was found in database with host: " + host);
        }
    }

    private static int deleteDataFromDB(Jedis jedis, String keyPattern, int dbIndex) {
        jedis.select(dbIndex);
        Set<String> keys = jedis.keys(keyPattern);
        for(String key : keys){
            jedis.del(key);
            System.out.println("The key: " + key + " has been deleted from database index: " + dbIndex);
        }

        return keys.size();
    }

}

Solution 27 - Redis

Ad of now, you can use a redis client and perform first SCAN (supports pattern matching) and then DEL each key individually.

However, there is an issue on official redis github to create a patter-matching-del here, go show it some love if you find it useful!

Solution 28 - Redis

If you are using Redis version below 4 you might try

redis-cli -h 127.0.0.1 -p 26379 -a `yourPassword` --scan --pattern data:* | xargs redis-cli del

and if you are using the above 4 versions, then

redis-cli -h 127.0.0.1 -p 26379 -a `yourPassword` --scan --pattern data:*| xargs redis-cli unlink

for checking your version enter your Redis terminal by using the following command

redis-cli -h 127.0.0.1 -p 26379 -a `yourPassword

then type

> INFO

# Server
redis_version:5.0.5
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:da75abdfe06a50f8
redis_mode:standalone
os:Linux 5.3.0-51-generic x86_64
arch_bits:64
multiplexing_api:epoll
atomicvar_api:atomic-builtin
gcc_version:7.5.0
process_id:14126
run_id:adfaeec5683d7381a2a175a2111f6159b6342830
tcp_port:6379
uptime_in_seconds:16860
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:15766886
executable:/tmp/redis-5.0.5/src/redis-server
config_file:

# Clients
connected_clients:22
....More Verbose

Solution 29 - Redis

If you use windows environment please follow this steps and it will definitely works:

  1. Download GOW from here - https://github.com/bmatzelle/gow/wiki (because xargs command doesn't works in windows)

  2. Download redis-cli for Windows (detailed explanation is here - https://medium.com/@binary10111010/redis-cli-installation-on-windows-684fb6b6ac6b)

  3. Run cmd and open directory where redis-cli stores (example: D:\Redis\Redis-x64-3.2.100)

  4. if you want to delete all keys which start with "Global:ProviderInfo" execute this query (it's require to change bold parameters (host, port, password, key) and write yours, because of this is only example):

    redis-cli -h redis.test.com -p 6379 -a redispassword --raw keys "Global:ProviderInfo*" | xargs redis-cli -h redis.test.com -p 6379 -a redispassword del

Solution 30 - Redis

this is the easiest way that comes to mind without using any xargs magic

pure bash!

redis-cli DEL $(redis-cli KEYS *pattern*)

Solution 31 - Redis

Spring RedisTemplate itself provides the functionality. RedissonClient in the latest version has deprecated the "deleteByPattern" functionality.

Set<String> keys = redisTemplate.keys("geotag|*");
redisTemplate.delete(keys);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlexander GladyshView Question on Stackoverflow
Solution 1 - RedisIlia KondrashovView Answer on Stackoverflow
Solution 2 - RedismcdizzleView Answer on Stackoverflow
Solution 3 - RedisEliView Answer on Stackoverflow
Solution 4 - RedisItamar HaberView Answer on Stackoverflow
Solution 5 - RedisrandomorView Answer on Stackoverflow
Solution 6 - RedisYashrajsinh JadejaView Answer on Stackoverflow
Solution 7 - RedisVishal ThakurView Answer on Stackoverflow
Solution 8 - RedisefarukView Answer on Stackoverflow
Solution 9 - RedisInc33View Answer on Stackoverflow
Solution 10 - RedisGuitanView Answer on Stackoverflow
Solution 11 - RedisQuentin S.View Answer on Stackoverflow
Solution 12 - RedisMaxView Answer on Stackoverflow
Solution 13 - RedisvampireView Answer on Stackoverflow
Solution 14 - RedistekumaraView Answer on Stackoverflow
Solution 15 - RedisalexpopescuView Answer on Stackoverflow
Solution 16 - RedisplhnView Answer on Stackoverflow
Solution 17 - RedisSuf_MalekView Answer on Stackoverflow
Solution 18 - RedisSpajusView Answer on Stackoverflow
Solution 19 - Redismahdi yousefiView Answer on Stackoverflow
Solution 20 - RedisAdrian BView Answer on Stackoverflow
Solution 21 - RedisKhaled AbuShqearView Answer on Stackoverflow
Solution 22 - RedisSumit SaurabhView Answer on Stackoverflow
Solution 23 - RedisJamesView Answer on Stackoverflow
Solution 24 - RedisTommyView Answer on Stackoverflow
Solution 25 - RedisChrisView Answer on Stackoverflow
Solution 26 - RedisDenysView Answer on Stackoverflow
Solution 27 - RedisAsalleView Answer on Stackoverflow
Solution 28 - RedisANIK ISLAM SHOJIBView Answer on Stackoverflow
Solution 29 - RedisAvtandil KavrelishviliView Answer on Stackoverflow
Solution 30 - RedisMassaynusView Answer on Stackoverflow
Solution 31 - RedisArijeet SahaView Answer on Stackoverflow