From 80d091a3628794287d9c0a81bdda7bd731ba609e Mon Sep 17 00:00:00 2001 From: free6om Date: Mon, 2 Sep 2024 19:15:00 +0800 Subject: [PATCH] chore: P10K optimizations (#8045) --- go.mod | 2 +- pkg/controller/instanceset/instance_util.go | 25 ++++-- .../instanceset/instance_util_test.go | 19 +++++ .../instanceset/reconciler_update.go | 18 +++-- pkg/controller/instanceset/revision_util.go | 15 +++- pkg/lru/cache.go | 76 +++++++++++++++++++ 6 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 pkg/lru/cache.go diff --git a/go.mod b/go.mod index 65d51173f34..ba77671bf7b 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 github.com/imdario/mergo v0.3.14 + github.com/json-iterator/go v1.1.12 github.com/klauspost/compress v1.17.8 github.com/kubernetes-csi/external-snapshotter/client/v3 v3.0.0 github.com/kubernetes-csi/external-snapshotter/client/v6 v6.2.0 @@ -154,7 +155,6 @@ require ( github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect diff --git a/pkg/controller/instanceset/instance_util.go b/pkg/controller/instanceset/instance_util.go index 919f28daefb..670e1e89a9a 100644 --- a/pkg/controller/instanceset/instance_util.go +++ b/pkg/controller/instanceset/instance_util.go @@ -23,7 +23,6 @@ import ( "encoding/json" "fmt" "reflect" - "regexp" "slices" "sort" "strconv" @@ -66,8 +65,6 @@ type instanceSetExt struct { instanceTemplates []*workloads.InstanceTemplate } -var instanceNameRegex = regexp.MustCompile("(.*)-([0-9]+)$") - var ( reader *zstd.Decoder writer *zstd.Encoder @@ -91,13 +88,15 @@ type instance struct { func ParseParentNameAndOrdinal(s string) (string, int) { parent := s ordinal := -1 - subMatches := instanceNameRegex.FindStringSubmatch(s) - if len(subMatches) < 3 { + + index := strings.LastIndex(s, "-") + if index < 0 { return parent, ordinal } - parent = subMatches[1] - if i, err := strconv.ParseInt(subMatches[2], 10, 32); err == nil { + ordinalStr := s[index+1:] + if i, err := strconv.ParseInt(ordinalStr, 10, 32); err == nil { ordinal = int(i) + parent = s[:index] } return parent, ordinal } @@ -110,8 +109,18 @@ func sortObjects[T client.Object](objects []T, rolePriorityMap map[string]int, r role := strings.ToLower(objects[i].GetLabels()[constant.RoleLabelKey]) return rolePriorityMap[role] } + + // cache the parent names and ordinals to accelerate the parsing process when there is a massive number of Pods. + namesCache := make(map[string]string, len(objects)) + ordinalsCache := make(map[string]int, len(objects)) getNameNOrdinalFunc := func(i int) (string, int) { - return ParseParentNameAndOrdinal(objects[i].GetName()) + if name, ok := namesCache[objects[i].GetName()]; ok { + return name, ordinalsCache[objects[i].GetName()] + } + name, ordinal := ParseParentNameAndOrdinal(objects[i].GetName()) + namesCache[objects[i].GetName()] = name + ordinalsCache[objects[i].GetName()] = ordinal + return name, ordinal } baseSort(objects, getNameNOrdinalFunc, getRolePriorityFunc, reverse) } diff --git a/pkg/controller/instanceset/instance_util_test.go b/pkg/controller/instanceset/instance_util_test.go index df3756e2a7a..81aeac2d9c2 100644 --- a/pkg/controller/instanceset/instance_util_test.go +++ b/pkg/controller/instanceset/instance_util_test.go @@ -22,9 +22,11 @@ package instanceset import ( "fmt" "strings" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gmeasure" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -1341,4 +1343,21 @@ var _ = Describe("instance util test", func() { Expect(affinity2).Should(Equal(expectMergedAffinity)) }) }) + + Context("ParseParentNameAndOrdinal", func() { + It("Benchmark", Serial, Label("measurement"), func() { + experiment := gmeasure.NewExperiment("ParseParentNameAndOrdinal Benchmark") + AddReportEntry(experiment.Name, experiment) + + experiment.Sample(func(idx int) { + experiment.MeasureDuration("ParseParentNameAndOrdinal", func() { + _, _ = ParseParentNameAndOrdinal("foo-bar-666") + }) + }, gmeasure.SamplingConfig{N: 100, Duration: time.Second}) + + parsingStats := experiment.GetStats("ParseParentNameAndOrdinal") + medianDuration := parsingStats.DurationFor(gmeasure.StatMedian) + Expect(medianDuration).To(BeNumerically("<", time.Millisecond)) + }) + }) }) diff --git a/pkg/controller/instanceset/reconciler_update.go b/pkg/controller/instanceset/reconciler_update.go index 38d5a38d405..26adbd0fb42 100644 --- a/pkg/controller/instanceset/reconciler_update.go +++ b/pkg/controller/instanceset/reconciler_update.go @@ -110,15 +110,17 @@ func (r *updateReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder } unavailable := maxUnavailable - currentUnavailable - // TODO(free6om): compute updateCount from PodManagementPolicy(Serial/OrderedReady, Parallel, BestEffortParallel). - // align MemberUpdateStrategy with PodManagementPolicy if it has nil value. - itsForPlan := getInstanceSetForUpdatePlan(its) - plan := NewUpdatePlan(*itsForPlan, oldPodList, IsPodUpdated) - podsToBeUpdated, err := plan.Execute() - if err != nil { - return kubebuilderx.Continue, err + // if it's a roleful InstanceSet, we use updateCount to represent Pods can be updated according to the spec.memberUpdateStrategy. + updateCount := len(oldPodList) + if len(its.Spec.Roles) > 0 { + itsForPlan := getInstanceSetForUpdatePlan(its) + plan := NewUpdatePlan(*itsForPlan, oldPodList, IsPodUpdated) + podsToBeUpdated, err := plan.Execute() + if err != nil { + return kubebuilderx.Continue, err + } + updateCount = len(podsToBeUpdated) } - updateCount := len(podsToBeUpdated) updatingPods := 0 updatedPods := 0 diff --git a/pkg/controller/instanceset/revision_util.go b/pkg/controller/instanceset/revision_util.go index 152097b26f7..ba138cf50f6 100644 --- a/pkg/controller/instanceset/revision_util.go +++ b/pkg/controller/instanceset/revision_util.go @@ -27,6 +27,7 @@ import ( "hash/fnv" "strconv" + jsoniter "github.com/json-iterator/go" apps "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -37,6 +38,7 @@ import ( workloads "github.com/apecloud/kubeblocks/apis/workloads/v1alpha1" "github.com/apecloud/kubeblocks/pkg/controller/model" + "github.com/apecloud/kubeblocks/pkg/lru" viper "github.com/apecloud/kubeblocks/pkg/viperx" ) @@ -47,6 +49,8 @@ var Codecs = serializer.NewCodecFactory(model.GetScheme()) var patchCodec = Codecs.LegacyCodec(workloads.SchemeGroupVersion) var controllerKind = apps.SchemeGroupVersion.WithKind("StatefulSet") +var jsonIter = jsoniter.ConfigCompatibleWithStandardLibrary + func NewRevision(its *workloads.InstanceSet) (*apps.ControllerRevision, error) { patch, err := getPatch(its) if err != nil { @@ -159,6 +163,8 @@ func DeepHashObject(hasher hash.Hash, objectToWrite interface{}) { fmt.Fprintf(hasher, "%v", dump.ForHash(objectToWrite)) } +var revisionsCache = lru.New(1024) + func GetRevisions(revisions map[string]string) (map[string]string, error) { if revisions == nil { return nil, nil @@ -167,6 +173,9 @@ func GetRevisions(revisions map[string]string) (map[string]string, error) { if !ok { return revisions, nil } + if revisionsInCache, ok := revisionsCache.Get(revisionsStr); ok { + return revisionsInCache.(map[string]string), nil + } revisionsData, err := base64.StdEncoding.DecodeString(revisionsStr) if err != nil { return nil, err @@ -176,9 +185,11 @@ func GetRevisions(revisions map[string]string) (map[string]string, error) { return nil, err } updateRevisions := make(map[string]string) - if err = json.Unmarshal(revisionsJSON, &updateRevisions); err != nil { + + if err = jsonIter.Unmarshal(revisionsJSON, &updateRevisions); err != nil { return nil, err } + revisionsCache.Put(revisionsStr, updateRevisions) return updateRevisions, nil } @@ -187,7 +198,7 @@ func buildRevisions(updateRevisions map[string]string) (map[string]string, error if len(updateRevisions) <= maxPlainRevisionCount { return updateRevisions, nil } - revisionsJSON, err := json.Marshal(updateRevisions) + revisionsJSON, err := jsonIter.Marshal(updateRevisions) if err != nil { return nil, err } diff --git a/pkg/lru/cache.go b/pkg/lru/cache.go new file mode 100644 index 00000000000..d118bab9dce --- /dev/null +++ b/pkg/lru/cache.go @@ -0,0 +1,76 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package lru + +import ( + "container/list" + "sync" +) + +type Cache struct { + capacity int + m sync.RWMutex + list *list.List + items map[string]*list.Element +} + +type cacheItem struct { + key string + value any +} + +func New(capacity int) *Cache { + return &Cache{ + capacity: capacity, + list: list.New(), + items: make(map[string]*list.Element, capacity), + } +} + +func (c *Cache) Get(key string) (any, bool) { + c.m.RLock() + defer c.m.RUnlock() + + if elem, ok := c.items[key]; ok { + c.list.MoveToFront(elem) + return elem.Value.(*cacheItem).value, true + } + return nil, false +} + +func (c *Cache) Put(key string, value any) { + c.m.Lock() + defer c.m.Unlock() + + if elem, ok := c.items[key]; ok { + c.list.MoveToFront(elem) + elem.Value.(*cacheItem).value = value + return + } + + if c.list.Len() == c.capacity { + last := c.list.Back() + c.list.Remove(last) + delete(c.items, last.Value.(*cacheItem).key) + } + + elem := c.list.PushFront(&cacheItem{key, value}) + c.items[key] = elem +}