Databases 15 min read

How Redis Overcame Single‑Thread Limits with Lazy Free and Multithreaded I/O

This article explains Redis’s original single‑threaded event‑driven architecture, its performance bottlenecks, and how versions 4.0 and 6.0 introduced Lazy Free and multithreaded I/O mechanisms—including asynchronous key deletion, background freeing, and I/O thread pools—to improve scalability while outlining their implementation details, limitations, and comparisons with Tair.

IT Architects Alliance
IT Architects Alliance
IT Architects Alliance
How Redis Overcame Single‑Thread Limits with Lazy Free and Multithreaded I/O

Single‑Threaded Core of Redis

Redis is an in‑memory cache that processes all events in a single thread, avoiding context switches and lock contention. The server runs an event‑driven loop handling two kinds of events: File events: socket operations such as accept, read, write, close generated by client connections. Time events: scheduled tasks like key expiration and statistics collection.

Redis uses a Reactor pattern with I/O multiplexing ( select / epoll) to monitor many sockets concurrently without locks.

Lazy Free Mechanism (Redis 4.0)

Large‑key deletions (e.g., a Set with millions of members) block the single thread while freeing memory. Enabling lazyfree_lazy_user_del makes DEL behave like UNLINK, unlinking the key and delegating the actual memory reclamation to a background lazy‑free thread.

void delCommand(client *c) {
    delGenericCommand(c, server.lazyfree_lazy_user_del);
}

void delGenericCommand(client *c, int lazy) {
    int numdel = 0, j;
    for (j = 1; j < c->argc; j++) {
        expireIfNeeded(c->db, c->argv[j]);
        int deleted = lazy ? dbAsyncDelete(c->db, c->argv[j])
                           : dbSyncDelete(c->db, c->argv[j]);
        if (deleted) {
            signalModifiedKey(c, c->db, c->argv[j]);
            notifyKeyspaceEvent(NOTIFY_GENERIC, "del", c->argv[j], c->db->id);
            server.dirty++;
            numdel++;
        }
    }
    addReplyLongLong(c, numdel);
}

The asynchronous path computes a “free effort” (e.g., 1 for a String, element count for a Set). If the effort exceeds LAZYFREE_THRESHOLD (default 64) and the object is not shared, a background job is created via bioCreateBackgroundJob. Otherwise the deletion falls back to synchronous freeing.

#define LAZYFREE_THRESHOLD 64
int dbAsyncDelete(redisDb *db, robj *key) {
    if (dictSize(db->expires) > 0) dictDelete(db->expires, key->ptr);
    dictEntry *de = dictUnlink(db->dict, key->ptr);
    if (de) {
        robj *val = dictGetVal(de);
        size_t free_effort = lazyfreeGetFreeEffort(val);
        if (free_effort > LAZYFREE_THRESHOLD && val->refcount == 1) {
            atomicIncr(lazyfree_objects, 1);
            bioCreateBackgroundJob(BIO_LAZY_FREE, val, NULL, NULL);
            dictSetVal(db->dict, de, NULL);
        }
    }
    return (de != NULL);
}

Multithreaded I/O (Redis 6.0)

Redis 6.0 adds a pool of I/O threads that perform the actual read and write system calls. The main event‑handling thread distributes ready read events to the I/O threads, waits for them to finish, processes the commands, then hands write events back to the I/O threads.

int handleClientsWithPendingReadsUsingThreads(void) {
    listIter li;
    listNode *ln;
    listRewind(server.clients_pending_read, &li);
    int item_id = 0;
    while ((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        int target_id = item_id % server.io_threads_num;
        listAddNodeTail(io_threads_list[target_id], c);
        item_id++;
    }
    /* wait for all I/O threads to finish */
    while (1) {
        unsigned long pending = 0;
        for (int j = 1; j < server.io_threads_num; j++)
            pending += io_threads_pending[j];
        if (pending == 0) break;
    }
    return processed;
}
void *IOThreadMain(void *myid) {
    while (1) {
        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;
    }
}

Performance gains are roughly 2× compared with the pure single‑threaded version, but the design has limitations: I/O threads can only read **or** write at a time, the main thread is idle during I/O, and the model is not a full pipeline, incurring polling overhead.

Comparison with Tair’s Multithreading

Tair uses a three‑stage model: a Main Thread for connection setup, an I/O Thread for request parsing and response sending, and a Worker Thread for command execution. Communication between I/O and Worker threads is done via lock‑free queues and pipes, eliminating shared objects and further reducing contention.

Key Takeaways

Redis 4.0 introduced the Lazy Free thread to off‑load large‑key deletions, preventing long‑lasting blocks.

Redis 6.0 added a limited multithreaded I/O layer that improves throughput (~2×) but does not make the core event loop multithreaded.

Future scalability in Redis is expected to rely more on clustering and module‑level slow‑operation threading rather than a full rewrite of the core event loop.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

performanceredisDatabase InternalsMultithreaded I/OLazy Free
IT Architects Alliance
Written by

IT Architects Alliance

Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.

0 followers
Reader feedback

How this landed with the community

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.