Redis · lesson 2 of 14
Beginner
4Intermediate
7- 5 MULTI/EXEC Transactions and the WATCH-Based Optimistic Lock
- 6 Pub/Sub: Channels, Patterns, and the Message That Never Arrives
- 7 RDB vs AOF: What Survives a Hard Kill and What Doesn't
- 8 Replication: A Read-Only Replica, and What WAIT Actually Guarantees
- 9 ACL: a User Restricted to One Key Pattern and One Command
- 12 maxmemory and Eviction: What Redis Throws Away When It's Full
- 13 Cache-Aside with Redis: Stale Reads, Races and Stampedes
Advanced
3Also part of Redis
3Expiry, Atomic Counters, and the -1 vs -2 TTL Trap
TTL returns -1 for a key that never expires and -2 for one that's already gone — mixing those up is the most common expiry bug. Then: INCR's atomic counters, and the error when a string isn't a number.
By SkillByExample · Published
Two things about strings look simple and have a real trap hiding in each: expiry, and treating a string as a number.
Setting a value that expires
docker exec redis-lesson redis-cli SET session:abc "active" EX 5
docker exec redis-lesson redis-cli TTL session:abc
docker exec redis-lesson redis-cli GET session:abc
OK
4
active
EX 5 sets the key to expire in 5 seconds. TTL shows 4 a moment later — it’s a
countdown, not a fixed snapshot, so seeing it a second lower than what you set is normal.
The bug: confusing “no expiry” with “gone”
docker exec redis-lesson redis-cli SET permanent "value"
docker exec redis-lesson redis-cli TTL permanent
OK
-1
# after session:abc's 5 seconds have passed
docker exec redis-lesson redis-cli GET session:abc
docker exec redis-lesson redis-cli TTL session:abc
-2
Two different negative numbers, two completely different situations. permanent has -1
because it was never given an expiry — it isn’t going anywhere. session:abc has -2 because
it expired and no longer exists at all — GET on it returns nothing. Code that treats -1
and -2 the same way (say, “refresh if TTL is negative”) will pointlessly regenerate a value
that was never expiring in the first place.
EXPIRE and PERSIST on an existing key
docker exec redis-lesson redis-cli SET temp "data"
docker exec redis-lesson redis-cli EXPIRE temp 100
docker exec redis-lesson redis-cli TTL temp
docker exec redis-lesson redis-cli PERSIST temp
docker exec redis-lesson redis-cli TTL temp
OK
1
99
1
-1
EXPIRE adds a TTL to a key that already exists — the 99 a moment later (not 100) is the
same countdown-already-running behavior as before. PERSIST does the opposite: it strips the
expiry entirely, and the key goes back to -1.
Atomic counters
docker exec redis-lesson redis-cli SET views 100
docker exec redis-lesson redis-cli INCR views
docker exec redis-lesson redis-cli INCRBY views 50
docker exec redis-lesson redis-cli GET views
OK
101
151
151
INCR and INCRBY happen atomically on the server — two clients incrementing the same
counter concurrently can’t lose an update to a race condition the way a client-side
read-modify-write (GET, add one, SET) could.
The bug: incrementing something that isn’t a number
docker exec redis-lesson redis-cli SET name "Dune"
docker exec redis-lesson redis-cli INCR name
OK
ERR value is not an integer or out of range
Same philosophy as the intro lesson’s WRONGTYPE error, applied to content instead of
structure: INCR needs the string to actually parse as an integer, and refuses outright
rather than guessing a starting value.
Practice
1. Use SET ... NX to implement a simple lock, and confirm a second attempt fails.
docker exec redis-lesson redis-cli SET lock:job1 "worker-1" NX
docker exec redis-lesson redis-cli SET lock:job1 "worker-2" NX
docker exec redis-lesson redis-cli GET lock:job1
OK
worker-1
The second SET ... NX returns nothing (not an error) because lock:job1 already exists —
worker-2 never wrote anything, and worker-1’s value is still there. This is Redis’s
simplest building block for a distributed lock.
2. Increment a value by a floating-point amount.
docker exec redis-lesson redis-cli SET price 9.99
docker exec redis-lesson redis-cli INCRBYFLOAT price 0.50
OK
10.49
INCR/INCRBY are integer-only; INCRBYFLOAT is the dedicated command for float arithmetic,
still atomic on the server.
3. Set a key to expire, then remove the expiry before it fires, and confirm it survives.
docker exec redis-lesson redis-cli SET reprieve "still here" EX 3
docker exec redis-lesson redis-cli PERSIST reprieve
OK
1
PERSIST returns 1 when it actually removed an expiry (and 0 if the key had none to
remove) — worth checking that return value in code that assumes the persist always “did
something.”
Next: hashes, lists, and sets — the structures with more than one value per key.