91 lines
No EOL
2.5 KiB
Go
91 lines
No EOL
2.5 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/metamorphosis-dev/samsa/internal/contracts"
|
|
)
|
|
|
|
// EngineCache wraps Cache with per-engine tier-aware Get/Set operations.
|
|
type EngineCache struct {
|
|
cache *Cache
|
|
overrides map[string]time.Duration
|
|
}
|
|
|
|
// NewEngineCache creates a new EngineCache with optional TTL overrides.
|
|
// If overrides is nil, default tier durations are used.
|
|
func NewEngineCache(cache *Cache, overrides map[string]time.Duration) *EngineCache {
|
|
return &EngineCache{
|
|
cache: cache,
|
|
overrides: overrides,
|
|
}
|
|
}
|
|
|
|
// Get retrieves a cached engine response. Returns (zero value, false) if not
|
|
// found or if cache is disabled.
|
|
func (ec *EngineCache) Get(ctx context.Context, engine, queryHash string) (CachedEngineResponse, bool) {
|
|
key := engineCacheKey(engine, queryHash)
|
|
|
|
data, ok := ec.cache.GetBytes(ctx, key)
|
|
if !ok {
|
|
return CachedEngineResponse{}, false
|
|
}
|
|
|
|
var cached CachedEngineResponse
|
|
if err := json.Unmarshal(data, &cached); err != nil {
|
|
ec.cache.logger.Warn("engine cache hit but unmarshal failed", "key", key, "error", err)
|
|
return CachedEngineResponse{}, false
|
|
}
|
|
|
|
ec.cache.logger.Debug("engine cache hit", "key", key, "engine", engine)
|
|
return cached, true
|
|
}
|
|
|
|
// Set stores an engine response in the cache with the engine's tier TTL.
|
|
func (ec *EngineCache) Set(ctx context.Context, engine, queryHash string, resp contracts.SearchResponse) {
|
|
if !ec.cache.Enabled() {
|
|
return
|
|
}
|
|
|
|
data, err := json.Marshal(resp)
|
|
if err != nil {
|
|
ec.cache.logger.Warn("engine cache set: marshal failed", "engine", engine, "error", err)
|
|
return
|
|
}
|
|
|
|
tier := EngineTier(engine, ec.overrides)
|
|
key := engineCacheKey(engine, queryHash)
|
|
|
|
cached := CachedEngineResponse{
|
|
Engine: engine,
|
|
Response: data,
|
|
StoredAt: time.Now(),
|
|
}
|
|
|
|
cachedData, err := json.Marshal(cached)
|
|
if err != nil {
|
|
ec.cache.logger.Warn("engine cache set: wrap marshal failed", "key", key, "error", err)
|
|
return
|
|
}
|
|
|
|
ec.cache.SetBytes(ctx, key, cachedData, tier.Duration)
|
|
}
|
|
|
|
// IsStale returns true if the cached response is older than the tier's TTL.
|
|
func (ec *EngineCache) IsStale(cached CachedEngineResponse, engine string) bool {
|
|
tier := EngineTier(engine, ec.overrides)
|
|
return time.Since(cached.StoredAt) > tier.Duration
|
|
}
|
|
|
|
// Logger returns the logger for background refresh logging.
|
|
func (ec *EngineCache) Logger() *slog.Logger {
|
|
return ec.cache.logger
|
|
}
|
|
|
|
// engineCacheKey builds the cache key for an engine+query combination.
|
|
func engineCacheKey(engine, queryHash string) string {
|
|
return "samsa:resp:" + engine + ":" + queryHash
|
|
} |