node: Refactor TTL cache #831
2 changed files with 65 additions and 20 deletions
|
@ -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]]
|
||||
|
||||
cache *expirable.LRU[K, *valueWithError[V]]
|
||||
netRdr netValueReader[K, V]
|
||||
fyrchik marked this conversation as resolved
Outdated
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
|
56
cmd/frostfs-node/cache_test.go
Normal file
56
cmd/frostfs-node/cache_test.go
Normal file
|
@ -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)
|
||||
aarifullin
commented
-> -> `assert.NoError`? :)
dstepanov-yadro
commented
`require.NoError` better
achuprov
commented
fixed fixed
|
||||
require.NotEqual(t, val, ti)
|
||||
aarifullin
commented
Am I right that values are not equeal because the entry is expired after Am I right that values are not equeal because the entry is expired after `time.Sleep(2 * ttlDuration)`?
achuprov
commented
Yes, you are correct. The values are not equal because the entry has expired after the Yes, you are correct. The values are not equal because the entry has expired after the `time.Sleep(2 * ttl Duration)` interval
|
||||
})
|
||||
|
||||
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
|
||||
}
|
Loading…
Reference in a new issue
If it is expireable, why do we use
*valueWithTime[V]
instead of*V
?Approved by accident, please disregard.
fixed