Problem
The LangChain service is deployed as a stateless Deployment behind an API gateway in a Kubernetes cluster. An HorizontalPodAutoscaler (HPA) is configured to scale the Deployment based on CPU utilization (target 70%) and a custom latency metric exported to Prometheus. Under load the request latency spikes and the CPU usage reported by the metrics-server exceeds the target, yet the number of pods remains constant. Clients experience timeouts and the service becomes unresponsive.
Root Cause
Investigation revealed two intertwined issues:
- Metric source mismatch: The HPA was referencing the
resourcemetriccpufrommetrics-serverwhile the custom latency metric was being scraped from Prometheus via theexternalmetric type. The HPA definition mistakenly combined them in a singlespec.metricsarray without settingtype: Externalfor the latency metric, causing the controller to ignore the external metric. - Stale metrics API version: The cluster ran Kubernetes v1.21, but the HPA manifest used
apiVersion: autoscaling/v2beta2. In this version theexternalmetric requires ametricSelectorthat references aPrometheusAdapterServiceMonitor. The adapter was mis‑configured, exposing the latency metric under the namelangchain_request_latency_secondswhile the HPA expectedrequest_latency_seconds. Consequently the HPA never received a valid external metric value.
Because the HPA could not obtain a valid external metric, it fell back to CPU only. However, the metrics-server was reporting CPU usage per pod based on the cgroup limit (500m) rather than the actual request load, resulting in a reported utilization of ~55% even when the pod was saturated. The combination of ignored latency metric and inaccurate CPU reading prevented any scaling decision.
Debug
1. Verify HPA status and metric values
kubectl get hpa langchain-hpa -o yaml
Sample output showing the missing external metric:
status:
currentMetrics:
- type: Resource
resource:
name: cpu
currentAverageUtilization: 55
conditions:
- type: AbleToScale
status: "True"
- type: ScalingActive
status: "True"
- type: ScalingLimited
status: "False"
- type: MetricAvailable
status: "False"
reason: ExternalMetricNotFound
message: "unable to get external metric request_latency_seconds: metric not found"
2. Inspect Prometheus Adapter configuration
kubectl -n monitoring get servicemonitor prometheus-adapter -o yaml
Key snippet:
spec:
endpoints:
- port: metrics
path: /metrics
metricRelabelings:
- sourceLabels: [__name__]
regex: ^langchain_request_latency_seconds$
action: replace
targetLabel: __name__
replacement: request_latency_seconds
The regex incorrectly matches the metric name, causing the adapter to expose request_latency_seconds only when the original metric name is langchain_request_latency_seconds. The actual metric exported by the LangChain container is langchain_request_latency_seconds, but the adapter’s relabeling rule drops the langchain_ prefix, resulting in a mismatch with the HPA expectation.
3. Check metrics-server CPU reporting
kubectl top pod -l app=langchain
Typical output:
NAME CPU(cores) MEMORY(bytes)
langchain-5d9f9c7b8-abcde 260m 128Mi
langchain-5d9f9c7b8-fghij 270m 130Mi
With a pod limit of 500m, the utilization appears to be ~52%, even though the pod is queuing requests. This is a known limitation of metrics-server when the container runtime reports CPU usage as a cumulative counter without accounting for throttling.
4. Review HPA manifest
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: langchain-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: langchain
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: External
external:
metric:
name: request_latency_seconds
selector:
matchLabels:
service: langchain
target:
type: Value
value: 200ms
Solution
1. Align metric names between Prometheus, adapter, and HPA
Update the Prometheus Adapter ConfigMap to expose the metric under the exact name expected by the HPA.
# Before (ConfigMap snippet)
metricRelabelings:
- sourceLabels: [__name__]
regex: ^langchain_request_latency_seconds$
action: replace
targetLabel: __name__
replacement: request_latency_seconds
# After
metricRelabelings:
- sourceLabels: [__name__]
regex: ^langchain_request_latency_seconds$
action: replace
targetLabel: __name__
replacement: request_latency_seconds
Alternatively, change the HPA to request langchain_request_latency_seconds directly, removing the relabeling entirely.
2. Upgrade HPA API version to autoscaling/v2
Kubernetes v1.23+ supports autoscaling/v2 with stable external metric handling. Apply the updated manifest:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: langchain-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: langchain
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: External
external:
metric:
name: request_latency_seconds
selector:
matchLabels:
service: langchain
target:
type: Value
value: 200ms
3. Improve CPU metric accuracy
Replace metrics-server with the kube-state-metrics + custom-metrics-apiserver stack, or enable the --kubelet-insecure-tls flag to allow the resource metric to use the container_cpu_usage_seconds_total counter from cAdvisor, which reflects throttling.
Alternatively, add a container resource request/limit that matches the observed load (e.g., cpu: "1") so that utilization percentages become more representative.
4. Deploy a test load generator to confirm scaling
# Deploy a simple load generator
apiVersion: batch/v1
kind: Job
metadata:
name: langchain-load
spec:
template:
spec:
containers:
- name: hey
image: rakyll/hey
args: ["-z", "2m", "-c", "100", "http://langchain.mycompany.com/api/v1/query"]
restartPolicy: Never
Verify
1. Confirm HPA now receives both metrics
kubectl get hpa langchain-hpa -o yaml | grep -A5 "currentMetrics"
Expected output:
currentMetrics:
- type: Resource
resource:
name: cpu
currentAverageUtilization: 85
- type: External
external:
metric:
name: request_latency_seconds
currentValue: 350ms
2. Observe scaling activity
kubectl get hpa langchain-hpa -w
When the load job runs, you should see the HPA increase desiredReplicas from 2 to 5 (or higher) within a few minutes.
3. Validate latency reduction
kubectl logs -l app=langchain -c langchain -f | grep latency
Post‑scaling logs should show average latency dropping below the 200 ms target.
Prevent
- Standardize metric naming conventions across services, Prometheus exporters, and HPA definitions.
- Pin HPA manifests to
autoscaling/v2and test them against the cluster version before deployment. - Prefer
custom.metrics.k8s.ioorexternal.metrics.k8s.ioadapters that expose the exact metric name without relabeling. - Set realistic CPU requests/limits that reflect peak load to make utilization metrics meaningful.
- Enable alerting on
HPA_status_condition{type="MetricAvailable",status="False"}to catch missing metrics early. - Periodically run a synthetic load test in a staging environment to verify that both CPU and custom metrics trigger scaling.
FAQ
- Why does the HPA ignore the external latency metric? The metric name exposed by the Prometheus Adapter did not match the name referenced in the HPA manifest, causing the controller to report
ExternalMetricNotFound. Aligning the names resolves the issue. - Can I rely solely on CPU utilization for scaling LangChain? CPU alone is insufficient because LangChain’s request processing time can be dominated by I/O or model inference latency. Combining CPU with a latency metric provides a more accurate scaling signal.
- What version of the HPA API should I use? Use
autoscaling/v2(or newer) for stable support of external metrics. The olderv2beta2version has stricter selector requirements and may drop metrics if the adapter configuration is imperfect. - How do I troubleshoot stale metrics from metrics-server? Verify the
metrics-serverflags--kubelet-insecure-tlsand--metric-resolution. If CPU utilization remains inaccurate, switch to a custom metrics pipeline that readscontainer_cpu_usage_seconds_totaldirectly from cAdvisor. - Is it safe to increase the CPU limit to improve utilization readings? Raising the limit changes the denominator for utilization calculations, but it does not affect actual throttling. It is better to set limits that match expected peak usage and rely on external latency metrics for scaling decisions.
Related Topic Hub: RAG Systems Troubleshooting Hub