github.com/thanos-io/thanos@v0.32.5/internal/cortex/chunk/cache/memcached_test.go (about) 1 // Copyright (c) The Cortex Authors. 2 // Licensed under the Apache License 2.0. 3 4 package cache_test 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "testing" 11 12 "github.com/bradfitz/gomemcache/memcache" 13 "github.com/go-kit/log" 14 "github.com/stretchr/testify/require" 15 "go.uber.org/atomic" 16 17 "github.com/thanos-io/thanos/internal/cortex/chunk/cache" 18 ) 19 20 func TestMemcached(t *testing.T) { 21 t.Run("unbatched", func(t *testing.T) { 22 client := newMockMemcache() 23 memcache := cache.NewMemcached(cache.MemcachedConfig{}, client, 24 "test", nil, log.NewNopLogger()) 25 26 testMemcache(t, memcache) 27 }) 28 29 t.Run("batched", func(t *testing.T) { 30 client := newMockMemcache() 31 memcache := cache.NewMemcached(cache.MemcachedConfig{ 32 BatchSize: 10, 33 Parallelism: 5, 34 }, client, "test", nil, log.NewNopLogger()) 35 36 testMemcache(t, memcache) 37 }) 38 } 39 40 func testMemcache(t *testing.T, memcache *cache.Memcached) { 41 numKeys := 1000 42 43 ctx := context.Background() 44 keysIncMissing := make([]string, 0, numKeys) 45 keys := make([]string, 0, numKeys) 46 bufs := make([][]byte, 0, numKeys) 47 48 // Insert 1000 keys skipping all multiples of 5. 49 for i := 0; i < numKeys; i++ { 50 keysIncMissing = append(keysIncMissing, fmt.Sprint(i)) 51 if i%5 == 0 { 52 continue 53 } 54 55 keys = append(keys, fmt.Sprint(i)) 56 bufs = append(bufs, []byte(fmt.Sprint(i))) 57 } 58 memcache.Store(ctx, keys, bufs) 59 60 found, bufs, missing := memcache.Fetch(ctx, keysIncMissing) 61 for i := 0; i < numKeys; i++ { 62 if i%5 == 0 { 63 require.Equal(t, fmt.Sprint(i), missing[0]) 64 missing = missing[1:] 65 continue 66 } 67 68 require.Equal(t, fmt.Sprint(i), found[0]) 69 require.Equal(t, fmt.Sprint(i), string(bufs[0])) 70 found = found[1:] 71 bufs = bufs[1:] 72 } 73 } 74 75 // mockMemcache whose calls fail 1/3rd of the time. 76 type mockMemcacheFailing struct { 77 *mockMemcache 78 calls atomic.Uint64 79 } 80 81 func newMockMemcacheFailing() *mockMemcacheFailing { 82 return &mockMemcacheFailing{ 83 mockMemcache: newMockMemcache(), 84 } 85 } 86 87 func (c *mockMemcacheFailing) GetMulti(keys []string) (map[string]*memcache.Item, error) { 88 calls := c.calls.Inc() 89 if calls%3 == 0 { 90 return nil, errors.New("fail") 91 } 92 93 return c.mockMemcache.GetMulti(keys) 94 } 95 96 func TestMemcacheFailure(t *testing.T) { 97 t.Run("unbatched", func(t *testing.T) { 98 client := newMockMemcacheFailing() 99 memcache := cache.NewMemcached(cache.MemcachedConfig{}, client, 100 "test", nil, log.NewNopLogger()) 101 102 testMemcacheFailing(t, memcache) 103 }) 104 105 t.Run("batched", func(t *testing.T) { 106 client := newMockMemcacheFailing() 107 memcache := cache.NewMemcached(cache.MemcachedConfig{ 108 BatchSize: 10, 109 Parallelism: 5, 110 }, client, "test", nil, log.NewNopLogger()) 111 112 testMemcacheFailing(t, memcache) 113 }) 114 } 115 116 func testMemcacheFailing(t *testing.T, memcache *cache.Memcached) { 117 numKeys := 1000 118 119 ctx := context.Background() 120 keysIncMissing := make([]string, 0, numKeys) 121 keys := make([]string, 0, numKeys) 122 bufs := make([][]byte, 0, numKeys) 123 // Insert 1000 keys skipping all multiples of 5. 124 for i := 0; i < numKeys; i++ { 125 keysIncMissing = append(keysIncMissing, fmt.Sprint(i)) 126 if i%5 == 0 { 127 continue 128 } 129 keys = append(keys, fmt.Sprint(i)) 130 bufs = append(bufs, []byte(fmt.Sprint(i))) 131 } 132 memcache.Store(ctx, keys, bufs) 133 134 for i := 0; i < 10; i++ { 135 found, bufs, missing := memcache.Fetch(ctx, keysIncMissing) 136 137 require.Equal(t, len(found), len(bufs)) 138 for i := range found { 139 require.Equal(t, found[i], string(bufs[i])) 140 } 141 142 keysReturned := make(map[string]struct{}) 143 for _, key := range found { 144 _, ok := keysReturned[key] 145 require.False(t, ok, "duplicate key returned") 146 147 keysReturned[key] = struct{}{} 148 } 149 for _, key := range missing { 150 _, ok := keysReturned[key] 151 require.False(t, ok, "duplicate key returned") 152 153 keysReturned[key] = struct{}{} 154 } 155 156 for _, key := range keys { 157 _, ok := keysReturned[key] 158 require.True(t, ok, "key missing %s", key) 159 } 160 } 161 } 162 163 func TestMemcacheStop(t *testing.T) { 164 t.Run("unbatched", func(t *testing.T) { 165 client := newMockMemcacheFailing() 166 memcache := cache.NewMemcached(cache.MemcachedConfig{}, client, 167 "test", nil, log.NewNopLogger()) 168 169 testMemcachedStopping(t, memcache) 170 }) 171 172 t.Run("batched", func(t *testing.T) { 173 client := newMockMemcacheFailing() 174 memcache := cache.NewMemcached(cache.MemcachedConfig{ 175 BatchSize: 10, 176 Parallelism: 5, 177 }, client, "test", nil, log.NewNopLogger()) 178 179 testMemcachedStopping(t, memcache) 180 }) 181 } 182 183 func testMemcachedStopping(t *testing.T, memcache *cache.Memcached) { 184 numKeys := 1000 185 ctx := context.Background() 186 keys := make([]string, 0, numKeys) 187 bufs := make([][]byte, 0, numKeys) 188 for i := 0; i < numKeys; i++ { 189 keys = append(keys, fmt.Sprint(i)) 190 bufs = append(bufs, []byte(fmt.Sprint(i))) 191 } 192 193 memcache.Store(ctx, keys, bufs) 194 195 go memcache.Fetch(ctx, keys) 196 memcache.Stop() 197 }