Files
graphql-monitoring-proxy/cache/redis/wrapper.go
T
lukaszraczylo 159faa68e0 perf(cache): sort-based eviction and consistent non-positive TTL handling
The count-based evictOldest used an O(n squared) selection sort with a
candidate cap over an unordered map range, so it could evict the wrong
entries. It now collects all entries and sorts by expiry, matching the
memory-based evictor.

A non-positive CACHE_TTL behaved differently per backend: the standard
memory cache panicked, Redis stored a permanent key, and the LRU cache
expired entries immediately. Each backend now clamps a non-positive TTL
to a shared default at its Set boundary. The nil-logger fallback in the
Redis wrapper now uses the constructor so it cannot panic.
2026-08-14 11:09:57 +01:00

117 lines
2.8 KiB
Go

package libpack_cache_redis
import (
"time"
libpack_logger "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
)
// CacheWrapper wraps RedisConfig to implement the CacheClient interface
// without returning errors, for backward compatibility
type CacheWrapper struct {
redis *RedisConfig
logger *libpack_logger.Logger
}
// NewCacheWrapper creates a new cache wrapper
func NewCacheWrapper(config *RedisConfig, logger *libpack_logger.Logger) *CacheWrapper {
if logger == nil {
// Use the proper constructor, not a zero-value &Logger{} -- its
// timeFormat field is a nil atomic.Pointer, so a zero-value Logger
// panics on the first log call instead of degrading gracefully.
logger = libpack_logger.New()
}
return &CacheWrapper{
redis: config,
logger: logger,
}
}
// Set stores a value with the given TTL
func (w *CacheWrapper) Set(key string, value []byte, ttl time.Duration) {
if err := w.redis.Set(key, value, ttl); err != nil {
w.logger.Error(&libpack_logger.LogMessage{
Message: "Redis set error",
Pairs: map[string]any{
"error": err.Error(),
"key": key,
},
})
}
}
// Get retrieves a value
func (w *CacheWrapper) Get(key string) ([]byte, bool) {
value, found, err := w.redis.Get(key)
if err != nil {
w.logger.Error(&libpack_logger.LogMessage{
Message: "Redis get error",
Pairs: map[string]any{
"error": err.Error(),
"key": key,
},
})
return nil, false
}
return value, found
}
// Delete removes a key
func (w *CacheWrapper) Delete(key string) {
if err := w.redis.Delete(key); err != nil {
w.logger.Error(&libpack_logger.LogMessage{
Message: "Redis delete error",
Pairs: map[string]any{
"error": err.Error(),
"key": key,
},
})
}
}
// Clear removes all keys
func (w *CacheWrapper) Clear() {
if err := w.redis.Clear(); err != nil {
w.logger.Error(&libpack_logger.LogMessage{
Message: "Redis clear error",
Pairs: map[string]any{
"error": err.Error(),
},
})
}
}
// CountQueries returns the number of queries
func (w *CacheWrapper) CountQueries() int64 {
count, err := w.redis.CountQueries()
if err != nil {
w.logger.Error(&libpack_logger.LogMessage{
Message: "Redis count queries error",
Pairs: map[string]any{
"error": err.Error(),
},
})
return 0
}
return count
}
// GetMemoryUsage returns 0 for Redis (not applicable)
func (w *CacheWrapper) GetMemoryUsage() int64 {
return 0
}
// GetMaxMemorySize returns 0 for Redis (not applicable)
func (w *CacheWrapper) GetMaxMemorySize() int64 {
return 0
}
// Close releases the underlying Redis client's connection pool at shutdown.
// Makes CacheWrapper satisfy io.Closer so the cache layer can close it.
func (w *CacheWrapper) Close() error {
if w == nil || w.redis == nil {
return nil
}
return w.redis.Close()
}