Problem
Batch processing jobs that invoke a TensorFlow Serving inference service on a Kubernetes cluster are experiencing timeouts. The underlying symptom is a steadily growing inference request queue inside the service pod, eventually causing the kubectl logs output to show a backlog of pending requests and the batch job to fail with deadline exceeded errors.
Root Cause
The backlog is caused by a mismatch between the concurrency limits of the inference pod and the parallelism of the batch job. Specifically:
- The inference service is configured with
--max-concurrent-requests=8(default for the Docker image) while the model uses a GPU that can handle only 4 simultaneous in‑process inference calls. - The batch job creates a
Jobwithparallelism: 20and each pod launches 10curlworkers that continuously POST to the inference endpoint. - Kubernetes Service load‑balancing (ClusterIP) distributes traffic round‑robin across the single replica of the inference pod, but the pod cannot keep up, so the internal request queue (implemented by gRPC server’s
grpc::ServerBuilder::SetMaxReceiveMessageSize) grows unchecked. - Because the pod does not expose back‑pressure (HTTP 429) and the client code retries with exponential back‑off, the retry storm further inflates the queue.
The combination of over‑provisioned client concurrency and under‑provisioned inference capacity creates a self‑reinforcing backlog that eventually exhausts the pod’s memory, triggers OOM kills, and forces the batch job to timeout.
Debug
1. Observe queue length in pod logs
2026-10-28T14:12:03.421Z INFO request_queue.go:45] Queue size: 124 (max: 200)
2026-10-28T14:12:03.422Z INFO request_queue.go:45] Queue size: 126 (max: 200)
...
2026-10-28T14:12:10.001Z WARN request_queue.go:78] Queue overflow, dropping request
2. Inspect Job pod logs for retry patterns
2026-10-28T14:12:04.112Z ERROR worker.go:102] POST https://inference-svc:8500/v1/models/my-model:deadline_exceeded
2026-10-28T14:12:04.113Z INFO retry.go:57] retrying in 2.3s (attempt 3/5)
3. Verify Service and Deployment configuration
# inference deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: inference-deployment
spec:
replicas: 1
selector:
matchLabels:
app: inference
template:
metadata:
labels:
app: inference
spec:
containers:
- name: tf-serving
image: tensorflow/serving:2.12.0
args:
- --model_name=my-model
- --model_base_path=/models/my-model
- --port=8500
- --rest_api_port=8501
- --max_concurrent_requests=8 # <-- default
resources:
limits:
nvidia.com/gpu: 1
requests:
cpu: "4"
memory: "8Gi"
4. Check Job parallelism
apiVersion: batch/v1
kind: Job
metadata:
name: batch-inference
spec:
parallelism: 20
completions: 200
template:
spec:
restartPolicy: Never
containers:
- name: worker
image: myorg/batch-worker:latest
command: ["./run-inference.sh"]
5. Metrics inspection
Using Prometheus, query the request queue gauge:
inference_queue_size{pod="inference-deployment-abc123"}
Values consistently above 100 indicated sustained overload.
Solution
1. Align concurrency limits with GPU capacity
Set --max_concurrent_requests to a value that matches the GPU's optimal throughput (4 in this case) and enable back‑pressure via --grpc_max_concurrent_streams to return HTTP 429 when the queue exceeds a safe threshold.
Before:
- --max_concurrent_requests=8
After:
- --max_concurrent_requests=4
- --grpc_max_concurrent_streams=4
- --enable_batching=true
- --batch_timeout_micros=5000
2. Scale inference service horizontally
Deploy two replicas behind a ClusterIP Service with session‑affinity disabled, allowing the load balancer to spread traffic.
Before (replicas: 1):
spec:
replicas: 1
After (replicas: 2):
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
3. Reduce Job parallelism and add client‑side throttling
Adjust the batch Job to launch at most 8 workers per pod and introduce a token‑bucket limiter in run-inference.sh to cap outbound request rate.
Before (parallelism: 20):
spec:
parallelism: 20
After (parallelism: 8):
spec:
parallelism: 8
4. Implement exponential back‑off with jitter and respect HTTP 429
Update the client library to stop retrying immediately after a 429 response and to back‑off for at least the Retry-After header.
Verify
- Deploy the updated inference Deployment and Job manifests.
- Monitor the queue size metric for at least 10 minutes:
inference_queue_size{pod=~"inference-deployment-.*"} == 0
The gauge should stay below 20, well under the max_concurrent_requests limit.
- Check Job pod logs for successful responses:
2026-10-28T15:02:01.332Z INFO worker.go:45] inference succeeded (latency: 42ms)
- Confirm that no Job pod exits with
deadline exceedederrors. - Validate overall batch completion time meets SLA (e.g., < 30 minutes for 200 items).
Prevent
- Capacity planning: Benchmark the model on the target GPU and set
max_concurrent_requestsaccordingly. - Horizontal pod autoscaling (HPA): Enable CPU/GPU‑based HPA on the inference Deployment to add replicas when
inference_queue_sizeexceeds a threshold. - Back‑pressure handling: Configure the inference server to emit HTTP 429 and ensure client libraries honor it.
- Job throttling: Use a ConfigMap to expose a
MAX_REQUESTS_PER_SECONDvalue that can be tuned without redeploying the worker image. - Observability: Create a Prometheus alert on
inference_queue_size > 0.8 * max_concurrent_requestsand on Job pod failures withdeadline_exceeded.
Related Topic Hub: Distributed Systems Troubleshooting Hub
FAQ
- Why does increasing the number of inference pod replicas sometimes make the backlog worse?
When the Service uses round‑robin without session affinity, each new replica receives the same traffic pattern, but if the client side continues to overload each pod (e.g., by opening many connections per pod), the total request rate may exceed the combined capacity, leading to a larger aggregate queue.
- Can I keep
max_concurrent_requestshigh and rely on GPU multiplexing?TensorFlow Serving does not multiplex GPU kernels efficiently beyond the hardware's native concurrency. Over‑provisioning the request limit typically results in kernel queueing inside the driver, higher latency, and OOM errors.
- How do I expose the internal request queue size to Prometheus?
Add the
--monitoring_config_fileflag pointing to a YAML that enables thetensorflow_serving.metricscollector, then scrape the/metricsendpoint of the container. - What is the recommended back‑off strategy for handling HTTP 429 from the inference service?
Use exponential back‑off with jitter, start with 500 ms, double on each retry, and respect the
Retry-Afterheader if present. Cap the maximum delay at 10 seconds to avoid excessive latency. - Is it safe to set
--enable_batching=trueon a GPU‑bound model?Batching can improve throughput if the model supports it and the batch size is tuned to the GPU memory. However, it adds latency for individual requests; test with realistic batch sizes before enabling in production.