Redis Threading Model: Debunking the Single-Threaded Myth

Redis Threading Model: Debunking the Single-Threaded Myth

Source: Dev.to

The Confusion I Had ## Common Misconceptions I Had ## 1. "Redis is completely single-threaded" ## 2. "Redis can't use multiple CPU cores" ## 3. "Single-threaded means Redis is slow" ## 4. "Race conditions can't happen with Redis" ## 5. "BLPOP blocks the server" ## Why Keep Command Execution Single-Threaded? ## How Redis Actually Performs ## The Main Takeaway ## Further Reading Redis is single-threaded. Or is it? Let me share what I learned after getting confused by this. About 5 years ago, when I was building my EventStreamMonitor project, I started working with Redis for the first time. Everyone kept saying "Redis is single-threaded." But then I'd read about Redis 6.0 adding I/O threading, and I'd see mentions of background threads. I was confused. Is it single-threaded or not? I was using Redis for caching and session management in my microservices monitoring platform, and I wanted to understand how it could handle thousands of concurrent requests if it was truly single-threaded. This confusion led me down a rabbit hole of research back then, and here's what I learned during that time. Here's what I learned: What IS single-threaded: What IS multi-threaded: So Redis is "mostly single-threaded" for command execution, but uses threads for everything else. That's the key thing to understand. I thought this was true, but it's not. Redis has had background threads for slow disk operations since the early versions. There's a subsystem called bio.c (Background I/O) that handles things like fsync, closing file descriptors, and since Redis 4.0, lazy memory freeing. Redis 6.0 added I/O threads specifically for socket operations. This one surprised me. Actually, Redis 6.0's I/O threading can use multiple cores for network operations. The benchmarks are impressive: So Redis can definitely use multiple cores - just not for executing commands. This is completely backwards! Single-threading actually makes Redis faster in many cases. Here's why: No locking overhead: When you have multiple threads, you need locks to protect shared data. Even uncontended locks cost 100-1000 CPU cycles. Contended locks can cost 10,000+ cycles. Redis avoids all of this. Better CPU cache usage: When data is in the CPU's L1 cache, it takes about 1 nanosecond to access. Main memory takes 60-100 nanoseconds. With a single thread, Redis keeps more data in the CPU cache. No context switching: When the OS switches between threads, it has to save and restore state. This takes time. With a single thread, there's no context switching overhead. I wish this were true, but it's not. Race conditions absolutely can happen, just not in the way you might think. Individual commands are atomic - a single SET or GET command will complete fully before another command runs. But if you do multiple commands in sequence, those sequences can interleave with commands from other clients. The official docs confirm this. I thought blocking commands like BLPOP would freeze the entire Redis server. Not true! Blocking commands only block the specific client connection, not the server. Other clients can still send commands and get responses. This was the part that confused me the most. Why not just make everything multi-threaded and get better performance? The creator of Redis, Antirez, explained this really well. He said something like: "A thread doing LPUSH needs to serve other threads doing LPOP. There is less to gain, and a lot of complexity to add." Redis's complex data structures (lists, sets, sorted sets, streams) would need fine-grained locking if accessed from multiple threads. Operations like hash rehashing and expiration happen without locks because only one thread touches the data. This eliminates entire categories of bugs and makes the code much simpler. The numbers are pretty impressive: Single-instance benchmarks: Twitter's production numbers: Key insight: CPU is not Redis's bottleneck. The official Redis position says: "Because the CPU is not Redis's bottleneck, it is most likely machine memory or network bandwidth. Since a single thread is easy to implement and the CPU will not become a bottleneck, it makes sense to adopt a single-threaded solution." Here's what I learned 5 years ago while working on EventStreamMonitor: Redis's single-threaded command execution isn't a limitation you need to work around. It's a deliberate architectural choice that: The evolution from purely single-threaded to I/O threading in Redis 6.0+ shows the Redis team threading around the core insight rather than abandoning it. They kept command execution single-threaded (the important part) and added threading only where it helps (network I/O). Understanding this back then helped me design better caching strategies in my EventStreamMonitor project and appreciate why Redis can handle the load from multiple microservices without breaking a sweat. I'm sharing these learnings now because they're still relevant, and I wish someone had explained it this clearly when I was starting out. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse - Command execution (the actual work of running SET, GET, etc.) - Main event loop (the thing that processes requests) - Accessing data structures (reading/writing to memory) - Background I/O for disk operations (writing to disk, closing files) - Lazy freeing (since Redis 4.0) - cleaning up memory in the background - I/O socket operations (since Redis 6.0) - reading from and writing to network sockets - RDB snapshots via fork() - creating backup files - Redis 8.0 shows 37% to 112% throughput improvement with io-threads set to 8 - AWS ElastiCache 7.1 achieves over 1 million requests per second per node - No locking overhead: When you have multiple threads, you need locks to protect shared data. Even uncontended locks cost 100-1000 CPU cycles. Contended locks can cost 10,000+ cycles. Redis avoids all of this. - Better CPU cache usage: When data is in the CPU's L1 cache, it takes about 1 nanosecond to access. Main memory takes 60-100 nanoseconds. With a single thread, Redis keeps more data in the CPU cache. - No context switching: When the OS switches between threads, it has to save and restore state. This takes time. With a single thread, there's no context switching overhead. - With pipelining: 1.5M+ SET operations/sec, 1.8M+ GET operations/sec - Without pipelining: ~100,000-140,000 QPS typical - Redis 8.0 with I/O threads: Up to 7.4 million QPS - 105TB RAM, 39 million QPS, 10,000+ instances - Latency through Redis: <0.5ms - Maintains 100K+ open connections per instance - Eliminates entire categories of bugs (no race conditions between threads) - Achieves throughput that most applications will never need - Keeps the code simple and maintainable - Understanding Connections and Threads in Backend Services - Complete guide on threading models - 6 Common Redis and Kafka Challenges I Faced and How I Solved Them - Real-world challenges from my EventStreamMonitor project - Redis Official Documentation - EventStreamMonitor Project - My microservices monitoring platform using Redis and Kafka