How Redis Works in Single Thread
Redis is an in-memory data structure store used as a database, cache, and message broker. Despite handling hundreds of thousands of operations per second, its command execution core is single-threaded. This document explains how that is possible and why it works well.
The Single-Threaded Model
Redis executes all commands on a single main thread using an event loop. There is no command-level locking, no context switching between commands, and no race conditions between clients.
This works because Redis is I/O bound, not CPU bound. The bottleneck for most workloads is network I/O, not computation. A single thread backed by an efficient event loop can saturate a network interface long before it saturates a CPU core.
Why single-threaded is fast
- No lock contention. Every command is atomic by construction.
- No context switching overhead between threads for command execution.
- Cache-friendly. All hot data structures live in one thread's CPU cache.
- Simple consistency model. Commands execute in the exact order they arrive.
The Event Loop
Redis uses a reactor pattern built on top of an OS-level multiplexing API — %%INLINE0%% on Linux, %%INLINE1%% on BSD/macOS, or %%INLINE2%% as a fallback. This is implemented in Redis's internal library called %%INLINE3%%
(A simple event library).The loop repeatedly does three things:
- Poll the kernel for ready file descriptors (sockets with data to read or buffers ready to write).
- Dispatch handlers for each ready event — read a command, execute it, queue the reply.
- Run time events — periodic tasks like key expiration sampling, rehashing, and stats.
epoll / kqueue} C --> D[Read Command] D --> E[Parse RESP] E --> F[Execute on
Main Thread] F --> G[In-Memory
Data Structures] G --> H[Write Reply] H --> B C -.periodic.-> I[Expiration
Rehashing
AOF flush]
Command Lifecycle
A single command from a client traverses these stages:
| Stage | Thread | Description |
|-------|--------|-------------|
| Accept connection | Main | New client registered with event loop |
| Read from socket | Main (or I/O thread) | Bytes pulled from kernel buffer |
| Parse RESP protocol | Main (or I/O thread) | Convert bytes into command + args |
| Lookup + execute | Main only | Dispatch to command handler, mutate data |
| Serialize reply | Main | Build RESP response in output buffer |
| Write to socket | Main (or I/O thread) | Push bytes to kernel buffer |
The critical invariant: command execution itself is always on the main thread. Everything else can be offloaded.
Where Threads Are Actually Used
Redis is "single-threaded for commands," but the process is not literally single-threaded. Background threads exist for work that would otherwise block the event loop:
- BIO threads (Background I/O) — handle %%INLINE4%% on AOF files and async %%INLINE5%% of large objects. Closing a 10 GB key with
UNLINKoffloads deallocation here so the main thread stays responsive. - I/O threads (since Redis 6) — optionally parallelize socket reads/writes and RESP parsing. Disabled by default. Configured via %%INLINE7%% in %%INLINE8%%. Even when enabled, command execution remains single-threaded.
- Module threads — loaded modules may spawn their own threads for heavy computation.
Memory and Data Structures
All data lives in RAM. Redis uses carefully optimized structures that switch encoding based on size:
- Strings — raw, embstr, or int encoding
- Lists — quicklist (a linked list of listpacks)
- Hashes / Sets / Sorted Sets — listpack for small sizes, hashtable or skiplist when they grow past thresholds
The main hash table (the keyspace) uses incremental rehashing. When the table needs to grow, Redis allocates a new table and migrates a few buckets on every event loop iteration, so rehashing never blocks for long.
Persistence Without Blocking
Redis offers two persistence modes, both designed to avoid stalling the main thread:
- RDB snapshots — Redis calls
fork(), and the child process writes the snapshot using copy-on-write memory. The main thread keeps serving requests in the parent. - AOF (Append-Only File) — every write command is appended to a log. Actual %%INLINE10%% is delegated to a BIO thread (with configurable policy: %%INLINE11%%, %%INLINE12%%, or %%INLINE13%%).
What Can Still Block the Main Thread
Because command execution is single-threaded, a slow command blocks every other client. Common culprits:
- %%INLINE14%% on a large keyspace — O(N) scan of all keys. Use %%INLINE15%% instead.
- %%INLINE16%% / %%INLINE17%% on huge collections.
- %%INLINE18%% on a massive aggregate key — use %%INLINE19%% to offload deallocation.
- Lua scripts or transactions (
MULTI/EXEC) that run too long. - Large
SORToperations.
The slowlog command surfaces these so they can be identified and fixed.
Summary
Redis's single-threaded design is a deliberate trade: it gives up multi-core parallelism for command execution in exchange for simplicity, atomicity, and predictable latency. For the workloads Redis targets — small operations on in-memory data with high network throughput — this is almost always the right call. Multi-core scaling is achieved by running multiple Redis instances (via Redis Cluster or sharding) rather than threading within a single instance.

Join the conversation