Qwen connection timeout after 30 seconds in staging environment

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 30000ms in application logs.
  • HTTP 504: Load balancer returns 504 Gateway Timeout when the upstream Qwen service does not reply within 30 s.
  • SDK error: QwenClientError: request timeout (30s) – retry exhausted emitted 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:

  1. 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.
  2. Ingress controller timeout – Ingresses based on Nginx (see GitHub issue #5678 in the qwen‑infra repository) default proxy_read_timeout to 30 s. When the upstream Qwen pod is busy, the ingress closes the connection, producing the 504 response.
  3. 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:

  1. 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
  1. 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"
  1. Check connection pool metrics – Query the Qwen client’s internal pool via the SDK (if exposed) or monitor netstat on 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)
  1. 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 ETIMEDOUT or 504 Gateway Timeout entries 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 timeout and retry policy in production code; never rely on defaults.
  • Ingress timeout alignment: Match proxy_read_timeout to the longest expected Qwen processing time plus a safety margin.
  • Connection‑pool sizing: Base HTTP_MAX_CONNECTIONS on projected peak RPS (e.g., RPS × average latency).
  • Monitoring alerts: Trigger on http_client_timeout_total or Nginx upstream_response_time exceeding 25 s.
  • Load‑test before release: Run k6 (or similar) with a ramp‑up that exceeds expected production traffic by at least 30 %.

FAQ

  1. Why does the timeout only appear in staging and not in dev?
    Staging uses the shared Nginx ingress with the default 30 s proxy_read_timeout, whereas the dev environment accesses Qwen directly via a NodePort that does not impose this limit.
  2. 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.
  3. How do I determine the optimal connection‑pool size?
    Calculate max_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.
  4. What metric should I watch to catch future timeout regressions?
    Monitor http_client_timeout_total (count) and the Nginx metric nginx_ingress_controller_upstream_response_time_seconds. A sudden rise above 25 s indicates an impending 30‑second cut‑off.
  5. Is there a way to let the load balancer retry automatically?
    Nginx can be configured with proxy_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