While working on Monk CI, we ended up using Redis in places where requests can arrive very quickly. That raised an obvious question:
If Redis executes most of the commands on a single main thread, why doesn't everything simply queue up and become slow?
It sounds like a contradiction.
Thousands of clients can be connected to a Redis server, while most commands are processed through a mostly serialized execution path.
The answer is surprisingly simple:
Redis does one thing at a time, but most of those things are extremely small.
That simple idea explains a lot about Redis architecture, Redis performance, and why certain Redis commands can suddenly cause latency problems.
How Does Redis Handle So Many Requests?
Redis uses an event-driven architecture.
It can manage thousands of client connections without creating a separate thread for every connection. The operating system tells Redis when sockets are ready for reading or writing. Redis processes those events, executes commands, sends responses, and moves on.
So many clients can be active concurrently even though most command execution is serialized.
This distinction matters:
Concurrency is not the same as parallel execution.
A Redis server may have thousands of connected clients waiting on the event loop, while the actual commands pass through a mostly single execution lane.
That works because typical Redis commands are tiny.
Operations such as:
- GET
- SET
- INCR
- Small hash lookups
- Small set operations
usually require very little work.
Because Redis is primarily an in-memory database, normal operations can access data without waiting for traditional disk reads. Redis also avoids much of the locking and synchronization overhead that can appear when multiple worker threads constantly compete to modify shared state.
Redis executes a command, returns the result, and moves to the next one.
Very quickly.
What Does "Redis Is Single-Threaded" Actually Mean?
One common misconception is that a single-threaded Redis architecture means Redis can only handle one client.
That's not true.
Redis can manage many client connections concurrently. What it generally does not do is execute many commands against the same shared dataset in parallel.
Modern Redis can also use additional threads for parts of networking and I/O, so saying simply "Redis is single-threaded" is an oversimplification.
A more useful mental model is:
Networking and connection handling can happen around the edges, while most command execution follows one ordered stream.
That ordered execution is actually useful for Redis performance.
Because Redis commands are not constantly competing across many worker threads, the system avoids a lot of locking and synchronization overhead.
This architectural simplicity is one reason Redis is so fast.
Is Redis Fast Just Because It Uses RAM?
Using RAM is a major advantage, but it is not the entire explanation.
Redis performance comes from a combination of factors:
- In-memory data access
- Efficient Redis data structures
- Event-driven networking
- Low synchronization overhead
- Mostly serialized command execution
- Fast, small operations
Redis is fast because its architecture assumes individual commands should usually complete quickly.
This is also why pipelining can improve Redis performance.
But Redis pipelining does not make commands execute in parallel. Instead, it allows an application to send multiple Redis commands without waiting for a network round trip after every request.
The commands still execute in order, but less time is wasted waiting on the network.
What Actually Makes Redis Slow?
Once you think of Redis as one very fast execution lane, Redis performance bottlenecks become easier to understand.
Imagine this queue:
GET → SET → INCR → GET → SETEach operation is small, so the queue moves quickly.
Now insert this:
GET → SET → KEYS * → GET → INCRIf the Redis database contains millions of keys, KEYS * may need to inspect the entire keyspace.
The problem is not only that KEYS itself can be expensive.
It occupies the execution lane while other Redis commands wait.
That is the Redis performance problem worth caring about.
One expensive operation can increase latency for completely unrelated clients waiting behind it.
Which Redis Commands Should You Be Careful With?
KEYS * is the classic example.
It scans the entire keyspace. For large Redis databases, SCAN is generally a safer approach when you need to iterate through keys because the work is spread across multiple smaller operations.
The same Redis best practices apply to commands that process or return very large collections.
For example, HGETALL on a hash containing 20 fields is very different from HGETALL on a hash containing hundreds of thousands of fields.
Other Redis commands worth examining carefully include:
- SMEMBERS on a huge set
- LRANGE 0 -1 on a very large list
- Large SUNION or SINTER operations
- Expensive SORT operations
- Long-running Lua scripts or Redis Functions
- Synchronous operations on extremely large values
These commands are not automatically bad.
Their cost depends on the size of the data they operate on.
A command may look harmless in development because it processes 50 elements. Six months later, the same command might process 500,000.
The Redis command did not change.
The data did.
And that changes its impact on Redis performance.
Why O(1) Does Not Always Mean Cheap
Big-O complexity is useful, but it does not tell the entire story.
Suppose a Redis key lookup is O(1), but the value stored under that key is a 25 MB blob.
Finding the value may be cheap.
Copying, processing, and sending 25 MB to the Redis client is not.
So when evaluating Redis commands, the better question is not simply:
"What is the Big-O complexity?"
Instead, ask:
"How much work could this command perform with the largest realistic dataset we might give it?"
That question is usually much more useful when thinking about Redis optimization and production performance.
The Redis Performance Mental Model
Redis is not fast because one thread somehow behaves like 100 threads.
It is fast because Redis architecture is built around a different assumption:
Keep each piece of work small, execute it with very little overhead, and immediately move to the next one.
Most of the time, that works remarkably well.
But the architecture has an important consequence.
When you give Redis one expensive operation, you're not just slowing down that request.
You're temporarily blocking the road that every other request wants to use.
For high-throughput infrastructure, that distinction matters. A small operation performed frequently can be manageable. A single unpredictable operation that blocks the execution path can affect every client waiting behind it.
For Monk CI, this is part of a broader infrastructure lesson: performance depends not just on how fast an individual component is, but on how the entire system behaves when load increases.
Build Faster Infrastructure with Monk CI
Performance problems rarely come from one component alone. Faster CI depends on how runners, caches, storage, and supporting services work together under load.
Connect your GitHub repository to Monk CI and start running builds on high-performance CI infrastructure.
So the practical Redis rule is fairly simple:
Small commands are your friend. Large scans, huge responses, and unpredictable work deserve suspicion.
Once that clicks, Redis being "single-threaded but extremely fast" stops sounding contradictory.
It's simply a very fast one-lane road.
The trouble starts when someone parks a truck in it.
Mahesh Kale