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 } 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 { 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(),