From 5a497f7d1fcd2c9a87913588816ef9e2cd20ad7a Mon Sep 17 00:00:00 2001 From: Kacy Fortner Date: Thu, 12 Feb 2026 11:56:07 -0500 Subject: [PATCH 1/3] perf: validate UTF-8 in-place in extract_string replaces data.to_vec() + String::from_utf8() with std::str::from_utf8() + to_owned(). validates UTF-8 directly on the Bytes buffer without an intermediate Vec allocation, then allocates only once for the final String. --- crates/ember-protocol/src/command.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/ember-protocol/src/command.rs b/crates/ember-protocol/src/command.rs index 923e4f25..b46f7501 100644 --- a/crates/ember-protocol/src/command.rs +++ b/crates/ember-protocol/src/command.rs @@ -680,11 +680,17 @@ impl Command { } /// Extracts a UTF-8 string from a Bulk or Simple frame. +/// +/// Validates UTF-8 in-place on the Bytes buffer to avoid an +/// intermediate Vec allocation from `to_vec()`. fn extract_string(frame: &Frame) -> Result { match frame { - Frame::Bulk(data) => String::from_utf8(data.to_vec()).map_err(|_| { - ProtocolError::InvalidCommandFrame("command name is not valid utf-8".into()) - }), + Frame::Bulk(data) => { + let s = std::str::from_utf8(data).map_err(|_| { + ProtocolError::InvalidCommandFrame("command name is not valid utf-8".into()) + })?; + Ok(s.to_owned()) + } Frame::Simple(s) => Ok(s.clone()), _ => Err(ProtocolError::InvalidCommandFrame( "expected bulk or simple string for command name".into(), From 7992844fbfe02eb7547ff330dc6828ceae4f2e3b Mon Sep 17 00:00:00 2001 From: Kacy Fortner Date: Thu, 12 Feb 2026 11:58:51 -0500 Subject: [PATCH 2/3] perf: add get_string() for direct Bytes return on GET MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adds Keyspace::get_string() that returns Option directly, avoiding the Value enum wrapper. the shard GET dispatch now uses this path — Bytes::clone() is just a refcount increment, and we skip the full Value::clone() + pattern match overhead. --- crates/ember-core/src/keyspace.rs | 21 +++++++++++++++++++++ crates/ember-core/src/shard.rs | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/ember-core/src/keyspace.rs b/crates/ember-core/src/keyspace.rs index 5f82232a..4dc40a92 100644 --- a/crates/ember-core/src/keyspace.rs +++ b/crates/ember-core/src/keyspace.rs @@ -369,6 +369,27 @@ impl Keyspace { } } + /// Retrieves the raw `Bytes` for a string key, avoiding the `Value` + /// enum wrapper. `Bytes::clone()` is a cheap refcount increment. + /// + /// Returns `Err(WrongType)` if the key holds a non-string value. + pub fn get_string(&mut self, key: &str) -> Result, WrongType> { + if self.remove_if_expired(key) { + return Ok(None); + } + match self.entries.get_mut(key) { + Some(e) => match &e.value { + Value::String(b) => { + let data = b.clone(); // Bytes::clone is a refcount bump + e.touch(); + Ok(Some(data)) + } + _ => Err(WrongType), + }, + None => Ok(None), + } + } + /// Returns the type name of the value at `key`, or "none" if missing. pub fn value_type(&mut self, key: &str) -> &'static str { if self.remove_if_expired(key) { diff --git a/crates/ember-core/src/shard.rs b/crates/ember-core/src/shard.rs index c17dc5e6..f9bfafd1 100644 --- a/crates/ember-core/src/shard.rs +++ b/crates/ember-core/src/shard.rs @@ -775,8 +775,8 @@ fn dispatch( #[cfg(feature = "protobuf")] schema_registry: &Option, ) -> ShardResponse { match req { - ShardRequest::Get { key } => match ks.get(key) { - Ok(val) => ShardResponse::Value(val), + ShardRequest::Get { key } => match ks.get_string(key) { + Ok(val) => ShardResponse::Value(val.map(Value::String)), Err(_) => ShardResponse::WrongType, }, ShardRequest::Set { From 910efe7b24775e5cc1e2672d8bfdd2b5fb104ad1 Mon Sep 17 00:00:00 2001 From: Kacy Fortner Date: Thu, 12 Feb 2026 11:59:42 -0500 Subject: [PATCH 3/3] perf: use ahash for shard routing instead of SipHash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit replaces DefaultHasher (SipHash-2-4) with ahash for shard key routing. ahash is ~3x faster for short keys. shard routing is trusted internal logic — the keys come from client commands, not untrusted input that could craft hash collisions. --- crates/ember-core/Cargo.toml | 1 + crates/ember-core/src/engine.rs | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/ember-core/Cargo.toml b/crates/ember-core/Cargo.toml index b84a020c..d0c72170 100644 --- a/crates/ember-core/Cargo.toml +++ b/crates/ember-core/Cargo.toml @@ -29,6 +29,7 @@ rand = { workspace = true } ordered-float = { workspace = true } prost-reflect = { workspace = true, optional = true } usearch = { workspace = true, optional = true } +ahash = "0.8" dashmap = "6" parking_lot = "0.12" diff --git a/crates/ember-core/src/engine.rs b/crates/ember-core/src/engine.rs index f5976660..801d1a00 100644 --- a/crates/ember-core/src/engine.rs +++ b/crates/ember-core/src/engine.rs @@ -4,7 +4,6 @@ //! of the key. Each shard is an independent tokio task — no locks on //! the hot path. -use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use crate::dropper::DropHandle; @@ -198,11 +197,12 @@ impl Engine { /// Pure function: maps a key to a shard index. /// -/// Uses `DefaultHasher` (SipHash) and modulo. Deterministic within a -/// single process — that's all we need for local sharding. CRC16 will -/// replace this when cluster-level slot assignment arrives. +/// Uses ahash (AHash) for fast, non-cryptographic hashing. ~3x faster +/// than SipHash for short keys. Deterministic within a single process — +/// that's all we need for local sharding. Shard routing is trusted +/// internal logic so DoS-resistant hashing is unnecessary here. fn shard_index(key: &str, shard_count: usize) -> usize { - let mut hasher = DefaultHasher::new(); + let mut hasher = ahash::AHasher::default(); key.hash(&mut hasher); (hasher.finish() as usize) % shard_count }