Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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

  1. Client sends a SET key value [option1] [option2] .... The options are parsed left to right by the Redis server.
  2. Redis checks state: For NX, it verifies the key doesn't exist. For XX, it checks that the key does exist. If the condition isn't met, Redis returns (nil) and does nothing.
  3. If condition passes: Redis stores the value. If EX or PX is present, it also attaches the TTL. If KEEPTTL is specified, the existing TTL is preserved (if any).
  4. 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 NX with XX — Redis will return an error. Choose one condition per command. If you need both a conditional set and a TTL, combine NX or XX with EX/PX.

Troubleshooting & edge cases

Common Mistakes

  • Forgetting that NX and XX are mutually exclusive. Attempting to pass both results in ERR syntax error.
  • Assuming SET key value NX returns an error when key exists. It actually returns (nil) — not an error. Your application must check for None / (nil).
  • Using SET with KEEPTTL on a key that has no TTL. This is fine — it simply sets the value without adding an expiry. No error.
  • Combining EX and PX at the same time. Redis 7.0+ allows this (uses the larger of the two), but earlier versions reject it with ERR invalid expire time. Stick to one.
  • Forgetting that SET with options is still SET — old value is replaced. If you use XX and the key exists, the value is overwritten. Use GETSET or WATCH if you need to return the old value.

Edge Cases

  • TTL collision: If you set a key with EX and later call EXPIRE on the same key, the second TTL overrides the first. SET with 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 (for NX) but another client deletes it right after, your SET will 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 NX and XX in the same SET command — Redis returns ERR syntax error. Choose one condition per command.
  • Assuming SET key value NX returns an error when key already exists — it returns (nil), not an error. Your code must check for None.
  • Using EX and PX together in Redis versions before 7.0 — they reject it with ERR invalid expire time. Stick to one.
  • Forgetting that KEEPTTL only preserves the existing TTL; if the key has no TTL, SET with KEEPTTL does not add one.
  • Expecting SET key value XX to refuse overwriting the old value — it overwrites. Use GETSET if you need the previous value.

Variations

  1. Use SETEX command (deprecated) as a shorthand for SET key value EX seconds — but prefer SET with EX for consistency and atomicity.
  2. Use PSETEX (deprecated) for PX milliseconds — same reasoning; the inline SET options are now the recommended approach.
  3. For conditional set with TTL in a single transaction, use WATCH + MULTI/EXEC — but this is heavier and less atomic than SET 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, then INCR increments for subsequent requests within the window.

Key takeaways

  • SET with 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) vs XX (set only if exists) are mutually exclusive; combine with EX/PX for TTL-based locking or caching.
  • The command returns OK on success and (nil) on failure (e.g., key already exists with NX) — always check the return value.
  • KEEPTTL preserves the key's existing TTL without resetting it — useful for updating cache content without changing expiry.
  • Use SET key value NX EX seconds as the standard pattern for distributed locks — it's the recommended Redis pattern.
  • Always test with redis-cli before writing production code — flags are strict about combination rules.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.