Skip to content
This repository was archived by the owner on Aug 23, 2023. It is now read-only.

Commit 75065dc

Browse files
committed
optimize AddRange
BEFORE: ~/g/s/g/g/m/m/cache ❯❯❯ taskset --cpu-list 5 go test -bench . -benchmem goos: linux goarch: amd64 pkg: github.com/grafana/metrictank/mdata/cache BenchmarkAddingManyChunksOneByOne 10000 1225701 ns/op 21688 B/op 5 allocs/op BenchmarkAddingManyChunksAtOnce 1000000 1146 ns/op 118 B/op 1 allocs/op PASS ok github.com/grafana/metrictank/mdata/cache 14.713s AFTER: ~/g/s/g/g/m/m/cache ❯❯❯ taskset --cpu-list 5 go test -bench . -benchmem goos: linux goarch: amd64 pkg: github.com/grafana/metrictank/mdata/cache BenchmarkAddingManyChunksOneByOne 10000 1226088 ns/op 21688 B/op 5 allocs/op BenchmarkAddingManyChunksAtOnce 1000000 1043 ns/op 118 B/op 1 allocs/op PASS ok github.com/grafana/metrictank/mdata/cache 14.620s
1 parent d321e7a commit 75065dc

File tree

1 file changed

+72
-32
lines changed

1 file changed

+72
-32
lines changed

mdata/cache/ccache_metric.go

+72-32
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,17 @@ func (mc *CCacheMetric) AddRange(prev uint32, itergens []chunk.IterGen) {
7878
return
7979
}
8080

81+
if len(itergens) == 1 {
82+
mc.Add(prev, itergens[0])
83+
return
84+
}
85+
8186
mc.Lock()
8287
defer mc.Unlock()
8388

84-
ts := itergens[0].Ts
89+
// handle the first one
90+
itergen := itergens[0]
91+
ts := itergen.Ts
8592

8693
// if previous chunk has not been passed we try to be smart and figure it out.
8794
// this is common in a scenario where a metric continuously gets queried
@@ -93,46 +100,74 @@ func (mc *CCacheMetric) AddRange(prev uint32, itergens []chunk.IterGen) {
93100
}
94101
}
95102

96-
for _, itergen := range itergens {
97-
ts = itergen.Ts
98-
99-
if _, ok := mc.chunks[ts]; ok {
100-
// chunk is already present. no need to error on that, just ignore it
101-
continue
102-
}
103+
// if the previous chunk is cached, link it
104+
if _, ok := mc.chunks[prev]; ok {
105+
mc.chunks[prev].Next = ts
106+
} else {
107+
prev = 0
108+
}
103109

110+
// add chunk if we don't have it yet (most likely)
111+
if _, ok := mc.chunks[ts]; !ok {
104112
mc.chunks[ts] = &CCacheChunk{
105113
Ts: ts,
106-
Prev: 0,
107-
Next: 0,
114+
Prev: prev,
115+
Next: itergens[1].Ts,
108116
Itgen: itergen,
109117
}
118+
}
110119

111-
// if the previous chunk is cached, link in both directions
112-
if _, ok := mc.chunks[prev]; ok {
113-
mc.chunks[prev].Next = ts
114-
mc.chunks[ts].Prev = prev
120+
prev = ts
121+
122+
// handle the 2nd until the last-but-one
123+
for i := 1; i < len(itergens)-1; i++ {
124+
itergen := itergens[i]
125+
ts := itergen.Ts
126+
// add chunk if we don't have it yet (most likely)
127+
if _, ok := mc.chunks[ts]; !ok {
128+
mc.chunks[ts] = &CCacheChunk{
129+
Ts: ts,
130+
Prev: prev,
131+
Next: itergens[i+1].Ts,
132+
Itgen: itergen,
133+
}
115134
}
116135
prev = ts
117136
}
118137

119-
nextTs := mc.nextTs(ts)
138+
// handle the last one
139+
itergen = itergens[len(itergens)-1]
140+
ts = itergen.Ts
120141

121142
// if nextTs() can't figure out the end date it returns ts
122-
if nextTs > ts {
143+
next := mc.nextTsCore(itergen, ts, prev, 0)
144+
if next == ts {
145+
next = 0
146+
} else {
123147
// if the next chunk is cached, link in both directions
124-
if _, ok := mc.chunks[nextTs]; ok {
125-
mc.chunks[nextTs].Prev = ts
126-
mc.chunks[ts].Next = nextTs
148+
if _, ok := mc.chunks[next]; ok {
149+
mc.chunks[next].Prev = ts
150+
} else {
151+
next = 0
127152
}
128153
}
129154

155+
// add chunk if we don't have it yet (most likely)
156+
if _, ok := mc.chunks[ts]; !ok {
157+
mc.chunks[ts] = &CCacheChunk{
158+
Ts: ts,
159+
Prev: prev,
160+
Next: next,
161+
Itgen: itergen,
162+
}
163+
}
130164
// regenerate the list of sorted keys after adding a chunk
131165
mc.generateKeys()
132166

133167
return
134168
}
135169

170+
// Add adds a chunk to the cache
136171
func (mc *CCacheMetric) Add(prev uint32, itergen chunk.IterGen) {
137172
ts := itergen.Ts
138173

@@ -201,24 +236,29 @@ func (mc *CCacheMetric) generateKeys() {
201236
// assumes we already have at least a read lock
202237
func (mc *CCacheMetric) nextTs(ts uint32) uint32 {
203238
chunk := mc.chunks[ts]
204-
span := chunk.Itgen.Span
239+
return mc.nextTsCore(chunk.Itgen, chunk.Ts, chunk.Prev, chunk.Next)
240+
}
241+
242+
// nextTsCore returns the ts of the next chunk, given a chunks key properties
243+
// (to the extent we know them). It guesses if necessary.
244+
// assumes we already have at least a read lock
245+
func (mc *CCacheMetric) nextTsCore(itgen chunk.IterGen, ts, prev, next uint32) uint32 {
246+
span := itgen.Span
205247
if span > 0 {
206248
// if the chunk is span-aware we don't need anything else
207-
return chunk.Ts + span
249+
return ts + span
208250
}
209251

210-
if chunk.Next == 0 {
211-
if chunk.Prev == 0 {
212-
// if a chunk has no next and no previous chunk we have to assume it's length is 0
213-
return chunk.Ts
214-
} else {
215-
// if chunk has no next chunk, but has a previous one, we assume the length of this one is same as the previous one
216-
return chunk.Ts + (chunk.Ts - chunk.Prev)
217-
}
218-
} else {
219-
// if chunk has a next chunk, then that's the ts we need
220-
return chunk.Next
252+
// if chunk has a next chunk, then that's the ts we need
253+
if next != 0 {
254+
return next
255+
}
256+
// if chunk has no next chunk, but has a previous one, we assume the length of this one is same as the previous one
257+
if prev != 0 {
258+
return ts + (ts - prev)
221259
}
260+
// if a chunk has no next and no previous chunk we have to assume it's length is 0
261+
return ts
222262
}
223263

224264
// lastTs returns the last Ts of this metric cache

0 commit comments

Comments
 (0)