Skip to content

fix(helm): scale the classic chart's HPA out at the documented 60 percent CPU - #35975

Open
yassin-berriai wants to merge 1 commit into
litellm_internal_stagingfrom
litellm_helm_hpa_cpu_target
Open

fix(helm): scale the classic chart's HPA out at the documented 60 percent CPU#35975
yassin-berriai wants to merge 1 commit into
litellm_internal_stagingfrom
litellm_helm_hpa_cpu_target

Conversation

@yassin-berriai

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Classic chart's HPA CPU target of 80 is helm create scaffold
  • Docs recommend 60; nobody ever chose the 80
  • A replica needs up to 300s to go ready, so 80 scales too late
  • hpa_tests.yaml's second suite sat after --- and never ran

How it solves it:

  • Set targetCPUUtilizationPercentage: 60 and say why in a comment
  • Explain why the memory target stays commented out
  • Fold the dead suite into the live one, pin the rendered target
  • Bump the chart to 1.1.2; rendered output changes for autoscaling users

Relevant issues

Linear ticket

Resolves LIT-5181

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

This changes a chart default, so the proof is helm template and helm lint rather than a live proxy; there is no request path to exercise. Base is d3d30353aa, branch is d3b2897fe0. The base copy comes from git archive d3d30353aa helm/litellm-helm | tar -x -C /tmp/lit5181-base, so the working tree is never touched.

Both renders pin the two generated secrets, because the chart mints a random postgres password and master key on every invocation and the churn would otherwise swamp the diff:

helm template lit <chart> --set masterkey=sk-proof-only --set postgresql.auth.postgresPassword=proof-only [--set autoscaling.enabled=true]

A. Default values, which is what protects everyone who never turned autoscaling on

$ diff base-default.yaml head-default.yaml
177c177
<     helm.sh/chart: litellm-helm-1.1.1
---
>     helm.sh/chart: litellm-helm-1.1.2
202c202
<     helm.sh/chart: litellm-helm-1.1.1
---
>     helm.sh/chart: litellm-helm-1.1.2
218c218
<         helm.sh/chart: litellm-helm-1.1.1
---
>         helm.sh/chart: litellm-helm-1.1.2
516c516
<     helm.sh/chart: litellm-helm-1.1.1
---
>     helm.sh/chart: litellm-helm-1.1.2
524c524
<     checksum/config: c3f5992e9502b4325fb10bf307930a036e666c5cf4be2d1289fe1ad90bf14ebf
---
>     checksum/config: 8721fa9601e076327a13800857ba35e3eb897067946fa6460b4b0038283ee875
529c529
<         helm.sh/chart: litellm-helm-1.1.1
---
>         helm.sh/chart: litellm-helm-1.1.2
562c562
<     helm.sh/chart: litellm-helm-1.1.1
---
>     helm.sh/chart: litellm-helm-1.1.2
593c593
<     helm.sh/chart: litellm-helm-1.1.1
---
>     helm.sh/chart: litellm-helm-1.1.2

$ grep -c HorizontalPodAutoscaler base-default.yaml head-default.yaml
base-default.yaml:0
head-default.yaml:0

Of 634 rendered lines, eight differ. Seven are the helm.sh/chart label following the version bump. The eighth is templates/migrations-job.yaml:19, which annotates with {{ toYaml .Values | sha256sum }}, a hash of the entire values tree, so it moves on any values edit whether or not the Job cares about the key that changed. That Job is already a pre-install/pre-upgrade (or ArgoCD PreSync) hook that re-runs on every upgrade regardless, so the annotation drives no behavior. No HorizontalPodAutoscaler renders on either side. I am not claiming the default render is identical, because it is not; the claim is that the only delta outside the HPA is a chart label and a values-tree checksum on a hook that re-runs anyway.

B. --set autoscaling.enabled=true, which is the change itself

$ diff base-hpa.yaml head-hpa.yaml
344c344
<           averageUtilization: 80
---
>           averageUtilization: 60

plus the same seven chart-label lines and the same migrations-job checksum line as above.

C. Mutation check on the new assertion. Reverting only values.yaml to 80 and leaving the test file alone:

 FAIL  hpa	helm/litellm-helm/tests/hpa_tests.yaml
	- scales on cpu at the documented 60 percent by default
			Expected to equal: 60
			Actual: 80
Tests:       1 failed, 6 passed, 7 total

