3
votes

Similar to this, but needing a solution for hashes instead of plain keys: How to atomically delete keys matching a pattern using Redis

I have a bunch of hashes with prefix like: "prefix:"

Under each hash are a bunch of keys like: "cc_XX", where "XX" is a 2 letter code.

I need to some how loop through all my redis hashes, and delete each of the cc_XX sub keys some how, and am looking for a cli/lua way to do this (not great with either).

Any advice would be greatly appreciated.

2

2 Answers

6
votes

The following EVAL script should do what you want:

local keys = redis.call('KEYS',KEYS[1])
for i,k in ipairs(keys) do
    local res = redis.call('HKEYS',k)
    for j,v in ipairs(res) do
        if string.find(v,ARGV[1]) then
            redis.call('HDEL',k,v)
        end
    end
end

You need to call it by providing the following parameters:

EVAL <script> 1 prefix:* cc_..

Please note it blocks the Redis event loop until the script is complete, so it can freeze Redis for a while if you have a large number of keys. Atomicity has a price.

Update:

If you don't need the atomicity, then the following script will avoid blocking Redis for too long (but please note, it will still block if you have a huge global number of keys or if one of your hash object is huge: there is no way to avoid this).

./redis-cli keys 'prefix:*' | awk '
BEGIN {
    script = "local res = redis.call('\''HKEYS'\'',KEYS[1]); \
          for j,v in ipairs(res) do                          \
            if string.find(v,ARGV[1]) then                   \
              redis.call('\''HDEL'\'',KEYS[1],v);            \
            end                                              \
          end"
}
{
    printf "EVAL \"%s\" 1 %s cc_..\n", script, $1
}' | ./redis-cli

(tested with bash)

2
votes

The way we store hashes in Redis, we suffix hash element id keys by object types within a hash. Sometimes in the data model, we update an object and we need to delete all hash elements of a given object type upon deployment. We wanted to accomplish this without having to delete the entire hash.

For example:

127.0.0.1:6379> keys client*
1) "client"

127.0.0.1:6379> type client
hash

127.0.0.1:6379> hkeys client
 1) "123-obj1"
 2) "123-obj2"
 3) "123-obj3"
 4) "123-obj4"
 5) "123-obj5"
 6) "456-obj1"
 7) "456-obj2"
 8) "456-obj3"
 9) "456-obj4"
10) "456-obj5"

If we add a new field in the application to obj5 and set with a default value, we would need to run the equivalent of "HDEL client *-obj5". However, this does not work in redis-cli.

I have found the following works via BASH:

redis-cli HKEYS 'client' | grep obj5 | awk '{ printf "HDEL client %s\n", $1 }' | redis-cli

If you use different Redis databases in your environment, add the "-n X" switch which is equivalent to "select X" in redis-cli. In this case, select database 4:

redis-cli -n 4 HKEYS 'client' | grep obj5 | awk '{ printf "HDEL client %s\n", $1 }' | redis-cli -n 4