From 59822f7fb404fd567dcc1f506ddff1506b4bd899 Mon Sep 17 00:00:00 2001
From: Pavel Karpy
Date: Wed, 22 Mar 2023 20:28:14 +0300
Subject: [PATCH] [#1248] node: Do not update cache twice
Do not request morph values on morph cache misses concurrently.
Signed-off-by: Pavel Karpy
---
CHANGELOG.md | 1 +
cmd/frostfs-node/cache.go | 64 +++++++++++++++++++++++++++++++++------
2 files changed, 56 insertions(+), 9 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a9bcfd41..3c2fe6811 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -70,6 +70,7 @@ Changelog for FrostFS Node
- Non-alphabet nodes do not try to handle alphabet events (#181)
- Failing SN and IR transactions because of incorrect scopes (#2230, #2263)
- Global scope used for some transactions (#2230, #2263)
+- Concurrent morph cache misses (#30)
### Removed
### Updated
diff --git a/cmd/frostfs-node/cache.go b/cmd/frostfs-node/cache.go
index 3d4fc7375..2dc8b7f69 100644
--- a/cmd/frostfs-node/cache.go
+++ b/cmd/frostfs-node/cache.go
@@ -24,9 +24,19 @@ type valueWithTime[V any] struct {
e error
}
+// valueInProgress is a struct that contains
+// values that are being fetched/updated.
+type valueInProgress[V any] struct {
+ m sync.RWMutex
+ v V
+ e error
+}
+
// entity that provides TTL cache interface.
type ttlNetCache[K comparable, V any] struct {
- ttl time.Duration
+ m sync.RWMutex // protects progMap
+ progMap map[K]*valueInProgress[V] // contains fetch-in-progress keys
+ ttl time.Duration
sz int
@@ -41,32 +51,68 @@ func newNetworkTTLCache[K comparable, V any](sz int, ttl time.Duration, netRdr n
fatalOnErr(err)
return &ttlNetCache[K, V]{
- ttl: ttl,
- sz: sz,
- cache: cache,
- netRdr: netRdr,
+ ttl: ttl,
+ sz: sz,
+ cache: cache,
+ netRdr: netRdr,
+ progMap: make(map[K]*valueInProgress[V]),
}
}
+func waitForUpdate[V any](vip *valueInProgress[V]) (V, error) {
+ vip.m.RLock()
+ defer vip.m.RUnlock()
+
+ return vip.v, vip.e
+}
+
// reads value by the key.
//
// updates the value from the network on cache miss or by TTL.
//
// returned value should not be modified.
func (c *ttlNetCache[K, V]) get(key K) (V, error) {
- val, ok := c.cache.Peek(key)
+ valWithTime, ok := c.cache.Peek(key)
if ok {
- if time.Since(val.t) < c.ttl {
- return val.v, val.e
+ if time.Since(valWithTime.t) < c.ttl {
+ return valWithTime.v, valWithTime.e
}
c.cache.Remove(key)
}
- v, err := c.netRdr(key)
+ c.m.RLock()
+ valInProg, ok := c.progMap[key]
+ c.m.RUnlock()
+ if ok {
+ return waitForUpdate(valInProg)
+ }
+
+ c.m.Lock()
+ valInProg, ok = c.progMap[key]
+ if ok {
+ c.m.Unlock()
+ return waitForUpdate(valInProg)
+ }
+
+ valInProg = &valueInProgress[V]{}
+ valInProg.m.Lock()
+ c.progMap[key] = valInProg
+
+ c.m.Unlock()
+
+ v, err := c.netRdr(key)
c.set(key, v, err)
+ valInProg.v = v
+ valInProg.e = err
+ valInProg.m.Unlock()
+
+ c.m.Lock()
+ delete(c.progMap, key)
+ c.m.Unlock()
+
return v, err
}