VPA: memory-aggregation-interval-count does not work as expected
Which component are you using?:
/area vertical-pod-autoscaler
What version of the component are you using?:
v1.7.1 (but I believe this is not recent and may have been introduced as far ago as https://github.com/kubernetes/autoscaler/pull/815)
Component version: registry.k8s.io/autoscaling/vpa-recommender@sha256:89cea705535f9d8df6e62d5084916ec447e85d64369cfff2f7c6ac9d1cc5cd1e
What k8s version are you using (kubectl version)?:
kubectl version Outputk version
Client Version: v1.34.1
Kustomize Version: v5.7.1
Server Version: v1.34.9-eks-bca9cf6What environment is this in?:
EKS
What did you expect to happen?:
I expected that the flag memory-aggregation-interval-count and the corresponding API field vpa.spec.resourcePolicy.containerPolicies.memoryAggregationIntervalCount defines a count such that memory recommendation is calculated strictly from a window where window = count * interval_length. So that, for example, with the default settings where interval=24, count=8, memory usage samples from > 8 days ago are not added to bucket weights used to calculate the recommendation.
What happened instead?:
While a workload keeps running the memory usage samples keep counting towards the recommendation.
If a workload is restarted with new Pod labels, then the old aggregate state built from the old samples stop counting towards the recommendation after window = count x inteval_length period, see AggregateContainerState.isExpired.
In practice, it seems to be:
- the actual logic of
memory-aggregation-interval-countseems to be 'If the workload's labels change, e.g. as a result of redeploy with newPodhash labels, the old memory usage samples will be discardedcount x interval_lengthperiod after the label change' - for the default settings with 24h half life, it likely does not matter too much because the samples beyond 8 days would constitute for less than 1%
- with longer half life however, samples older than window length alone could constitute upperbound or even target. We were considering 14 day half life with 28 day count, it seems that in practice samples up to 60 days back could influence the recommendation
2^(−60/14) ~= 0.05 = 5%
How to reproduce it (as minimally and precisely as possible):
I've tested this with vpa.spec.resourcePolicy.containerPolicies.memoryAggregationIntervalCount field rather than the flag, but I believe that the flag behavior is identical.
- Deploy vpa with
--feature-gates=PerVPAConfig=trueflag - Create a deployment that on startup consumes ~512MB memory for 10 minutes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vpa-count-test
spec:
replicas: 1
selector:
matchLabels: {app: vpa-count-test}
template:
metadata:
labels: {app: vpa-count-test}
spec:
containers:
- name: app
image: ubuntu
command:
- bash
- -c
- |
DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install python3 -y
sleep 600 # give VPA a chance to discover the Pod
python3 -c 'import time; b = bytearray(512*1024*1024); time.sleep(600)'
sleep infinity
resources:
requests: {cpu: 50m, memory: 64Mi}
- Create a
VPAobject for the Deployment with 10 minute interval and interval count of 2:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: vpa-count-test
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: vpa-count-test
updatePolicy:
updateMode: "Off"
resourcePolicy:
containerPolicies:
- containerName: app
memoryAggregationIntervalSeconds: 600
memoryAggregationIntervalCount: 2
- Observe that while this should have had a 20 minute window, 2 hours later the bucket
28that would correspond to the memory usage from the first 20 minutes is still present in the checkpoint and determines thetargetandupperbound:
k get vpacheckpoint vpa-count-test-app -oyaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscalerCheckpoint
metadata:
creationTimestamp: "2026-09-06T11:15:02Z"
generation: 25
name: vpa-count-test-app
...
spec:
containerName: app
vpaObjectName: vpa-count-test
status:
cpuHistogram:
bucketWeights:
"0": 10000
referenceTimestamp: "2026-09-06T00:00:00Z"
totalWeight: 3.560273162411728
firstSampleStart: "2026-09-06T11:14:17Z"
lastSampleStart: "2026-09-06T13:14:06Z"
lastUpdateTime: "2026-09-06T13:15:02Z"
memoryHistogram:
bucketWeights:
"6": 10000
"28": 1943
referenceTimestamp: "2026-09-06T00:00:00Z"
totalWeight: 17.131182927492507
totalSamplesCount: 25
version: v3
$ k get vpa vpa-count-test -oyaml
...
spec:
resourcePolicy:
containerPolicies:
- containerName: app
memoryAggregationIntervalCount: 2
memoryAggregationIntervalSeconds: 600
targetRef:
apiVersion: apps/v1
kind: Deployment
name: vpa-count-test
updatePolicy:
updateMode: "Off"
status:
...
recommendation:
containerRecommendations:
- containerName: app
...
target:
cpu: 15m
memory: "716711186"
uncappedTarget:
cpu: 15m
memory: "716711186"
upperBound:
cpu: 620m
memory: "40411484564"
Anything else we need to know?:
Slack chat https://kubernetes.slack.com/archives/C09R1LV8S/p1788441086365319
I think that this bug was introduced in https://github.com/kubernetes/autoscaler/pull/815 where memory histogram changed from FloatSlidingWindowHistogram to DecayingHistogram.
I think a fix would need to involve re-introducing a sliding window, perhaps another histogram wrapped by the decaying one. By looking at the PR and the changes I assume that the histogram type change was a side effect of moving to per-labelset state and the interval-count was left behind.