Files
styx/dataset/storage_cache.go
2025-10-08 20:57:13 +02:00

143 lines
2.9 KiB
Go

package dataset
import (
"fmt"
"net"
"net/netip"
"sync"
"time"
"git.maze.io/maze/styx/logger"
)
const MinCacheExpire = 10 * time.Second
type cache struct {
Storage
expire time.Duration
groupByID sync.Map
clientByAddr sync.Map
listByGroup sync.Map
closed chan struct{}
}
type cacheItem struct {
cachedAt time.Time
value any
}
// Cache items returned from a Storage for the specified duration.
//
// Does not cache negative hits.
func Cache(storage Storage, expire time.Duration) Storage {
if expire < MinCacheExpire {
expire = MinCacheExpire
}
logger.StandardLog.Value("expire", expire).Debug("Caching Storage responses")
s := &cache{
Storage: storage,
expire: expire,
closed: make(chan struct{}, 1),
}
go s.cleanUpTimer()
return s
}
func (s *cache) cleanUpTimer() {
ticker := time.NewTicker(s.expire)
defer ticker.Stop()
for {
select {
case <-s.closed:
return
case now := <-ticker.C:
logger.StandardLog.Trace("Cache cleanup running")
s.cleanUp(now, &s.groupByID)
s.cleanUp(now, &s.clientByAddr)
s.cleanUp(now, &s.listByGroup)
}
}
}
func (s *cache) cleanUp(now time.Time, cacheMap *sync.Map) {
cacheMap.Range(func(key, item any) bool {
cached := item.(cacheItem)
if ago := now.Sub(cached.cachedAt); ago >= s.expire {
logger.StandardLog.Values(logger.Values{
"ago": ago,
"type": fmt.Sprintf("%T", cached.value),
"item": fmt.Sprintf("%s", cached.value),
}).Debug("Cache removing expired item")
cacheMap.Delete(key)
}
return true
})
}
func (s *cache) load(now time.Time, cacheMap *sync.Map, key any) (value any, ok bool) {
var item any
if item, ok = cacheMap.Load(key); !ok {
return
}
cached := item.(cacheItem)
if now.Sub(cached.cachedAt) < s.expire {
return cached.value, true
}
return nil, false
}
func (s *cache) save(now time.Time, cacheMap *sync.Map, key, value any) {
cacheMap.Store(key, cacheItem{
cachedAt: now,
value: value,
})
}
func (s *cache) GroupByID(id int64) (Group, error) {
now := time.Now()
if value, ok := s.load(now, &s.groupByID, id); ok {
return value.(Group), nil
}
group, err := s.Storage.GroupByID(id)
if err == nil {
s.save(now, &s.groupByID, id, group)
}
return group, err
}
func (s *cache) ClientByIP(ip net.IP) (Client, error) {
addr, _ := netip.AddrFromSlice(ip)
return s.ClientByAddr(addr)
}
func (s *cache) ClientByAddr(ip netip.Addr) (Client, error) {
now := time.Now()
if value, ok := s.load(now, &s.clientByAddr, ip); ok {
return value.(Client), nil
}
client, err := s.Storage.ClientByAddr(ip)
if err == nil {
s.save(now, &s.clientByAddr, ip, client)
}
return client, err
}
func (s *cache) ListsByGroup(group Group) ([]List, error) {
now := time.Now()
if value, ok := s.load(now, &s.listByGroup, group.ID); ok {
return value.([]List), nil
}
lists, err := s.Storage.ListsByGroup(group)
if err == nil {
s.save(now, &s.listByGroup, group.ID, lists)
}
return lists, err
}