How to atomically delete keys matching a pattern using Redis
Asked Answered
M

34

782

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

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

Mcdonough answered 23/10, 2010 at 22:4 Comment(7)
Hi Steve, There is some issue with my website, I have added it to my other blog mind-geek.net/nosql/redis/delete-keys-specific-expiry-time , Hope this helps.Stallfeed
This is such a common scenario that I wish the Redis team would consider adding a native command for it.Abdominal
Nowadays you can just do that with Lua, see below.Mcdonough
@ToddMenier Just suggested, got this reasoning back for why it will never happen: github.com/antirez/redis/issues/2042Bearden
Lots of people asking related questions about how to handle a large number of keys, keys with special characters, etc. I created a separate question as we are having this problem now and I don't think the answer is posted on this question. Here is the other question: #32891148Fitment
Also check this question on how to delete keys matching a pattern in Redis Cluster mode.Frederickfredericka
I find this useful: rdbtools.com/blog/redis-delete-keys-matching-pattern-using-scanInsolence
L
500

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.

Lowland answered 6/6, 2013 at 23:47 Comment(15)
Important note: this fails if you have more than a couple thousand keys matching the prefix.Centerboard
This one is working for big number of keys: EVAL "local keys = redis.call('keys', ARGV[1]) \n for i=1,#keys,5000 do \n redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) \n end \n return keys" 0 prefix:*Ruddock
Ouch... redis is used a lot as simple key/store cache. This seems del prefix:* should be a fundamental operation :/Bearden
@Bearden frankly, if you need that feature you should simply partition the data by numetic database or server, and use flush / flushdbToxemia
@MarcGravell I heard that multiple DB's on a single redis deploy is being deprecated. To partition without functionaly this you'd need several redis servers running. Not sure adding infrastructure real or virtual to maintain would be a better option than if an efficient del prefix:* existed.Bearden
@Bearden It isn't being deprecated - but: it isn't supported in "cluster". As I understand it, it is remaining a "thing" in "server". But meh; spinning up redis-server nodes is cheap too. If you think the del {pattern} feature should exist, maybe take that discussion to the redis-db list on google.Toxemia
@MarcGravell there are a number of use cases that are precluded by partitioning, such as set operations that involve sets in different dbs (e.g. intersection). See "Disadvantages of Partitioning" here for other examples: redis.io/topics/partitioningBott
Another option: add keys that should be deleted together to a hash/list/set and delete them at once, as one object.Mulberry
@sheerun, perhaps not returning the keys is better for many applications. If we really have so many of them, it may end up sending back tens or hundreds of megabytes.Bracer
Any Lua-based solution will violate the semantics of EVAL since it doesn't specify in advance the keys that it will operate on. It should work on a single instance but don't expect it to work with Redis Cluster.Tailored
This script fails if keys prefix:* returns 0Giamo
Yes it fails if no key matches pattern. To fix that I added a default key: EVAL "return redis.call('del', 'defaultKey', unpack(redis.call('keys', ARGV[1])))" 0 prefix:*Fourthly
To prevent empty match error: EVAL 'local keys = redis.call("keys", ARGV[1]); return #keys > 0 and redis.call("del", unpack(keys)) or 0' 0 prefix:*Domini
But this uses KEYS? The docs say we are not supposed to use this in production.Chassepot
This works mostly - but it fails if a key has a single quote in it, because xargs complains.Foetor
F
896

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.

