fivenines

Theory / 10 min

Command Table And Dispatch

A Redis command is more than a function with a name. It is a unit of behavior with metadata: arity, flags, key positions, permissions, replication rules, cluster routing implications, and special handling in modes such as transactions or Pub/Sub.

That is why mature Redis-like systems use a command table rather than a long chain of conditionals.

The dispatch path is:

argv -> command name -> command spec -> validation -> execution or queuing -> reply

Metadata Is Part Of The Command

A command table entry might look like this:

type CommandSpec = {
  name: string;
  arity: number | { min: number };
  flags: Set<"write" | "readOnly" | "admin" | "denyOom" | "pubsub">;
  firstKey?: number;
  lastKey?: number;
  step?: number;
  handler: (server, client, argv) => RespValue;
};

The handler performs the command's core behavior, but the metadata lets the rest of the server reason about the command before it runs.

Is this a write command that should be appended to AOF? Is it allowed on a read-only replica? Can it run while the client is subscribed? Which arguments are keys for cluster slot validation? Should it be rejected when memory is exhausted? Those answers should not be scattered through unrelated code.

Arity Comes First

Before a command reaches its handler, Redis needs to know whether the request has the right shape.

Redis convention counts the command name as part of the argument count:

GET key        argc = 2
SET key value  argc = 3 or more when options exist
DEL k1 k2      argc >= 2

In Redis internals, positive arity means exact argument count and negative arity means minimum argument count. For example, 2 means exactly two arguments including the command name, while -3 means at least three.

This may look small, but early validation keeps command handlers simpler. A GET handler should not be responsible for explaining every malformed GET.

Dispatch Depends On Client State

Command lookup by name is only the beginning.

A client inside MULTI should queue most commands rather than execute them immediately. A subscribed client should be restricted to Pub/Sub-related commands. A replica connection should not be treated like an ordinary application client. A server loading from disk may reject normal commands until recovery completes.

So dispatch is a layered decision:

Does the command exist?
Is the argument count valid?
Is the client allowed to run it now?
Is the server allowed to run it now?
Should it execute immediately or be queued?

The command table provides the facts. Dispatch applies those facts to the current context.

Errors Are Replies

Redis protocol errors may close a connection, but command errors are usually ordinary replies:

-ERR unknown command 'NOPE'\r\n
-ERR wrong number of arguments for 'get' command\r\n
-WRONGTYPE Operation against a key holding the wrong kind of value\r\n

This is an important boundary. A command can fail semantically without the server itself failing. The client receives an error reply and the connection continues unless the error indicates a deeper protocol or resource problem.

The Command Table Becomes A Map Of The Server

As Redis grows, the command table becomes one of the most valuable pieces of structure in the codebase. It connects syntax to semantics and semantics to system policy.

Replication, AOF, cluster routing, memory checks, transactions, ACLs, documentation, and introspection all need to ask questions about commands. A table lets those questions be answered consistently.

In a small server, a command table may feel like ceremony. In a real one, it is the difference between adding features and slowly tying knots.

Next step

See what actually stuck.

Take the practice scenarios now.