2026-04-18 22:57:44 +08:00
|
|
|
// Package cache provides in-memory L1 cache with true O(1) LRU eviction.
|
|
|
|
|
//
|
|
|
|
|
// Implementation uses a doubly-linked list + hash-map, giving O(1) for Get, Set,
|
|
|
|
|
// Delete and eviction — compared to the previous O(n) slice-scan approach which
|
|
|
|
|
// became a bottleneck under high concurrency (10 000-item cache, 1 000+ QPS).
|
|
|
|
|
//
|
|
|
|
|
// Thread-safety: a single sync.RWMutex guards the whole structure.
|
|
|
|
|
// Reads (Get) promote the entry to MRU and therefore must take a write lock.
|
|
|
|
|
// If read-heavy workloads dominate, consider a sharded variant.
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
package cache
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-18 22:57:44 +08:00
|
|
|
"container/list"
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2026-04-18 22:57:44 +08:00
|
|
|
// defaultMaxItems is the maximum number of entries held in L1Cache.
|
|
|
|
|
// Entries beyond this limit are evicted using LRU policy (O(1)).
|
|
|
|
|
defaultMaxItems = 10000
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
)
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// CacheItem holds a cached value together with its expiry timestamp.
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
type CacheItem struct {
|
|
|
|
|
Value interface{}
|
2026-04-18 22:57:44 +08:00
|
|
|
Expiration int64 // UnixNano; 0 means no expiration
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Expired reports whether this item has passed its TTL.
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
func (item *CacheItem) Expired() bool {
|
|
|
|
|
return item.Expiration > 0 && time.Now().UnixNano() > item.Expiration
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// lruEntry is the value stored inside the doubly-linked list element.
|
|
|
|
|
type lruEntry struct {
|
|
|
|
|
key string
|
|
|
|
|
item *CacheItem
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// L1Cache is an in-process LRU cache backed by a hash-map and a doubly-linked
|
|
|
|
|
// list. All exported methods are safe for concurrent use.
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
type L1Cache struct {
|
2026-04-18 22:57:44 +08:00
|
|
|
mu sync.RWMutex
|
|
|
|
|
items map[string]*list.Element // key → list element
|
|
|
|
|
lruList *list.List // front = MRU, back = LRU
|
|
|
|
|
maxItems int
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// NewL1Cache creates a new L1Cache with the default capacity (10 000 items).
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
func NewL1Cache() *L1Cache {
|
|
|
|
|
return &L1Cache{
|
2026-04-18 22:57:44 +08:00
|
|
|
items: make(map[string]*list.Element, defaultMaxItems),
|
|
|
|
|
lruList: list.New(),
|
|
|
|
|
maxItems: defaultMaxItems,
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// NewL1CacheWithSize creates a new L1Cache with a custom capacity.
|
|
|
|
|
func NewL1CacheWithSize(maxItems int) *L1Cache {
|
|
|
|
|
if maxItems <= 0 {
|
|
|
|
|
maxItems = defaultMaxItems
|
|
|
|
|
}
|
|
|
|
|
return &L1Cache{
|
|
|
|
|
items: make(map[string]*list.Element, maxItems),
|
|
|
|
|
lruList: list.New(),
|
|
|
|
|
maxItems: maxItems,
|
|
|
|
|
}
|
|
|
|
|
}
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Set inserts or updates key with the given value and TTL.
|
|
|
|
|
// A zero or negative TTL means the entry never expires.
|
|
|
|
|
// O(1) amortised.
|
|
|
|
|
func (c *L1Cache) Set(key string, value interface{}, ttl time.Duration) {
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
var expiration int64
|
|
|
|
|
if ttl > 0 {
|
|
|
|
|
expiration = time.Now().Add(ttl).UnixNano()
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
c.mu.Lock()
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if elem, ok := c.items[key]; ok {
|
|
|
|
|
// Update existing entry and move to front (MRU).
|
|
|
|
|
c.lruList.MoveToFront(elem)
|
|
|
|
|
entry := elem.Value.(*lruEntry)
|
|
|
|
|
entry.item = &CacheItem{Value: value, Expiration: expiration}
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Evict LRU entry if at capacity.
|
|
|
|
|
if c.lruList.Len() >= c.maxItems {
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
c.evictLRU()
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Insert new entry at front.
|
|
|
|
|
entry := &lruEntry{
|
|
|
|
|
key: key,
|
|
|
|
|
item: &CacheItem{Value: value, Expiration: expiration},
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
2026-04-18 22:57:44 +08:00
|
|
|
elem := c.lruList.PushFront(entry)
|
|
|
|
|
c.items[key] = elem
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// evictLRU removes the least-recently-used entry. Must be called with c.mu held.
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
func (c *L1Cache) evictLRU() {
|
2026-04-18 22:57:44 +08:00
|
|
|
back := c.lruList.Back()
|
|
|
|
|
if back == nil {
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
return
|
|
|
|
|
}
|
2026-04-18 22:57:44 +08:00
|
|
|
entry := back.Value.(*lruEntry)
|
|
|
|
|
delete(c.items, entry.key)
|
|
|
|
|
c.lruList.Remove(back)
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Get retrieves a value from the cache.
|
|
|
|
|
// On a hit the entry is promoted to MRU (requires write lock).
|
|
|
|
|
// On expiry the entry is removed and (nil, false) is returned.
|
|
|
|
|
// O(1).
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
func (c *L1Cache) Get(key string) (interface{}, bool) {
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
elem, ok := c.items[key]
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
if !ok {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
entry := elem.Value.(*lruEntry)
|
|
|
|
|
if entry.item.Expired() {
|
|
|
|
|
c.lruList.Remove(elem)
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
delete(c.items, key)
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Promote to MRU.
|
|
|
|
|
c.lruList.MoveToFront(elem)
|
|
|
|
|
return entry.item.Value, true
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Delete removes a key from the cache. No-op if the key is absent.
|
|
|
|
|
// O(1).
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
func (c *L1Cache) Delete(key string) {
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
if elem, ok := c.items[key]; ok {
|
|
|
|
|
c.lruList.Remove(elem)
|
|
|
|
|
delete(c.items, key)
|
|
|
|
|
}
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Clear removes all entries from the cache.
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
func (c *L1Cache) Clear() {
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
c.items = make(map[string]*list.Element, c.maxItems)
|
|
|
|
|
c.lruList.Init()
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Size returns the number of entries currently held (including potentially
|
|
|
|
|
// expired ones that have not yet been evicted).
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
func (c *L1Cache) Size() int {
|
|
|
|
|
c.mu.RLock()
|
|
|
|
|
defer c.mu.RUnlock()
|
|
|
|
|
return len(c.items)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Cleanup scans all entries and removes those that have expired.
|
|
|
|
|
// This is a background maintenance operation; normal eviction is lazy (on Get).
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
func (c *L1Cache) Cleanup() {
|
|
|
|
|
c.mu.Lock()
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
now := time.Now().UnixNano()
|
2026-04-18 22:57:44 +08:00
|
|
|
var toRemove []*list.Element
|
|
|
|
|
for elem := c.lruList.Back(); elem != nil; elem = elem.Prev() {
|
|
|
|
|
entry := elem.Value.(*lruEntry)
|
|
|
|
|
if entry.item.Expiration > 0 && now > entry.item.Expiration {
|
|
|
|
|
toRemove = append(toRemove, elem)
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-18 22:57:44 +08:00
|
|
|
for _, elem := range toRemove {
|
|
|
|
|
entry := elem.Value.(*lruEntry)
|
|
|
|
|
delete(c.items, entry.key)
|
|
|
|
|
c.lruList.Remove(elem)
|
feat: backend core - auth, user, role, permission, device, webhook, monitoring, cache, repository, service, middleware, API handlers
2026-04-02 11:19:50 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-18 13:45:09 +08:00
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Increment atomically adds delta to the int64 counter stored at key,
|
|
|
|
|
// creating it with value delta if it does not exist.
|
|
|
|
|
// Used for rate-limit counters, login-failure counters, etc.
|
|
|
|
|
// O(1).
|
2026-04-18 13:45:09 +08:00
|
|
|
func (c *L1Cache) Increment(key string, delta int64, ttl time.Duration) int64 {
|
|
|
|
|
var expiration int64
|
|
|
|
|
if ttl > 0 {
|
|
|
|
|
expiration = time.Now().Add(ttl).UnixNano()
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
c.mu.Lock()
|
|
|
|
|
defer c.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if elem, ok := c.items[key]; ok {
|
|
|
|
|
entry := elem.Value.(*lruEntry)
|
|
|
|
|
if !entry.item.Expired() {
|
|
|
|
|
current := int64(0)
|
|
|
|
|
switch v := entry.item.Value.(type) {
|
|
|
|
|
case int64:
|
2026-04-18 13:45:09 +08:00
|
|
|
current = v
|
2026-04-18 22:57:44 +08:00
|
|
|
case int:
|
2026-04-18 13:45:09 +08:00
|
|
|
current = int64(v)
|
2026-04-18 22:57:44 +08:00
|
|
|
case float64:
|
2026-04-18 13:45:09 +08:00
|
|
|
current = int64(v)
|
|
|
|
|
}
|
2026-04-18 22:57:44 +08:00
|
|
|
newVal := current + delta
|
|
|
|
|
entry.item = &CacheItem{Value: newVal, Expiration: expiration}
|
|
|
|
|
c.lruList.MoveToFront(elem)
|
|
|
|
|
return newVal
|
2026-04-18 13:45:09 +08:00
|
|
|
}
|
2026-04-18 22:57:44 +08:00
|
|
|
// Expired: remove and recreate below.
|
|
|
|
|
c.lruList.Remove(elem)
|
|
|
|
|
delete(c.items, key)
|
2026-04-18 13:45:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-18 22:57:44 +08:00
|
|
|
// Key absent or expired: insert fresh counter.
|
|
|
|
|
if c.lruList.Len() >= c.maxItems {
|
|
|
|
|
c.evictLRU()
|
2026-04-18 13:45:09 +08:00
|
|
|
}
|
2026-04-18 22:57:44 +08:00
|
|
|
entry := &lruEntry{
|
|
|
|
|
key: key,
|
|
|
|
|
item: &CacheItem{Value: delta, Expiration: expiration},
|
2026-04-18 13:45:09 +08:00
|
|
|
}
|
2026-04-18 22:57:44 +08:00
|
|
|
elem := c.lruList.PushFront(entry)
|
|
|
|
|
c.items[key] = elem
|
|
|
|
|
return delta
|
2026-04-18 13:45:09 +08:00
|
|
|
}
|