API Reference
The kubemq-rayserve package surface — adapter methods, extension methods, the autoscaling policy, metrics, and TaskResult statuses.
This page is the authoritative reference for the kubemq-rayserve API: every exported symbol, the
adapter's methods, the extension methods, the autoscaling policy, the metrics dictionary, and the
TaskResult status model. For the configuration fields and dependencies, see the
configuration reference. For the design model, see Concepts.
Package surface
kubemq-rayserve implements Ray Serve's TaskProcessorAdapter framework — the first non-Celery
adapter — and is published as version 1.0.0 (Development Status: Beta) under the MIT license. The
public API consists of exactly three symbols, exported from kubemq_rayserve:
from kubemq_rayserve.adapter import KubeMQTaskProcessorAdapter
from kubemq_rayserve.autoscaling import kubemq_queue_depth_policy
from kubemq_rayserve.config import KubeMQAdapterConfig
__all__ = [
"KubeMQAdapterConfig",
"KubeMQTaskProcessorAdapter",
"kubemq_queue_depth_policy",
]| Symbol | Kind | Purpose |
|---|---|---|
KubeMQAdapterConfig | Pydantic BaseModel | Connection and behavior configuration (reference) |
KubeMQTaskProcessorAdapter | TaskProcessorAdapter subclass | Queue-based async + Query-based sync inference engine |
kubemq_queue_depth_policy | Function | Ray Serve custom autoscaling policy driven by queue depth |
Everything outside these three symbols (the _internal package, ResultBackend,
MetricsCollector) is private and may change between releases. Do not import it.
KubeMQTaskProcessorAdapter
KubeMQTaskProcessorAdapter implements Ray Serve's TaskProcessorAdapter ABC (7 abstract + 4
optional methods) plus two extension methods. The constructor stores the config only — no network
connections are made until initialize() runs.
from kubemq_rayserve import KubeMQAdapterConfig, KubeMQTaskProcessorAdapter
adapter = KubeMQTaskProcessorAdapter(KubeMQAdapterConfig(address="localhost:50000"))Core methods
These are the methods Ray Serve's framework calls during the deployment lifecycle.
| Method | Description |
|---|---|
initialize(consumer_concurrency=1, **kwargs) | Create SDK clients and store concurrency + task_processor_config fields. Must run before any other method. |
register_task_handler(func, name=None) | Register a callable as a named task handler. Falls back to func.__name__ when name is None. |
enqueue_task_sync(task_name, args=None, kwargs=None, **options) | Serialize the task, send to the KubeMQ queue, return TaskResult(status="PENDING"). |
get_task_status_sync(task_id) | Peek the result channel and return a TaskResult with the current status. |
start_consumer(**kwargs) | Start the queue polling thread plus the Query subscription thread. |
stop_consumer(timeout=10.0) | Cancel the token, stop threads, and close SDK clients. |
health_check() | Return True if the broker is reachable via SDK ping(). |
cancel_task_sync(task_id) | Soft cancel — overwrite the result with CANCELLED. Always returns True. |
get_metrics_sync() | Return the 8-metric dict (see Metrics). |
Convenience aliases drop the _sync suffix and point at the same implementations:
| Alias | Target |
|---|---|
enqueue_task | enqueue_task_sync |
get_task_status | get_task_status_sync |
get_metrics | get_metrics_sync |
cancel_task | cancel_task_sync |
Extension methods
These two methods are not part of the TaskProcessorAdapter ABC. They are KubeMQ-specific
additions that exploit Queries and Events.
| Method | Description |
|---|---|
query_task_sync(task_name, args=None, kwargs=None, timeout=30, **options) | Synchronous inference via a KubeMQ Query. Blocks until the handler responds or the timeout elapses. |
report_progress(task_id, pct, detail="") | Publish a progress event via KubeMQ Events on {queue_name}.progress. |
Method signatures and raises
The two methods most likely to raise on the caller's thread are enqueue_task_sync and
query_task_sync. Their exact signatures and exception contracts are below.
enqueue_task_sync
def enqueue_task_sync(
self,
task_name: str,
args: Any = None,
kwargs: Any = None,
**options: Any,
) -> TaskResult:
...A "PENDING" result is stored on the result channel before the queue message is sent, so a fast
consumer cannot overwrite a SUCCESS result with a late PENDING store. Returns
TaskResult(id=task_id, status="PENDING", result=None).
| Raises | When |
|---|---|
ValueError | task_name is empty. |
TypeError | args or kwargs are not JSON-serializable. |
KubeMQConnectionError | The broker is unreachable. |
KubeMQAuthenticationError | The auth token is invalid. |
RuntimeError | initialize() was not called first. |
Arguments are serialized with json.dumps. NumPy arrays, tensors, and other non-JSON types raise
TypeError with guidance to convert first (for example ndarray.tolist()). Pluggable serializers
are planned for a future release.
query_task_sync
def query_task_sync(
self,
task_name: str,
args: Any = None,
kwargs: Any = None,
timeout: int = 30,
**options: Any,
) -> TaskResult:
...Sends a KubeMQ Query on the task channel and blocks until the consumer's Query subscription
responds. The effective timeout is timeout or config.sync_inference_timeout. The returned
TaskResult carries the handler's status (SUCCESS / FAILURE) and result.
| Raises | When |
|---|---|
KubeMQTimeoutError | The query times out with no response. |
TypeError | args or kwargs are not JSON-serializable. |
kubemq_queue_depth_policy
kubemq_queue_depth_policy is a Ray Serve custom autoscaling policy. It queries KubeMQ queue depth
via the Python SDK and returns a desired replica count for each deployment in ctxs.
def kubemq_queue_depth_policy(
ctxs: dict[str, Any],
kubemq_address: str = "localhost:50000",
queue_name: str = "",
tasks_per_replica: int = 5,
auth_token: str = "",
) -> tuple[dict[str, int], dict]:
...| Argument | Type | Default | Description |
|---|---|---|---|
ctxs | dict[str, Any] | required | Maps deployment_id to its AutoscalingContext. |
kubemq_address | str | "localhost:50000" | KubeMQ broker address (host:port). |
queue_name | str | "" | Queue channel name to monitor. |
tasks_per_replica | int | 5 | Target number of queued tasks per replica. |
auth_token | str | "" | JWT authentication token. |
The function returns a (decisions, state) tuple: decisions maps each deployment_id to a
desired replica count, and state carries the observed queue_depth and computed
desired_replicas for observability.
The scaling formula is:
desired_replicas = max(1, math.ceil(queue_depth / tasks_per_replica))There is a hard floor of 1 replica — scale-to-zero is not supported, and Ray Serve's built-in
downscale_delay_s handles cooldown when the queue is empty. On any error the policy logs a warning
and returns each deployment's current replica count unchanged, so a transient broker outage never
forces a scale-to-zero. A module-level, lock-protected client cache reuses a single gRPC connection
across the ~10s policy invocation interval. See the autoscaling guide for
both the in-process policy and the KEDA path.
from functools import partial
from kubemq_rayserve import kubemq_queue_depth_policy
policy = partial(
kubemq_queue_depth_policy,
kubemq_address="kubemq:50000",
queue_name="inference-tasks",
tasks_per_replica=10,
)Metrics
get_metrics_sync() returns a dict[str, Any] with exactly 8 entries. Gauge metrics query KubeMQ
live on each call; counter and histogram metrics are tracked in-memory by a thread-safe collector.
| Metric | Type | Description |
|---|---|---|
queue_depth | Gauge (int) | Current waiting messages in the task queue, from list_queues_channels().incoming.waiting. |
in_flight | Gauge (int) | Tasks currently being processed by handlers. |
dlq_depth | Gauge (int) | Waiting messages in the failed-task (DLQ) channel. |
tasks_enqueued_total | Counter (int) | Total tasks enqueued since startup. |
tasks_completed_total | Counter (dict) | Completed counts by status: {"SUCCESS": int, "FAILURE": int}. |
task_processing_duration_seconds | Histogram (dict) | Running stats: {"min", "max", "avg", "count"}. |
result_storage_retries_total | Counter (int) | Total result-storage retry attempts. |
consumer_poll_latency_seconds | Gauge (float) | Duration of the last receive_queue_messages poll. |
metrics = adapter.get_metrics_sync()
# {
# "queue_depth": 12,
# "in_flight": 2,
# "dlq_depth": 0,
# "tasks_enqueued_total": 148,
# "tasks_completed_total": {"SUCCESS": 144, "FAILURE": 4},
# "task_processing_duration_seconds": {"min": 0.04, "max": 1.21, "avg": 0.18, "count": 148},
# "result_storage_retries_total": 0,
# "consumer_poll_latency_seconds": 0.003,
# }task_processing_duration_seconds uses O(1) running statistics — min, max, sum, and count
— rather than storing every duration, so memory stays flat under sustained load. Before any task
completes, the histogram reports all-zero stats. See the metrics guide for
collection and dashboard patterns.
Task statuses and TaskResult
Every async API returns a Ray Serve TaskResult with three fields: id, status, and result.
The status is one of five string values that track a task through its lifecycle.
| Status | Meaning |
|---|---|
PENDING | Enqueued (or no result yet on the channel); awaiting a consumer. |
STARTED | Reserved status string for in-progress reporting. |
SUCCESS | Handler returned; result holds the JSON-serializable return value. |
FAILURE | Handler raised after retries were exhausted; an error field is stored alongside. |
CANCELLED | Result was overwritten by cancel_task_sync() (soft cancel). |
The stored result payload (returned by the peek-based backend) carries more than the three
TaskResult fields:
{
"task_id": "f1c2...",
"status": "SUCCESS",
"result": { "label": "POSITIVE", "score": 0.95 },
"created_at": "2026-06-01T10:00:00.000000+00:00",
"completed_at": "2026-06-01T10:00:00.180000+00:00",
"error": null
}get_task_status_sync() returns TaskResult(status="PENDING", result=None) whenever the result
channel does not exist yet, so polling a not-yet-processed task is always safe.
Progress event schema
report_progress(task_id, pct, detail="") publishes a KubeMQ Event on a derived channel. Subscribe
to that channel to stream progress without touching the result backend.
| Field | Value |
|---|---|
| Channel | {queue_name}.progress |
| Body (JSON) | { "task_id": str, "pct": float, "detail": str } |
| Tags | { "task_id": str, "pct": str } (the pct tag is the integer-truncated percentage) |
def long_task(adapter, task_id, items):
for i, item in enumerate(items):
process(item)
adapter.report_progress(task_id, (i + 1) / len(items) * 100, detail=f"item {i + 1}")See Progress Tracking for the full subscriber-side walkthrough.
Result-channel naming and expiry
Each task's result lives on its own KubeMQ queue channel, named by concatenating the configured prefix with the task id:
{result_channel_prefix}{task_id}With the default prefix this is rayserve-result-{task_id}. Results are written with
expiration_in_seconds = result_expiry_seconds (default 3600), so stale results self-purge after
the TTL. Writes use a purge-then-write pattern (ack_all_queue_messages then send_queue_message)
so state transitions overwrite cleanly. Storage retries up to 3 times with backoff and never
re-raises — for expensive ML inference, losing a result is preferable to reprocessing the task, so
on persistent failure the backend falls back to a bounded in-memory dict.
Appendix: kubemq-rayserve vs CeleryTaskProcessorAdapter
Ray Serve's reference adapter is Celery-backed. The table below contrasts the two for teams choosing a task backend.
| Feature | kubemq-rayserve | CeleryTaskProcessorAdapter |
|---|---|---|
| Message Broker | KubeMQ (Kubernetes-native) | Redis / RabbitMQ |
| Sync Inference | Built-in (query_task_sync) | Not available |
| Progress Tracking | Built-in (report_progress) | Requires custom signals |
| Autoscaling Policy | kubemq_queue_depth_policy | Manual HPA configuration |
| DLQ Monitoring | on_dlq callback | Requires Celery signals + custom code |
| Result Backend | Queue-peek (no extra infra) | Requires separate Redis/DB |
| KEDA Integration | Native (kubemq-keda-scaler) | Requires custom KEDA scaler |
| Kubernetes-native | Yes (single binary, no Erlang) | No (Redis/RabbitMQ dependencies) |
| Health Check | SDK ping() | Celery inspect().ping() |
| Setup Complexity | 1 dependency (KubeMQ) | 2+ dependencies (broker + backend) |
Was this page helpful?