Skip to content

Command Execution

The command execution pipeline, Command trait interface, arity validation, command flags, and dispatch flow.

1. Connection accepted by Acceptor
+-- Assigned to Thread N via round-robin (connection pinned for I/O)
2. Client sends: SET mykey myvalue
+-- Thread N's event loop receives bytes
3. Protocol parser (RESP2)
+-- Parses into ParsedCommand { name: "SET", args: ["mykey", "myvalue"] }
4. Command lookup
+-- Registry.get("SET") -> SetCommand
5. Key routing check
+-- SetCommand.keys(args) -> ["mykey"]
+-- hash("mykey") % num_shards -> Shard M
6. If M == N (local):
+-- Execute directly on local store
If M != N (remote):
+-- Send ShardMessage::Execute to Shard M
+-- Await response via oneshot channel
7. Execute command
+-- SetCommand.execute(ctx, args)
+-- ctx.store.set("mykey", StringValue::new("myvalue"))
8. Persistence (async)
+-- Append to WAL batch
9. Encode response
+-- Protocol.encode(Response::Ok) -> "+OK\r\n"
10. Send to client

Each command implements a Command trait that provides: name, arity, flags, key extraction, execution strategy, WAL strategy, and an execute method.


Error handling uses a CommandError enum with variants for wrong arity, wrong type, syntax errors, out-of-range values, and other command-specific errors. See architecture.md for the key error variants.


ModeDescriptionExample
Fixed(n)Exactly n arguments (including command name)GET = Fixed(2)
AtLeast(n)Minimum n argumentsDEL = AtLeast(2)
Range(min, max)Between min and max argumentsSET = Range(3, 7)

FlagDescriptionBehavioral Effect
WRITEModifies dataReplicated, blocked during readonly mode
READONLYRead-only operationAllowed on replicas
FASTO(1) or O(log N)Excluded from slowlog by default
BLOCKINGMay block clientSpecial shard handling, timeout support
MULTI_KEYOperates on multiple keysRequires slot validation
PUBSUBPub/Sub commandRouted to pub/sub subsystem
SCRIPTScript-relatedSubject to script restrictions
NOSCRIPTCannot be called from scriptsRejected inside EVAL
LOADINGAllowed during loadingAvailable before full startup
STALEAllowed on stale replicasAvailable during sync
SKIP_SLOWLOGNever logged to slowlogInternal/meta commands
RANDOMNon-deterministic outputAffects script replication
ADMINAdministrative commandRestricted by ACL +@admin
NONDETERMINISTICOutput varies between runsAffects replication mode
NO_PROPAGATENot replicated to replicasConnection-local state changes
TRACKS_KEYSPACETriggers keyspace notificationsUsed by client tracking

Each command declares an execution strategy that determines how it is dispatched:

StrategyDescriptionExample Commands
StandardExecute on the owning shardGET, SET, HGET
ConnectionLevelExecute on connection’s shard, no routingAUTH, SELECT, CLIENT
BlockingMay suspend, needs waiter registrationBLPOP, BRPOP, BLMOVE
ScatterGatherFan out to all shards, merge resultsDBSIZE, KEYS, SCAN 0
RaftConsensusRequires Raft quorum (cluster mode)Cluster config changes
AsyncExternalOffloaded to async taskBGSAVE, DEBUG SLEEP
ServerWideBroadcast to all shards, aggregateFLUSHDB, FLUSHALL
CombinationEffect
`WRITEMULTI_KEY`
`READONLYMULTI_KEY`
`WRITEBLOCKING`
`WRITESCRIPT`
`READONLYSTALE`
`ADMINNO_PROPAGATE`

Mutual Exclusivity:

Flag AFlag BRelationship
WRITEREADONLYMutually exclusive (one must be set)
FASTBLOCKINGMutually exclusive
PUBSUBMULTI_KEYMutually exclusive

FrogDB validates commands in this order (matching Redis):

  1. Parse - Extract command name and args from RESP frame
  2. Lookup - Find command in registry (unknown -> ERR unknown command)
  3. Arity - Check argument count (wrong -> ERR wrong number of arguments)
  4. Auth - Check authentication state if required
  5. ACL - Check user permissions
  6. Execute - Run command logic

