Keys, expiry and SCAN

Redis · Essentials · 9 min

Expiring data correctly is what makes Redis a cache, and safe iteration keeps production responsive.

Any key can have a time to live. EXPIRE key 60 sets it in seconds (PEXPIRE for milliseconds, EXPIREAT for a Unix timestamp), TTL key reports the remaining seconds, and PERSIST key removes it. TTL returns -1 for a key without expiry and -2 for a missing key.

Expiry details that bite:

  • overwriting a key with SET (without KEEPTTL) clears its TTL,
  • commands that modify a value in place, such as INCR, LPUSH or HSET, keep it,
  • expired keys are removed lazily when accessed and by a background sampler, so memory is reclaimed gradually, but an expired key is never returned.

Deleting: DEL key ... frees immediately (blocking for huge values), UNLINK frees in a background thread, and RENAME moves a key.

To find keys, never use KEYS pattern in production: it walks the whole keyspace in one blocking call. SCAN cursor MATCH pattern COUNT n returns a small batch plus the next cursor; you keep calling until the cursor is 0. It may return a key more than once and only guarantees keys present for the whole scan, so make your processing idempotent. HSCAN, SSCAN and ZSCAN iterate inside big collections the same way.

Good key design (app:entity:id:field) makes SCAN MATCH and debugging much easier.

Example

redis-cli SET otp:ana 123456 EX 300     # OK
redis-cli TTL otp:ana                   # (integer) 300
redis-cli SET otp:ana 654321            # OK  (plain SET clears the TTL)
redis-cli TTL otp:ana                   # (integer) -1
redis-cli EXPIRE otp:ana 120            # (integer) 1
redis-cli TTL nothing-here              # (integer) -2

redis-cli MSET user:1 a user:2 b order:1 c   # OK
redis-cli SCAN 0 MATCH 'user:*' COUNT 100
# 1) "0"            next cursor (0 = finished)
# 2) 1) "user:1"
#    2) "user:2"
redis-cli --scan --pattern 'user:*'     # redis-cli loops over SCAN for you
redis-cli UNLINK user:1 user:2          # (integer) 2

Remember

  • EXPIRE/TTL manage time to live; -1 = no TTL, -2 = no key
  • A plain SET resets the TTL; in-place updates keep it
  • Use SCAN (or redis-cli --scan), never KEYS, on live servers
  • UNLINK frees big values without blocking

Watch out for

  • Refreshing a cached value with SET and forgetting to pass EX again, so it never expires.

Check yourself

  1. A key has TTL 100. You run SET on it without options. What is its TTL now?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: redis.io/docs/latest/develop/using-commands/keyspace/