Build Redis (10 scenes)
Scene 02 · One thread, one command at a time
Why a single event loop is fast — and why one slow command (KEYS *, big LRANGE, slow Lua) stalls every client.
Previously

You've named the parts. Here's the first thing that defines how Redis behaves — there is exactly ONE event loop between every client and every byte of data.

Scene 02
One thread, one command at a time
Diagram
Three clients on the left, each with its own queued commands and a wait-time strip that grows while it's blocked. They all funnel into one server box on the right that holds the dataset and runs a single event loop. The 'now executing' badge in the loop shows the command in flight — every other client's strip inflates until the loop frees up.
CLIENTS · 3Client Aidleno in-flightwait 0 µsClient Bidleno in-flightwait 0 µsClient Cidleno in-flightwait 0 µsredis · event loopsingle thread · runs one command at a timeio-threads (parses sockets)parses RESP · queues commands · never executescount = 1 (effectively disabled)io0COMMAND QUEUECURRENT COMMANDloop idleELAPSED0 µsDATASET · 6 keysuser:42{…}session:9okcart:1[…]counter127user:73{…}pingpongClient latencyacross all clientsP500 µsP990 µsMAX0 µs
One thread pulls one command at a time.
Three clients dispatch GETs and SETs into the loop. One thread pulls one command at a time, runs it to completion, then takes the next. With fast commands, every client gets sub-millisecond latency.
Implementation
EventLoop.runOnce
the ae loop: one command, start to finish, before the next
1while server.running:
2 ready = epoll_wait(fds)
3 for fd in ready:
4 readQueryFromClient(fd) # parses RESP
5 cmd = nextReadyCommand()
6 if cmd is not None:
7 reply = execute(cmd) # touches the dataset
8 addReplyToClient(cmd.client, reply)
9 # nothing else runs until execute() returns
10 flushPendingWrites()
Command.KEYS
scans the whole keyspace in one command — no yielding
1def keysCommand(pattern):
2 matches = []
3 for key in server.db.dict: # every key, every time
4 if stringmatch(pattern, key):
5 matches.append(key)
6 # loop is held the entire time;
7 # no other client is served until we return
8 return matches
9
10# contrast: SCAN returns a cursor after ~COUNT keys
11# so the loop is free between batches