Use SET with options
Use SET with options — Redis tutorial, lesson 7. Learn how to configure flags like NX, XX, EX, PX, and more to control key behavior during assignment. Includes hands-on walkthrough, troubleshooting edge cases, and practical examples for real-world caching and data management.
Focus: use set with options
The basic SET key value command works fine for storing a simple string, but what happens when you need to set a key only if it doesn't exist, or automatically expire it after a few seconds? Doing this manually with separate commands like EXISTS, SET, and EXPIRE not only bloats your code but also risks race conditions in concurrent environments. Redis solves this elegantly with SET options — inline flags that let you combine assignment, conditional logic, and expiration into a single atomic operation. This lesson unlocks that power.
The problem this lesson solves
When you're building real-world applications like session stores, rate limiters, or distributed locks, you can't afford to write fragile sequences of commands. For example, you want to set a session token for a user only if they don't already have one, and make it expire after 30 minutes. A naive approach would be:
import redis
r = redis.Redis()
if not r.exists("session:42"):
r.set("session:42", "abc123")
r.expire("session:42", 1800)
This works — but it's not atomic. Between the EXISTS check and the SET, another client might set the key, causing a race condition. You'd either overwrite their value or double-set. SET with options eliminates this gap entirely.
Core concept / mental model
Think of SET key value [options] as a smart setter. It takes the basic command and adds a toolbox of flags that control when to set (conditionally), how long the value lives (TTL), and whether to update an existing key. The Redis documentation calls these flags "modifiers" — they change the behavior of SET from a simple assignment into a compact policy statement.
The key insight: all flags are processed atomically. Redis guarantees that no other command can interfere between evaluating the condition and performing the set. This makes it safe for distributed locking and caching.
Available flags at a glance
| Flag | Purpose | Example |
|---|---|---|
NX |
Set only if key does not exist | SET lock:1 1 NX creates a lock if not held |
XX |
Set only if key already exists | SET config:v2 "json" XX updates only if v2 exists |
EX seconds |
Set TTL in seconds | SET cache:weather "sunny" EX 3600 expires after 1 hour |
PX milliseconds |
Set TTL in milliseconds | SET token "abc" PX 5000 expires after 5 seconds |
KEEPTTL |
Retain existing TTL (Redis 6.0+) | SET counter 10 KEEPTTL updates value without resetting expiry |
You can combine some flags (like NX + EX), but never NX with XX — they're mutually exclusive.
How it works step by step
- Client sends a
SET key value [option1] [option2] .... The options are parsed left to right by the Redis server. - Redis checks state: For
NX, it verifies the key doesn't exist. ForXX, it checks that the key does exist. If the condition isn't met, Redis returns(nil)and does nothing. - If condition passes: Redis stores the value. If
EXorPXis present, it also attaches the TTL. IfKEEPTTLis specified, the existing TTL is preserved (if any). - Return: On success, Redis returns
OK. On failure (condition not met), it returns(nil).
Atomicity guarantee
Because step 2 and 3 happen inside a single command handler, no other client can change the key between the check and the write. This is the foundation for reliable distributed locks (via SET key value NX EX timeout).
Hands-on walkthrough
Let's get practical. Connect to your Redis instance and open redis-cli or use Python with redis-py (version 4.0+).
Example 1: Distributed lock (NX + EX)
# Acquire lock for resource 'order:100' with 30-second timeout
127.0.0.1:6379> SET lock:order:100 "worker-1" NX EX 30
OK
# Try to acquire again (second client)
127.0.0.1:6379> SET lock:order:100 "worker-2" NX EX 30
(nil)
# Lock is held — worker-2 gets nil
In Python:
import redis
r = redis.Redis()
lock_key = "lock:order:100"
# Acquire lock (returns True if set, False otherwise)
if r.set(lock_key, "worker-1", nx=True, ex=30):
print("Lock acquired")
# Do critical work...
r.delete(lock_key) # Release
else:
print("Lock held by another worker")
Example 2: Session timeout (px)
# Set a session token that expires after 2.5 seconds
127.0.0.1:6379> SET session:alice "token_xyz" PX 2500
OK
# Check TTL (in milliseconds)
127.0.0.1:6379> PTTL session:alice
(integer) 2299
Example 3: KEEPTTL — update value without resetting expiry
# First set a key with TTL
127.0.0.1:6379> SET temp "old" EX 60
OK
# Update value but keep the remaining TTL
127.0.0.1:6379> SET temp "new" KEEPTTL
OK
# TTL is still decreasing from the original 60
127.0.0.1:6379> TTL temp
(integer) 42
Example 4: Conditional update (XX)
import redis
r = redis.Redis()
# Only update if key already exists
result = r.set("config:v2", "new_value", xx=True)
# If config:v2 already exists, result is True; otherwise False
if result:
print("Configuration updated")
else:
print("Key does not exist — no update performed")
Compare options / when to choose what
| Use Case | Recommended Flags | Why |
|---|---|---|
| Distributed lock | NX + EX (or PX) |
Set only if key missing, auto-expire to prevent deadlock |
| Session store | XX + EX (or no NX) |
Usually you want to update existing session; XX prevents accidental creation |
| Cache with expiry | EX (or PX) |
Simple TTL; no conditional logic needed |
| Rate limiter | NX + EX |
Create a counter key only if first request; use INCR afterward |
| Update cache without changing TTL | KEEPTTL |
Retain original expiry when refreshing content |
Pro tip: Never use
NXwithXX— Redis will return an error. Choose one condition per command. If you need both a conditional set and a TTL, combineNXorXXwithEX/PX.
Troubleshooting & edge cases
Common Mistakes
- Forgetting that
NXandXXare mutually exclusive. Attempting to pass both results inERR syntax error. - Assuming
SET key value NXreturns an error when key exists. It actually returns(nil)— not an error. Your application must check forNone/(nil). - Using
SETwithKEEPTTLon a key that has no TTL. This is fine — it simply sets the value without adding an expiry. No error. - Combining
EXandPXat the same time. Redis 7.0+ allows this (uses the larger of the two), but earlier versions reject it withERR invalid expire time. Stick to one. - Forgetting that
SETwith options is stillSET— old value is replaced. If you useXXand the key exists, the value is overwritten. UseGETSETorWATCHif you need to return the old value.
Edge Cases
- TTL collision: If you set a key with
EXand later callEXPIREon the same key, the second TTL overrides the first.SETwith options has no special protection against this. - Large values: TTL-based expiry can cause sudden evictions if many keys expire at once. Use random jitter in expiry times (e.g.,
EX 3600 + random(0, 300)) to spread load. - Race with
DEL: If you check a key does not exist (forNX) but another client deletes it right after, yourSETwill succeed — which is correct, as the key no longer exists at the moment of command execution.
What you learned & what's next
You now understand how to SET keys with options: NX for create-only, XX for update-only, EX/PX for TTL, and KEEPTTL for preserving expiry. These flags make your Redis operations atomic, concise, and production-ready — no more multi-command race conditions. You applied this with a distributed lock example, a session timeout, and a conditional update.
Next up: You'll learn how to retrieve and inspect keys using GET, MGET, STRLEN, and EXISTS — building on your setter skills to read data efficiently.
Practice recap
Mini exercise: In redis-cli, try creating a key named test:lock with value true that expires after 10 seconds, but only if it doesn't already exist. Then immediately run the same command again — what response do you get? Next, simulate a session update by setting a key session:user1 with value token1 and XX flag (it should fail because the key doesn't exist). Finally, set it without XX, then update it with KEEPTTL to confirm the TTL persists. This exercise reinforces atomic conditionals and TTL control.
Common mistakes
- Passing both
NXandXXin the sameSETcommand — Redis returnsERR syntax error. Choose one condition per command. - Assuming
SET key value NXreturns an error when key already exists — it returns(nil), not an error. Your code must check forNone. - Using
EXandPXtogether in Redis versions before 7.0 — they reject it withERR invalid expire time. Stick to one. - Forgetting that
KEEPTTLonly preserves the existing TTL; if the key has no TTL,SETwithKEEPTTLdoes not add one. - Expecting
SET key value XXto refuse overwriting the old value — it overwrites. UseGETSETif you need the previous value.
Variations
- Use
SETEXcommand (deprecated) as a shorthand forSET key value EX seconds— but preferSETwithEXfor consistency and atomicity. - Use
PSETEX(deprecated) forPXmilliseconds — same reasoning; the inlineSEToptions are now the recommended approach. - For conditional set with TTL in a single transaction, use
WATCH+MULTI/EXEC— but this is heavier and less atomic thanSET NX EX.
Real-world use cases
- Implementing a distributed lock for a job queue worker:
SET lock:job:123 worker_id NX EX 60— only one worker acquires the lock, auto-expires after 60 seconds. - Caching user session tokens with TTL:
SET session:user_id token XX EX 3600— only updates existing sessions, refreshing expiry each time. - Rate limiting API endpoints:
SET rate:ip:192.168.1.1 1 NX EX 60— creates a counter key on first request, thenINCRincrements for subsequent requests within the window.
Key takeaways
SETwith options (NX,XX,EX,PX,KEEPTTL) makes key assignment atomic, safe, and expressive — never split condition + set across commands.NX(set only if not exists) vsXX(set only if exists) are mutually exclusive; combine withEX/PXfor TTL-based locking or caching.- The command returns
OKon success and(nil)on failure (e.g., key already exists withNX) — always check the return value. KEEPTTLpreserves the key's existing TTL without resetting it — useful for updating cache content without changing expiry.- Use
SET key value NX EX secondsas the standard pattern for distributed locks — it's the recommended Redis pattern. - Always test with
redis-clibefore writing production code — flags are strict about combination rules.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.