Forsworn answered 23/10, 2010 at 22:4 Comment(22)
Sorry, but this is not atomic deletion. Someone may add new keys between KEYS and DEL. I do not want to delete those.Mcdonough
Keys, that will be created after KEYS command will not be deleted.Jylland
OK, you're right, sorry, wrong reason (5 AM here). The correct one: If the value changes between KEYS and DEL, I do not want to delete it. Furthermore, if value is changed between KEYS and DEL, I want the change to be of... "insert" kind, not "update" (if you get what I mean) -- important for hsets for example.Mcdonough
IMHO, it is better to post a new answer in such cases -- so old answer's "karma" would not harm new one.Mcdonough
Anyway, a good solution, thanks. But it requires a single read for each write I do -- a bit of overhead. Is it possible to do what I want without that overhead?Mcdonough
Thank you, i will create new answer next time. About your question - i think no. GET is extremely cheap operation in Redis, it will not be a bottleneck.Jylland
I just needed to clear out some bad keys, so Casey's first answer was spot on, except I had to move keys outside of the quotes: redis-cli KEYS "prefix:*" | xargs redis-cli DELEddings
The first answer also helped me out. Another variant if your redis keys contain quotes or other characters that mess up xargs: redis-cli KEYS "prefix:*" | xargs --delim='\n' redis-cli DELSouterrain
If you have multible databases (keyspaces) then this is the trick: Lets say you need to delete keys in db3: redis-cli -n 3 KEYS "prefix:*" | xargs redis-cli -n 3 DELDestined
If KEYS returns a substantial amount of keys, I believe xargs will fail. See in-ulm.de/~mascheck/various/argmaxCarey
Just ran this against around 80,000 keys with no problem. Can also confirm that it didn't delete new keys created during that time.Luana
I have keys with embedded spaces. I was able to use sed to quote the keys: redis-cli KEYS "prefix:*" | sed 's/\(.*\)/"\1"/' | xargs redis-cli DELFonville
Is there an equivalent to redis-cli KEYS "prefix:*" | xargs redis-cli DEL for windows cmd?Nervine
The only option that comes to my mind is cygwin, or, which is even better - try vagrant environment for your project, like puphpet.comJylland
For other host with selected db : redis-cli -h host -n db KEYS "prefix1:prefix2:*" | xargs redis-cli -h host -n db DELCharacterization
How would you do it with remote -h host and user and password authentication? Adding them to a -u redis://user:[email protected] does not seem to work in redis-cliRusselrussell
I had to use redis-cli keys "stats.*" | cut -d ' ' -f2 | xargs -d '\n' redis-cli DEL; the above didnt work for meUvarovite
In case you are running a remote (port-forwarded) redis instance and need run the command there. Note that xargs also need to -u host... : redis-cli -u redis://localhost:6382 KEYS "*pull-status*" | xargs redis-cli -u redis://localhost:6382 DELBeverleybeverlie
Dealing with doctrine keys containing backslash and dollars, I had to extend to: redis-cli KEYS 'Db_Doctrine*' | cut -f2 | sed -e's/\\/\\\\/g' | sed -e's/\$/\\$/g' | xargs -i{} redis-cli DEL "{}"Lowbrow
# redis-cli -n 1 --scan --pattern 'prefix:*' | xargs redis-cli -n 1 del ////------> the -n 1 means select database index 1 if you store your keys in another db indexBerkowitz
@Souterrain when dealing with keys that have sensitive characters, in a Docker redis:x.x.x-alpine based container xargs does not come with the --delim argument. Need to use the -I replace argument instead: redis-cli --raw KEYS "prefix:*" | xargs -I{} redis-cli DEL "{}"Leandroleaning
Is it possible to pipe the KEYS output to DEL inside the Redis shell? As opposed to adding multiple redis-cli prefixes from the Bash shellGunny
L
500

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.