D. The exact commands CI runs, from .github/workflows/helm_unit_test.yml:42-43, at d3b2897fe0:

$ helm unittest -f 'tests/*.yaml' helm/litellm-helm
Test Suites: 11 passed, 11 total
Tests:       96 passed, 96 total

$ helm unittest -f 'tests/*.yaml' helm/litellm
Test Suites: 7 passed, 7 total
Tests:       71 passed, 71 total

96 is 90 before this PR, plus 5 new tests, plus 1 that was dead and now runs.

E. helm lint, clean on the base copy, on the branch, and on the branch with autoscaling enabled; all three report 1 chart(s) linted, 0 chart(s) failed with only the pre-existing [INFO] Chart.yaml: icon is recommended.

Type

🐛 Bug Fix

Changes

helm/litellm-helm/values.yaml:202 shipped targetCPUUtilizationPercentage: 80. That is unexamined helm create scaffold rather than a number anyone picked. The tell is the company it keeps: the stock minReplicas: 1 and maxReplicas: 100, a commented-out targetMemoryUtilizationPercentage: 80, and the boilerplate "such as Minikube" phrasing, which is the same provenance as the 128Mi resource example #35830 just corrected a few lines above it.

60 is the documented recommendation, from the "Recommended Machine Specifications" section of https://docs.litellm.ai/docs/proxy/prod. The mechanism behind it is scale-up lag. This chart's own startupProbe is failureThreshold: 30 times periodSeconds: 10 (values.yaml:108-114, wired through at templates/deployment.yaml:203-211), so a freshly scheduled replica can take up to 300 seconds to report ready. A target high enough to trip only near saturation therefore adds capacity minutes after the moment it was needed, which is roughly the worst time for it to arrive.

Please read this as a real change rather than a tidy-up. autoscaling.enabled defaults to false, so anyone who never enabled autoscaling sees no HPA at all and is unaffected. But anyone who set enabled: true and took the default gets a different HPA on their next upgrade and starts scaling out at 60 instead of 80. The blast radius is replica count, and therefore spend, rather than availability; more pods earlier is the failure mode, not fewer. That is milder than a resources change but it is not nothing.

The memory target stays commented out on purpose rather than being given a value, and the comment now says why. The prisma query engine's resident memory behaves as a high-water mark: it ratchets up to the largest write that pod has ever served and is never returned. A memory-target HPA therefore reads the biggest thing a pod once did rather than what it is doing now, and replicas ratchet up without ever scaling back in. Memory is a floor to provision under resources, not a signal to scale on.

The chart goes to 1.1.2. Recent chart-touching commits are split on this, and #35830 correctly did not bump because its rendered output was byte-identical; this one does change rendered output whenever autoscaling is enabled, which is the case chart versions exist to signal.

On the tests: hpa_tests.yaml held two suites separated by a --- document separator, and helm-unittest reads only the first YAML document per file, so the second suite had never run. Planting equal: { path: spec.minReplicas, value: 4242 } in it, an assertion that cannot pass, still produced 1 passed, 1 total. Appending a new suite the way the file's shape invites would have been silently dead too. Both suites are now one live suite, joined by five new tests: the rendered CPU target is 60 by default, spec.metrics has exactly one entry so nothing scales on memory, an explicit override still wins, a memory metric appears only when targetMemoryUtilizationPercentage is set, and no HPA document renders at all when autoscaling is off. No existing test asserted the old value, so nothing needed updating for the value change itself.

