You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

100 lines
2.2 KiB
Go

package cache
import (
"context"
"encoding/json"
"sync"
"sync/atomic"
"time"
domain "knowfoolery/backend/services/question-bank-service/internal/domain/question"
sharedredis "knowfoolery/backend/shared/infra/database/redis"
)
// RandomQuestionCache provides cache behavior for random question retrieval.
type RandomQuestionCache struct {
client *sharedredis.Client
version atomic.Uint64
localMu sync.RWMutex
local map[string]cacheEntry
}
type cacheEntry struct {
question domain.Question
expiresAt time.Time
}
// NewRandomQuestionCache creates a new cache adapter.
func NewRandomQuestionCache(client *sharedredis.Client) *RandomQuestionCache {
c := &RandomQuestionCache{
client: client,
local: make(map[string]cacheEntry),
}
c.version.Store(1)
return c
}
// Get returns a cached question if present.
func (c *RandomQuestionCache) Get(ctx context.Context, key string) (*domain.Question, bool) {
nsKey := c.namespacedKey(key)
if c.client != nil {
value, err := c.client.Get(ctx, nsKey)
if err == nil {
var q domain.Question
if json.Unmarshal([]byte(value), &q) == nil {
return &q, true
}
}
}
c.localMu.RLock()
entry, ok := c.local[nsKey]
c.localMu.RUnlock()
if !ok || time.Now().After(entry.expiresAt) {
return nil, false
}
q := entry.question
return &q, true
}
// Set stores a question in cache.
func (c *RandomQuestionCache) Set(ctx context.Context, key string, q *domain.Question, ttl time.Duration) {
nsKey := c.namespacedKey(key)
payload, err := json.Marshal(q)
if err == nil && c.client != nil {
_ = c.client.Set(ctx, nsKey, string(payload), ttl)
}
c.localMu.Lock()
c.local[nsKey] = cacheEntry{question: *q, expiresAt: time.Now().Add(ttl)}
c.localMu.Unlock()
}
// Invalidate bumps cache namespace version to invalidate current keys.
func (c *RandomQuestionCache) Invalidate(ctx context.Context) {
_ = ctx
c.version.Add(1)
}
func (c *RandomQuestionCache) namespacedKey(key string) string {
return "v" + itoa(c.version.Load()) + ":" + key
}
func itoa(i uint64) string {
if i == 0 {
return "0"
}
buf := [20]byte{}
pos := len(buf)
for i > 0 {
pos--
buf[pos] = byte('0' + i%10)
i /= 10
}
return string(buf[pos:])
}