Loading learning content...
In the landscape of distributed caching solutions, Redis stands apart—not merely as a cache, but as a sophisticated in-memory data structure server that has fundamentally reshaped how engineers architect high-performance systems. Originally created by Salvatore Sanfilippo in 2009, Redis (Remote Dictionary Server) has evolved from a simple key-value store into a versatile platform supporting complex data types, persistence options, clustering, pub/sub messaging, and even Lua scripting.
What distinguishes Redis from simpler caching solutions is its native support for rich data structures—lists, sets, sorted sets, hashes, streams, and more—each implemented with optimal time complexities that would take significant effort to replicate in application code. This means Redis doesn't just store your data; it provides powerful operations on that data while it resides in memory.
Today, Redis powers caching layers at Netflix, Twitter, GitHub, Stack Overflow, and virtually every major technology company. Its reputation for blazing speed (capable of handling millions of operations per second on modest hardware) combined with operational flexibility has made it the de facto choice for scenarios ranging from session management to real-time analytics to message brokering.
By the end of this page, you will understand Redis's core data structures and their algorithmic properties, master the nuances of Redis persistence mechanisms (RDB vs AOF) and their durability trade-offs, comprehend Redis Cluster architecture for horizontal scaling, and develop the expertise to make informed decisions about Redis deployment topologies for various use cases.
Before diving into specific features, it's essential to understand Redis's architectural philosophy. Redis achieves its remarkable performance through a set of deliberate design decisions that prioritize speed while maintaining correctness.
Single-Threaded Event Loop:
Contrary to what many engineers initially assume, Redis's core operations run in a single-threaded event loop. This design eliminates the overhead of context switching and locking that multi-threaded systems incur. Because there's only one thread processing commands, operations are naturally serialized—no locks are needed, and there's no risk of race conditions on data structures.
This doesn't mean Redis can't utilize multiple cores. Starting with Redis 6, I/O threading allows multiple threads to handle network I/O while the main thread processes commands. Background threads handle persistence operations, and Redis Cluster distributes load across multiple processes. But command execution remains single-threaded, which is why individual Redis operations are atomic.
| Component | Threading Model | Purpose | Performance Impact |
|---|---|---|---|
| Command Processing | Single-threaded | Atomicity, simplicity, no locks | Predictable latency, ~300K ops/sec per core |
| Network I/O (Redis 6+) | Multi-threaded (optional) | Handle high connection counts | 2-3x throughput improvement |
| Persistence (RDB) | Background fork | Point-in-time snapshots | Minimal impact on main thread |
| Persistence (AOF rewrite) | Background fork | Log compaction | Minimal impact on main thread |
| Lazy object freeing | Background thread | Avoid blocking on large deletes | Instant DEL commands |
Memory-First Architecture:
Every piece of data in Redis resides primarily in RAM. This is the source of Redis's sub-millisecond latency—there are no disk seeks involved in reads or writes. The data structures are implemented with memory-efficient representations that automatically upgrade as data grows:
This automatic encoding optimization means developers get performance benefits without manual tuning in most cases.
Since all data must fit in memory, Redis capacity planning is fundamentally different from disk-based databases. Your Redis dataset size is bounded by available RAM (minus overhead for the OS and Redis internals). For datasets larger than available memory, you must either use Redis Cluster to shard across machines or accept that some data will be evicted according to your configured eviction policy.
RESP Protocol (Redis Serialization Protocol):
Redis communicates using a simple, human-readable text protocol called RESP. Commands are sent as arrays of bulk strings, and responses can be strings, integers, arrays, or errors. This simplicity has enabled Redis client libraries in virtually every programming language.
*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n
Translates to: SET mykey myvalue
The RESP protocol's simplicity keeps parsing overhead minimal, contributing to Redis's low latency characteristics.
Redis's power comes from its rich collection of data structures, each providing specific operations with guaranteed time complexities. Understanding these structures—and when to use each—is fundamental to effective Redis usage.
Redis Strings are the simplest data type but far more versatile than they appear. A String can hold text, serialized objects, binary data (up to 512MB), or numeric values. Redis automatically handles numeric operations on String values:
SET key value — O(1)GET key — O(1)INCR key / DECR key — O(1) atomic counterAPPEND key value — O(1) amortizedGETRANGE key start end — O(N) where N is substring lengthCommon Use Cases:
SET session:abc123 "{user_id: 42, expires: ...}"INCR page:views:/homeSET ratelimit:user:42 1 EX 60 NX (set if not exists, 60-second expiry)SET lock:resource abc123 EX 30 NX123456789101112131415161718
# Basic string operationsSET user:1:name "Alice Johnson"GET user:1:name # Returns: "Alice Johnson" # Atomic counter - thread-safe without locksINCR article:123:views # Returns: 1INCR article:123:views # Returns: 2INCRBY article:123:views 100 # Returns: 102 # Set with expiration (session management)SET session:xyz789 "user_data..." EX 3600 # Expires in 1 hour # Conditional set (distributed locking)SET lock:order:5001 "worker-1" NX EX 30 # Only sets if key doesn't exist # Batch operations (reduces network round trips)MSET user:1:name "Alice" user:1:email "alice@example.com"MGET user:1:name user:1:emailRedis Lists are doubly-linked lists of strings, optimized for insertion and removal at both ends. They're perfect for queues, recent activity feeds, and bounded collections.
LPUSH key value / RPUSH key value — O(1) per elementLPOP key / RPOP key — O(1)LRANGE key start stop — O(S+N) where S is offset, N is elements returnedLLEN key — O(1)LINDEX key index — O(N) for middle elementsBLPOP key timeout — Blocking pop (for queue consumers)Internal Implementation:
Small lists use listpack (formerly ziplist)—a contiguous block of memory that's cache-friendly. Larger lists upgrade to quicklist—a doubly-linked list of listpacks, balancing memory efficiency with access speed.
12345678910111213141516
# Task queue patternRPUSH jobs:email '{"to":"user@example.com","subject":"Welcome"}'RPUSH jobs:email '{"to":"admin@example.com","subject":"New signup"}' # Worker consumes from queue (blocking)BLPOP jobs:email 30 # Blocks up to 30 seconds for item # Recent activity feed (capped at 100 entries)LPUSH activity:user:42 '{"action":"login","time":"2024-01-15T10:30:00Z"}'LTRIM activity:user:42 0 99 # Keep only 100 most recent # Get last 10 activitiesLRANGE activity:user:42 0 9 # Reliable queue with BRPOPLPUSH (atomic move between lists)BRPOPLPUSH jobs:pending jobs:processing 30 # Move job to processing listRedis Sets are unordered collections of unique strings. They support fast membership testing and powerful set operations (union, intersection, difference).
SADD key member — O(1)SISMEMBER key member — O(1)SMEMBERS key — O(N)SINTER key1 key2 — O(N*M) where N is smallest set cardinalitySUNION key1 key2 — O(N) sum of all elementsSCARD key — O(1)Use Cases:
SADD article:123:tags "redis" "caching" "database"SADD user:42:friends 101 102 103SINTER user:42:friends user:101:friendsSADD visitors:2024-01-15 "ip:1.2.3.4"Sorted Sets are perhaps Redis's most powerful data structure. Each member has an associated score, and the set is automatically sorted by score. This enables leaderboards, time-series windows, rate limiters, and priority queues.
ZADD key score member — O(log N)ZRANK key member — O(log N)ZRANGE key start stop [WITHSCORES] — O(log N + M)ZRANGEBYSCORE key min max — O(log N + M)ZINCRBY key increment member — O(log N)ZCARD key — O(1)Internal Implementation:
Sorted Sets use a skip list for ordered iteration and a hash table for O(1) score lookups by member. This dual-structure design provides the best of both worlds at the cost of additional memory.
12345678910111213141516171819202122
# Leaderboard systemZADD game:leaderboard 1500 "player:alice"ZADD game:leaderboard 2200 "player:bob"ZADD game:leaderboard 1800 "player:charlie" # Get top 10 playersZREVRANGE game:leaderboard 0 9 WITHSCORES # Get player rank (0-indexed)ZREVRANK game:leaderboard "player:bob" # Returns: 0 (top position) # Increment score atomicallyZINCRBY game:leaderboard 500 "player:alice" # Now 2000 # Sliding window rate limiter (timestamp as score)ZADD ratelimit:user:42 1705300000000 "request:abc"ZREMRANGEBYSCORE ratelimit:user:42 0 1705296400000 # Remove old entriesZCARD ratelimit:user:42 # Count in window # Priority queueZADD jobs:priority 1 "urgent-job" 5 "normal-job" 10 "low-priority-job"ZPOPMIN jobs:priority # Get highest priority (lowest score)Redis Hashes are maps between string fields and string values—ideal for representing objects. They're more memory-efficient than storing JSON strings for objects with many fields.
HSET key field value — O(1)HGET key field — O(1)HMSET key field1 value1 field2 value2 — O(N)HGETALL key — O(N)HINCRBY key field increment — O(1)HDEL key field — O(1)Memory Efficiency:
Small hashes (below hash-max-listpack-entries and hash-max-listpack-value) use listpack encoding, which is extremely memory efficient. A user object with 10 fields uses far less memory as a Hash than as a serialized JSON String.
1234567891011121314151617181920
# User profile as hashHSET user:42 name "Alice Johnson" email "alice@example.com" signup_date "2024-01-15"HSET user:42 login_count 0 last_active "2024-01-15T10:30:00Z" # Get specific fields (more efficient than HGETALL)HMGET user:42 name email # Increment counter fieldHINCRBY user:42 login_count 1 # Get all fieldsHGETALL user:42 # Check field existenceHEXISTS user:42 premium_tier # Returns 0 (doesn't exist) # Shopping cart implementationHINCRBY cart:session:xyz product:123 2 # Add 2 of product 123HINCRBY cart:session:xyz product:456 1 # Add 1 of product 456HDEL cart:session:xyz product:123 # Remove product• Strings: Simple values, counters, serialized blobs, locks • Lists: Queues, stacks, recent items, activity feeds • Sets: Unique collections, tags, membership testing, set operations • Sorted Sets: Leaderboards, priority queues, time-series, range queries • Hashes: Objects with multiple fields, counters per entity
Beyond the core structures, Redis provides specialized types for specific use cases that would otherwise require complex combinations of basic types.
Introduced in Redis 5.0, Streams provide an append-only log structure similar to Kafka. They support consumer groups for distributing work across multiple consumers, making them ideal for event sourcing, activity feeds, and lightweight message queuing.
Key Operations:
XADD stream-key * field value — Append entry, auto-generate IDXREAD STREAMS stream-key ID — Read entries after IDXREADGROUP GROUP group consumer STREAMS stream-key > — Consumer group readXACK stream-key group id — Acknowledge message processingXPENDING stream-key group — List pending (unacknowledged) messages12345678910111213141516
# Event stream for order processingXADD orders:events * event "order_created" order_id "5001" customer_id "42"XADD orders:events * event "payment_received" order_id "5001" amount "99.99"XADD orders:events * event "order_shipped" order_id "5001" tracking "TRK123" # Create consumer group starting from beginningXGROUP CREATE orders:events fulfillment-workers $ MKSTREAM # Worker reads from group (blocking)XREADGROUP GROUP fulfillment-workers worker-1 COUNT 10 BLOCK 5000 STREAMS orders:events > # Acknowledge processed messageXACK orders:events fulfillment-workers 1705300000000-0 # View pending messages (not yet acknowledged)XPENDING orders:events fulfillment-workersHyperLogLog (HLL) is a probabilistic data structure that estimates the cardinality (unique count) of large sets using minimal memory. A single HLL uses only 12KB regardless of how many elements are added, with a standard error of 0.81%.
PFADD key element — Add element(s)PFCOUNT key — Estimate unique countPFMERGE destkey sourcekey1 sourcekey2 — Merge HLLsUse Cases:
12345678910
# Track unique visitors by dayPFADD visitors:2024-01-15 "user:1" "user:2" "user:3" "user:1" # user:1 counted oncePFCOUNT visitors:2024-01-15 # Returns: 3 # Merge for weekly countPFMERGE visitors:week:3 visitors:2024-01-15 visitors:2024-01-16 visitors:2024-01-17PFCOUNT visitors:week:3 # Compare: exact count would require O(N) memory# HyperLogLog uses fixed 12KB for any N (billions of elements)Bitmaps treat strings as bit arrays, enabling extremely memory-efficient storage of boolean values. One million boolean values require only 125KB.
SETBIT key offset value — Set bit at positionGETBIT key offset — Get bit at positionBITCOUNT key [start end] — Count set bitsBITOP AND destkey key1 key2 — Bitwise operationsUse Cases:
Redis Geo commands store longitude/latitude pairs and enable proximity queries. Internally, this uses Sorted Sets with geohash encoding.
GEOADD key longitude latitude memberGEODIST key member1 member2 [km|m|mi]GEORADIUS key longitude latitude radius unitGEOSEARCH key FROMMEMBER member BYRADIUS radius unit1234567891011
# Store restaurant locationsGEOADD restaurants -122.419 37.775 "restaurant:pizza-palace"GEOADD restaurants -122.421 37.773 "restaurant:burger-barn"GEOADD restaurants -122.417 37.777 "restaurant:taco-town" # Find restaurants within 1km of coordinatesGEORADIUS restaurants -122.420 37.774 1 km WITHDIST# Returns: pizza-palace (0.15km), burger-barn (0.14km), taco-town (0.38km) # Distance between two placesGEODIST restaurants "restaurant:pizza-palace" "restaurant:taco-town" kmRedis's in-memory nature raises an obvious concern: what happens when Redis restarts? Without persistence, all data is lost. Redis provides two persistence mechanisms—RDB and AOF—each with distinct trade-offs.
RDB persistence creates point-in-time snapshots of your dataset at specified intervals. The result is a compact, single-file representation of all data that can be used for backups, disaster recovery, or faster restart times.
How RDB Works:
The Fork Operation:
The fork() system call creates a child process with a copy of the parent's memory. Modern operating systems use copy-on-write, meaning pages are only physically copied when modified. This allows the snapshot to capture a consistent view while the parent continues processing writes.
| Configuration | Default | Description |
|---|---|---|
| save 900 1 | Enabled | Save after 900 seconds if at least 1 key changed |
| save 300 10 | Enabled | Save after 300 seconds if at least 10 keys changed |
| save 60 10000 | Enabled | Save after 60 seconds if at least 10000 keys changed |
| stop-writes-on-bgsave-error | yes | Stop accepting writes if background save fails |
| rdbcompression | yes | Use LZF compression for strings in RDB |
| rdbchecksum | yes | Add CRC64 checksum for corruption detection |
1234567891011121314151617181920212223
# RDB snapshot configuration# Format: save <seconds> <changes>save 900 1 # Save after 15 minutes if at least 1 changesave 300 10 # Save after 5 minutes if at least 10 changessave 60 10000 # Save after 1 minute if at least 10000 changes # Disable RDB entirely (not recommended for production)# save "" # RDB file locationdir /var/lib/redisdbfilename dump.rdb # Enable compression (recommended)rdbcompression yes # Enable checksum verificationrdbchecksum yes # Manual snapshot command# BGSAVE - Async background save# SAVE - Sync save (blocks server - avoid in production)# LASTSAVE - Timestamp of last successful saveOn a Redis instance with 24GB of data, the fork() operation can take 200-500ms, during which Redis blocks all client requests. For datasets approaching 50GB+, fork latency can exceed 1 second. This is particularly problematic on VMs with overcommitted memory. Monitor 'latest_fork_usec' in INFO output to track fork performance.
The Append-Only File (AOF) provides durability guarantees closer to traditional databases. Instead of point-in-time snapshots, AOF logs every write operation, enabling recovery by replaying the command log.
Fsync Policies:
The critical configuration is how often Redis forces the OS to flush the AOF buffer to disk. This determines your durability guarantee:
always — Fsync after every command. Maximum durability, slowest performance.everysec — Fsync once per second. Balanced approach, recommended.no — Let the OS decide when to flush. Fastest, but up to 30+ seconds of data loss possible.| Policy | Data Loss Risk | Performance | Recommended For |
|---|---|---|---|
| always | None (one command) | Slowest (10x slower) | Financial transactions, audit logs |
| everysec | ~1 second | Good (slight overhead) | Most production workloads (default) |
| no | Up to 30+ seconds | Fastest | Ephemeral cache, acceptable loss |
1234567891011121314151617181920212223242526
# Enable AOFappendonly yes # AOF file nameappendfilename "appendonly.aof" # Fsync policy# always: every command - maximum durability, slowest# everysec: once per second - recommended balance# no: OS decides - fastest, least durableappendfsync everysec # Rewrite threshold# Rewrite when AOF is 100% larger than after last rewriteauto-aof-rewrite-percentage 100# Minimum size before rewrite triggersauto-aof-rewrite-min-size 64mb # Disable fsync during rewrites (may lose data on crash during rewrite)no-appendfsync-on-rewrite no # Handle truncated AOF on startupaof-load-truncated yes # Enable hybrid RDB+AOF format for faster loading (Redis 4.0+)aof-use-rdb-preamble yesAs commands accumulate, the AOF file grows unboundedly. If you increment a counter 1 million times, the AOF contains 1 million INCR commands. AOF rewrite compacts this by generating the minimum set of commands to recreate the current state.
Rewrite Mechanism:
The result: 1 million INCRs become a single SET counter 1000000.
Redis 4.0 introduced hybrid persistence, combining the advantages of both approaches. When enabled via aof-use-rdb-preamble yes, the AOF rewrite produces a file that starts with an RDB snapshot followed by AOF commands that accumulated during the rewrite.
Benefits:
This is the recommended configuration for production Redis deployments where durability matters.
For most production deployments, enable both RDB and AOF with hybrid persistence:
everysec fsync for durabilityThis provides sub-second recovery from crashes and efficient point-in-time backups.
Redis replication enables horizontal read scaling and high availability through a leader-follower (master-replica) model. Replicas maintain copies of the master's data and can serve read requests, distributing load across multiple instances.
Asynchronous by Default:
Redis replication is asynchronous—the master doesn't wait for replicas to acknowledge writes before responding to clients. This prioritizes performance but means replicas may lag behind the master.
Replication Flow:
PSYNC command1234567891011121314151617181920212223
# On replica instancesreplicaof 192.168.1.100 6379 # Master IP and port # Authentication (if master requires password)masterauth your-strong-password # Make replica read-only (recommended)replica-read-only yes # Serve stale data during sync or disconnectionreplica-serve-stale-data yes # Disk-less replication (faster for slow disks)repl-diskless-sync yesrepl-diskless-sync-delay 5 # Wait 5 seconds for more replicas # Replication backlog for partial resyncrepl-backlog-size 64mbrepl-backlog-ttl 3600 # Minimum replicas for writes (for durability)min-replicas-to-write 1min-replicas-max-lag 10When a replica briefly disconnects (network blip, restart), it doesn't need a full sync. Redis maintains a replication backlog—a circular buffer of recent write commands on the master. If the replica's replication offset is within the backlog, only the missed commands are sent.
Replication IDs:
Each Redis instance has a unique replication ID. When a failover promotes a replica to master, the replication ID changes. Replicas check this ID to determine if partial sync is possible or if full sync is required.
Replicas can serve read requests, enabling read scaling. However, this introduces consistency considerations:
For use cases where this is acceptable (displaying cached data, analytics queries), replica reads dramatically increase read throughput.
Monitor replica lag using 'INFO replication' (check 'lag' field for each replica). High lag indicates the replica can't keep up with write volume. Solutions: reduce write rate, use faster network between master and replica, or upgrade replica hardware. Lag greater than your replication backlog size will force a full resync on any disconnect.
Redis Sentinel provides automatic failover for Redis replication setups. Without Sentinel, a master failure requires manual intervention to promote a replica. Sentinel monitors Redis instances, detects failures, and orchestrates failover automatically.
A Sentinel deployment consists of:
Sentinel Responsibilities:
Subjective Down (SDOWN): A single Sentinel marks the master as "subjectively down" if it doesn't respond to PING within the configured timeout.
Objective Down (ODOWN): Once a Sentinel declares SDOWN, it queries other Sentinels. If a quorum (configurable, usually majority) agrees the master is down, it's marked "objectively down."
Leader Election: Sentinels elect a leader to perform the failover using a Raft-like consensus algorithm.
Failover Execution:
REPLICAOF NO ONE)12345678910111213141516171819202122
# Sentinel configurationport 26379 # Monitor a master named "mymaster" at given address# Quorum of 2 means 2 Sentinels must agree master is downsentinel monitor mymaster 192.168.1.100 6379 2 # How long to wait before considering master down (ms)sentinel down-after-milliseconds mymaster 5000 # How many replicas can sync from new master simultaneously during failoversentinel parallel-syncs mymaster 1 # Failover timeout (ms) - time to complete failoversentinel failover-timeout mymaster 60000 # Authenticationsentinel auth-pass mymaster your-master-password # Scripts to run on eventssentinel notification-script mymaster /var/lib/redis/notify.shsentinel client-reconfig-script mymaster /var/lib/redis/reconfig.shRedis Cluster provides automatic sharding across multiple Redis nodes, enabling datasets that exceed single-machine memory limits. Unlike Sentinel (which provides HA for a single dataset), Cluster partitions data across multiple masters, each responsible for a subset of the keyspace.
Redis Cluster divides the keyspace into 16,384 hash slots. Each key is assigned to a slot using:
slot = CRC16(key) mod 16384
Each master in the cluster is responsible for a range of slots. For a 3-master cluster:
Key Hashing and Hash Tags:
By default, the entire key determines the slot. Hash tags allow controlling which portion of the key is hashed, ensuring related keys land on the same slot:
user:{1234}:profile → hash only "{1234}"
user:{1234}:sessions → hash only "{1234}" → same slot!
This enables multi-key operations (MGET, transactions) on related keys.
Cluster clients must be cluster-aware. They maintain a mapping of slots to nodes and route commands accordingly.
MOVED Redirection:
When a client sends a command to the wrong node:
> GET user:5000
-MOVED 5846 192.168.1.102:6379
The client should update its slot mapping and retry at the correct node.
ASK Redirection:
During slot migration, some keys may temporarily be on the target node:
> GET migrating-key
-ASK 5846 192.168.1.102:6379
The client sends ASKING followed by the command to the target node.
Smart Clients:
Production Redis clients (Jedis, lettuce, redis-py, ioredis) handle redirections automatically, cache slot mappings, and refresh mappings on topology changes.
123456789101112131415161718192021222324
# Create cluster with 3 masters and 3 replicas (minimum recommended)redis-cli --cluster create \ 192.168.1.101:6379 \ 192.168.1.102:6379 \ 192.168.1.103:6379 \ 192.168.1.104:6379 \ 192.168.1.105:6379 \ 192.168.1.106:6379 \ --cluster-replicas 1 # Check cluster statusredis-cli -c -h 192.168.1.101 -p 6379 CLUSTER INFO # View slot distributionredis-cli -c -h 192.168.1.101 -p 6379 CLUSTER SLOTS # Add a new noderedis-cli --cluster add-node 192.168.1.107:6379 192.168.1.101:6379 # Reshard slots to new noderedis-cli --cluster reshard 192.168.1.101:6379 # Remove a node (must be empty first)redis-cli --cluster del-node 192.168.1.101:6379 <node-id>Each master in a Redis Cluster can have replicas. When a master fails:
Failover Timing:
cluster-node-timeout: How long before a node is considered failing (default: 15s)cluster-node-timeout + election time| Limitation | Description | Mitigation |
|---|---|---|
| Multi-key operations | Commands spanning multiple slots fail | Use hash tags to co-locate related keys |
| Transactions | MULTI/EXEC only work within single slot | Design data model around hash tags |
| Lua scripts | All keys must be on same node | Pass all keys via KEYS, use hash tags |
| Database selection | Only database 0 is available | Use key prefixes for logical separation |
| Large key values | Max 512MB per key (as always) | Split into multiple keys if needed |
• Minimum production: 6 nodes (3 masters + 3 replicas) • Optimal slot distribution: Roughly equal slots per master • Cross-zone replicas: Place replicas in different availability zones • Memory planning: Account for ~10-15% overhead beyond data size • Network: Use dedicated low-latency network for cluster bus traffic
Redis's combination of speed, versatility, and operational flexibility has made it indispensable in modern distributed systems. Let's consolidate the key takeaways:
What's Next:
In the next page, we'll examine Memcached—a simpler, high-performance caching solution that excels in specific scenarios. Understanding both Redis and Memcached's strengths will enable you to make informed technology selections based on your system's requirements.
You now have a comprehensive understanding of Redis's architecture, data structures, persistence mechanisms, replication, and clustering. This knowledge forms the foundation for designing high-performance caching layers in distributed systems.