Redis Single-Threaded, Explained: The Event Loop Under the Hood
Why Redis commits to a single thread, how the I/O model really works, and where multithreading slips in.
Redis is famously "single-threaded." That phrase oversells the simplicity and undersells the engineering. The command-execution loop is single-threaded — and that is the reason INCR is atomic without a mutex — but several other things are not.
The core loop
A single thread multiplexes thousands of client sockets with epoll/kqueue. When a command is ready, it is executed in a tight loop, end to end, before the next command is serviced. That's why every single Redis command is atomic: nothing can interleave.
while (!server.shutdown_asap) {
aeProcessEvents(el, AE_ALL_EVENTS); // readable fds, writable fds, timers
}
What is not single-threaded
- Persistence.
BGSAVEforks;BGREWRITEAOFforks. The copy-on-write semantics mean memory briefly doubles for hot pages. - I/O threads (since 6.0).
io-threads 4offloads socket reads/writes. Command execution is still single-threaded. - Lazy freeing.
UNLINKandFLUSHDB ASYNChand memory reclamation to a background thread.
Consequences for your code
A slow command blocks everyone. KEYS * on a million-key database doesn't just take a second — it makes every other client wait a second. Replace it with SCAN:
redis-cli --scan --pattern 'user:*' | head
Same rule applies to SUNIONSTORE over huge sets, DEBUG SLEEP, and very large MGET batches. The slowlog is your friend:
CONFIG SET slowlog-log-slower-than 5000
SLOWLOG GET 10
When to add threads
Enable I/O threads once you are CPU-bound on network serialization (usually above ~100k ops/sec on a single node). Past that, shard — Cluster gives you linear scaling of the command loop itself.