Lowland answered 6/6, 2013 at 23:47 Comment(15)
Important note: this fails if you have more than a couple thousand keys matching the prefix.Centerboard
This one is working for big number of keys: EVAL "local keys = redis.call('keys', ARGV[1]) \n for i=1,#keys,5000 do \n redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) \n end \n return keys" 0 prefix:*Ruddock
Ouch... redis is used a lot as simple key/store cache. This seems del prefix:* should be a fundamental operation :/Bearden
@Bearden frankly, if you need that feature you should simply partition the data by numetic database or server, and use flush / flushdbToxemia
@MarcGravell I heard that multiple DB's on a single redis deploy is being deprecated. To partition without functionaly this you'd need several redis servers running. Not sure adding infrastructure real or virtual to maintain would be a better option than if an efficient del prefix:* existed.Bearden
@Bearden It isn't being deprecated - but: it isn't supported in "cluster". As I understand it, it is remaining a "thing" in "server". But meh; spinning up redis-server nodes is cheap too. If you think the del {pattern} feature should exist, maybe take that discussion to the redis-db list on google.Toxemia
@MarcGravell there are a number of use cases that are precluded by partitioning, such as set operations that involve sets in different dbs (e.g. intersection). See "Disadvantages of Partitioning" here for other examples: redis.io/topics/partitioningBott
Another option: add keys that should be deleted together to a hash/list/set and delete them at once, as one object.Mulberry
@sheerun, perhaps not returning the keys is better for many applications. If we really have so many of them, it may end up sending back tens or hundreds of megabytes.Bracer
Any Lua-based solution will violate the semantics of EVAL since it doesn't specify in advance the keys that it will operate on. It should work on a single instance but don't expect it to work with Redis Cluster.Tailored
This script fails if keys prefix:* returns 0Giamo
Yes it fails if no key matches pattern. To fix that I added a default key: EVAL "return redis.call('del', 'defaultKey', unpack(redis.call('keys', ARGV[1])))" 0 prefix:*Fourthly
To prevent empty match error: EVAL 'local keys = redis.call("keys", ARGV[1]); return #keys > 0 and redis.call("del", unpack(keys)) or 0' 0 prefix:*Domini
But this uses KEYS? The docs say we are not supposed to use this in production.Chassepot
This works mostly - but it fails if a key has a single quote in it, because xargs complains.Foetor
H
90

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.

Hogwash answered 10/6, 2013 at 4:20 Comment(5)
The code above will tank if you have a significant number of keys (the error is "too many elements to unpack"). I recommend using a loop on the Lua part: for _,k in ipairs(redis.call('keys', KEYS[1])) do redis.call('del', k) endDiscount
@kikito, yes, if lua cannot grow the stack to the number of keys you want to delete (most likely due to lack of memory), you'll need to do it with a for loop. I wouldn't recommend doing this unless you have to.Hogwash
Lua's unpack transforms a table in a "list of independent variables" (other languages call that explode) but the max number is not dependent on the syste memory; it's fixed in lua through the LUAI_MAXSTACK constant. In Lua 5.1 & LuaJIT it's 8000 and in Lua 5.2 is 100000. The for loop option is recommended IMO.Discount
It's worth noting that lua scripting is only available from Redis 2.6 upApetalous
Any Lua-based solution will violate the semantics of EVAL since it doesn't specify in advance the keys that it will operate on. It should work on a single instance but don't expect it to work with Redis Cluster.Tailored
D
84

Disclaimer: the following solution doesn't provide atomicity.

Starting with v2.8 you really want to use the SCAN 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] KEYS 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
Deka answered 30/4, 2014 at 22:26 Comment(7)
Nevertheless, avoiding KEYS is definitely considered best practice, so this is a great solution wherever non-atomic deletes are feasible.Mulberry
This worked for me; however, my keys happened to be in database 1. So I had to add -n 1 to each redis-cli invocation: redis-cli -n 1 --scan --pattern "*:foo:bar:*" | xargs -L 100 redis-cli -n 1 DELDirections
Note that this does not work if your keys contain special charsNaara
Interesting and valuable find... I wonder if there's a way to quote things for xargs...Deka
what does -L 100 do??Pest
-L number Call utility for every number non-empty lines read.Overstuff
This fails for many cases including where pattern doesn't find anything. Created a version of this that's safer. gist.github.com/dk8996/1f8c92c4c5ea1b4e8997b80e826b90f4Leader
S
70

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

Shaker answered 19/3, 2014 at 15:32 Comment(1)
This works irrespective of the number of items, which is much better than other answers here.Oxidate
A
49

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 :)

