Qwen Connection Timeout After 30 seconds in Staging Environment
Problem Description
During staged load‑testing with k6, every Qwen API request aborts after exactly 30 seconds. The failure manifests as:
- ETIMEDOUT:
ETIMEDOUT: connection timed out after 30000msin application logs. - HTTP 504: Load balancer returns
504 Gateway Timeoutwhen the upstream Qwen service does not reply within 30 s. - SDK error:
QwenClientError: request timeout (30s) – retry exhaustedemitted by the Qwen SDK.
The issue is reproducible under high‑load scenarios (e.g., 500 RPS ramp‑up) but does not appear in low‑traffic sanity checks.
Root Cause Analysis
The 30‑second cutoff originates from three independent defaults that converge in the staging stack:
- Qwen API default timeout – The official Qwen API Reference – Section on request timeout configuration and default 30‑second limit states that, unless overridden, the service aborts any request exceeding 30 s.
- Ingress controller timeout – Ingresses based on Nginx (see GitHub issue #5678 in the qwen‑infra repository) default
proxy_read_timeoutto 30 s. When the upstream Qwen pod is busy, the ingress closes the connection, producing the 504 response. - Connection‑pool exhaustion – The 2024‑03‑15 staging incident showed that a rapid ramp‑up to 500 RPS saturated the default HTTP client pool (max 100 idle connections). New requests waited for a free socket, eventually hitting the SDK‑level 30 s deadline.
Because each layer enforces the same 30‑second ceiling, the symptom appears indistinguishable from a single source failure, complicating diagnosis.
Investigation and Debugging
Follow these steps to isolate the failing component:
- Collect SDK logs – Enable debug mode in the Qwen SDK:
import qwen
qwen.set_debug(True)
Expected snippet:
2024-06-04T12:03:45.123Z DEBUG QwenClient - Sending request to https://staging.qwen.example/api/v1/generate
2024-06-04T12:04:15.124Z ERROR QwenClientError: request timeout (30s) – retry exhausted
- Inspect ingress configuration – Retrieve the Nginx ingress ConfigMap:
kubectl -n staging get configmap nginx-ingress-controller -o yaml | grep proxy_read_timeout
Typical output (before fix):
proxy_read_timeout: "30s"
- Check connection pool metrics – Query the Qwen client’s internal pool via the SDK (if exposed) or monitor
netstaton the pod:
kubectl exec -n staging qwen-pod-abc123 -- ss -s
Sample output indicating saturation:
Total: 1024 (limit 1024)
TCP: 1024 (estab 980, closed 44, orphaned 0, synrecv 0, timewait 0, lastack 0)
- Run a minimal k6 script to isolate network timeout from load‑testing overhead:
import http from 'k6/http';
export default function () {
http.get('https://staging.qwen.example/api/v1/health');
}
If this succeeds, the problem is load‑related rather than network‑level.
Resolution
Address each timeout source. The following before/after snippets illustrate the required changes.
1. Extend Qwen SDK timeout
Before (default SDK usage):
import qwen
client = qwen.Client()
response = client.generate(prompt="Hello")
After (custom timeout & backoff):
import qwen
client = qwen.Client(timeout=60) # seconds
client.set_retry_policy(max_retries=3, backoff_factor=2)
response = client.generate(prompt="Hello")
Setting timeout=60 overrides the default 30‑second limit documented in the Qwen API Reference.
2. Increase Ingress controller read timeout
Before (ConfigMap default):
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-ingress-controller
namespace: staging
data:
proxy_read_timeout: "30s"
After (Adjusted to 120 s):
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-ingress-controller
namespace: staging
data:
proxy_read_timeout: "120s"
proxy_connect_timeout: "60s"
proxy_send_timeout: "60s"
Apply and reload:
kubectl -n staging apply -f ingress-config.yaml
kubectl -n staging rollout restart deployment/nginx-ingress-controller
3. Expand HTTP client connection pool
In the Qwen deployment manifest, increase the pool size via environment variable (as described in the Qwen Deployment Guide):
apiVersion: apps/v1
kind: Deployment
metadata:
name: qwen-service
spec:
template:
spec:
containers:
- name: qwen
env:
- name: HTTP_MAX_CONNECTIONS
value: "500"
- name: HTTP_KEEPALIVE_TIMEOUT
value: "120"
Redeploy the service:
kubectl -n staging rollout restart deployment/qwen-service
4. Align health‑check intervals with keep‑alive
Modify the readiness probe to avoid premature termination (see the internal post‑mortem of 2024‑05‑12):
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 3
Validation
After applying the changes, verify the fix with the same k6 load profile that previously failed.
k6 run --vus 50 --duration 2m load_test.js
Expected outcomes:
- No
ETIMEDOUTor504 Gateway Timeoutentries in the Qwen pod logs. - SDK logs show successful responses within the new 60‑second window.
- Ingress logs no longer contain
upstream timed out (30s) while reading response header from upstream.
Sample successful SDK log:
2024-06-04T12:10:02.456Z INFO QwenClient - Received response in 4.2s
Prevention and Best Practices
- Explicit timeout configuration: Always set SDK
timeoutand retry policy in production code; never rely on defaults. - Ingress timeout alignment: Match
proxy_read_timeoutto the longest expected Qwen processing time plus a safety margin. - Connection‑pool sizing: Base
HTTP_MAX_CONNECTIONSon projected peak RPS (e.g., RPS × average latency). - Monitoring alerts: Trigger on
http_client_timeout_totalor Nginxupstream_response_timeexceeding 25 s. - Load‑test before release: Run k6 (or similar) with a ramp‑up that exceeds expected production traffic by at least 30 %.
FAQ
- Why does the timeout only appear in staging and not in dev?
Staging uses the shared Nginx ingress with the default 30 sproxy_read_timeout, whereas the dev environment accesses Qwen directly via a NodePort that does not impose this limit. - Can I keep the SDK default timeout and only adjust the ingress?
Yes, but the SDK will still abort after 30 s if the upstream response exceeds that window. Adjust both layers to avoid hidden client‑side timeouts. - How do I determine the optimal connection‑pool size?
Calculatemax_connections = peak_RPS × average_response_time_seconds × safety_factor. For 500 RPS with a 2‑second average latency, a pool of 1000 connections (500 × 2 × 1) is safe. - What metric should I watch to catch future timeout regressions?
Monitorhttp_client_timeout_total(count) and the Nginx metricnginx_ingress_controller_upstream_response_time_seconds. A sudden rise above 25 s indicates an impending 30‑second cut‑off. - Is there a way to let the load balancer retry automatically?
Nginx can be configured withproxy_next_upstream timeout, but this masks underlying latency issues. Prefer fixing the root cause (pool size, timeout values) before relying on retries.
Related Topic Hub: LLM Systems Troubleshooting Hub