@@ -74,8 +74,6 @@ func (a *Analyzer) AnalyzeModelSaturation(
7474 var totalSpareKv float64
7575 var totalSpareQueue float64
7676 var nonSaturatedCount int
77- var maxKvUsage float64
78- var maxQueueLen int
7977
8078 variantAnalyses := make ([]interfaces.VariantSaturationAnalysis , 0 , len (variantMap ))
8179
@@ -87,14 +85,6 @@ func (a *Analyzer) AnalyzeModelSaturation(
8785 nonSaturatedCount += variantAnalysis .NonSaturatedCount
8886 totalSpareKv += variantAnalysis .AvgSpareKvCapacity * float64 (variantAnalysis .NonSaturatedCount )
8987 totalSpareQueue += variantAnalysis .AvgSpareQueueLength * float64 (variantAnalysis .NonSaturatedCount )
90-
91- // Track worst-case metrics
92- if variantAnalysis .MaxKvCacheUsage > maxKvUsage {
93- maxKvUsage = variantAnalysis .MaxKvCacheUsage
94- }
95- if variantAnalysis .MaxQueueLength > maxQueueLen {
96- maxQueueLen = variantAnalysis .MaxQueueLength
97- }
9888 }
9989
10090 analysis .TotalReplicas = len (replicaMetrics )
@@ -115,9 +105,12 @@ func (a *Analyzer) AnalyzeModelSaturation(
115105 )
116106
117107 // Step 4: Determine if scale-down is safe
108+ // Pass pre-calculated average spare capacities to avoid redundant iteration
118109 analysis .ScaleDownSafe = a .isScaleDownSafe (
119110 ctx ,
120- replicaMetrics ,
111+ nonSaturatedCount ,
112+ analysis .AvgSpareKvCapacity ,
113+ analysis .AvgSpareQueueLength ,
121114 config ,
122115 )
123116
@@ -236,22 +229,12 @@ func (a *Analyzer) shouldScaleUp(
236229// redistributing that load across (N-1) replicas to determine if spare Saturation remains adequate.
237230func (a * Analyzer ) isScaleDownSafe (
238231 ctx context.Context ,
239- replicaMetrics []interfaces.ReplicaMetrics ,
232+ nonSaturatedCount int ,
233+ avgSpareKv float64 ,
234+ avgSpareQueue float64 ,
240235 config interfaces.SaturationScalingConfig ,
241236) bool {
242237
243- // Collect non-saturated replicas
244- var nonSaturatedMetrics []interfaces.ReplicaMetrics
245- for _ , m := range replicaMetrics {
246- isSaturated := m .KvCacheUsage >= config .KvCacheThreshold ||
247- float64 (m .QueueLength ) >= config .QueueLengthThreshold
248- if ! isSaturated {
249- nonSaturatedMetrics = append (nonSaturatedMetrics , m )
250- }
251- }
252-
253- nonSaturatedCount := len (nonSaturatedMetrics )
254-
255238 // Require minimum non-saturated replicas for scale-down safety
256239 // With fewer replicas, we cannot safely redistribute load without risking saturation
257240 if nonSaturatedCount < MinNonSaturatedReplicasForScaleDown {
@@ -260,20 +243,20 @@ func (a *Analyzer) isScaleDownSafe(
260243 return false
261244 }
262245
263- // Calculate total load across all non-saturated replicas
264- var totalKvLoad float64
265- var totalQueueLoad int
266- for _ , m := range nonSaturatedMetrics {
267- totalKvLoad += m .KvCacheUsage
268- totalQueueLoad += m .QueueLength
269- }
246+ // Calculate current average load per replica
247+ // Load = Threshold - Spare
248+ avgKvLoad := config .KvCacheThreshold - avgSpareKv
249+ avgQueueLoad := config .QueueLengthThreshold - avgSpareQueue
270250
271- // Simulate removing one replica: redistribute total load across remaining replicas
251+ // Simulate removing one replica: load increases by factor of N/(N-1)
252+ // New avg load = current avg load × N/(N-1)
272253 remainingCount := nonSaturatedCount - 1
273- avgKvAfterRemoval := totalKvLoad / float64 (remainingCount )
274- avgQueueAfterRemoval := float64 (totalQueueLoad ) / float64 (remainingCount )
254+ scaleFactor := float64 (nonSaturatedCount ) / float64 (remainingCount )
255+ avgKvAfterRemoval := avgKvLoad * scaleFactor
256+ avgQueueAfterRemoval := avgQueueLoad * scaleFactor
275257
276- // Calculate spare Saturation after redistribution
258+ // Calculate spare capacity after redistribution
259+ // Spare = Threshold - Load
277260 remainingSpareKv := config .KvCacheThreshold - avgKvAfterRemoval
278261 remainingSpareQueue := config .QueueLengthThreshold - avgQueueAfterRemoval
279262
0 commit comments