Skip to content

Commit 72c2f93

Browse files
committed
Describe why the additional bucket handling is necessary
1 parent 2aba671 commit 72c2f93

File tree

2 files changed

+35
-0
lines changed

2 files changed

+35
-0
lines changed

vertical-pod-autoscaler/pkg/recommender/util/histogram.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,14 @@ func (h *histogram) LoadFromCheckpoint(checkpoint *vpa_types.HistogramCheckpoint
277277
h.bucketWeight[bucket] += float64(weight) * ratio
278278
}
279279
h.totalWeight += checkpoint.TotalWeight
280+
281+
// In some cases where the weight of the max bucket is close (equal or less) to `MaxCheckpointWeight` times epsilon
282+
// and there are buckets with weights slightly higher or equal to epsilon, saving the histogram to a checkpoint and
283+
// then loading it will cause the weights that are close to epsilon to become smaller than epsilon due to rounding errors
284+
// and differences between the load and save algorithm. If one of those weights is the min weight, this will cause the
285+
// histogram to incorrectly become "empty" and the `Percentile(...)` function to always return 0.
286+
// To cover for such cases, the min and max buckets are updated here, so that those less than epsilon are dropped.
287+
// For more information check https://github.com/kubernetes/autoscaler/issues/7726
280288
h.updateMinAndMaxBucket()
281289

282290
return nil

vertical-pod-autoscaler/pkg/recommender/util/histogram_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,33 @@ func TestHistogramLoadFromCheckpointReturnsErrorOnNilInput(t *testing.T) {
266266
}
267267

268268
func TestHistogramIsNotEmptyAfterSavingAndLoadingCheckpointsWithBoundaryValues(t *testing.T) {
269+
// There is a specific scenario in which the weights of the minimum and maximum histogram buckets,
270+
// when saved to a VPACheckpoint and subsequently loaded, result in diminished weights for the minimum buckets.
271+
272+
// This issue arises due to rounding errors when converting float weights to integers in the VPACheckpoint.
273+
// For instance, consider the weights:
274+
// `w1` which approximates but is slightly larger than or equal to `epsilon`,
275+
// `w2` which approximates but is slightly smaller than or equal to (`MaxCheckpointWeight` * `epsilon`) - `epsilon`.
276+
277+
// When these weights are stored in a VPACheckpoint, they are translated to integers:
278+
// `w1` rounds to `1` (`wi1`),
279+
// `w2` rounds to `MaxCheckpointWeight` (`wi2`).
280+
281+
// Upon loading from the VPACheckpoint, the histogram reconstructs its weights using a calculated ratio,
282+
// aimed at reverting integer weights back to float values. This ratio is derived from:
283+
// (`w1` + `w2`) / (`wi1` + `wi2`)
284+
// Reference: https://github.com/plkokanov/autoscaler/blob/2aba67154cd4f117da4702b60a10c38c0651e659/vertical-pod-autoscaler/pkg/recommender/util/histogram.go#L256-L269
285+
286+
// Given the maximum potential values for `w1`, `w2`, `wi1` and `wi2` we arrive at:
287+
// (`epsilon` + `MaxCheckpointWeight` * `epsilon` - `epsilon`) / (1 + MaxCheckpointWeight) = epsilon * `MaxCheckpointWeight` / (1 + MaxCheckpointWeight)
288+
289+
// Consequently, the maximum value for this ratio is less than `epsilon`, implying that when `w1`,
290+
// initially scaled to `1`, is adjusted by this ratio, its recalculated weight falls short of `epsilon`.
291+
// The same behavior can be observed when there are more than two weights.
292+
293+
// This test ensures that in such cases the histogram does not become empty.
294+
// For more information check https://github.com/kubernetes/autoscaler/issues/7726
295+
269296
histogram := NewHistogram(testHistogramOptions)
270297
histogram.AddSample(1, weightEpsilon, anyTime)
271298
histogram.AddSample(2, (float64(MaxCheckpointWeight)*weightEpsilon - weightEpsilon), anyTime)

0 commit comments

Comments
 (0)