Skip to content

Commit 661f47b

Browse files
authored
Fix race on chunks multilevel cache + Optimize to avoid refetching already found keys. (#6312)
* Creating a test to show the race on the multilevel cache Signed-off-by: alanprot <[email protected]> * fix the race problem * Only fetch keys that were not found on the previous cache Signed-off-by: alanprot <[email protected]> --------- Signed-off-by: alanprot <[email protected]>
1 parent c25b18d commit 661f47b

File tree

2 files changed

+69
-8
lines changed

2 files changed

+69
-8
lines changed

pkg/storage/tsdb/multilevel_chunk_cache.go

+15-2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ func (m *multiLevelChunkCache) Fetch(ctx context.Context, keys []string) map[str
9898
timer := prometheus.NewTimer(m.fetchLatency.WithLabelValues())
9999
defer timer.ObserveDuration()
100100

101+
missingKeys := keys
101102
hits := map[string][]byte{}
102103
backfillItems := make([]map[string][]byte, len(m.caches)-1)
103104

@@ -108,13 +109,25 @@ func (m *multiLevelChunkCache) Fetch(ctx context.Context, keys []string) map[str
108109
if ctx.Err() != nil {
109110
return nil
110111
}
111-
if data := c.Fetch(ctx, keys); len(data) > 0 {
112+
if data := c.Fetch(ctx, missingKeys); len(data) > 0 {
112113
for k, d := range data {
113114
hits[k] = d
114115
}
115116

116117
if i > 0 && len(hits) > 0 {
117-
backfillItems[i-1] = hits
118+
// lets fetch only the mising keys
119+
m := missingKeys[:0]
120+
for _, key := range missingKeys {
121+
if _, ok := hits[key]; !ok {
122+
m = append(m, key)
123+
}
124+
}
125+
126+
missingKeys = m
127+
128+
for k, b := range hits {
129+
backfillItems[i-1][k] = b
130+
}
118131
}
119132

120133
if len(hits) == len(keys) {

pkg/storage/tsdb/multilevel_chunk_cache_test.go

+54-6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66
"testing"
77
"time"
88

9+
"github.com/go-kit/log"
910
"github.com/prometheus/client_golang/prometheus"
1011
"github.com/stretchr/testify/require"
12+
"github.com/thanos-io/thanos/pkg/cache"
1113
)
1214

1315
func Test_MultiLevelChunkCacheStore(t *testing.T) {
@@ -72,6 +74,43 @@ func Test_MultiLevelChunkCacheStore(t *testing.T) {
7274
}
7375
}
7476

77+
func Test_MultiLevelChunkCacheFetchRace(t *testing.T) {
78+
cfg := MultiLevelChunkCacheConfig{
79+
MaxAsyncConcurrency: 10,
80+
MaxAsyncBufferSize: 100000,
81+
MaxBackfillItems: 10000,
82+
BackFillTTL: time.Hour * 24,
83+
}
84+
reg := prometheus.NewRegistry()
85+
86+
m1 := newMockChunkCache("m1", map[string][]byte{
87+
"key1": []byte("value1"),
88+
"key2": []byte("value2"),
89+
})
90+
91+
inMemory, err := cache.NewInMemoryCacheWithConfig("test", log.NewNopLogger(), reg, cache.InMemoryCacheConfig{MaxSize: 10 * 1024, MaxItemSize: 1024})
92+
require.NoError(t, err)
93+
94+
inMemory.Store(map[string][]byte{
95+
"key2": []byte("value2"),
96+
"key3": []byte("value3"),
97+
}, time.Minute)
98+
99+
c := newMultiLevelChunkCache("chunk-cache", cfg, reg, inMemory, m1)
100+
101+
hits := c.Fetch(context.Background(), []string{"key1", "key2", "key3", "key4"})
102+
103+
require.Equal(t, 3, len(hits))
104+
105+
// We should be able to change the returned values without any race problem
106+
delete(hits, "key1")
107+
108+
mlc := c.(*multiLevelChunkCache)
109+
//Wait until async operation finishes.
110+
mlc.backfillProcessor.Stop()
111+
112+
}
113+
75114
func Test_MultiLevelChunkCacheFetch(t *testing.T) {
76115
cfg := MultiLevelChunkCacheConfig{
77116
MaxAsyncConcurrency: 10,
@@ -81,12 +120,14 @@ func Test_MultiLevelChunkCacheFetch(t *testing.T) {
81120
}
82121

83122
testCases := map[string]struct {
84-
m1ExistingData map[string][]byte
85-
m2ExistingData map[string][]byte
86-
expectedM1Data map[string][]byte
87-
expectedM2Data map[string][]byte
88-
expectedFetchedData map[string][]byte
89-
fetchKeys []string
123+
m1ExistingData map[string][]byte
124+
m2ExistingData map[string][]byte
125+
expectedM1Data map[string][]byte
126+
expectedM2Data map[string][]byte
127+
expectedFetchedData map[string][]byte
128+
expectedM1FetchedKeys []string
129+
expectedM2FetchedKeys []string
130+
fetchKeys []string
90131
}{
91132
"fetched data should be union of m1, m2 and 'key2' and `key3' should be backfilled to m1": {
92133
m1ExistingData: map[string][]byte{
@@ -96,6 +137,8 @@ func Test_MultiLevelChunkCacheFetch(t *testing.T) {
96137
"key2": []byte("value2"),
97138
"key3": []byte("value3"),
98139
},
140+
expectedM1FetchedKeys: []string{"key1", "key2", "key3"},
141+
expectedM2FetchedKeys: []string{"key2", "key3"},
99142
expectedM1Data: map[string][]byte{
100143
"key1": []byte("value1"),
101144
"key2": []byte("value2"),
@@ -119,6 +162,8 @@ func Test_MultiLevelChunkCacheFetch(t *testing.T) {
119162
m2ExistingData: map[string][]byte{
120163
"key2": []byte("value2"),
121164
},
165+
expectedM1FetchedKeys: []string{"key1", "key2", "key3"},
166+
expectedM2FetchedKeys: []string{"key2", "key3"},
122167
expectedM1Data: map[string][]byte{
123168
"key1": []byte("value1"),
124169
"key2": []byte("value2"),
@@ -157,6 +202,8 @@ type mockChunkCache struct {
157202
mu sync.Mutex
158203
name string
159204
data map[string][]byte
205+
206+
fetchedKeys []string
160207
}
161208

162209
func newMockChunkCache(name string, data map[string][]byte) *mockChunkCache {
@@ -180,6 +227,7 @@ func (m *mockChunkCache) Fetch(_ context.Context, keys []string) map[string][]by
180227
h := map[string][]byte{}
181228

182229
for _, k := range keys {
230+
m.fetchedKeys = append(m.fetchedKeys, k)
183231
if _, ok := m.data[k]; ok {
184232
h[k] = m.data[k]
185233
}

0 commit comments

Comments
 (0)