From c628e756db1af0d947f61a19b09afb9ee9190905 Mon Sep 17 00:00:00 2001 From: Kacy Fortner Date: Thu, 26 Feb 2026 20:49:26 -0500 Subject: [PATCH 1/2] feat: add FLUSHALL and MEMORY USAGE commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FLUSHALL [ASYNC] is an alias for FLUSHDB — ember is single-database, so the behavior is identical. it's a high-visibility command that redis users reach for immediately when setting up a fresh environment. MEMORY USAGE key [SAMPLES count] returns the estimated memory footprint of a key in bytes using the cached value size on each entry (O(1), no collection walk). the SAMPLES option is accepted and silently ignored since ember's estimate is always based on the cached size. returns nil if the key does not exist or is expired. --- crates/ember-cli/src/commands.rs | 12 +++++++ crates/ember-core/src/keyspace/mod.rs | 13 +++++++ crates/ember-core/src/shard/mod.rs | 7 ++++ .../ember-protocol/src/command/attributes.rs | 8 ++++- crates/ember-protocol/src/command/mod.rs | 6 ++++ crates/ember-protocol/src/command/parse.rs | 35 +++++++++++++++++++ crates/ember-server/src/acl.rs | 1 + crates/ember-server/src/connection/execute.rs | 24 +++++++++++++ 8 files changed, 105 insertions(+), 1 deletion(-) diff --git a/crates/ember-cli/src/commands.rs b/crates/ember-cli/src/commands.rs index 556e032e..48c83d7c 100644 --- a/crates/ember-cli/src/commands.rs +++ b/crates/ember-cli/src/commands.rs @@ -632,6 +632,18 @@ pub static COMMANDS: &[CommandInfo] = &[ group: "server", summary: "remove all keys from the current database", }, + CommandInfo { + name: "FLUSHALL", + args: "[ASYNC]", + group: "server", + summary: "remove all keys from all databases (alias for FLUSHDB in ember)", + }, + CommandInfo { + name: "MEMORY USAGE", + args: "key [SAMPLES count]", + group: "server", + summary: "estimate memory usage for a key in bytes", + }, CommandInfo { name: "INFO", args: "[section]", diff --git a/crates/ember-core/src/keyspace/mod.rs b/crates/ember-core/src/keyspace/mod.rs index 18036204..21da495c 100644 --- a/crates/ember-core/src/keyspace/mod.rs +++ b/crates/ember-core/src/keyspace/mod.rs @@ -906,6 +906,19 @@ impl Keyspace { } } + /// Returns the estimated memory usage in bytes for the given key, + /// or `None` if the key does not exist or is expired. + /// + /// Uses the cached value size for O(1) cost. The estimate covers the + /// key string, the serialized value, and the per-entry overhead. + pub fn memory_usage(&mut self, key: &str) -> Option { + if self.remove_if_expired(key) { + return None; + } + let entry = self.entries.get(key)?; + Some(entry.entry_size(key)) + } + /// Removes the expiration from a key. /// /// Returns `true` if the key existed and had a timeout that was removed. diff --git a/crates/ember-core/src/shard/mod.rs b/crates/ember-core/src/shard/mod.rs index 3eca665f..0c879be7 100644 --- a/crates/ember-core/src/shard/mod.rs +++ b/crates/ember-core/src/shard/mod.rs @@ -234,6 +234,10 @@ pub enum ShardRequest { Ttl { key: String, }, + /// MEMORY USAGE. Returns the estimated memory footprint of a key in bytes. + MemoryUsage { + key: String, + }, Persist { key: String, }, @@ -1773,6 +1777,9 @@ fn dispatch( ShardResponse::Bool(ks.expireat(key, *timestamp)) } ShardRequest::Ttl { key } => ShardResponse::Ttl(ks.ttl(key)), + ShardRequest::MemoryUsage { key } => { + ShardResponse::Integer(ks.memory_usage(key).map(|n| n as i64).unwrap_or(-1)) + } ShardRequest::Persist { key } => ShardResponse::Bool(ks.persist(key)), ShardRequest::Pttl { key } => ShardResponse::Ttl(ks.pttl(key)), ShardRequest::Pexpire { key, milliseconds } => { diff --git a/crates/ember-protocol/src/command/attributes.rs b/crates/ember-protocol/src/command/attributes.rs index 0079b184..d242184f 100644 --- a/crates/ember-protocol/src/command/attributes.rs +++ b/crates/ember-protocol/src/command/attributes.rs @@ -83,6 +83,8 @@ impl Command { Command::BgSave => "bgsave", Command::BgRewriteAof => "bgrewriteaof", Command::FlushDb { .. } => "flushdb", + Command::FlushAll { .. } => "flushall", + Command::MemoryUsage { .. } => "memory", Command::ConfigGet { .. } => "config", Command::ConfigSet { .. } => "config", Command::ConfigRewrite => "config", @@ -310,6 +312,7 @@ impl Command { | Command::SMove { .. } // server / persistence | Command::FlushDb { .. } + | Command::FlushAll { .. } | Command::ConfigSet { .. } | Command::Exec | Command::BgRewriteAof @@ -514,6 +517,8 @@ impl Command { } Command::BgSave | Command::BgRewriteAof => SERVER | ADMIN | SLOW, Command::FlushDb { .. } => KEYSPACE | WRITE | ADMIN | DANGEROUS | SLOW, + Command::FlushAll { .. } => KEYSPACE | WRITE | ADMIN | DANGEROUS | SLOW, + Command::MemoryUsage { .. } => READ | KEYSPACE | SLOW, Command::ConfigGet { .. } => SERVER | ADMIN | SLOW, Command::ConfigSet { .. } | Command::ConfigRewrite => SERVER | ADMIN | SLOW, Command::SlowLogGet { .. } | Command::SlowLogLen | Command::SlowLogReset => { @@ -676,7 +681,8 @@ impl Command { | Command::Restore { key, .. } | Command::Sort { key, .. } | Command::GetDel { key } - | Command::GetEx { key, .. } => Some(key), + | Command::GetEx { key, .. } + | Command::MemoryUsage { key } => Some(key), Command::LMove { source, .. } => Some(source), Command::Copy { source, .. } => Some(source), Command::SMove { source, .. } => Some(source), diff --git a/crates/ember-protocol/src/command/mod.rs b/crates/ember-protocol/src/command/mod.rs index f45ed0c3..3c5fe85c 100644 --- a/crates/ember-protocol/src/command/mod.rs +++ b/crates/ember-protocol/src/command/mod.rs @@ -173,6 +173,12 @@ pub enum Command { /// FLUSHDB \[ASYNC\]. Removes all keys from the database. FlushDb { async_mode: bool }, + /// FLUSHALL \[ASYNC\]. Removes all keys (alias for FLUSHDB — Ember is single-database). + FlushAll { async_mode: bool }, + + /// MEMORY USAGE key \[SAMPLES count\]. Returns estimated memory usage in bytes. + MemoryUsage { key: String }, + /// CONFIG GET `pattern`. Returns matching server configuration parameters. ConfigGet { pattern: String }, diff --git a/crates/ember-protocol/src/command/parse.rs b/crates/ember-protocol/src/command/parse.rs index 1fc51656..04b48b78 100644 --- a/crates/ember-protocol/src/command/parse.rs +++ b/crates/ember-protocol/src/command/parse.rs @@ -115,6 +115,8 @@ impl Command { "BGSAVE" => parse_bgsave(&frames[1..]), "BGREWRITEAOF" => parse_bgrewriteaof(&frames[1..]), "FLUSHDB" => parse_flushdb(&frames[1..]), + "FLUSHALL" => parse_flushall(&frames[1..]), + "MEMORY" => parse_memory_cmd(&frames[1..]), "SCAN" => parse_scan(&frames[1..]), "SSCAN" => parse_key_scan(&frames[1..], "SSCAN"), "HSCAN" => parse_key_scan(&frames[1..], "HSCAN"), @@ -1021,6 +1023,39 @@ fn parse_flushdb(args: &[Frame]) -> Result { Err(wrong_arity("FLUSHDB")) } +fn parse_flushall(args: &[Frame]) -> Result { + if args.is_empty() { + return Ok(Command::FlushAll { async_mode: false }); + } + if args.len() == 1 { + let arg = extract_string(&args[0])?; + if arg.eq_ignore_ascii_case("ASYNC") { + return Ok(Command::FlushAll { async_mode: true }); + } + } + Err(wrong_arity("FLUSHALL")) +} + +fn parse_memory_cmd(args: &[Frame]) -> Result { + if args.is_empty() { + return Err(ProtocolError::WrongArity("memory".into())); + } + let subcommand = extract_string(&args[0])?; + if subcommand.eq_ignore_ascii_case("USAGE") { + if args.len() < 2 { + return Err(wrong_arity("MEMORY USAGE")); + } + let key = extract_string(&args[1])?; + // Accept but ignore SAMPLES count — we always use the cached value size. + Ok(Command::MemoryUsage { key }) + } else { + Err(ProtocolError::InvalidCommandFrame(format!( + "unknown subcommand '{}' for 'memory' command", + subcommand + ))) + } +} + fn parse_unlink(args: &[Frame]) -> Result { if args.is_empty() { return Err(wrong_arity("UNLINK")); diff --git a/crates/ember-server/src/acl.rs b/crates/ember-server/src/acl.rs index cb8857ea..eb5bec05 100644 --- a/crates/ember-server/src/acl.rs +++ b/crates/ember-server/src/acl.rs @@ -873,6 +873,7 @@ fn commands_in_category(flag: u64) -> Vec<&'static str> { Command::BgSave, Command::BgRewriteAof, Command::FlushDb { async_mode: false }, + Command::FlushAll { async_mode: false }, Command::ConfigGet { pattern: String::new(), }, diff --git a/crates/ember-server/src/connection/execute.rs b/crates/ember-server/src/connection/execute.rs index eebbd263..78e694e3 100644 --- a/crates/ember-server/src/connection/execute.rs +++ b/crates/ember-server/src/connection/execute.rs @@ -856,6 +856,30 @@ pub(super) async fn execute( } } + // Ember is single-database, so FLUSHALL is identical to FLUSHDB. + Command::FlushAll { async_mode } => { + let req = if async_mode { + || ShardRequest::FlushDbAsync + } else { + || ShardRequest::FlushDb + }; + match engine.broadcast(req).await { + Ok(_) => Frame::Simple("OK".into()), + Err(e) => Frame::Error(format!("ERR {e}")), + } + } + + Command::MemoryUsage { key } => { + let idx = engine.shard_for_key(&key); + let req = ShardRequest::MemoryUsage { key: key.clone() }; + match engine.send_to_shard(idx, req).await { + Ok(ShardResponse::Integer(-1)) => Frame::Null, + Ok(ShardResponse::Integer(n)) => Frame::Integer(n), + Ok(other) => Frame::Error(format!("ERR unexpected shard response: {other:?}")), + Err(e) => Frame::Error(format!("ERR {e}")), + } + } + Command::Keys { pattern } => { match engine .broadcast(|| ShardRequest::Keys { From 18363382d0678faf9da258b06fa0804ffed44bea Mon Sep 17 00:00:00 2001 From: Kacy Fortner Date: Thu, 26 Feb 2026 20:49:33 -0500 Subject: [PATCH 2/2] docs: mark geo as coming soon, clarify lua exclusion, add not-supported section - FLUSHALL and MEMORY USAGE rows updated in compatibility matrix - geo commands changed from "not implemented yet" to "coming in a future release" - lua scripting exclusion made explicit (not just anti-goal; not planned) - new "not supported" section in README listing excluded features with rationale - new "notable differences from redis" section covering shard rename semantics, transaction atomicity, and the RESP3 vs gRPC port split --- README.md | 20 +++++++++++++++++++- docs/compatibility.md | 8 ++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0a44c9b8..9f979d51 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ a low-latency, memory-efficient, distributed cache written in Rust. designed to - **sets** — SADD, SREM, SMEMBERS, SISMEMBER, SCARD, SMISMEMBER, SUNION, SINTER, SDIFF, SUNIONSTORE, SINTERSTORE, SDIFFSTORE, SRANDMEMBER, SPOP, SSCAN, SMOVE, SINTERCARD - **bitmaps** — GETBIT, SETBIT, BITCOUNT, BITPOS, BITOP - **key commands** — DEL, UNLINK, EXISTS, EXPIRE, EXPIREAT, EXPIRETIME, TTL, PEXPIRE, PEXPIREAT, PEXPIRETIME, PTTL, PERSIST, TYPE, SCAN, KEYS, RENAME, COPY, TOUCH, RANDOMKEY, SORT, OBJECT ENCODING/REFCOUNT, WAIT -- **server commands** — PING, ECHO, INFO, DBSIZE, FLUSHDB, BGSAVE, BGREWRITEAOF, AUTH, QUIT, CONFIG GET/SET/REWRITE, SLOWLOG, CLIENT ID/SETNAME/GETNAME/LIST, TIME, LASTSAVE, ROLE, MONITOR +- **server commands** — PING, ECHO, INFO, DBSIZE, FLUSHDB, FLUSHALL, MEMORY USAGE, BGSAVE, BGREWRITEAOF, AUTH, QUIT, CONFIG GET/SET/REWRITE, SLOWLOG, CLIENT ID/SETNAME/GETNAME/LIST, TIME, LASTSAVE, ROLE, MONITOR - **transactions** — MULTI, EXEC, DISCARD, WATCH/UNWATCH for optimistic locking - **acl** — per-user command permissions and key pattern restrictions: ACL SETUSER, GETUSER, DELUSER, LIST, WHOAMI, CAT, USERS - **pub/sub** — SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, plus PUBSUB introspection @@ -46,6 +46,24 @@ a low-latency, memory-efficient, distributed cache written in Rust. designed to - **interactive CLI** — `ember-cli` with REPL, syntax highlighting, tab-completion, inline hints, cluster subcommands, and built-in benchmark - **graceful shutdown** — drains active connections on SIGINT/SIGTERM before exiting +## not supported + +ember is purpose-built for caching. some Redis features are intentionally excluded: + +- **lua scripting** — `EVAL`, `EVALSHA`, `SCRIPT *` are not implemented and not planned. server-side logic may be supported via WASM extensions in a future release. +- **streams** — `XADD`, `XREAD`, and related commands are not implemented. use a dedicated stream store (Kafka, Redpanda, etc.) for this. +- **multiple databases** — `SELECT`, `MOVE`, `SWAPDB` are not supported. ember is single-database by design. +- **bitfield** — `BITFIELD` and `BITFIELD_RO` are not planned; use application-level serialization instead. + +see [docs/compatibility.md](docs/compatibility.md) for the full command support matrix. + +## notable differences from redis + +- **RENAME** requires the source and destination keys to hash to the same shard. cross-shard renames return an error. +- **transactions** (MULTI/EXEC) are fully atomic within a single shard. cross-shard transactions execute in order but are not globally atomic — the same limitation as Redis Cluster. +- **geo commands** are coming in a future release. +- `redis-cli` connects on port 6379 (RESP3). the typed gRPC API is on port 6380 (when `--features grpc` is compiled in). + ## install **homebrew (macOS/Linux)** diff --git a/docs/compatibility.md b/docs/compatibility.md index 6c785f47..67d54619 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -242,7 +242,7 @@ Ember also exposes port `6379` by default, the same as Redis, so most default co | CLIENT SETNAME | ✓ | | | CLIENT GETNAME | ✓ | | | CLIENT LIST | ✓ | | -| FLUSHALL | ✗ | use FLUSHDB instead | +| FLUSHALL | ✓ | ASYNC mode supported; single-database, equivalent to FLUSHDB | | SAVE | ✗ | use BGSAVE instead | | SHUTDOWN | ✗ | use SIGTERM instead | | DEBUG | ✗ | not implemented | @@ -257,7 +257,7 @@ Ember also exposes port `6379` by default, the same as Redis, so most default co | CLIENT NO-EVICT | ✗ | not implemented | | CLIENT NO-TOUCH | ✗ | not implemented | | LATENCY | ✗ | not implemented | -| MEMORY USAGE | ✗ | not implemented | +| MEMORY USAGE | ✓ | returns estimated key memory in bytes; SAMPLES option accepted and ignored | | MEMORY STATS | ✗ | not implemented | | MEMORY DOCTOR | ✗ | not implemented | | RESET | ✗ | not implemented | @@ -334,7 +334,7 @@ all cluster commands are implemented. see the cluster documentation for operatio some Redis commands are explicitly not planned for Ember: **scripting** -- `EVAL`, `EVALSHA`, `EVALRO`, `SCRIPT LOAD`, `SCRIPT EXISTS`, `SCRIPT FLUSH` — Lua scripting is an anti-goal. We may support WASM-based extensions in the future instead. +- `EVAL`, `EVALSHA`, `EVALRO`, `SCRIPT LOAD`, `SCRIPT EXISTS`, `SCRIPT FLUSH` — Lua scripting is explicitly not planned. if you need server-side logic, WASM-based extensions may be supported in a future release. - `FCALL`, `FUNCTION LOAD`, `FUNCTION LIST`, `FUNCTION DELETE` — same reasoning as EVAL. **streams** @@ -344,7 +344,7 @@ some Redis commands are explicitly not planned for Ember: - `BITFIELD`, `BITFIELD_RO` — not planned; use application-level serialization if needed. **geo** -- `GEOADD`, `GEOPOS`, `GEODIST`, `GEORADIUS`, `GEORADIUSBYMEMBER`, `GEOSEARCH`, `GEOSEARCHSTORE`, `GEOHASH` — not implemented yet. +- `GEOADD`, `GEOPOS`, `GEODIST`, `GEORADIUS`, `GEORADIUSBYMEMBER`, `GEOSEARCH`, `GEOSEARCHSTORE`, `GEOHASH` — coming in a future release. **other** - `LOLWUT` — not implemented.