Redis

Redis is an in-memory data store. Calling it a cache undersells it: the values are data structures — strings, hashes, lists, sets, sorted sets, streams — with commands that operate on them atomically on the server. That is why it shows up as a cache, a session store, a rate limiter, a leaderboard, a queue and a lock service, sometimes all in one system.

Its speed comes from two choices: everything lives in memory, and commands execute on a single thread, one at a time.

Licensing. Redis moved away from an open-source licence in 2024, which led to the Valkey fork under the Linux Foundation; Redis later added an AGPL option. Valkey is protocol-compatible and is what several cloud providers now offer by default. Everything here applies to both.

Data types

TypeThink of it asTypical useKey commands
StringBytes, or a numberCached objects, counters, flagsGET SET INCR SETEX
HashA small map of fieldsAn object whose fields are updated separatelyHSET HGET HINCRBY
ListA linked listSimple queues, recent-items listsLPUSH RPOP BLPOP LTRIM
SetUnique, unordered membersTags, unique visitors, membership testsSADD SISMEMBER SINTER
Sorted setMembers ordered by a scoreLeaderboards, priority queues, sliding-window rate limitsZADD ZRANGE ZRANGEBYSCORE
StreamAn append-only log with consumer groupsEvent queues with acknowledgementXADD XREADGROUP XACK
Bitmap / HyperLogLogBit arrays; probabilistic countersFeature flags per user; approximate distinct counts in 12 kBSETBIT PFADD PFCOUNT
GeospatialPoints on a sphere”Nearby” queriesGEOADD GEOSEARCH
Pub/SubFire-and-forget broadcastLive notifications; no persistence, no delivery guaranteePUBLISH SUBSCRIBE

Choosing the right type usually removes application code. A leaderboard is one ZADD per score and one ZREVRANGE per page, not a query and a sort.

The single-threaded model

Commands are executed sequentially by one thread (network I/O may use additional threads). Consequences:

  • Every command is atomic. INCR needs no lock.
  • One slow command blocks everyone. KEYS *, SMEMBERS on a set with millions of members, or a large DEL stalls every client for its duration. Use SCAN to iterate and UNLINK to delete asynchronously.
  • Throughput scales by sharding, not by adding cores to one instance.

For multi-step atomic operations there are two tools: MULTI/EXEC transactions, which queue commands and run them without interleaving (there is no rollback), and Lua scripts or functions, which run entirely on the server as one unit.

Pipelining — sending many commands without waiting for each reply — removes network round trips and is the simplest large performance win.

Persistence

In-memory does not have to mean volatile.

ModeHowData lost on crashNotes
NoneNothing writtenEverythingFine for a pure cache
RDB snapshotsA point-in-time dump, produced by a forked childEverything since the last snapshotCompact, fast restart
AOFEvery write appended to a log, periodically rewrittenUp to one second with appendfsync everysecLarger files; always is durable but slow
BothAOF for durability, RDB for backupsUp to one secondThe usual choice when the data matters

Snapshots use fork() and copy-on-write. During a snapshot under heavy writes, memory use can approach double. Size instances accordingly and make sure the host’s overcommit settings permit the fork — see sysctl.

Even with AOF, treat Redis as holding data you could rebuild, unless you have designed carefully for the alternative. It is not a substitute for a transactional system of record.

Memory and eviction

Set maxmemory explicitly. Without it, Redis grows until the operating system kills it. When the limit is reached, the eviction policy decides:

PolicyBehaviourUse for
noevictionWrites fail with an errorData that must not disappear silently
allkeys-lru / allkeys-lfuEvict the least recently / least frequently used keyA cache
volatile-lru / volatile-ttlEvict only among keys that have an expiryMixed cache and persistent data

Mixing cache data and must-keep data in one instance is a common mistake: under memory pressure something gives. Use separate instances with different policies.

Always set a TTL on cache keys. Expired keys are removed lazily on access and by periodic sampling, so memory is not freed the instant a key expires.

Caching patterns

PatternHowTrade-off
Cache-asideRead cache; on a miss read the database and populate the cacheSimple and resilient; first read is slow; staleness until TTL or invalidation
Write-throughWrite to cache and database togetherCache always fresh; slower writes; caches data nobody reads
Write-behindWrite to cache, flush to the database asynchronouslyFast writes; data loss if the cache dies first

Three failure modes to design for:

ProblemWhat happensMitigation
Cache stampedeA hot key expires; hundreds of requests recompute it at onceA per-key lock or request coalescing; refresh before expiry
Cache avalancheMany keys expire together, or the cache restarts emptyJitter on TTLs; warm-up; the database must survive a cold cache
Cache penetrationRepeated lookups for keys that do not exist always hit the databaseCache negative results briefly; a Bloom filter

The last point in the avalanche row is the important one: if the system cannot function without the cache, the cache is not a cache, it is a critical dependency. More in caching.

Other common uses

Rate limiting. A fixed window is INCR plus EXPIRE; a sliding window uses a sorted set of timestamps; a token bucket is a short Lua script. See rate limiting.

Distributed locks. SET lock:order:42 <random-token> NX PX 30000 acquires a lock with an expiry; release it with a script that deletes only if the token matches. This is adequate for efficiency (avoiding duplicate work). It is not sufficient for correctness: a paused process can outlive its lock and act anyway. When correctness matters, add a fencing token checked by the resource, or use a consensus system — cluster management.

Queues. Lists with BLPOP are simple but lose a job if the worker dies after popping it. Streams with consumer groups add acknowledgement, pending-entry tracking and redelivery. For durable, replayable event logs across many consumers, use Kafka.

Sessions. A hash per session with a TTL, which keeps application servers stateless.

Replication and high availability

TopologyWhat it givesLimits
ReplicationOne primary, asynchronous replicas for reads and redundancyNo automatic failover by itself
SentinelMonitoring processes that detect primary failure and promote a replicaStill one primary for writes; clients must be Sentinel-aware
ClusterData sharded over 16,384 hash slots across several primaries, each with replicas, with failoverMulti-key operations must stay within one slot

Replication is asynchronous: a write acknowledged by the primary can be lost if it fails before the replica receives it. WAIT reduces the window; it does not make Redis strongly consistent.

In Cluster mode, keys used together must hash to the same slot. A hash tag forces that: {user:42}:cart and {user:42}:profile share a slot because only the part in braces is hashed.

Managed options — ElastiCache and MemoryDB, Azure Cache for Redis, Google Memorystore — handle failover, patching and scaling. MemoryDB adds a durable transaction log for use as a primary database.

Operational pitfalls

PitfallWhy it hurts
KEYS, FLUSHALL, unbounded LRANGE in productionBlocks the single thread
Big keys (a hash or list with millions of entries)Slow to read, serialise, replicate and delete
Hot keysOne shard saturated while others idle
No maxmemoryThe OOM killer chooses for you
No authentication, bound to a public addressA frequent source of data breaches and cryptomining compromises
A new connection per requestConnection set-up dominates latency — use a pool
Treating Pub/Sub as reliableMessages to disconnected subscribers are simply dropped

Diagnose with SLOWLOG GET, LATENCY DOCTOR, INFO memory, redis-cli --bigkeys and --hotkeys. Watch memory fragmentation, evictions, replication lag and connected clients. Secure it with TLS, ACL users, and network isolation in private subnets behind security groups.

Across the wiki