Set default max scale to 5 replicas and a 10% increment

Sets a new default maximum scale limit of 5 replicas out of
the box for CE users, CE meaning "Community" rather than
"Commercial".

The increment factor of 10 vs 25 should not make a difference
to genuine community and hobbyist users.

Tested and verified with unit tests and hey with a CE cluster
where the maximum limit was reached over several minutes,
finally going back to 1 replica.

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
This commit is contained in:
Alex Ellis (OpenFaaS Ltd)
2023-01-17 21:01:36 +00:00
parent 21a8f0cec1
commit 8e1c34e222
6 changed files with 64 additions and 71 deletions

View File

@ -102,6 +102,7 @@ func scaleService(alert requests.PrometheusInnerAlert, service scaling.ServiceQu
func CalculateReplicas(status string, currentReplicas uint64, maxReplicas uint64, minReplicas uint64, scalingFactor uint64) uint64 { func CalculateReplicas(status string, currentReplicas uint64, maxReplicas uint64, minReplicas uint64, scalingFactor uint64) uint64 {
var newReplicas uint64 var newReplicas uint64
maxReplicas = uint64(math.Min(float64(maxReplicas), float64(scaling.DefaultMaxReplicas)))
step := uint64(math.Ceil(float64(maxReplicas) / 100 * float64(scalingFactor))) step := uint64(math.Ceil(float64(maxReplicas) / 100 * float64(scalingFactor)))
if status == "firing" && step > 0 { if status == "firing" && step > 0 {

View File

@ -1,4 +1,4 @@
// Copyright (c) Alex Ellis 2017. All rights reserved. // Copyright (c) Alex Ellis 1017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
package handlers package handlers
@ -12,9 +12,9 @@ import (
func TestDisabledScale(t *testing.T) { func TestDisabledScale(t *testing.T) {
minReplicas := uint64(1) minReplicas := uint64(1)
scalingFactor := uint64(0) scalingFactor := uint64(0)
newReplicas := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor) got := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != minReplicas { if got != minReplicas {
t.Logf("Expected not to scale, but replicas were: %d", newReplicas) t.Logf("Expected not to scale, but replicas were: %d", got)
t.Fail() t.Fail()
} }
} }
@ -22,20 +22,23 @@ func TestDisabledScale(t *testing.T) {
func TestParameterEdge(t *testing.T) { func TestParameterEdge(t *testing.T) {
minReplicas := uint64(0) minReplicas := uint64(0)
scalingFactor := uint64(0) scalingFactor := uint64(0)
newReplicas := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor) got := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 0 { if got != 0 {
t.Log("Expected not to scale") t.Log("Expected not to scale")
t.Fail() t.Fail()
} }
} }
func TestScalingWithSameUpperLowerLimit(t *testing.T) { func TestScaling_SameUpperLowerLimit(t *testing.T) {
minReplicas := uint64(1) minReplicas := uint64(5)
scalingFactor := uint64(20) maxReplicas := uint64(5)
// status string, currentReplicas uint64, maxReplicas uint64, minReplicas uint64, scalingFactor uint64) scalingFactor := uint64(10)
newReplicas := CalculateReplicas("firing", minReplicas, minReplicas, minReplicas, scalingFactor)
if newReplicas != 1 { got := CalculateReplicas("firing", minReplicas, minReplicas, maxReplicas, scalingFactor)
t.Logf("Replicas - want: %d, got: %d", minReplicas, newReplicas)
want := minReplicas
if want != got {
t.Logf("Replicas - want: %d, got: %d", want, got)
t.Fail() t.Fail()
} }
} }
@ -43,58 +46,62 @@ func TestScalingWithSameUpperLowerLimit(t *testing.T) {
func TestMaxScale(t *testing.T) { func TestMaxScale(t *testing.T) {
minReplicas := uint64(1) minReplicas := uint64(1)
scalingFactor := uint64(100) scalingFactor := uint64(100)
newReplicas := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor) got := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas*2, minReplicas, scalingFactor)
if newReplicas != 20 { if got != scaling.DefaultMaxReplicas {
t.Log("Expected ceiling of 20 replicas") t.Fatalf("want ceiling: %d, but got: %d", scaling.DefaultMaxReplicas, got)
t.Fail()
} }
} }
func TestInitialScale(t *testing.T) { func TestInitialScale_From1_Factor10(t *testing.T) {
minReplicas := uint64(1) minReplicas := uint64(1)
scalingFactor := uint64(20) scalingFactor := uint64(10)
newReplicas := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor) got := CalculateReplicas("firing", scaling.DefaultMinReplicas, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 5 { want := uint64(2)
t.Log("Expected the increment to equal 5")
t.Fail() if got != want {
t.Fatalf("want: %d, but got: %d", want, got)
} }
} }
func TestScale(t *testing.T) { func TestScale_midrange_factor25(t *testing.T) {
minReplicas := uint64(1) minReplicas := uint64(1)
scalingFactor := uint64(20) scalingFactor := uint64(25)
newReplicas := CalculateReplicas("firing", 4, scaling.DefaultMaxReplicas, minReplicas, scalingFactor) current := uint64(4)
if newReplicas != 8 { maxReplicas := uint64(scaling.DefaultMaxReplicas)
t.Log("Expected newReplicas to equal 8")
t.Fail() got := CalculateReplicas("firing", current, maxReplicas, minReplicas, scalingFactor)
want := uint64(5)
if want != got {
t.Fatalf("want: %d, but got: %d", want, got)
} }
} }
func TestScaleCeiling(t *testing.T) { func TestScale_Ceiling_IsDefaultMaxReplicas(t *testing.T) {
minReplicas := uint64(1) minReplicas := uint64(1)
scalingFactor := uint64(20) scalingFactor := uint64(10)
newReplicas := CalculateReplicas("firing", 20, scaling.DefaultMaxReplicas, minReplicas, scalingFactor) current := uint64(scaling.DefaultMaxReplicas)
if newReplicas != 20 {
t.Log("Expected ceiling of 20 replicas") got := CalculateReplicas("firing", current, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
t.Fail() if got != scaling.DefaultMaxReplicas {
t.Fatalf("want: %d, but got: %d", scaling.DefaultMaxReplicas, got)
} }
} }
func TestScaleCeilingEdge(t *testing.T) { func TestScaleCeilingReplicasOver(t *testing.T) {
minReplicas := uint64(1) minReplicas := uint64(1)
scalingFactor := uint64(20) scalingFactor := uint64(10)
newReplicas := CalculateReplicas("firing", 19, scaling.DefaultMaxReplicas, minReplicas, scalingFactor) got := CalculateReplicas("firing", 19, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 20 {
t.Log("Expected ceiling of 20 replicas") if got != scaling.DefaultMaxReplicas {
t.Fail() t.Fatalf("want: %d, but got: %d", scaling.DefaultMaxReplicas, got)
} }
} }
func TestBackingOff(t *testing.T) { func TestBackingOff(t *testing.T) {
minReplicas := uint64(1) minReplicas := uint64(1)
scalingFactor := uint64(20) scalingFactor := uint64(10)
newReplicas := CalculateReplicas("resolved", 8, scaling.DefaultMaxReplicas, minReplicas, scalingFactor) got := CalculateReplicas("resolved", 8, scaling.DefaultMaxReplicas, minReplicas, scalingFactor)
if newReplicas != 1 { if got != 1 {
t.Log("Expected backing off to 1 replica") t.Log("Expected backing off to 1 replica")
t.Fail() t.Fail()
} }
@ -104,9 +111,9 @@ func TestScaledUpFrom1(t *testing.T) {
currentReplicas := uint64(1) currentReplicas := uint64(1)
maxReplicas := uint64(5) maxReplicas := uint64(5)
scalingFactor := uint64(30) scalingFactor := uint64(30)
newReplicas := CalculateReplicas("firing", currentReplicas, maxReplicas, scaling.DefaultMinReplicas, scalingFactor) got := CalculateReplicas("firing", currentReplicas, maxReplicas, scaling.DefaultMinReplicas, scalingFactor)
if newReplicas <= currentReplicas { if got <= currentReplicas {
t.Log("Expected newReplicas > currentReplica") t.Log("Expected got > currentReplica")
t.Fail() t.Fail()
} }
} }
@ -115,9 +122,9 @@ func TestScaledUpWithSmallParam(t *testing.T) {
currentReplicas := uint64(1) currentReplicas := uint64(1)
maxReplicas := uint64(4) maxReplicas := uint64(4)
scalingFactor := uint64(1) scalingFactor := uint64(1)
newReplicas := CalculateReplicas("firing", currentReplicas, maxReplicas, scaling.DefaultMinReplicas, scalingFactor) got := CalculateReplicas("firing", currentReplicas, maxReplicas, scaling.DefaultMinReplicas, scalingFactor)
if newReplicas <= currentReplicas { if got <= currentReplicas {
t.Log("Expected newReplicas > currentReplica") t.Log("Expected got > currentReplica")
t.Fail() t.Fail()
} }
} }

View File

@ -7,7 +7,7 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io"
"log" "log"
"net" "net"
"net/http" "net/http"
@ -89,7 +89,7 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
var bytesOut []byte var bytesOut []byte
if res.Body != nil { if res.Body != nil {
bytesOut, _ = ioutil.ReadAll(res.Body) bytesOut, _ = io.ReadAll(res.Body)
defer res.Body.Close() defer res.Body.Close()
} }
@ -111,20 +111,17 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
scalingFactor := uint64(scaling.DefaultScalingFactor) scalingFactor := uint64(scaling.DefaultScalingFactor)
availableReplicas := function.AvailableReplicas availableReplicas := function.AvailableReplicas
targetLoad := uint64(scaling.DefaultTargetLoad)
if function.Labels != nil { if function.Labels != nil {
labels := *function.Labels labels := *function.Labels
minReplicas = extractLabelValue(labels[scaling.MinScaleLabel], minReplicas) minReplicas = extractLabelValue(labels[scaling.MinScaleLabel], minReplicas)
maxReplicas = extractLabelValue(labels[scaling.MaxScaleLabel], maxReplicas) maxReplicas = extractLabelValue(labels[scaling.MaxScaleLabel], maxReplicas)
extractedScalingFactor := extractLabelValue(labels[scaling.ScalingFactorLabel], scalingFactor) extractedScalingFactor := extractLabelValue(labels[scaling.ScalingFactorLabel], scalingFactor)
targetLoad = extractLabelValue(labels[scaling.TargetLoadLabel], targetLoad)
if extractedScalingFactor >= 0 && extractedScalingFactor <= 100 { if extractedScalingFactor > 0 && extractedScalingFactor <= 100 {
scalingFactor = extractedScalingFactor scalingFactor = extractedScalingFactor
} else { } else {
log.Printf("Bad Scaling Factor: %d, is not in range of [0 - 100]. Will fallback to %d", extractedScalingFactor, scalingFactor) return scaling.ServiceQueryResponse{}, fmt.Errorf("bad scaling factor: %d, is not in range of [0 - 100]", extractedScalingFactor)
} }
} }
@ -135,7 +132,6 @@ func (s ExternalServiceQuery) GetReplicas(serviceName, serviceNamespace string)
ScalingFactor: scalingFactor, ScalingFactor: scalingFactor,
AvailableReplicas: availableReplicas, AvailableReplicas: availableReplicas,
Annotations: function.Annotations, Annotations: function.Annotations,
TargetLoad: targetLoad,
}, err }, err
} }

View File

@ -75,7 +75,6 @@ func TestGetReplicasExistentFn(t *testing.T) {
MinReplicas: uint64(scaling.DefaultMinReplicas), MinReplicas: uint64(scaling.DefaultMinReplicas),
ScalingFactor: uint64(scaling.DefaultScalingFactor), ScalingFactor: uint64(scaling.DefaultScalingFactor),
AvailableReplicas: 0, AvailableReplicas: 0,
TargetLoad: 10,
} }
var injector middleware.AuthInjector var injector middleware.AuthInjector

View File

@ -5,13 +5,10 @@ const (
DefaultMinReplicas = 1 DefaultMinReplicas = 1
// DefaultMaxReplicas is the amount of replicas a service will auto-scale up to. // DefaultMaxReplicas is the amount of replicas a service will auto-scale up to.
DefaultMaxReplicas = 20 DefaultMaxReplicas = 5
// DefaultScalingFactor is the defining proportion for the scaling increments. // DefaultScalingFactor is the defining proportion for the scaling increments.
DefaultScalingFactor = 20 DefaultScalingFactor = 10
// DefaultTargetLoad
DefaultTargetLoad = 10
DefaultTypeScale = "rps" DefaultTypeScale = "rps"
@ -23,10 +20,4 @@ const (
// ScalingFactorLabel label indicates the scaling factor for a function // ScalingFactorLabel label indicates the scaling factor for a function
ScalingFactorLabel = "com.openfaas.scale.factor" ScalingFactorLabel = "com.openfaas.scale.factor"
// TargetLoadLabel see also DefaultTargetScale
TargetLoadLabel = "com.openfaas.scale.target"
// ScaleTypeLabel see also DefaultScaleType
ScaleTypeLabel = "com.openfaas.scale.type"
) )

View File

@ -17,5 +17,4 @@ type ServiceQueryResponse struct {
ScalingFactor uint64 ScalingFactor uint64
AvailableReplicas uint64 AvailableReplicas uint64
Annotations *map[string]string Annotations *map[string]string
TargetLoad uint64
} }