Arity is checked BEFORE auth for performance (reject malformed commands early without auth overhead).

Commands with subcommands (CLIENT, CONFIG, ACL, etc.) use hierarchical arity validation. The parent command requires at least 1 arg (the subcommand name), and each subcommand has its own arity.

Commands with Subcommands:

Parent CommandSubcommands
CLIENTGETNAME, SETNAME, ID, INFO, LIST, KILL, PAUSE, UNPAUSE, REPLY, NO-EVICT
CONFIGGET, SET, REWRITE, RESETSTAT
ACLLIST, GETUSER, SETUSER, DELUSER, CAT, GENPASS, WHOAMI, LOG, LOAD, SAVE
DEBUGSLEEP, STRUCTSIZE, HASHING, DUMP-VLL-QUEUE, DUMP-CONNECTIONS
MEMORYUSAGE, DOCTOR, STATS, MALLOC-SIZE, PURGE
SLOWLOGGET, LEN, RESET
CLUSTERINFO, NODES, SLOTS, MEET, ADDSLOTS, DELSLOTS, FAILOVER, …
OBJECTENCODING, FREQ, IDLETIME, REFCOUNT, HELP
SCRIPTLOAD, EXISTS, FLUSH, KILL, DEBUG
LATENCYDOCTOR, GRAPH, HISTORY, LATEST, RESET, HELP

Commands are registered at startup in a static hashmap:

lazy_static! {
static ref COMMANDS: HashMap<&'static str, Box<dyn Command>> = {
let mut m = HashMap::new();
m.insert("GET", Box::new(GetCommand));
m.insert("SET", Box::new(SetCommand));
m.insert("DEL", Box::new(DelCommand));
// ... register all commands
m
};
}
fn dispatch(cmd: &ParsedCommand) -> Result<Response, Error> {
let handler = COMMANDS.get(cmd.name.to_uppercase().as_str())
.ok_or(Error::UnknownCommand)?;
handler.execute(cmd)
}

Most commands expect a specific type and return an error if the key holds a different type.

Type-Overwriting Commands (Replace Regardless of Type)

Section titled “Type-Overwriting Commands (Replace Regardless of Type)”

SET, GETSET, SETEX, SETNX, PSETEX unconditionally overwrite the key, changing its type if necessary.

DEL, EXISTS, TYPE, EXPIRE, TTL, PERSIST, RENAME, OBJECT, DUMP, RESTORE operate on keys regardless of their type.


The CommandContext struct provides commands access to execution state:

pub struct CommandContext<'a> {
pub store: &'a mut dyn Store,
pub shard_senders: &'a Arc<Vec<mpsc::Sender<ShardMessage>>>,
pub shard_id: usize,
pub num_shards: usize,
pub conn_id: u64,
pub protocol_version: ProtocolVersion,
pub replication_tracker: Option<&'a Arc<ReplicationTrackerImpl>>,
pub replication_state: Option<&'a Arc<RwLock<ReplicationState>>>,
pub cluster_state: Option<&'a Arc<ClusterState>>,
pub node_id: Option<u64>,
pub raft: Option<&'a Arc<ClusterRaft>>,
pub network_factory: Option<&'a Arc<ClusterNetworkFactory>>,
pub quorum_checker: Option<&'a dyn QuorumChecker>,
pub command_registry: Option<&'a Arc<CommandRegistry>>,
pub dirty_delta: i64,
}

Most commands only need store. The shard_senders field is used by multi-key commands that need scatter-gather coordination.


Client Request -> Parse Command -> Execute Command -> WAL Append -> Response
|
+--- Async mode ---> Response sent immediately
+--- Sync mode ----> Block until min_replicas_to_write ACK

Sequence numbers are assigned at WAL append time, not at command execution time. This ensures monotonically increasing sequences for replication ordering.

Command TypeReplica Behavior
Read (READONLY flag)Execute locally, may return stale data
Write (WRITE flag)Return -READONLY error
Replication commandsExecute (PSYNC, REPLCONF, etc.)
Admin commandsDepends on command (INFO allowed, SHUTDOWN not)