Abelmosk answered 8/5, 2017 at 7:33 Comment(2)
this doesn't do anything for me.Fogbound
redis-cli keys '*'|xargs redis-cli del worksCoracorabel
B
39

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
Beals answered 12/1, 2017 at 18:31 Comment(3)
Thanks, but piping output to redis-cli del is not atomic.Mcdonough
doesn't work if key has spaces or double-quotes.Fogbound
I had to do this in order it to work redis-cli --scan --pattern 'xyz_category_fpc*' | xargs -I{} redis-cli del '{}'Watt
V
29

@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...

Volga answered 19/12, 2014 at 7:20 Comment(1)
using lua is loooooot faster than using xargs, in the order to 10^4.Deutsch
H
19

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.

Hamlett answered 19/5, 2021 at 7:39 Comment(3)
This worked, but i had to use single quotes. Example: EVAL "return redis.call('del', unpack(redis.call('keys', 'my_pattern_here*')))" 0Underexposure
For those who trying to clean but got: (error) ERR Error running script (call to ...): @user_script:1: user_script:1: too many results to unpack, try a solution from comments of the similar answer above.Boz
Finally something that works, thanks!Cammiecammy
E
17

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
Extend answered 4/3, 2015 at 23:48 Comment(0)
P
17

// 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

Polymerous answered 18/11, 2020 at 9:22 Comment(3)
this works (nothing else did) except for when a key has quotes.Fogbound
adding use when data needs to be deleted from database redis-cli -n <database-name> KEYS "*" | xargs -i redis-cli EXPIRE {} 1Agility
This is not atomic.Mcdonough
A
15

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
Ascariasis answered 15/11, 2019 at 14:45 Comment(3)
This script works perfect, tested with more than 25000 keys.Sleeper
You could also add the single quotes in awk using this funny expression ` awk '{ print "'"'"'" $1 "'"'"'"}'`Crossways
the above command works well, but with scan and pattern it was taking a lot of time to complete ( for 1600 keys ). To speed it up used: keys command redis-cli keys sf_* | awk '{print $1}' | sed "s/^/'/;s/$/'/" | xargs redis-cli delLoraineloralee
V
13

@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.

Vidette answered 23/6, 2015 at 2:18 Comment(0)
A
12

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).

Atomize answered 18/12, 2010 at 19:6 Comment(4)
Good solution, but my values are hashes themselves. And Redis store hash inside another hash.Mcdonough
However, the fields within a hash lack the expire functionality, which is sometimes really useful.Convexoconcave
to me this is the cleanest/simplest answer so farTrish
Doesn't a set make way more sense ?Stalinism
C
11

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_*
Culicid answered 25/10, 2021 at 16:45 Comment(1)
Gold!! First one that uses SCAN instead of keys. tyvm!Tut
D
9

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

pure bash!

redis-cli DEL $(redis-cli KEYS *pattern*)
Dormie answered 7/2, 2022 at 15:17 Comment(3)
This is not an atomic operationMcdonough
Why would that be? Any keys added after this command starts executing will still be there ! it would also batch delete everything else .Dormie
In my case, i have to specify the host and the db index, eg. redis-cli -h localhost -n 1 DEL $(redis-cli -h localhost -n 1 KEYS *pattern*). Thank you.Tisza
G
6

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
Godin answered 8/5, 2017 at 7:8 Comment(0)
D
6

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
Danielson answered 26/6, 2017 at 1:23 Comment(1)
This solution worked well for me even on approx 7m keys!Ecclesiology
G
5

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.

Gascon answered 24/10, 2010 at 8:51 Comment(1)
But I can't figure out how to use them here. DEL is atomic by itself (or so I think). And I can't get values from KEYS until I do EXEC, so I can't use KEYS and DEL in the same MULTI.Mcdonough
M
5

Please use this command and try :

redis-cli --raw keys "$PATTERN" | xargs redis-cli del
Mycenaean answered 29/11, 2018 at 14:2 Comment(1)
Not atomic, and duplicates other answers.Sonatina
I
4

