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(withoutKEEPTTL) clears its TTL, - commands that modify a value in place, such as
INCR,LPUSHorHSET, 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) 2Remember
- 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.