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
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:])
|
|
}
|