Databases 22 min read

Unlocking Redis 6.0 Multithreaded I/O: How It Works and Boosts Performance

This article explains Redis 6.0's multithreaded I/O feature, covering its background, configuration parameters, execution flow, source code analysis, performance benchmarking against single‑threaded mode, identified limitations, and a brief comparison with Valkey 8.0's advanced I/O design.

Sanyou's Java Diary
Sanyou's Java Diary
Sanyou's Java Diary
Unlocking Redis 6.0 Multithreaded I/O: How It Works and Boosts Performance

Background

Redis is often described as a single‑threaded event‑driven server, but it actually uses a single main thread for command execution while handling network I/O with non‑blocking operations.

Redis 6.0 Multithreaded I/O Overview

Starting with Redis 4.0, background threads were added for asynchronous eviction and large‑key deletion. Redis 6.0 introduced multithreaded I/O, increasing single‑node request capacity from ~100k to ~200k operations per second.

Parameters and Configuration

Two configuration directives control the feature:

<code># io-threads 4  IO thread count
# io-threads-do-reads no  Whether reads are also handled by I/O threads</code>

io-threads sets the number of I/O threads; values greater than 1 enable multithreading (maximum 128).

io-threads-do-reads defaults to

no

; set to

yes

to let I/O threads also read and parse client data.

These settings cannot be changed at runtime with

CONFIG SET

and are disabled when SSL is enabled.

Execution Flow Overview

The main thread accepts connections and places sockets in a global queue.

After reading, the main thread distributes sockets to I/O threads (or keeps them itself) using round‑robin.

The main thread reads its own assigned data, then waits for I/O threads to finish reading.

I/O threads read and parse data but do not execute commands.

The main thread executes the commands.

Write responses are similarly distributed: the main thread and I/O threads write to clients, then the main thread installs write handlers.

Source Code Analysis

The multithreaded I/O implementation resides in

networking.c

. Initialization occurs in

initThreadedIO()

, which creates the I/O thread pool based on

io-threads

. The main thread calls

initThreadedIO()

from

InitServerLast()

.

<code>/* Initialize the data structures needed for threaded I/O. */
void initThreadedIO(void) {
    io_threads_active = 0; /* start with threads inactive */
    if (server.io_threads_num == 1) return; /* single‑thread mode */
    if (server.io_threads_num > IO_THREADS_MAX_NUM) {
        serverLog(LL_WARNING, "Fatal: too many I/O threads configured. The maximum number is %d.", IO_THREADS_MAX_NUM);
        exit(1);
    }
    for (int i = 0; i < server.io_threads_num; i++) {
        io_threads_list[i] = listCreate();
        if (i == 0) continue; /* thread 0 is the main thread */
        pthread_t tid;
        pthread_mutex_init(&io_threads_mutex[i], NULL);
        io_threads_pending[i] = 0;
        pthread_mutex_lock(&io_threads_mutex[i]);
        if (pthread_create(&tid, NULL, IOThreadMain, (void*)(long)i) != 0) {
            serverLog(LL_WARNING, "Fatal: Can't initialize IO thread.");
            exit(1);
        }
        io_threads[i] = tid;
    }
}
</code>

The I/O thread main loop (

IOThreadMain

) waits for a start condition, processes assigned clients for either read or write operations, and then signals completion by resetting

io_threads_pending

.

<code>void *IOThreadMain(void *myid) {
    long id = (unsigned long)myid;
    char thdname[16];
    snprintf(thdname, sizeof(thdname), "io_thd_%ld", id);
    redis_set_thread_title(thdname);
    redisSetCpuAffinity(server.server_cpulist);
    while (1) {
        // wait for start condition
        for (int j = 0; j < 1000000; j++) {
            if (io_threads_pending[id] != 0) break;
        }
        if (io_threads_pending[id] == 0) {
            pthread_mutex_lock(&io_threads_mutex[id]);
            pthread_mutex_unlock(&io_threads_mutex[id]);
            continue;
        }
        // process clients
        listIter li; listNode *ln;
        listRewind(io_threads_list[id], &li);
        while ((ln = listNext(&li))) {
            client *c = listNodeValue(ln);
            if (io_threads_op == IO_THREADS_OP_WRITE) {
                writeToClient(c, 0);
            } else if (io_threads_op == IO_THREADS_OP_READ) {
                readQueryFromClient(c->conn);
            } else {
                serverPanic("io_threads_op value is unknown");
            }
        }
        listEmpty(io_threads_list[id]);
        io_threads_pending[id] = 0;
    }
}
</code>

Dynamic Pause and Resume

During write handling,

stopThreadedIOIfNeeded()

pauses I/O threads when the pending write queue is less than twice the number of threads; otherwise

startThreadedIO()

unlocks the mutexes to resume work.

<code>int stopThreadedIOIfNeeded(void) {
    int pending = listLength(server.clients_pending_write);
    if (server.io_threads_num == 1) return 1;
    if (pending < (server.io_threads_num * 2)) {
        if (io_threads_active) stopThreadedIO();
        return 1;
    } else {
        return 0;
    }
}
</code>

Performance Comparison

Benchmarks were run on two 12‑core CentOS machines (1.5 GHz, 256 GB RAM). Using

redis-benchmark

, SET/GET throughput roughly doubled when four I/O threads were enabled, especially with many clients and small values. Enabling

io-threads-do-reads=yes

gave a modest additional gain.

Conclusion

Redis 6.0’s multithreaded I/O improves network throughput by offloading socket read/write and protocol parsing to worker threads, while command execution remains single‑threaded. Properly configuring thread count and CPU cores can double request capacity, but the design still under‑utilizes CPU resources and lacks TLS support. Valkey 8.0 addresses these gaps with asynchronous I/O and batch memory pre‑fetching, achieving up to 1 M QPS per instance.

performancedatabaseRedisbenchmarkSource CodeMultithreaded I/O
Sanyou's Java Diary
Written by

Sanyou's Java Diary

Passionate about technology, though not great at solving problems; eager to share, never tire of learning!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.