82 lines
1.8 KiB
Diff
82 lines
1.8 KiB
Diff
# UNDF: UNDF-2026-000001087
|
|
--- a/cache/ttl_cache.go
|
|
+++ b/cache/ttl_cache.go
|
|
@@ -14,6 +14,7 @@ import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
+ "sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
@@ -25,6 +26,7 @@ type TTLCache[K comparable, V any] struct {
|
|
mx sync.RWMutex
|
|
cache map[K]cacheEntry[V]
|
|
purgeStop chan struct{}
|
|
+ group singleflightGroup[K, V]
|
|
getter Getter[K, V]
|
|
maxAge time.Duration
|
|
countHit atomic.Int64
|
|
@@ -200,13 +202,27 @@ func (c *TTLCache[K, V]) Get(ctx context.Context, key K) (V, error) {
|
|
item, ok := c.fetch(key, now)
|
|
if ok {
|
|
return item, nil
|
|
}
|
|
|
|
- item, err := c.getter.Find(ctx, key)
|
|
+ // Use singleflight to prevent thundering herd: concurrent goroutines that
|
|
+ // miss the cache for the same key share a single backend fetch instead of
|
|
+ // each issuing their own database call.
|
|
+ item, err, _ := c.group.Do(key, func() (V, error) {
|
|
+ return c.getter.Find(ctx, key)
|
|
+ })
|
|
if err != nil {
|
|
return nothing, fmt.Errorf("cache: failed to find one: %w", err)
|
|
}
|
|
|
|
c.mx.Lock()
|
|
c.cache[key] = cacheEntry[V]{
|
|
added: now,
|
|
data: item,
|
|
}
|
|
c.mx.Unlock()
|
|
|
|
return item, nil
|
|
}
|
|
|
|
+// singleflightGroup is a minimal generic singleflight implementation.
|
|
+type singleflightGroup[K comparable, V any] struct {
|
|
+ mu sync.Mutex
|
|
+ m map[K]*call[V]
|
|
+}
|
|
+
|
|
+type call[V any] struct {
|
|
+ wg sync.WaitGroup
|
|
+ val V
|
|
+ err error
|
|
+}
|
|
+
|
|
+func (g *singleflightGroup[K, V]) Do(key K, fn func() (V, error)) (V, error, bool) {
|
|
+ g.mu.Lock()
|
|
+ if g.m == nil {
|
|
+ g.m = make(map[K]*call[V])
|
|
+ }
|
|
+ if c, ok := g.m[key]; ok {
|
|
+ g.mu.Unlock()
|
|
+ c.wg.Wait()
|
|
+ return c.val, c.err, true
|
|
+ }
|
|
+ c := &call[V]{}
|
|
+ c.wg.Add(1)
|
|
+ g.m[key] = c
|
|
+ g.mu.Unlock()
|
|
+
|
|
+ c.val, c.err = fn()
|
|
+ c.wg.Done()
|
|
+
|
|
+ g.mu.Lock()
|
|
+ delete(g.m, key)
|
|
+ g.mu.Unlock()
|
|
+
|
|
+ return c.val, c.err, false
|
|
+}
|