Skip to content

Commit 3e2d1e9

Browse files
committed
perf: use concurrent map when storing metrics
In busy/large clusters, will prevent timeouts from long living locks/concurrency issues, as the writing to the map takes overly long, blocking the metrics-reading thread and as the lock doesn't get released in a timely manner, timing out the request. Inpired by previous PR at #1028
1 parent f50205a commit 3e2d1e9

File tree

2 files changed

+22
-31
lines changed

2 files changed

+22
-31
lines changed

pkg/metrics_store/metrics_store.go

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import (
2020
"sync"
2121

2222
"k8s.io/apimachinery/pkg/api/meta"
23-
"k8s.io/apimachinery/pkg/types"
24-
2523
"k8s.io/kube-state-metrics/v2/pkg/metric"
2624
)
2725

@@ -33,7 +31,7 @@ type MetricsStore struct {
3331
// metric families, containing a slice of metrics. We need to keep metrics
3432
// grouped by metric families in order to zip families with their help text in
3533
// MetricsStore.WriteAll().
36-
metrics map[types.UID][][]byte
34+
metrics sync.Map
3735

3836
// generateMetricsFunc generates metrics based on a given Kubernetes object
3937
// and returns them grouped by metric family.
@@ -42,17 +40,14 @@ type MetricsStore struct {
4240
// later on zipped with with their corresponding metric families in
4341
// MetricStore.WriteAll().
4442
headers []string
45-
46-
// Protects metrics
47-
mutex sync.RWMutex
4843
}
4944

5045
// NewMetricsStore returns a new MetricsStore
5146
func NewMetricsStore(headers []string, generateFunc func(interface{}) []metric.FamilyInterface) *MetricsStore {
5247
return &MetricsStore{
5348
generateMetricsFunc: generateFunc,
5449
headers: headers,
55-
metrics: map[types.UID][][]byte{},
50+
metrics: sync.Map{},
5651
}
5752
}
5853

@@ -66,17 +61,14 @@ func (s *MetricsStore) Add(obj interface{}) error {
6661
return err
6762
}
6863

69-
s.mutex.Lock()
70-
defer s.mutex.Unlock()
71-
7264
families := s.generateMetricsFunc(obj)
7365
familyStrings := make([][]byte, len(families))
7466

7567
for i, f := range families {
7668
familyStrings[i] = f.ByteSlice()
7769
}
7870

79-
s.metrics[o.GetUID()] = familyStrings
71+
s.metrics.Store(o.GetUID(), familyStrings)
8072

8173
return nil
8274
}
@@ -95,10 +87,7 @@ func (s *MetricsStore) Delete(obj interface{}) error {
9587
return err
9688
}
9789

98-
s.mutex.Lock()
99-
defer s.mutex.Unlock()
100-
101-
delete(s.metrics, o.GetUID())
90+
s.metrics.Delete(o.GetUID())
10291

10392
return nil
10493
}
@@ -126,9 +115,7 @@ func (s *MetricsStore) GetByKey(_ string) (item interface{}, exists bool, err er
126115
// Replace will delete the contents of the store, using instead the
127116
// given list.
128117
func (s *MetricsStore) Replace(list []interface{}, _ string) error {
129-
s.mutex.Lock()
130-
s.metrics = map[types.UID][][]byte{}
131-
s.mutex.Unlock()
118+
s.metrics.Clear()
132119

133120
for _, o := range list {
134121
err := s.Add(o)

pkg/metrics_store/metrics_writer.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,31 +56,35 @@ func (m MetricsWriter) WriteAll(w io.Writer) error {
5656
return nil
5757
}
5858

59-
for _, s := range m.stores {
60-
s.mutex.RLock()
61-
defer func(s *MetricsStore) {
62-
s.mutex.RUnlock()
63-
}(s)
64-
}
65-
6659
for i, help := range m.stores[0].headers {
6760
if help != "" && help != "\n" {
6861
help += "\n"
6962
}
7063

71-
if len(m.stores[0].metrics) > 0 {
72-
_, err := w.Write([]byte(help))
64+
var err error
65+
m.stores[0].metrics.Range(func(key interface{}, value interface{}) bool {
66+
_, err = w.Write([]byte(help))
7367
if err != nil {
74-
return fmt.Errorf("failed to write help text: %v", err)
68+
err = fmt.Errorf("failed to write help text: %v", err)
7569
}
70+
return false
71+
})
72+
if err != nil {
73+
return err
7674
}
7775

7876
for _, s := range m.stores {
79-
for _, metricFamilies := range s.metrics {
80-
_, err := w.Write(metricFamilies[i])
77+
s.metrics.Range(func(key interface{}, value interface{}) bool {
78+
metricFamilies := value.([][]byte)
79+
_, err = w.Write(metricFamilies[i])
8180
if err != nil {
82-
return fmt.Errorf("failed to write metrics family: %v", err)
81+
err = fmt.Errorf("failed to write metrics family: %v", err)
82+
return false
8383
}
84+
return true
85+
})
86+
if err != nil {
87+
return err
8488
}
8589
}
8690
}

0 commit comments

Comments
 (0)