109 lines
2.4 KiB
Go
109 lines
2.4 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"mal/internal/db"
|
|
"mal/internal/observability"
|
|
)
|
|
|
|
type Store struct {
|
|
db db.Querier
|
|
metrics *observability.Metrics
|
|
}
|
|
|
|
func NewStore(queries db.Querier, metrics *observability.Metrics) *Store {
|
|
return &Store{db: queries, metrics: metrics}
|
|
}
|
|
|
|
// Get retrieves a fresh cached value by key.
|
|
func (s *Store) Get(parentCtx context.Context, key string, out any) bool {
|
|
ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second)
|
|
defer cancel()
|
|
defer s.observeStats(parentCtx)
|
|
|
|
data, err := s.db.GetJikanCache(ctx, key)
|
|
if err != nil {
|
|
s.metrics.ObserveCache("jikan", "miss")
|
|
return false
|
|
}
|
|
|
|
if err := json.Unmarshal([]byte(data), out); err != nil {
|
|
s.metrics.ObserveCache("jikan", "miss")
|
|
return false
|
|
}
|
|
|
|
s.metrics.ObserveCache("jikan", "hit")
|
|
return true
|
|
}
|
|
|
|
// GetStale retrieves an expired-but-available cached value by key.
|
|
func (s *Store) GetStale(parentCtx context.Context, key string, out any) bool {
|
|
ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second)
|
|
defer cancel()
|
|
defer s.observeStats(parentCtx)
|
|
|
|
data, err := s.db.GetJikanCacheStale(ctx, key)
|
|
if err != nil {
|
|
s.metrics.ObserveCache("jikan_stale", "miss")
|
|
return false
|
|
}
|
|
|
|
if err := json.Unmarshal([]byte(data), out); err != nil {
|
|
s.metrics.ObserveCache("jikan_stale", "miss")
|
|
return false
|
|
}
|
|
|
|
s.metrics.ObserveCache("jikan_stale", "hit")
|
|
return true
|
|
}
|
|
|
|
// Set stores data in cache with the specified TTL.
|
|
func (s *Store) Set(parentCtx context.Context, key string, data any, ttl time.Duration) {
|
|
ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second)
|
|
defer cancel()
|
|
defer s.observeStats(parentCtx)
|
|
|
|
bytes, err := json.Marshal(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
err = s.db.SetJikanCache(ctx, db.SetJikanCacheParams{
|
|
Key: key,
|
|
Data: string(bytes),
|
|
ExpiresAt: time.Now().Add(ttl),
|
|
})
|
|
if err != nil {
|
|
observability.LogJSON(
|
|
observability.LogLevelError,
|
|
"jikan_cache_set",
|
|
"jikan",
|
|
"",
|
|
map[string]any{"cache_key": key},
|
|
err,
|
|
)
|
|
}
|
|
}
|
|
|
|
func (s *Store) observeStats(parentCtx context.Context) {
|
|
ctx, cancel := context.WithTimeout(parentCtx, 2*time.Second)
|
|
defer cancel()
|
|
|
|
stats, err := s.db.GetJikanCacheStats(ctx)
|
|
if err != nil {
|
|
observability.LogJSON(
|
|
observability.LogLevelWarn,
|
|
"jikan_cache_stats",
|
|
"jikan",
|
|
"",
|
|
nil,
|
|
err,
|
|
)
|
|
return
|
|
}
|
|
s.metrics.ObserveJikanCacheStats(stats.TotalRows, stats.ExpiredRows, stats.OldestExpiresAtSeconds)
|
|
}
|