From faa7fdf82e84e81ed6ddcaa002d555da25edb7d7 Mon Sep 17 00:00:00 2001 From: Cesar Rodas Date: Mon, 20 Jul 2026 11:44:59 -0300 Subject: [PATCH] Avoid heap allocations when encoding integer and float parameters The sharding hasher and the query-rewrite paths formatted integers and floats with to_string() only to read the resulting bytes and immediately drop the String. On the per-query and per-message paths this is a wasted heap allocation on every call, since Parameter::new and BytesMut copy the bytes into an owned buffer right after. Format into stack itoa/ryu buffers instead. The bytes fed to the hasher and the wire encoder are unchanged for integers (decimal, no padding), so behavior is identical. While in the sharding hasher, compute the SHA1 shard key directly from the digest bytes instead of formatting the digest to a hex string, slicing the last eight characters, and parsing them back with from_str_radix. The result is the same value, without the allocation or the unwrap. --- Cargo.lock | 2 ++ pgdog/Cargo.toml | 2 ++ .../router/parser/rewrite/statement/offset.rs | 4 +++- .../router/parser/rewrite/statement/plan.rs | 2 +- .../router/parser/rewrite/statement/update.rs | 14 ++++++++------ pgdog/src/frontend/router/sharding/hasher.rs | 7 ++----- pgdog/src/net/parameter.rs | 4 +++- 7 files changed, 21 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afd91087f..48c89ba89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3360,6 +3360,7 @@ dependencies = [ "hyper-util", "indexmap", "itertools 0.15.0", + "itoa", "lazy_static", "libc", "lru", @@ -3384,6 +3385,7 @@ dependencies = [ "rust_decimal", "rustls-native-certs", "rustls-pki-types", + "ryu", "scram", "semver", "serde", diff --git a/pgdog/Cargo.toml b/pgdog/Cargo.toml index f47e11135..53ba28b52 100644 --- a/pgdog/Cargo.toml +++ b/pgdog/Cargo.toml @@ -41,6 +41,8 @@ toml = "0.8" pgdog-plugin.workspace = true tokio-util = { version = "0.7", features = ["rt"] } fnv = "1" +itoa = "1" +ryu = "1" scram = { git = "https://github.com/pgdogdev/scram.git", rev = "053e108210a35232efe60023ec43288fd601d8c4" } base64 = "0.22" md5 = "0.7" diff --git a/pgdog/src/frontend/router/parser/rewrite/statement/offset.rs b/pgdog/src/frontend/router/parser/rewrite/statement/offset.rs index a44cf6227..770ea0783 100644 --- a/pgdog/src/frontend/router/parser/rewrite/statement/offset.rs +++ b/pgdog/src/frontend/router/parser/rewrite/statement/offset.rs @@ -66,7 +66,9 @@ impl OffsetPlan { let fmt = bind.parameter_format(idx)?; let param = match fmt { Format::Binary => Parameter::new(&(new_limit as i64).to_be_bytes()), - Format::Text => Parameter::new(new_limit.to_string().as_bytes()), + Format::Text => { + Parameter::new(itoa::Buffer::new().format(new_limit).as_bytes()) + } }; bind.set_param(idx, param); } diff --git a/pgdog/src/frontend/router/parser/rewrite/statement/plan.rs b/pgdog/src/frontend/router/parser/rewrite/statement/plan.rs index 53cb51d32..6b2bbb9e8 100644 --- a/pgdog/src/frontend/router/parser/rewrite/statement/plan.rs +++ b/pgdog/src/frontend/router/parser/rewrite/statement/plan.rs @@ -90,7 +90,7 @@ impl RewritePlan { let id = generator.next_id(); let param = match format { Format::Binary => Parameter::new(&id.to_be_bytes()), - Format::Text => Parameter::new(id.to_string().as_bytes()), + Format::Text => Parameter::new(itoa::Buffer::new().format(id).as_bytes()), }; bind.push_param(param, format); } diff --git a/pgdog/src/frontend/router/parser/rewrite/statement/update.rs b/pgdog/src/frontend/router/parser/rewrite/statement/update.rs index fa75bc4e1..e230215bc 100644 --- a/pgdog/src/frontend/router/parser/rewrite/statement/update.rs +++ b/pgdog/src/frontend/router/parser/rewrite/statement/update.rs @@ -353,15 +353,17 @@ impl Insert { bind.push_param(param.parameter().clone(), param.format()) } - Value::Integer(int) => { - bind.push_param(Parameter::new(int.to_string().as_bytes()), Format::Text) - } + Value::Integer(int) => bind.push_param( + Parameter::new(itoa::Buffer::new().format(int).as_bytes()), + Format::Text, + ), Value::String(s) => bind.push_param(Parameter::new(s.as_bytes()), Format::Text), - Value::Float(f) => { - bind.push_param(Parameter::new(f.to_string().as_bytes()), Format::Text) - } + Value::Float(f) => bind.push_param( + Parameter::new(ryu::Buffer::new().format(f).as_bytes()), + Format::Text, + ), Value::Boolean(b) => bind.push_param( Parameter::new(if b { "t".as_bytes() } else { "f".as_bytes() }), diff --git a/pgdog/src/frontend/router/sharding/hasher.rs b/pgdog/src/frontend/router/sharding/hasher.rs index 78bc4183a..ac73f1f3b 100644 --- a/pgdog/src/frontend/router/sharding/hasher.rs +++ b/pgdog/src/frontend/router/sharding/hasher.rs @@ -15,7 +15,7 @@ impl Hasher { pub fn bigint(&self, value: i64) -> u64 { match self { Hasher::Postgres => bigint(value), - Hasher::Sha1 => Self::sha1(value.to_string().as_bytes()), + Hasher::Sha1 => Self::sha1(itoa::Buffer::new().format(value).as_bytes()), } } @@ -38,10 +38,7 @@ impl Hasher { hasher.update(bytes); let hash = hasher.finalize(); - let hex = format!("{:x}", hash); - let key = i64::from_str_radix(&hex[hex.len() - 8..], 16).unwrap(); - - key as u64 + u32::from_be_bytes([hash[16], hash[17], hash[18], hash[19]]) as u64 } } diff --git a/pgdog/src/net/parameter.rs b/pgdog/src/net/parameter.rs index cd4583a82..88bcc76af 100644 --- a/pgdog/src/net/parameter.rs +++ b/pgdog/src/net/parameter.rs @@ -84,7 +84,9 @@ impl ToBytes for ParameterValue { .join(", ".as_bytes()); bytes.put(Bytes::from(values)); } - Self::Integer(integer) => bytes.put_slice(integer.to_string().as_bytes()), + Self::Integer(integer) => { + bytes.put_slice(itoa::Buffer::new().format(*integer).as_bytes()) + } } bytes.put_u8(0);