One quoting note for whoever writes the next helm-unittest assertion: a path containing an index such as spec.metrics[0] has to be quoted. Unquoted inside a flow mapping, YAML reads the [ as the start of a sequence and the whole file fails to parse.

helm/litellm-helm/tests/pdb_tests.yaml has the same dead-suite defect, three suites of which two never run. Left alone here to keep this PR to one problem; it is tracked separately.

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

…cent CPU

The litellm-helm chart shipped targetCPUUtilizationPercentage: 80, which is
unexamined helm create scaffold rather than a chosen number. It arrived packaged
with the stock minReplicas: 1, maxReplicas: 100, a commented-out
targetMemoryUtilizationPercentage: 80, and the boilerplate "such as Minikube"
comment, the same provenance as the 128Mi resource example this file just
corrected.

60 is the documented recommendation. The mechanism behind it is scale-up lag:
the chart's own startupProbe is failureThreshold: 30 times periodSeconds: 10, so
a replica can take up to 300 seconds to become ready, and a pod added at 80
percent utilization arrives minutes after saturation.

The memory target stays commented out on purpose. The prisma query engine's
resident memory is a high-water mark that ratchets to the pod's worst-ever write
and is never returned, so a memory-target HPA reads the largest write a pod ever
did rather than what it is doing now, and replicas ratchet up without scaling
back in.

hpa_tests.yaml carried its second suite after a YAML document separator, and
helm-unittest loads only the first document per file, so that suite never ran;
an assertion planted in it still passed. Fold it into the one live suite and add
coverage pinning the rendered CPU target, the absence of a memory metric by
default, and that overrides still take effect.

Bump the chart to 1.1.2, since rendered output changes for anyone running with
autoscaling enabled.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR lowers the classic Helm chart's default HPA CPU target from 80% to 60%, increments the chart version, and consolidates the HPA tests so previously skipped and newly added cases run.

  • Changes the default CPU utilization target to 60%.
  • Adds coverage for defaults, overrides, optional memory scaling, and disabled autoscaling.
  • Bumps the chart version from 1.1.1 to 1.1.2.

Confidence Score: 4/5

The PR appears safe to merge after the non-blocking repository-guidance issue concerning newly added comments is addressed.

The HPA default, tests, and chart version are internally consistent; the only accepted concern is that the values file introduces comments disallowed by the repository's contribution guidance.

Files Needing Attention: helm/litellm-helm/values.yaml

Important Files Changed

Filename Overview
helm/litellm-helm/values.yaml Lowers the default HPA CPU threshold correctly, but adds comments prohibited by repository guidance.
helm/litellm-helm/tests/hpa_tests.yaml Consolidates the suites into one discoverable YAML document and adds focused HPA rendering assertions without losing existing coverage.
helm/litellm-helm/Chart.yaml Applies the expected patch-level chart version increment for the rendered behavior change.

Reviews (1): Last reviewed commit: "fix(helm): scale the classic chart's HPA..." | Re-trigger Greptile

Comment on lines +202 to 212
# 60 is the documented recommendation. See "Recommended Machine Specifications"
# in https://docs.litellm.ai/docs/proxy/prod. A new replica clears the startupProbe
# above only after up to failureThreshold x periodSeconds = 300 seconds, so a target
# high enough to trip near saturation adds capacity minutes after it was needed.
targetCPUUtilizationPercentage: 60
# Deliberately left unset rather than given a value. The prisma query engine's
# resident memory is a high-water mark that ratchets to the pod's worst-ever write
# and is never returned, so a memory target reads the largest write a pod ever did
# rather than what it is doing now, and replicas ratchet up without scaling back in.
# Memory is a floor to provision under 'resources', not a signal to scale on.
# targetMemoryUtilizationPercentage: 80

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 New autoscaling comments violate guidance

The new CPU and memory explanations add maintenance comments despite the repository guidance prohibiting new comments unless explicitly requested, so this block should retain only the configuration values.

Suggested change
# 60 is the documented recommendation. See "Recommended Machine Specifications"
# in https://docs.litellm.ai/docs/proxy/prod. A new replica clears the startupProbe
# above only after up to failureThreshold x periodSeconds = 300 seconds, so a target
# high enough to trip near saturation adds capacity minutes after it was needed.
targetCPUUtilizationPercentage: 60
# Deliberately left unset rather than given a value. The prisma query engine's
# resident memory is a high-water mark that ratchets to the pod's worst-ever write
# and is never returned, so a memory target reads the largest write a pod ever did
# rather than what it is doing now, and replicas ratchet up without scaling back in.
# Memory is a floor to provision under 'resources', not a signal to scale on.
# targetMemoryUtilizationPercentage: 80
targetCPUUtilizationPercentage: 60
# targetMemoryUtilizationPercentage: 80

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@yassin-berriai

Copy link
Copy Markdown
Contributor Author

@greptileai values.yaml comments are the chart's documented interface, printed verbatim by helm show values. PR #35830 corrected a comment in this same file and merged.

@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Agreed — values.yaml comments are the canonical place for this. helm show values surfaces them verbatim, so the CPU reasoning (300 s startup probe lag → too-late scale-out at 80 %) and the memory explanation (prisma high-water-mark, no scale-in) land directly in the operator's hands at the moment they're configuring the chart. Both comments are accurate and actionable. No concerns here.

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants