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  }