Databases 14 min read

Inside Redis: How Commands Are Processed by Its Event‑Driven Engine

Redis, the high‑performance in‑memory key‑value store, uses a single‑threaded event‑driven architecture that handles client connections, command parsing, execution, and reply transmission; this article explains its deployment modes, core modules, and step‑by‑step command processing flow, including code snippets and key data structures.

dbaplus Community
dbaplus Community
dbaplus Community
Inside Redis: How Commands Are Processed by Its Event‑Driven Engine

Redis Deployment Architectures (Macro View)

Standalone mode : a single Redis instance without HA, suitable for development or non‑critical workloads.

Master‑Slave replication : asynchronous replication from a master to one or more slaves, providing read‑write separation and redundancy.

Sentinel mode : builds on master‑slave replication by adding monitoring nodes that automatically fail over to a new master when the current master fails.

Cluster mode : data is sharded across multiple nodes using hash slots, supporting automatic fail‑over and horizontal scaling for large datasets and high concurrency.

Core Components (Micro View)

Event‑driven engine : a high‑performance network model based on I/O multiplexing (epoll/kqueue). A single thread (the reactor) processes concurrent requests, avoiding thread‑switch overhead.

Command processing layer : parses client commands, validates them, and dispatches to the appropriate function (e.g., GET, SET, HSET) for the supported data structures.

Memory management subsystem : allocates and reclaims memory, implements eviction policies such as LRU, LFU, random, or TTL‑based eviction.

Persistence module : provides RDB snapshots and AOF logs; the AOF can be rewritten to keep the log size bounded.

Monitoring & statistics : exposes metrics like used_memory, instantaneous_ops_per_sec, commandstats, and slow‑query logs for operational tuning.

Command Execution Flow

Connection establishment : the client opens a TCP connection. The event loop (ae) accepts the socket via acceptTcpHandler, creates a client object and registers a read handler.

Read & parse phase : the read event triggers readQueryFromClient. If I/O threading is enabled, the client is placed in clients_pending_read for worker threads; otherwise the main thread reads the RESP payload, resolves the command to a function pointer in redisCommandTable, and fills client->argv and client->argc.

Command execution phase : processPendingCommandsAndResetClient calls processCommand (validation), call (pre‑hooks), the actual command function (e.g., setCommand), propagate (replication/AOF), and finally addReply to queue the response.

Response sending phase : the reply is added to clients_pending_write. In the next loop iteration handleClientsWithPendingWritesUsingThreads (or the non‑threaded variant) writes the buffered reply to the client socket, registering a write event only if the buffer is not fully drained.

Key Data Structures

typedef struct aeEventLoop {
    int maxfd;               // highest file descriptor
    aeFileEvent *events;     // registered file events
    aeTimeEvent *timeEventHead; // linked list of time events
    aeFiredEvent *fired;     // array of fired events
} aeEventLoop;
void initServer(void) {
    if (createSocketAcceptHandler(&server.ipfd, acceptTcpHandler) != C_OK) {
        serverPanic("Unrecoverable error creating TCP socket accept handler.");
    }
}
void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        aeProcessEvents(eventLoop,
                         AE_ALL_EVENTS|AE_CALL_BEFORE_SLEEP|AE_CALL_AFTER_SLEEP);
    }
}

Reactor Model

The event loop registers two kinds of events:

File events : triggered when a socket becomes readable or writable. Handlers such as acceptTcpHandler (new connection) and readQueryFromClient (read) are registered during server startup.

Time events : periodic tasks like serverCron (default every 100 ms) that perform key expiration, persistence triggers, replication health checks, and cluster gossip.

Multi‑Threaded I/O (Redis 6.0+)

Redis can enable a pool of I/O threads. The main thread still performs command execution and event scheduling, while I/O threads handle socket reads and writes after the afterSleep hook. When a client is marked CLIENT_PENDING_READ, the read request is dispatched to an I/O thread; the result is later processed by the main thread.

Command Lookup

All command descriptors are stored in redisCommandTable. For example, the SET command is defined as:

struct redisCommand redisCommandTable[] = {
    ...
    {"set", setCommand, -3, "write use-memory @string", 0, NULL, 1,1,1,0,0,0},
    ...
};

Processing a Command

int processPendingCommandsAndResetClient(client *c) {
    processCommand(c);          // validation, ACL, memory checks
    call(c);                    // monitor/watch hooks
    c->cmd->proc(c);           // actual command (e.g., setCommand)
    propagate(c);               // replication/AOF
    addReply(c, replyObj);      // queue response
}
addReply

places the reply into clients_pending_write so that the next beforeSleep iteration can flush it.

void addReply(client *c, robj *obj) {
    if (prepareClientToWrite(c) != C_OK) return;
    /* reply is now in the write queue */
}

Flushing Replies

int handleClientsWithPendingWritesUsingThreads(void) {
    if (server.io_threads_num == 1 || stopThreadedIOIfNeeded()) {
        return handleClientsWithPendingWrites();
    }
    /* distribute write tasks to I/O threads */
    while ((ln = listNext(&li))) {
        int target_id = item_id % server.io_threads_num;
        listAddNodeTail(io_threads_list[target_id], c);
        item_id++;
    }
    /* main thread processes its own slice */
    listRewind(io_threads_list[0], &li);
    while ((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        writeToClient(c, 0);
    }
    /* wait for other I/O threads to finish */
    while (1) {
        unsigned long pending = 0;
        for (int j = 1; j < server.io_threads_num; j++)
            pending += getIOPendingCount(j);
        if (pending == 0) break;
    }
    /* register write events for any remaining data */
    listRewind(server.clients_pending_write, &li);
    while ((ln = listNext(&li))) {
        client *c = listNodeValue(ln);
        if (clientHasPendingReplies(c) &&
            connSetWriteHandler(c->conn, sendReplyToClient) == AE_ERR) {
            freeClientAsync(c);
        }
    }
    listEmpty(server.clients_pending_write);
    return 0;
}

Illustrative Diagrams

Redis macro architecture diagram
Redis macro architecture diagram
Redis core components diagram
Redis core components diagram
Command execution flow diagram
Command execution flow diagram
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.

BackenddatabaseredisEvent-drivenCommand Execution
dbaplus Community
Written by

dbaplus Community

Enterprise-level professional community for Database, BigData, and AIOps. Daily original articles, weekly online tech talks, monthly offline salons, and quarterly XCOPS&DAMS conferences—delivered by industry experts.

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.