How Redis Works in Single Thread

Redis is an in-memory data structure store used as a database, cache, and message broker. Despite handling hundreds of thousands of operations per second, its command execution core is single-threaded. This document explains how that is possible and why it works well.


The Single-Threaded Model

Redis executes all commands on a single main thread using an event loop. There is no command-level locking, no context switching between commands, and no race conditions between clients.

This works because Redis is I/O bound, not CPU bound. The bottleneck for most workloads is network I/O, not computation. A single thread backed by an efficient event loop can saturate a network interface long before it saturates a CPU core.

Why single-threaded is fast

  • No lock contention. Every command is atomic by construction.
  • No context switching overhead between threads for command execution.
  • Cache-friendly. All hot data structures live in one thread's CPU cache.
  • Simple consistency model. Commands execute in the exact order they arrive.

The Event Loop

Redis uses a reactor pattern built on top of an OS-level multiplexing API — %%INLINE0%% on Linux, %%INLINE1%% on BSD/macOS, or %%INLINE2%% as a fallback. This is implemented in Redis's internal library called %%INLINE3%%

(A simple event library).

The loop repeatedly does three things:

  1. Poll the kernel for ready file descriptors (sockets with data to read or buffers ready to write).
  2. Dispatch handlers for each ready event — read a command, execute it, queue the reply.
  3. Run time events — periodic tasks like key expiration sampling, rehashing, and stats.
flowchart LR A[Clients] -->|TCP| B[Socket Buffers] B --> C{Event Loop
epoll / kqueue} C --> D[Read Command] D --> E[Parse RESP] E --> F[Execute on
Main Thread] F --> G[In-Memory
Data Structures] G --> H[Write Reply] H --> B C -.periodic.-> I[Expiration
Rehashing
AOF flush]

Command Lifecycle

A single command from a client traverses these stages:

| Stage | Thread | Description |

|-------|--------|-------------|

| Accept connection | Main | New client registered with event loop |

| Read from socket | Main (or I/O thread) | Bytes pulled from kernel buffer |

| Parse RESP protocol | Main (or I/O thread) | Convert bytes into command + args |

| Lookup + execute | Main only | Dispatch to command handler, mutate data |

| Serialize reply | Main | Build RESP response in output buffer |

| Write to socket | Main (or I/O thread) | Push bytes to kernel buffer |

The critical invariant: command execution itself is always on the main thread. Everything else can be offloaded.


Where Threads Are Actually Used

Redis is "single-threaded for commands," but the process is not literally single-threaded. Background threads exist for work that would otherwise block the event loop:

  • BIO threads (Background I/O) — handle %%INLINE4%% on AOF files and async %%INLINE5%% of large objects. Closing a 10 GB key with UNLINK offloads deallocation here so the main thread stays responsive.
  • I/O threads (since Redis 6) — optionally parallelize socket reads/writes and RESP parsing. Disabled by default. Configured via %%INLINE7%% in %%INLINE8%%. Even when enabled, command execution remains single-threaded.
  • Module threads — loaded modules may spawn their own threads for heavy computation.

Memory and Data Structures

All data lives in RAM. Redis uses carefully optimized structures that switch encoding based on size:

  • Strings — raw, embstr, or int encoding
  • Lists — quicklist (a linked list of listpacks)
  • Hashes / Sets / Sorted Sets — listpack for small sizes, hashtable or skiplist when they grow past thresholds

The main hash table (the keyspace) uses incremental rehashing. When the table needs to grow, Redis allocates a new table and migrates a few buckets on every event loop iteration, so rehashing never blocks for long.


Persistence Without Blocking

Redis offers two persistence modes, both designed to avoid stalling the main thread:

  • RDB snapshots — Redis calls fork(), and the child process writes the snapshot using copy-on-write memory. The main thread keeps serving requests in the parent.
  • AOF (Append-Only File) — every write command is appended to a log. Actual %%INLINE10%% is delegated to a BIO thread (with configurable policy: %%INLINE11%%, %%INLINE12%%, or %%INLINE13%%).

What Can Still Block the Main Thread

Because command execution is single-threaded, a slow command blocks every other client. Common culprits:

  • %%INLINE14%% on a large keyspace — O(N) scan of all keys. Use %%INLINE15%% instead.
  • %%INLINE16%% / %%INLINE17%% on huge collections.
  • %%INLINE18%% on a massive aggregate key — use %%INLINE19%% to offload deallocation.
  • Lua scripts or transactions (MULTI/EXEC) that run too long.
  • Large SORT operations.

The slowlog command surfaces these so they can be identified and fixed.


Summary

Redis's single-threaded design is a deliberate trade: it gives up multi-core parallelism for command execution in exchange for simplicity, atomicity, and predictable latency. For the workloads Redis targets — small operations on in-memory data with high network throughput — this is almost always the right call. Multi-core scaling is achieved by running multiple Redis instances (via Redis Cluster or sharding) rather than threading within a single instance.

Building Tech Startups. Experience in Full Stack Web Development & Data Engineering.