60

In my setup, the info command shows me the following:

[keys] => 1128
[expires] => 1125

I'd like to find those 3 keys without an expiration date. I've already checked the docs to no avail. Any ideas?

Duru Can Celasun
  • 1,621
  • 1
  • 16
  • 28
  • I can't help but wonder if everybody else that arrived here did so because a `getset` command nuked the TTL they had on a key. That's not something that's documented and it's something that happens. `getset` is slated for deprecation, but this bit us hard. – mlissner May 11 '21 at 19:57

6 Answers6

69

Modified from a site that I can't find now.

redis-cli keys  "*" | while read LINE ; do TTL=`redis-cli ttl "$LINE"`; if [ $TTL -eq  -1 ]; then echo "$LINE"; fi; done;

edit: Note, this is a blocking call.

Waynn Lue
  • 11,344
  • 8
  • 51
  • 76
  • 8
    TTL is `-1` for no expiration keys (instead of `3600`). – Niloct Mar 22 '12 at 22:38
  • 1
    That is an excellent point, thanks for pointing it out. Edited the answer and upvoting your comment. – Waynn Lue Mar 23 '12 at 00:01
  • 3
    I get an error when I try this on a remote redis host. I connect to the remote host and then from the prompt I run: `keys "*" | while read LINE ; do TTL=`redis-cli ttl $LINE`; if [ $TTL -eq -1 ]; then echo "$LINE"; fi; done;` but I get an "invalid arguments" error – emersonthis Jun 02 '15 at 15:29
  • 3
    Please make sure to double-quote `$TTL`. If one of the keys has a space, bash will give the `bash: [: too many arguments` error. Also, I had to use `==` over `-eq` due to this error: `bash: [: ERR wrong number of arguments for 'ttl' command: integer expression expected`. – Donovan Hernandez Aug 13 '15 at 21:21
  • 1
    well, maybe it's this [link](http://web.performancerasta.com/deleting-all-non-expiring-keys-in-a-redis-sever-from-command-line/) – fibonacci Apr 14 '16 at 08:41
  • 1
    This will launch a `redis-cli` process and reconnect to redis for every single key in the store. That may be millions or even billions. Is there not a better way to do this with a single connection? – AndrewF Jan 18 '18 at 23:48
  • 1
    if you're doing this on production, make sure to take a dump and runs this command. Running keys will hamper the performance significantly – supamaze May 30 '18 at 08:03
  • 3
    The [Redis KEYS command](https://redis.io/commands/KEYS) can stop Redis from processing requests. Using SCAN will take only about 10% engine CPU depending on your architecture. – teastburn May 22 '19 at 22:36
57

@Waynn Lue's answer runs but uses the Redis KEYS command which Redis warns about:

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.

Redis documentation recommends using SCAN.

redis-cli --scan | while read LINE ; do TTL=`redis-cli ttl "$LINE"`; if [ $TTL -eq  -1 ]; then echo "$LINE"; fi; done;

If you want to scan for a specific key pattern, use:

 redis-cli --scan --pattern "something*"
teastburn
  • 2,938
  • 1
  • 20
  • 12
4

In case somebody is getting bad arguments or wrong number of arguments error, put double quotes around $LINE.

So,it would be

redis-cli keys  "*" | while read LINE ; do TTL=`redis-cli ttl "$LINE"`; if [ $TTL -eq  -1 ]; then echo "$LINE"; fi; done;

This happens when there are spaces in the key.

zephyr
  • 1,775
  • 6
  • 20
  • 31
  • 6
    This is an improvement (bug fix) upon the existing answer and as such should be an edit of that answer. – AndrewF Jan 18 '18 at 23:50
3

To me the accepted answer appears unusable for a medium-sized dataset, as it will run a redis-cli command for each and every key.

Instead I used this lua script to filter the keys inside the redis server:

local show_persistent = ARGV[1] ~= "expiring"

local keys = {}
for i, name in ipairs(redis.call("keys", "*")) do
    local persistent = redis.call("pttl", name) < 0
    if persistent == show_persistent then
        table.insert(keys, name)
    end
end

return keys

This can be called as

$ redis-cli --eval show-persistent-keys.lua

to get all keys without an expiration time. It also can be called as

$ redis-cli --eval show-persistent-keys.lua , expiring

to find the opposite key set of all keys with an expiration time set.

On the downside this may block for too long (appears fine for 1 M keys). I'd use scan instead but I happen to have to run this against a legacy Redis at version 2.6, which does not have scan available.

Bluehorn
  • 2,956
  • 2
  • 22
  • 29
2

I needed to extract non-expiring keys from bigger (40GB) dataset, so using keys command was not suitable for me. So in case someone is looking for offline/non-blocking solution, you can use https://github.com/sripathikrishnan/redis-rdb-tools for extraction of non-expiring keys from redis rdb dump.

You can just install libraries via pip:

pip install rdbtools python-lzf

Then create simple parser which extracts keys and values which has expiration set to None:

from rdbtools import RdbParser, RdbCallback
from rdbtools.encodehelpers import bytes_to_unicode

class ParserCallback(RdbCallback):

    def __init__(self):
        super(ParserCallback, self).__init__(string_escape=None)

    def encode_key(self, key):
        return bytes_to_unicode(key, self._escape, skip_printable=True)

    def encode_value(self, val):
        return bytes_to_unicode(val, self._escape)

    def set(self, key, value, expiry, info):
        if expiry is None:
            print('%s = %s' % (self.encode_key(key), self.encode_value(value)))


callback = ParserCallback()
parser = RdbParser(callback)
parser.parse('/path/to/dump.rdb')
aron23
  • 293
  • 2
  • 3
  • 11
-1
#!/usr/bin/env python

import argparse
import redis

p = argparse.ArgumentParser()
p.add_argument("-i", '--host', type=str, default="127.0.0.1", help="redis host", required=False)
p.add_argument("-p", '--port', type=int, default=6379, help="redis port", required=False)
p.add_argument("-n", '--db', type=int, default=0, help="redis database", required=False)

args = p.parse_args()

r = redis.Redis(host=args.host, port=args.port, db=args.db)

try:
    keys = r.keys()

    for key in keys:
        if r.ttl(key) < 0:
            print(key)
except KeyboardInterrupt:
    pass
destator
  • 171
  • 1
  • 5