From 4bf0cdb396a6dda58ed6c902df8a954e6f3dad16 Mon Sep 17 00:00:00 2001 From: abrar Date: Sat, 7 Feb 2026 08:35:18 +0000 Subject: [PATCH 1/2] [Serve] Eliminate per-replica-per-tick Pydantic rebuild in update_actor_details Signed-off-by: abrar --- python/ray/serve/_private/deployment_state.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/python/ray/serve/_private/deployment_state.py b/python/ray/serve/_private/deployment_state.py index 37c7a5f90d4c..8f6e999dd542 100644 --- a/python/ray/serve/_private/deployment_state.py +++ b/python/ray/serve/_private/deployment_state.py @@ -1474,9 +1474,14 @@ def update_state(self, state: ReplicaState) -> None: self.update_actor_details(state=state) def update_actor_details(self, **kwargs) -> None: - details_kwargs = self._actor_details.dict() - details_kwargs.update(kwargs) - self._actor_details = ReplicaDetails(**details_kwargs) + # Fast path: skip if all provided values are already current. + # This avoids unnecessary object creation on every tick when the + # pop-iterate-readd pattern re-adds replicas without state changes. + if all(getattr(self._actor_details, k, None) == v for k, v in kwargs.items()): + return + # Use .copy(update=...) instead of .dict() + reconstruction to avoid + # full Pydantic serialization and validation on every update. + self._actor_details = self._actor_details.copy(update=kwargs) def resource_requirements(self) -> Tuple[str, str]: """Returns required and currently available resources. From 76a6cf7780b58f7023c2888e8d8aceacfb55129f Mon Sep 17 00:00:00 2001 From: abrar Date: Sat, 7 Feb 2026 08:48:10 +0000 Subject: [PATCH 2/2] use sentinel Signed-off-by: abrar --- python/ray/serve/_private/deployment_state.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/python/ray/serve/_private/deployment_state.py b/python/ray/serve/_private/deployment_state.py index 8f6e999dd542..940276c7a73c 100644 --- a/python/ray/serve/_private/deployment_state.py +++ b/python/ray/serve/_private/deployment_state.py @@ -1473,11 +1473,19 @@ def update_state(self, state: ReplicaState) -> None: """Updates state in actor details.""" self.update_actor_details(state=state) + _SENTINEL = object() + def update_actor_details(self, **kwargs) -> None: # Fast path: skip if all provided values are already current. # This avoids unnecessary object creation on every tick when the # pop-iterate-readd pattern re-adds replicas without state changes. - if all(getattr(self._actor_details, k, None) == v for k, v in kwargs.items()): + # We use _SENTINEL (not None) as the getattr default so that an + # invalid field name always fails the check and falls through to + # .copy(), which will raise an appropriate error. + if all( + getattr(self._actor_details, k, self._SENTINEL) == v + for k, v in kwargs.items() + ): return # Use .copy(update=...) instead of .dict() + reconstruction to avoid # full Pydantic serialization and validation on every update.