~/ hasil@local
offline
~/blog / programming / curious-case-of-redis-the-single-threaded-mandela-effect

Curious case of Redis - the single threaded Mandela effect

Hasil T · 2026-06-27 · 14 min read · programming

Everyone knows Redis is single-threaded.

They say it in interviews. They say it in architecture reviews. They say it on Reddit like a person who read one blog post in 2016 and never checked again.

I said it too. For years. I said it like I was stating a law of physics.

Then one day I actually looked at the Redis 6 release notes and realized I had been walking around with a fact that expired in 2020.

It used to be true

For the first ten years of Redis, it really was single-threaded. One process, one thread, one event loop. Commands came in one by one, got executed one by one, and replies went out one by one.

And it was fast. Unreasonably fast. Hundreds of thousands of operations per second on a single core.

The trick was not threading. The trick was memory. Redis keeps everything in RAM, so there is no disk seek, no page fault. A GET command does not fetch data from a disk block. It just follows a pointer in memory and returns the value.

Also, Redis commands are simple. Most are O(1) or O(log n). There is no query planner, no JOIN, no index scan that takes fourteen seconds and makes you question your career choices. The longest command you are supposed to run is maybe LRANGE with a sane limit. If you run KEYS * in production, your coworkers can exile you.

And because there is only one thread, there are no locks. No mutexes. No atomic operations moving cache lines between CPU cores. No context switches because four threads are fighting over the same hash table. One thread owns everything, so it never has to wait for itself.

This is why the single-threaded model worked. Not because threads are bad, but because for an in-memory store with microsecond commands, the cost of threading is higher than the benefit.

The event loop and epoll

Redis does not use libevent or libev or any fancy async library. It wrote its own event loop called ae.c, because Salvatore Sanfilippo, or antirez, the creator, did not want dependencies. The whole thing is a few hundred lines of C.

On Linux, it wraps epoll. On BSD and macOS, it wraps kqueue. If it has to, it falls back to select, which is basically a punishment.

Here is how the loop works:

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

That is it. An infinite loop that calls aeProcessEvents over and over.

Inside aeProcessEvents, Redis does three things:

  1. Check when the next timer event fires.
  2. Call epoll_wait to block until a socket has data.
  3. Iterate the fired events and call their callbacks.

The callback for a listening socket is acceptHandler, which accepts the client connection and registers a new read event. The callback for a client socket is readQueryFromClient, which reads the bytes, parses the Redis protocol, and eventually calls processCommandAndResetClient. That function executes the command and writes the reply to the client’s output buffer. Then the loop goes back to epoll_wait and waits for the next thing.

All of this happens on one thread. One brain. No handoffs, no queues, no thread pools.

Redis uses level-triggered epoll by default. This means if you do not read all the data from a socket, epoll_wait will keep telling you the socket is ready. It is simpler than edge-triggered, which only notifies you once and then assumes you will empty the buffer.

Where the speed really comes from

People say Redis is fast because it is single-threaded. That is backwards.

Redis is fast because:

  • Everything is in memory. No disk I/O on the hot path. Persistence happens in the background, not a blocker.
  • Data structures are simple and cache-friendly. SDS strings store length inside so strlen is O(1). Ziplists and listpacks pack small entries together so they stay in L1 cache. Skiplists give sorted sets O(log n) access with less jumping around with pointers than a balanced tree.
  • No synchronization overhead. One thread means no locks, no semaphores, no atomic variables, no cache-line bouncing between CPU cores.
  • Commands are bounded. Redis is designed so that no normal command takes a long time. There is no SELECT * FROM huge_table. If a command is slow, it is your fault, not Redis’s.

The single-threadedness was a side effect of these choices, not the cause of the speed. When your commands finish in microseconds and your data is already in RAM, you do not need multiple threads to keep the CPU busy. One core is enough.

The plot twist

In January 2019, antirez wrote a blog post titled “An update about Redis developments in 2019.” In it, he said:

“I/O threading is not going to happen in Redis AFAIK, because after much consideration I think it is a lot of complexity without a good reason.”

He believed the right way to scale was running multiple Redis instances, not making one instance multi-threaded. He wanted each instance to share nothing. This was a reasonable position from the person who created the thing.

Then in May 2020, Redis 6.0.0 shipped. And it had I/O threads.

Not command threads. The main thread still executes every single GET, SET, HGET, LPUSH, and ZADD. Alone. One by one. But other threads now handle reading from client sockets and writing replies back.

Here is what actually changed:

  • I/O threads read incoming bytes from sockets, parse the Redis protocol, and build the query buffer. Then they hand the client back to the main thread.
  • The main thread executes the command, mutates the data structures, and prepares the reply. Then it either hands the client back to an I/O thread for writing, or writes the reply itself if the client needs main-thread-only handling.
  • They communicate through lists protected by locks and event notifiers (eventfd or pipes).

You enable it with two config lines:

io-threads 4
io-threads-do-reads yes

Default is io-threads 1, which means off. Most people never change it.

The creator said it would never happen. Then it happened. And somehow, most of the industry missed the memo.

The Reddit thread

A few months ago I was scrolling r/programming and saw a post asking how Redis handles ten thousand concurrent connections with one thread.

The top reply was beautiful. It explained the event loop, epoll, level-triggered and edge-triggered, the whole thing. It got three hundred upvotes and a gold award. It was also completely wrong about Redis being single-threaded.

Someone replied: “Redis 6 has I/O threads though.”

The top reply edited their comment. They added a paragraph that said something like: “To clarify, Redis 6 introduced I/O threads for network operations, but command execution is still single-threaded, so technically the original explanation is correct.”

It was not correct. The original explanation said Redis is single-threaded. Redis is not single-threaded. It has multiple threads doing real work. But the commenter changed their comment just enough to keep the old belief alive.

This is how the Mandela effect works. The fact mutates to survive.

Why we all still believe it

The phrase “Redis is single-threaded” got stuck in our brains because:

  • It was true for ten years, which is forever in software time.
  • Old blog posts from 2014 still rank on Google.
  • Interviewers ask it as trivia, and the expected answer is “yes, it is single-threaded, and that is why it is fast.”
  • The correction is too subtle for a soundbite. You cannot say “Redis is multi-threaded” because command execution is not. You cannot say “Redis is single-threaded” because I/O is not. The truth requires a paragraph, and nobody has time for that.

So we keep repeating the shorthand. And the shorthand is wrong.

What to say instead

If someone asks you in an interview, here is the correct answer:

Redis commands execute one by one on a single thread. The main thread owns the event loop, the data structures, and all command execution. But since Redis 6, network I/O is multi-threaded. Other threads read from sockets and write replies, so the main thread can focus on actual work instead of moving bytes.

The speed was never about being single-threaded. The speed was about memory, simple data structures, and avoiding disk I/O.

If you are running Redis on a machine with many cores and many clients, enable io-threads. It actually helps.

I also just found out

I only learned this properly last year. I had been saying “Redis is single-threaded” in interviews and code reviews since 2017. I was not completely wrong. But I was wrong enough.

The annoying part is that the wrong version is easier to say. It fits in a tweet. It makes you sound like you understand systems. The real version requires footnotes.

But facts are not supposed to be convenient. They are just supposed to be true.

This one stopped being true in 2020. And most of us, including me, are still catching up.

#redis#systems#databases