If we want to make sure of atom operation we can try to write a Lua script.

If your Redis version support SCAN and UNLINK which is higher than 4.0.0,I prefer to use SCAN and UNLINK instead of Key and DEL in the production environment, because Key and DEL commands might block

they can be used in production without the downside of commands like KEYS or SMEMBERS that may block the server for a long time (even several seconds) when called against big collections of keys or elements.

EVAL "local cursor = 0 repeat local result = redis.call('SCAN', cursor, 'MATCH', ARGV[1])    for _,key in ipairs(result[2]) do  redis.call('UNLINK', key)   end  cursor = tonumber(result[1]) until cursor == 0 " 0 prefix:*

We can change prefix:* as we want.

Idden answered 13/6, 2022 at 7:23 Comment(2)
I'd question whether SCAN has much benefit over KEYS here since you're running it in a script that will also block until completion. Memory usage perhaps.Lexis
This is the correct way to do this in modern times. UNLINK is a very fast operation and combining this with nc -v <yourredisserver> 6379 I was able to remove 3M keys out of a 95m key redis instance in 2.5 mins. This is on redis 5 and it would be even faster on 6.Filiano
B
3

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
Brie answered 22/2, 2016 at 14:1 Comment(0)
P
3

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 
Pyrethrin answered 11/7, 2019 at 4:13 Comment(0)
R
2

Below command worked for me.

redis-cli -h redis_host_url KEYS "*abcd*" | xargs redis-cli -h redis_host_url DEL
Roundtheclock answered 12/9, 2019 at 8:28 Comment(2)
Any specific reason for getting downvoted? This worked for me too.Vann
I do not think this answer deletes keys atomically and is incorrect answer. Keys are deleted in multiple operations.Henbane
S
2

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
Sukkah answered 9/3, 2021 at 15:45 Comment(1)
This is not atomic.Mcdonough
U
1

This one worked for me but may not be atomic:

redis-cli keys "stats.*" | cut -d ' ' -f2 | xargs -d '\n' redis-cli DEL
Uvarovite answered 10/5, 2021 at 16:40 Comment(1)
This is non atomic.Hastate
E
1

I know this one is already answered and works. However, it is not uncommon to work with dockerized Redis. Then the command :

docker exec redis-docker-container-name redis-cli --scan --pattern "*" | while read key; do   docker exec redis-docker-container-name redis-cli del "$key"; done
Enki answered 30/9, 2023 at 10:36 Comment(0)
H
0

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.

Henceforth answered 13/3, 2014 at 16:28 Comment(0)
K
0

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();
    }

}
Korney answered 1/9, 2016 at 18:52 Comment(0)
C
0

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!

Coati answered 12/9, 2019 at 14:14 Comment(0)
G
0

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
Guenzi answered 8/12, 2021 at 5:6 Comment(5)
This is not an atomic operationMcdonough
thanks, @AlexanderGladysh but I could not get why unlink or delete is not automatic, do you care to explain.Guenzi
The set of keys may change between first and subsequent redis-cli invocations. You have to enumerate the keys and delete them in a single atomic operation to prevent this. Please refer to the accepted answer for an example.Mcdonough
so you mean if I use EVAL and lua script then it will be atomic?Guenzi
Yes, if you enumerate and delete keys within a single script invocation, it should be atomic.Mcdonough
L
0

If you don't mind installing a GUI, Redis provide an app called RedisInsight that supports bulk actions, so you can filter keys using a wildcard and delete them all.

Lightman answered 6/10, 2023 at 9:11 Comment(0)
I
-1

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

Ivonne answered 27/10, 2020 at 11:39 Comment(1)
This is not atomic.Mcdonough
A
-4

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);
Abranchiate answered 30/11, 2015 at 10:29 Comment(1)
I updated Redisson sample code. Your code is not in atomic approach like Redisson does. There are new keys could appear between keys and delete methods invocations.Spat

© 2022 - 2024 — McMap. All rights reserved.