Why Is Redis So Fast? Six Design Decisions Explained
A Redis GET can be constant-time and still miss its latency target when a large Lua script is ahead of it, the client opens a new TLS connection, or the value takes longer to deserialize than to fetch. “Redis is fast” is only useful when you can name which work its design removes—and which work remains in your request.
This article owns the mechanism question: memory-oriented storage, serialized command execution, native data structures, command complexity, event-driven I/O, and compact encodings. For cache layers and eviction start with Caching 101; for application patterns use Caching Strategies.
1. In-Memory Storage
Redis serves its active dataset from RAM, so a command does not perform a storage lookup to locate the value. The request still pays for client work, a network hop, queueing, command execution, reply transfer, and application deserialization.
RAM is finite, so capacity requires an eviction policy, more nodes, or a product-specific tiering option. RDB snapshots and AOF add durability choices, but persistence is not free: forks, copy-on-write memory, filesystem behavior, and AOF fsync policy can affect latency. Measure with the durability configuration you plan to run.
A disk-backed database may also answer from its buffer pool, so “RAM versus disk” is not a complete benchmark. Redis earns its role when a memory-oriented structure and command API remove enough origin work to justify another stateful service.
Quick reference
- Benchmark end to end: client pool, network, command, payload, persistence settings, and application parsing.
- Working set must fit in memory (or use Redis Enterprise / tiered options for overflow).
- RDB: point-in-time snapshots; AOF: replay log — both optional durability trade-offs.
- Use case fit: hot keys, sessions, rate limits, leaderboards — not your only copy of critical data without backup.
- Measure memory with representative keys; object metadata, allocator behavior, encoding, and fragmentation vary.
- Pair with PostgreSQL/MySQL: Redis as cache or ephemeral store, SQL as source of truth.
Remember this
In-memory is Redis's largest speed gain — and its main capacity constraint. Size the working set deliberately.
2. Single-Threaded Command Execution
Redis serializes most command execution on the main thread. One command mutates a data structure before the next command runs, which avoids locks between command workers and makes individual command behavior easier to reason about.
Modern Redis can use threads for selected I/O and background work, so “Redis is single-threaded” is shorthand, not a description of every task in the process. The important boundary is that a costly command on the main execution path delays unrelated commands queued behind it.
That trade-off makes command complexity operationally visible. An unbounded Lua script, KEYS *, or a huge collection response can create tail-latency spikes even while ordinary commands are efficient. Prefer bounded work, incremental scans, and workload isolation.
Quick reference
- No lock contention on internal structures — simplicity and predictable latency.
- I/O threads (optional): parallel network reads/writes; execution still single-threaded.
- Blocking commands: KEYS, FLUSHALL, heavy SORT — avoid in production.
- Use SCAN, SSCAN, HSCAN for iteration instead of blocking full-key scans.
- CPU scaling: shard across Redis nodes when measured command execution saturates a node and the key model permits it.
- Compare concurrency models by measured queueing and workload, not by thread count alone.
Remember this
Single-threaded execution trades parallel CPU on one node for zero lock overhead — keep commands fast and non-blocking.
3. Optimized Native Data Structures
Redis is not a dumb string blob store. Each type — string, list, hash, set, sorted set — has a C implementation tuned for its access pattern, and the engine picks internal encodings based on size and content.
Strings use SDS (Simple Dynamic String) — O(1) length, binary-safe, less reallocation than C strings. Lists use quicklist (linked list of ziplists/listpack nodes) for efficient head/tail ops. Hashes and sets start compact (listpack) and upgrade to hash tables when they grow. Sorted sets combine a hash table (member → score) with a skip list for rank/range queries.
You choose the logical type in your app; Redis chooses the physical encoding. That is why HGET on a small hash and ZRANGE on a leaderboard stay fast without you managing indexes manually.
Quick reference
- SDS: constant-time STRLEN, append-friendly buffer growth.
- QuickList: doubly linked list of compact nodes — LPUSH/RPOP in O(1).
- Listpack / hash table dual encoding for hashes and sets — compact when small.
- Sorted set: skip list + dict — O(log N) rank/range, O(1) score lookup by member.
- OBJECT ENCODING key — inspect which internal encoding a key uses (debugging).
- Match command to type: counters on strings, fields on hashes, ranks on sorted sets.
Remember this
Redis speed comes from specialized structures — SDS, quicklist, skip lists — not from storing JSON strings for everything.
4. Mostly O(1) Commands
Many Redis commands have constant or logarithmic algorithmic complexity relative to the relevant collection. GET, SET, HGET, HSET, SADD, SISMEMBER, and INCR are documented as O(1) average case, while sorted-set operations often include a logarithmic lookup plus returned-result work.
Big-O does not set a latency guarantee. Payload size, hash-table resizing, allocator pressure, persistence, network queueing, and commands ahead in the event loop still matter. Complexity tells you how work grows; a representative benchmark tells you whether the complete request meets its budget.
Quick reference
- O(1): GET, SET, INCR, HGET, HSET, LPUSH, LPOP, SADD, SISMEMBER, SCARD.
- O(log N): ZADD, ZRANGE, ZRANK — skip list depth grows slowly.
- Avoid O(N): KEYS, FLUSHDB without understanding, SMEMBERS on massive sets.
- Pipelining: batch many commands in one round trip without waiting per command.
- MGET/MSET: amortize network latency across multiple keys.
- Design keys so hot paths use O(1) ops — not full scans.
Remember this
Redis commands are designed for constant-time hot paths — choose the right type and avoid full-key scans.
5. Event-Driven I/O Multiplexing
Redis uses an event-driven architecture with platform I/O-multiplexing facilities such as epoll on Linux and kqueue on BSD-derived systems. The server can watch many sockets without assigning one command-execution thread to each connection.
Trace a GET: the event loop observes readable socket data, parses RESP, queues and executes the command, buffers the reply, and writes when the socket is ready. Waiting clients do not require command threads that sit blocked, but every ready command still competes for execution time and output bandwidth.
Connection capacity is environment-dependent. File-descriptor limits, client buffers, TLS, payload sizes, slow consumers, and command mix can become the bottleneck before the advertised connection setting does.
Quick reference
- Multiplexing APIs: epoll (Linux), kqueue (macOS/BSD), select/poll fallbacks.
- RESP protocol: simple text/binary framing — cheap to parse.
- Connection pooling on client side still recommended — reduces handshake overhead.
- TLS adds CPU and handshake work; benchmark it while preserving the required end-to-end security boundary.
- Large replies and slow consumers consume output-buffer memory; set and monitor appropriate client limits.
- Contrast with thread-per-request servers at high connection counts.
Remember this
The event loop plus epoll/kqueue lets one process serve many idle connections without thread explosion.
6. Memory-Efficient Encodings
Redis does not allocate a full hash table for a hash with two fields. Small collections use compact encodings (listpack, intset for integer-only sets) and upgrade in place as data grows — similar to dynamic array resizing in application code.
Integers stored as strings may be encoded as raw int64 when parsable. Short strings embed metadata efficiently via SDS. The result: lower RAM per key, higher effective cache density, fewer evictions under maxmemory pressure.
When you benchmark Redis vs "we cached JSON in another store," part of the gap is bytes per key — not just microseconds per op.
Quick reference
- intset: ordered integer arrays for sets when all members are numbers and count is small.
- listpack: contiguous encoding for small hashes, lists, and zset entries.
- Encoding upgrades are automatic — monitor memory when keys grow past thresholds.
- MEMORY USAGE key — inspect per-key footprint in Redis 4+.
- Prefer hashes over many string keys when storing object fields — often more compact.
- Choose maxmemory-policy from the key mix; allkeys-lru is one option for a cache where every key may be evicted.
Remember this
Compact encodings for small data mean more keys fit in RAM — speed and density compound.
Fast by Design — Not One Trick
Redis performance comes from stacked choices: memory-oriented serving avoids storage lookup on the command path, serialized execution avoids command-worker locking, native types expose bounded operations, multiplexing handles socket readiness, and compact encodings increase useful data per byte. None removes network, queueing, persistence, or application work.
Use a concrete decision flow. If the request is slow before Redis, split client wait, network, server queue, command execution, reply transfer, and deserialization. A high server-side command time points to command shape or saturation; low command time with high client time points elsewhere. If the source data must be durable and relationally consistent, Redis may remain a cache rather than the source of truth.
Quick reference
- Not a replacement for PostgreSQL as primary transactional store.
- Cluster/sharding for horizontal scale when one node's RAM or CPU caps out.
- Monitor: latency, memory usage, evicted keys, blocked clients, slowlog.
- Related: cache-aside patterns in production — see caching strategies article.
- Alternatives: Memcached (simpler, strings only), KeyDB (multi-thread fork), Dragonfly (modern reimplementation).
- Use Redis where its six pillars match your access pattern — not everywhere.
- Fallback decision: if Redis is unavailable, either read the durable source under bounded load or return a deliberate error; never create an uncontrolled retry storm.
Remember this
Redis wins on stacked design decisions — in-memory, single-thread, native types, O(1) ops, event I/O, compact encodings — not a single hack.
Key takeaway
Redis removes specific work through memory-oriented serving, serialized command execution, specialized structures, bounded command complexity, event-driven I/O, and compact encodings. Those mechanisms explain the opportunity; your command mix, payloads, durability settings, network, and client code determine the result.
Practice (20 min): Run redis-benchmark or a small client test with your value size and TLS/persistence settings; record latency for one GET and a pipelined batch plus SLOWLOG and memory usage. Intentionally add one expensive but bounded command and simulate the cache-down path. Recover by bounding or replacing the command and exercising the application's source/error fallback. Pass when you can identify the dominant latency stage and justify whether the next change belongs in the command, client, node capacity, or architecture.
Related Articles
Explore this topic