Skip to content

Commit 631dbb0

Browse files
Drop columnar frames this process cannot decode
The producers numpy is not the controllers: replicas carry per-deployment runtime_envs, so a handle can have numpy where the controller does not. Wire-detect reads the magic bytes and needs no numpy, but decoding does, so such a frame reached np.frombuffer with np set to None and failed inside ingest -- losing the report with no explanation and undercounting load at exactly the fan-in sizes this path targets. Check decodability before decoding and drop the frame with a one-shot warning naming the cause and the fix. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: John Taylor <john.taylor@anyscale.com>
1 parent 78a9151 commit 631dbb0

3 files changed

Lines changed: 45 additions & 1 deletion

File tree

python/ray/serve/_private/autoscaling_metrics_codec.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@
4848
logger = logging.getLogger(SERVE_LOGGER_NAME)
4949

5050
_MAGIC = b"SCR1"
51-
# Warn at most once per process that numpy is missing (see should_encode_columnar).
51+
# Warn at most once per process each (see should_encode_columnar / can_decode_columnar).
5252
_WARNED_NUMPY_MISSING = False
53+
_WARNED_NUMPY_UNDECODABLE = False
5354

5455

5556
def is_columnar(buf: bytes) -> bool:
@@ -153,6 +154,27 @@ def should_encode_columnar(
153154
return _widest_metric(report) >= RAY_SERVE_COLUMNAR_METRICS_MIN_REPLICAS
154155

155156

157+
def can_decode_columnar() -> bool:
158+
"""Whether this process can decode columnar frames. The producer's numpy is not
159+
the controller's: replicas carry per-deployment runtime_envs, so a frame can
160+
arrive here from a process that had numpy when this one does not."""
161+
return np is not None
162+
163+
164+
def warn_columnar_undecodable_once() -> None:
165+
"""Warn once per process; a dropped report would otherwise just undercount load."""
166+
global _WARNED_NUMPY_UNDECODABLE
167+
if _WARNED_NUMPY_UNDECODABLE:
168+
return
169+
_WARNED_NUMPY_UNDECODABLE = True
170+
logger.warning(
171+
"Dropping columnar autoscaling metrics: they were sent by a process that has "
172+
"numpy, but numpy is not installed here, so they cannot be decoded. Autoscaling "
173+
"will undercount load for the affected deployments until numpy is installed in "
174+
"this process's environment."
175+
)
176+
177+
156178
def _widest_metric(report: HandleMetricReport) -> int:
157179
"""Replica count of the report's widest metric, which is what the width gate reads."""
158180
return max(

python/ray/serve/_private/controller.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ def record_autoscaling_metrics_from_replica(
417417
_ingest_start = time.time()
418418
if isinstance(replica_metric_report, bytes):
419419
if autoscaling_metrics_codec.is_columnar(replica_metric_report):
420+
if not autoscaling_metrics_codec.can_decode_columnar():
421+
# Wire-detect works without numpy but decoding does not.
422+
autoscaling_metrics_codec.warn_columnar_undecodable_once()
423+
return
420424
if (
421425
RAY_SERVE_AGGREGATE_METRICS_AT_CONTROLLER
422426
or RAY_SERVE_ENABLE_DIRECT_INGRESS
@@ -481,6 +485,10 @@ def record_autoscaling_metrics_from_handle(
481485
_ingest_start = time.time()
482486
if isinstance(handle_metric_report, bytes):
483487
if autoscaling_metrics_codec.is_columnar(handle_metric_report):
488+
if not autoscaling_metrics_codec.can_decode_columnar():
489+
# Wire-detect works without numpy but decoding does not.
490+
autoscaling_metrics_codec.warn_columnar_undecodable_once()
491+
return
484492
if (
485493
RAY_SERVE_AGGREGATE_METRICS_AT_CONTROLLER
486494
or RAY_SERVE_ENABLE_DIRECT_INGRESS

python/ray/serve/tests/unit/test_columnar_controller_dispatch.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,20 @@ def test_handle_cloudpickle_uses_object_store(monkeypatch):
146146
asm.record_request_metrics_for_handle.assert_called_once()
147147

148148

149+
def test_columnar_frame_dropped_when_this_process_lacks_numpy(monkeypatch):
150+
"""A producer with numpy (replicas get their own runtime_env) can send columnar
151+
frames to a controller without it. Wire-detect needs no numpy but decoding does,
152+
so the frame must be dropped with a warning rather than raising inside ingest."""
153+
monkeypatch.setattr(codec, "np", None)
154+
assert codec.can_decode_columnar() is False
155+
monkeypatch.setattr(codec, "_WARNED_NUMPY_UNDECODABLE", False)
156+
warn = MagicMock()
157+
monkeypatch.setattr(codec.logger, "warning", warn)
158+
codec.warn_columnar_undecodable_once()
159+
codec.warn_columnar_undecodable_once()
160+
assert warn.call_count == 1 # once per process, not once per report
161+
162+
149163
if __name__ == "__main__":
150164
import sys
151165

0 commit comments

Comments
 (0)