From 27f7b2a8a22e0be13b14a888dee7a9ee7ef48582 Mon Sep 17 00:00:00 2001 From: David Lutterkort Date: Fri, 13 Feb 2026 13:17:50 -0800 Subject: [PATCH] graph: Avoid unnecessary entity deep clone in as_modifications Use Arc::try_unwrap to move the entity out of the Arc without cloning when the refcount is 1 (the common case after remove() from the LFU cache). Also make merge_remove_null_fields return whether it changed anything, replacing the post-merge full entity comparison. --- graph/src/components/store/entity_cache.rs | 8 +++++--- graph/src/data/store/mod.rs | 21 ++++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/graph/src/components/store/entity_cache.rs b/graph/src/components/store/entity_cache.rs index 4993339831a..7353ad17709 100644 --- a/graph/src/components/store/entity_cache.rs +++ b/graph/src/components/store/entity_cache.rs @@ -518,12 +518,14 @@ impl EntityCache { } // Entity may have been changed (Some(current), EntityOp::Update(updates)) => { - let mut data = current.as_ref().clone(); - data.merge_remove_null_fields(updates) + let mut data = + Arc::try_unwrap(current).unwrap_or_else(|arc| arc.as_ref().clone()); + let changed = data + .merge_remove_null_fields(updates) .map_err(|e| key.unknown_attribute(e))?; let data = Arc::new(data); self.current.insert(key.clone(), Some(data.cheap_clone())); - if current != data { + if changed { Some(Overwrite { key, data, diff --git a/graph/src/data/store/mod.rs b/graph/src/data/store/mod.rs index cdd9be06a37..9bcbf52f08f 100644 --- a/graph/src/data/store/mod.rs +++ b/graph/src/data/store/mod.rs @@ -1014,14 +1014,25 @@ impl Entity { /// If a key exists in both entities, the value from `update` is chosen. /// If a key only exists on one entity, the value from that entity is chosen. /// If a key is set to `Value::Null` in `update`, the key/value pair is removed. - pub fn merge_remove_null_fields(&mut self, update: Entity) -> Result<(), InternError> { + pub fn merge_remove_null_fields(&mut self, update: Entity) -> Result { + let mut changed = false; for (key, value) in update.0.into_iter() { match value { - Value::Null => self.0.remove(&key), - _ => self.0.insert(&key, value)?, - }; + Value::Null => { + if self.0.remove(&key).is_some() { + changed = true; + } + } + _ => { + let different = self.0.get(key.as_str()).is_none_or(|old| *old != value); + self.0.insert(&key, value)?; + if different { + changed = true; + } + } + } } - Ok(()) + Ok(changed) } /// Remove all entries with value `Value::Null` from `self`