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"]
+-- CRC16("mykey") % 16384 % 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 is a type implementing the Command trait (frogdb-server/crates/core/src/command.rs). The trait has two required methods and derives everything else from a single declarative specification:

pub trait Command: Send + Sync {
/// The single source of truth for this command's mechanics.
fn spec(&self) -> &'static CommandSpec;
/// Execute the command against the local shard.
fn execute(&self, ctx: &mut CommandContext, args: &[Bytes])
-> Result<Response, CommandError>;
// name(), arity(), flags(), execution_strategy(), wal_strategy(),
// keys(), keys_with_flags() are default methods that read from spec().
}

CommandSpec is a 'static struct carrying the command’s name, arity, flags, strategy (execution strategy), key specification, per-key access specification, WAL strategy, keyspace-event specification, and related routing metadata. Because the derived accessors all read from spec(), a command declares its mechanics once. For example, GetCommand:

impl Command for GetCommand {
fn spec(&self) -> &'static CommandSpec {
static SPEC: CommandSpec = CommandSpec {
name: "GET",
arity: Arity::Fixed(1),
flags: CommandFlags::READONLY.union(CommandFlags::FAST),
keys: KeySpec::First,
strategy: ExecutionStrategy::Standard,
// ...access, wal, event, lookup, mutation, requires_same_slot
};
&SPEC
}
fn execute(&self, ctx: &mut CommandContext, args: &[Bytes])
-> Result<Response, CommandError> { /* ... */ }
}

Connection-level commands (AUTH, HELLO, CONFIG, and other commands handled directly by the connection handler rather than routed to a shard) implement the sibling ConnectionCommand trait, which likewise carries a CommandSpec and an execute against a connection context. See Command Registry.


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.


Arity is checked against the argument count excluding the command name (the name is held separately on ParsedCommand, and validation calls arity.check(args.len())). This is the opposite convention to Redis’s COMMAND output, which includes the command token.

ModeDescriptionExample
Fixed(n)Exactly n argumentsGET key = Fixed(1)
AtLeast(n)Minimum n argumentsDEL key [key ...] = AtLeast(1)
Range { min, max }Between min and max arguments inclusiveEXPIRE key seconds [NX|XX|GT|LT] = Range { min: 2, max: 4 }

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
MOVABLEKEYSKey positions depend on argument valuesKeys resolved by inspecting args (SORT, EVAL, XREAD); reported by COMMAND INFO

Flags are a bitflags set (CommandFlags) in frogdb-server/crates/core/src/command.rs.

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
DEBUGOBJECT, SLEEP, STRUCTSIZE, HASHING, SET-ACTIVE-EXPIRE, RELOAD, LOCKTABLE, WAITQUEUE, VLL
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 into a CommandRegistry (frogdb-server/crates/core/src/registry.rs), not a lazy_static map. The server calls frogdb_commands::register_all(&mut registry) (frogdb-server/crates/commands/src/lib.rs, invoked from the server’s register.rs), which registers each command struct by name (case-insensitive):

pub fn register_all(registry: &mut CommandRegistry) {
registry.register(basic::GetCommand);
registry.register(basic::SetCommand);
registry.register(basic::DelCommand);
// ... every command
}

Each registry entry is a CommandEntry (an alias for CommandImpl), a tagged union so that a registered command carries exactly one execution path and there is no never-called stub:

pub enum CommandImpl {
/// Shard-local executor: execute(&mut CommandContext).
/// Standard, ScatterGather, Blocking, and ServerWide commands.
Shard(Arc<dyn Command>),
/// Connection-level executor: execute(&ConnCtx).
/// Migrated connection-level commands (e.g. CONFIG).
Connection(&'static dyn ConnectionCommand),
}

register() inserts a Shard entry; register_connection() inserts a Connection entry. In debug builds, registration validates that a command’s declared ExecutionStrategy agrees with its entry variant — a Connection executor must declare ExecutionStrategy::ConnectionLevel — so a migrated command’s never-reached path is unrepresentable rather than a latent routing bug. Lookup (get_entry) upper-cases the command name.

This registry is the authoritative surface of the command set. The generated command-compatibility matrix (work item S1, commands-gen) is produced by dumping register_all() into commands.json; the matrix is derived from the registry, not hand-maintained. Command counts therefore live in the generated matrix, not in this page.


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 (frogdb-server/crates/core/src/command.rs) provides commands access to execution state. Its current shape:

pub struct CommandContext<'a> {
pub store: &'a mut dyn Store,
pub shard_senders: &'a Arc<Vec<ShardSender>>,
pub shard_id: usize,
pub num_shards: usize,
pub conn_id: u64,
pub protocol_version: ProtocolVersion,
pub replication_tracker: Option<&'a Arc<ReplicationTrackerImpl>>,
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 is_replica: bool,
pub is_replica_flag: Option<Arc<AtomicBool>>,
pub role_controller: Option<Arc<dyn RoleController>>,
pub master_host: Option<String>,
pub master_port: Option<u16>,
pub effects: CommandEffects,
}

Most commands only need store. shard_senders is used by multi-key commands that coordinate scatter-gather; the cluster/replication fields are None unless those subsystems are enabled. effects is the command’s out-buffer for everything it produces besides the Response (keyspace events, WAL actions), drained by the execution seam. This struct changes as features land; treat the listing as current-shape, not stable API.


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)