From 7f6852bbd2f440b5c49b616966e96bfd5ff30c65 Mon Sep 17 00:00:00 2001 From: Alexander Chuprov Date: Thu, 30 Nov 2023 11:45:31 +0300 Subject: [PATCH] [#639] node: Refactor TTL cache Migrate from internal to external TTL implementation Signed-off-by: Alexander Chuprov --- cmd/frostfs-node/cache.go | 29 ++++++------------ cmd/frostfs-node/cache_test.go | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 cmd/frostfs-node/cache_test.go diff --git a/cmd/frostfs-node/cache.go b/cmd/frostfs-node/cache.go index cd383a5c..bf68d245 100644 --- a/cmd/frostfs-node/cache.go +++ b/cmd/frostfs-node/cache.go @@ -12,38 +12,29 @@ import ( cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" lru "github.com/hashicorp/golang-lru/v2" + "github.com/hashicorp/golang-lru/v2/expirable" ) type netValueReader[K any, V any] func(K) (V, error) -type valueWithTime[V any] struct { +type valueWithError[V any] struct { v V - t time.Time // cached error in order to not repeat failed request for some time e error } // entity that provides TTL cache interface. type ttlNetCache[K comparable, V any] struct { - ttl time.Duration - - sz int - - cache *lru.Cache[K, *valueWithTime[V]] - - netRdr netValueReader[K, V] - + cache *expirable.LRU[K, *valueWithError[V]] + netRdr netValueReader[K, V] keyLocker *utilSync.KeyLocker[K] } // complicates netValueReader with TTL caching mechanism. func newNetworkTTLCache[K comparable, V any](sz int, ttl time.Duration, netRdr netValueReader[K, V]) *ttlNetCache[K, V] { - cache, err := lru.New[K, *valueWithTime[V]](sz) - fatalOnErr(err) + cache := expirable.NewLRU[K, *valueWithError[V]](sz, nil, ttl) return &ttlNetCache[K, V]{ - ttl: ttl, - sz: sz, cache: cache, netRdr: netRdr, keyLocker: utilSync.NewKeyLocker[K](), @@ -57,7 +48,7 @@ func newNetworkTTLCache[K comparable, V any](sz int, ttl time.Duration, netRdr n // returned value should not be modified. func (c *ttlNetCache[K, V]) get(key K) (V, error) { val, ok := c.cache.Peek(key) - if ok && time.Since(val.t) < c.ttl { + if ok { return val.v, val.e } @@ -65,15 +56,14 @@ func (c *ttlNetCache[K, V]) get(key K) (V, error) { defer c.keyLocker.Unlock(key) val, ok = c.cache.Peek(key) - if ok && time.Since(val.t) < c.ttl { + if ok { return val.v, val.e } v, err := c.netRdr(key) - c.cache.Add(key, &valueWithTime[V]{ + c.cache.Add(key, &valueWithError[V]{ v: v, - t: time.Now(), e: err, }) @@ -84,9 +74,8 @@ func (c *ttlNetCache[K, V]) set(k K, v V, e error) { c.keyLocker.Lock(k) defer c.keyLocker.Unlock(k) - c.cache.Add(k, &valueWithTime[V]{ + c.cache.Add(k, &valueWithError[V]{ v: v, - t: time.Now(), e: e, }) } diff --git a/cmd/frostfs-node/cache_test.go b/cmd/frostfs-node/cache_test.go new file mode 100644 index 00000000..6e076abf --- /dev/null +++ b/cmd/frostfs-node/cache_test.go @@ -0,0 +1,56 @@ +package main + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestTTLNetCache(t *testing.T) { + ttlDuration := time.Millisecond * 50 + cache := newNetworkTTLCache[string, time.Time](10, ttlDuration, testNetValueReader) + + key := "key" + + t.Run("Test Add and Get", func(t *testing.T) { + ti := time.Now() + cache.set(key, ti, nil) + val, err := cache.get(key) + require.NoError(t, err) + require.Equal(t, ti, val) + }) + + t.Run("Test TTL", func(t *testing.T) { + ti := time.Now() + cache.set(key, ti, nil) + time.Sleep(2 * ttlDuration) + val, err := cache.get(key) + require.NoError(t, err) + require.NotEqual(t, val, ti) + }) + + t.Run("Test Remove", func(t *testing.T) { + ti := time.Now() + cache.set(key, ti, nil) + cache.remove(key) + val, err := cache.get(key) + require.NoError(t, err) + require.NotEqual(t, val, ti) + }) + + t.Run("Test Cache Error", func(t *testing.T) { + cache.set("error", time.Now(), errors.New("mock error")) + _, err := cache.get("error") + require.Error(t, err) + require.Equal(t, "mock error", err.Error()) + }) +} + +func testNetValueReader(key string) (time.Time, error) { + if key == "error" { + return time.Now(), errors.New("mock error") + } + return time.Now(), nil +}