Found and cleanup keys without expiration in Redis
If for some reason, your Redis memory grow and grow but you would like to know why the first step should be to check how many keys you have without expiration.
You can easily check it by running the info keyspace
command:
# Keyspace
db0:keys=3277631,expires=447528,avg_ttl=238402708
Where keys
is the total number of key and expires
the number of keys with an expiration. In this example, we have 13% of our keys that will expire a day. The average expiration time of those keys is avg_ttl
in ms.
Good, a lot of our keys doesn't have an expiration, that should be our problem!
Now, founding which key expire or not is not a simple job, but it's a really useful information when you want to fixe the code that creates them.
Juste a little reminder:
Note: Never run
keys
in production !
Yes! Really, never do that please
What the simplest way to finding the keys without listing all the keys? If your Redis server is configured for RDB / AOF persistence, just use the dump and it's what we will do.
We will use an RDB dump in our example, but it's the same command with an AOF log.
For more information about Redis persistence, the redis documentation explain the two option with pro and cons
If you don't have persistence enabled, you can take a one time dump by running BGSAVE
doc.
The RDB/AOF dump should be in /var/lib/redis/
, copy it temporarly to an another place because redis could rewrite it during a transfert: cp /var/lib/redis/dump.rdb /tmp/dump.rdb
.
Change the ownership of the file for allowing your local user to have access to it: chown jmaitrehenry. /tmp/dump.rdb
And download it on your local computer: scp myredis:/tmp/dump.rdb ./
Note: If like me, you have a gateway server between Redis and internet, I assume you know how to download your dump to your local computer.
Good, we can now work on our dump!
I prefer to load the dump in a new Redis server, and for that, why not using Docker?
docker run --name redis_dump -d -v `pwd`:/data -p 6379 redis:3.2
Note: If you don't have a local Redis client or don't want to use it, you can enter into the previous docker container:
docker exec -ti redis_dump bash
Ok, let extract all the key without expiration
redis-cli keys "*" > keys
cat keys | xargs -n 1 -L 1 redis-cli ttl > ttl
paste -d " " keys ttl | grep .*-1$ | cut -d " " -f 1 > without_ttl
# We can create a script for deleting the keys
cat without_ttl | awk '{print "redis-cli del "$1}' > redis.sh
You can now review your keys without TTL and upload your cleanup script to your Redis server and run it with sh redis.sh
.
In our case, we found the problem in a ruby code that not set the expiration
$redis.with do |redis|
redis.set redis_key, info, ttl: 3.days
end
The right key is ex
and not ttl
but we miss it in our code review process, and I think a lot of teams could miss it too.
Bonus - How to find largest keys
In some case, it's normal to not have an expiration on some keys, and we can skip them in the first cleanup. Redis has a way to scan the keyspace and found the biggest key per type with redis-cli --bigkeys
doc
$ redis-cli --bigkeys
# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
[00.00%] Biggest string found so far 'production_session:xxx' with 437 bytes
[00.00%] Biggest string found so far 'production_session:yyy' with 440 bytes
[00.00%] Biggest string found so far 'production_session:zzz' with 477 bytes
[...]
-------- summary -------
Sampled 362668 keys in the keyspace!
Total key length in bytes is 25195300 (avg len 69.47)
Biggest string found 'production:a/b/c' has 212156 bytes
Biggest list found 'sidekiq-logs' has 18127306 items
Biggest set found 'production:xyz' has 140 members
Biggest hash found 'production:abc' has 140 fields
Biggest zset found 'production:dead' has 8484 members
361073 strings with 253642736 bytes (99.56% of keys, avg size 702.47)
9 lists with 18127422 items (00.00% of keys, avg size 2014158.00)
507 sets with 18067 members (00.14% of keys, avg size 35.64)
1044 hashs with 20986 fields (00.29% of keys, avg size 20.10)
35 zsets with 9951 members (00.01% of keys, avg size 284.31)
It's how we found the real problems, the sidekiq-logs
list just grow and grow.
Thanks for reading!
If you find a typo, have a problem when trying what you find on this article, please contact me!