github.com/klaytn/klaytn@v1.12.1/storage/statedb/cache_redis_test.go (about) 1 // Copyright 2020 The klaytn Authors 2 // This file is part of the klaytn library. 3 // 4 // The klaytn library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The klaytn library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>. 16 17 package statedb 18 19 import ( 20 "bytes" 21 "net" 22 "strings" 23 "sync" 24 "testing" 25 "time" 26 27 "github.com/go-redis/redis/v7" 28 "github.com/klaytn/klaytn/storage" 29 "github.com/stretchr/testify/assert" 30 ) 31 32 const sleepDurationForAsyncBehavior = 100 * time.Millisecond 33 34 func getTestRedisConfig() *TrieNodeCacheConfig { 35 return &TrieNodeCacheConfig{ 36 CacheType: CacheTypeRedis, 37 LocalCacheSizeMiB: 100, 38 RedisEndpoints: []string{"localhost:6379"}, 39 RedisClusterEnable: false, 40 } 41 } 42 43 func TestSubscription(t *testing.T) { 44 storage.SkipLocalTest(t) 45 46 msg1 := "testMessage1" 47 msg2 := "testMessage2" 48 49 wg := sync.WaitGroup{} 50 wg.Add(1) 51 52 go func() { 53 cache, err := newRedisCache(getTestRedisConfig()) 54 assert.Nil(t, err) 55 56 ch := cache.SubscribeBlockCh() 57 58 select { 59 case actualMsg := <-ch: 60 assert.Equal(t, msg1, actualMsg.Payload) 61 case <-time.After(time.Second): 62 panic("timeout") 63 } 64 65 select { 66 case actualMsg := <-ch: 67 assert.Equal(t, msg2, actualMsg.Payload) 68 case <-time.After(time.Second): 69 panic("timeout") 70 } 71 72 wg.Done() 73 }() 74 time.Sleep(sleepDurationForAsyncBehavior) 75 76 cache, err := newRedisCache(getTestRedisConfig()) 77 assert.Nil(t, err) 78 79 if err := cache.PublishBlock(msg1); err != nil { 80 t.Fatal(err) 81 } 82 83 if err := cache.PublishBlock(msg2); err != nil { 84 t.Fatal(err) 85 } 86 87 wg.Wait() 88 } 89 90 // TestRedisCache tests basic operations of redis cache 91 func TestRedisCache(t *testing.T) { 92 storage.SkipLocalTest(t) 93 94 cache, err := newRedisCache(getTestRedisConfig()) 95 assert.Nil(t, err) 96 97 key, value := randBytes(32), randBytes(500) 98 cache.Set(key, value) 99 100 getValue := cache.Get(key) 101 assert.Equal(t, bytes.Compare(value, getValue), 0) 102 103 hasValue, ok := cache.Has(key) 104 assert.Equal(t, ok, true) 105 assert.Equal(t, bytes.Compare(value, hasValue), 0) 106 } 107 108 // TestRedisCache_Set_LargeData check whether redis cache can store an large data (5MB). 109 func TestRedisCache_Set_LargeData(t *testing.T) { 110 storage.SkipLocalTest(t) 111 112 cache, err := newRedisCache(getTestRedisConfig()) 113 if err != nil { 114 t.Fatal(err) 115 } 116 117 key, value := randBytes(32), randBytes(5*1024*1024) // 5MB value 118 cache.Set(key, value) 119 120 retValue := cache.Get(key) 121 assert.Equal(t, bytes.Compare(value, retValue), 0) 122 } 123 124 // TestRedisCache_SetAsync tests basic operations of redis cache using SetAsync instead of Set. 125 func TestRedisCache_SetAsync(t *testing.T) { 126 storage.SkipLocalTest(t) 127 128 cache, err := newRedisCache(getTestRedisConfig()) 129 assert.Nil(t, err) 130 131 key, value := randBytes(32), randBytes(500) 132 cache.SetAsync(key, value) 133 time.Sleep(sleepDurationForAsyncBehavior) 134 135 getValue := cache.Get(key) 136 assert.Equal(t, bytes.Compare(value, getValue), 0) 137 138 hasValue, ok := cache.Has(key) 139 assert.Equal(t, ok, true) 140 assert.Equal(t, bytes.Compare(value, hasValue), 0) 141 } 142 143 // TestRedisCache_SetAsync_LargeData check whether redis cache can store an large data asynchronously (5MB). 144 func TestRedisCache_SetAsync_LargeData(t *testing.T) { 145 storage.SkipLocalTest(t) 146 147 cache, err := newRedisCache(getTestRedisConfig()) 148 if err != nil { 149 t.Fatal(err) 150 } 151 152 key, value := randBytes(32), randBytes(5*1024*1024) // 5MB value 153 cache.SetAsync(key, value) 154 time.Sleep(sleepDurationForAsyncBehavior) 155 156 retValue := cache.Get(key) 157 assert.Equal(t, bytes.Compare(value, retValue), 0) 158 } 159 160 // TestRedisCache_SetAsync_LargeNumberItems asynchronously sets lots of items exceeding channel size. 161 func TestRedisCache_SetAsync_LargeNumberItems(t *testing.T) { 162 storage.SkipLocalTest(t) 163 164 cache, err := newRedisCache(getTestRedisConfig()) 165 if err != nil { 166 t.Fatal(err) 167 } 168 169 itemsLen := redisSetItemChannelSize * 2 170 items := make([]setItem, itemsLen) 171 for i := 0; i < itemsLen; i++ { 172 items[i].key = randBytes(32) 173 items[i].value = randBytes(500) 174 } 175 176 go func() { 177 // wait for a while to avoid redis setItem channel full 178 time.Sleep(sleepDurationForAsyncBehavior) 179 180 for i := 0; i < itemsLen; i++ { 181 if i == redisSetItemChannelSize { 182 // sleep for a while because Set command can drop an item if setItem channel is full 183 time.Sleep(2 * time.Second) 184 } 185 // set writes items asynchronously 186 cache.SetAsync(items[i].key, items[i].value) 187 } 188 }() 189 190 start := time.Now() 191 for i := 0; i < itemsLen; i++ { 192 // terminate if test lasts long 193 if time.Since(start) > 5*time.Second { 194 t.Fatalf("timeout checking %dth item", i+1) 195 } 196 197 v := cache.Get(items[i].key) 198 if v == nil { 199 // if the item is not set yet, wait and retry 200 time.Sleep(sleepDurationForAsyncBehavior) 201 i-- 202 } else { 203 assert.Equal(t, v, items[i].value) 204 } 205 } 206 } 207 208 // TestRedisCache_Timeout tests timout feature of redis client. 209 func TestRedisCache_Timeout(t *testing.T) { 210 storage.SkipLocalTest(t) 211 212 go func() { 213 tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:11234") 214 if err != nil { 215 t.Error(err) 216 return 217 } 218 219 listen, err := net.ListenTCP("tcp", tcpAddr) 220 if err != nil { 221 t.Error(err) 222 return 223 } 224 defer listen.Close() 225 226 for { 227 if err := listen.SetDeadline(time.Now().Add(10 * time.Second)); err != nil { 228 t.Error(err) 229 return 230 } 231 _, err := listen.AcceptTCP() 232 if err != nil { 233 if strings.Contains(err.Error(), "timeout") { 234 return 235 } 236 t.Error(err) 237 return 238 } 239 } 240 }() 241 242 var cache TrieNodeCache = &RedisCache{redis.NewClient(&redis.Options{ 243 Addr: "localhost:11234", 244 DialTimeout: redisCacheDialTimeout, 245 ReadTimeout: redisCacheTimeout, 246 WriteTimeout: redisCacheTimeout, 247 MaxRetries: 0, 248 }), nil, nil} 249 250 key, value := randBytes(32), randBytes(500) 251 252 start := time.Now() 253 redisCache := cache.(*RedisCache) // Because RedisCache.Set writes item asynchronously, use RedisCache.set 254 redisCache.Set(key, value) 255 assert.Equal(t, redisCacheTimeout, time.Since(start).Round(redisCacheTimeout/2)) 256 257 start = time.Now() 258 _ = cache.Get(key) 259 assert.Equal(t, redisCacheTimeout, time.Since(start).Round(redisCacheTimeout/2)) 260 261 start = time.Now() 262 _, _ = cache.Has(key) 263 assert.Equal(t, redisCacheTimeout, time.Since(start).Round(redisCacheTimeout/2)) 264 }