One thread. Many clients. No locks. This is how real Redis is shaped.
make 09-event-loopNow open four redis-cli -p 6380 sessions and hammer them in parallel. They all get served by one Python thread.
Step 7 used one thread per client. That works for tens of clients. For thousands, you spend more time context-switching than serving requests, and each thread costs an 8 MB stack.
The event loop trades concurrency-via-threads for concurrency-via-non-blocking-IO. The kernel tracks which sockets have data ready and tells us in batches. We process each in turn. While one client's command is running, the others are paused, but only for microseconds at a time.
sel = selectors.DefaultSelector()
sel.register(server_sock, EVENT_READ, "ACCEPTOR")
while True:
events = sel.select(timeout=1.0)
for key, mask in events:
if key.data == "ACCEPTOR":
on_accept(key.fileobj)
else:
on_readable / on_writableThat's the whole loop. sel.select() blocks until at least one socket is ready. We dispatch by what kind of event happened.
We keep an in_buf and out_buf per client. Bytes arrive into in_buf, get parsed as RESP frames, dispatched, and the replies go into out_buf. When the socket becomes writable, we drain out_buf. If it can't take it all at once, we keep the rest and try again on the next writable event.
This is what makes the event loop work with slow clients: their stuck send() doesn't block the whole server.
- Replication (step 8) — would compose, just more state to thread through
- Pub/Sub (step 7) — would also compose, with channel -> set of states
Adding those back is the natural next exercise. Real Redis runs ALL of this in one event loop.
Step 10 is the capstone: a benchmark that compares our server against real Redis on a few standard workloads, and the lessons we can extract from the gap.