mirror of
https://github.com/Dvorinka/SEEN.git
synced 2026-06-04 12:33:02 +00:00
236 lines
5.9 KiB
Go
236 lines
5.9 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
"github.com/tdvorak/seen/backend/internal/config"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Manager provides centralized cache management
|
|
type Manager struct {
|
|
client *redis.Client
|
|
service *Service
|
|
session *SessionCache
|
|
catalog *CatalogCache
|
|
download *DownloadCache
|
|
log *zap.Logger
|
|
}
|
|
|
|
// NewManager creates a new cache manager with all cache services
|
|
func NewManager(ctx context.Context, cfg config.CacheConfig, log *zap.Logger) (*Manager, error) {
|
|
client, err := NewClient(ctx, cfg, log)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create cache client: %w", err)
|
|
}
|
|
|
|
service := NewService(client)
|
|
namespace := "seen"
|
|
|
|
manager := &Manager{
|
|
client: client,
|
|
service: service,
|
|
session: NewSessionCache(service, namespace),
|
|
catalog: NewCatalogCache(service, namespace),
|
|
download: NewDownloadCache(service, namespace),
|
|
log: log,
|
|
}
|
|
|
|
// Start background cleanup tasks
|
|
go manager.startCleanupTasks(ctx)
|
|
|
|
return manager, nil
|
|
}
|
|
|
|
// Client returns the underlying Redis client
|
|
func (m *Manager) Client() *redis.Client {
|
|
return m.client
|
|
}
|
|
|
|
// Service returns the cache service
|
|
func (m *Manager) Service() *Service {
|
|
return m.service
|
|
}
|
|
|
|
// Session returns the session cache
|
|
func (m *Manager) Session() *SessionCache {
|
|
return m.session
|
|
}
|
|
|
|
// Catalog returns the catalog cache
|
|
func (m *Manager) Catalog() *CatalogCache {
|
|
return m.catalog
|
|
}
|
|
|
|
// Download returns the download cache
|
|
func (m *Manager) Download() *DownloadCache {
|
|
return m.download
|
|
}
|
|
|
|
// Ping checks if the cache is responsive
|
|
func (m *Manager) Ping(ctx context.Context) error {
|
|
return m.service.Ping(ctx)
|
|
}
|
|
|
|
// Close closes all cache connections
|
|
func (m *Manager) Close() error {
|
|
m.log.Info("closing cache connections")
|
|
return m.service.Close()
|
|
}
|
|
|
|
// Stats returns cache statistics
|
|
func (m *Manager) Stats(ctx context.Context) (map[string]interface{}, error) {
|
|
info, err := m.client.Info(ctx, "stats", "memory", "keyspace").Result()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get cache stats: %w", err)
|
|
}
|
|
|
|
dbSize, err := m.client.DBSize(ctx).Result()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get db size: %w", err)
|
|
}
|
|
|
|
stats := map[string]interface{}{
|
|
"dbSize": dbSize,
|
|
"info": info,
|
|
}
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
// FlushAll clears all cache data (use with extreme caution!)
|
|
func (m *Manager) FlushAll(ctx context.Context) error {
|
|
m.log.Warn("flushing all cache data")
|
|
return m.service.FlushDB(ctx)
|
|
}
|
|
|
|
// startCleanupTasks starts background tasks for cache maintenance
|
|
func (m *Manager) startCleanupTasks(ctx context.Context) {
|
|
ticker := time.NewTicker(5 * time.Minute)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
m.log.Info("stopping cache cleanup tasks")
|
|
return
|
|
case <-ticker.C:
|
|
m.runCleanupTasks(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
// runCleanupTasks performs periodic cache maintenance
|
|
func (m *Manager) runCleanupTasks(ctx context.Context) {
|
|
// Cleanup stale download progress (older than 1 hour)
|
|
if err := m.download.CleanupStaleProgress(ctx, 1*time.Hour); err != nil {
|
|
m.log.Error("failed to cleanup stale download progress", zap.Error(err))
|
|
}
|
|
|
|
// Log cache stats
|
|
stats, err := m.Stats(ctx)
|
|
if err != nil {
|
|
m.log.Error("failed to get cache stats", zap.Error(err))
|
|
return
|
|
}
|
|
|
|
if dbSize, ok := stats["dbSize"].(int64); ok {
|
|
m.log.Debug("cache stats", zap.Int64("keys", dbSize))
|
|
}
|
|
}
|
|
|
|
// InvalidateUser removes all cached data for a user
|
|
func (m *Manager) InvalidateUser(ctx context.Context, userID string) error {
|
|
m.log.Info("invalidating user cache", zap.String("userId", userID))
|
|
|
|
// Invalidate user data
|
|
if err := m.session.DeleteUser(ctx, userID); err != nil {
|
|
m.log.Error("failed to delete user cache", zap.Error(err))
|
|
}
|
|
|
|
// Invalidate user sessions
|
|
if err := m.session.InvalidateUserSessions(ctx, userID); err != nil {
|
|
m.log.Error("failed to invalidate user sessions", zap.Error(err))
|
|
}
|
|
|
|
// Invalidate catalog data
|
|
if err := m.catalog.InvalidateUserCatalog(ctx, userID); err != nil {
|
|
m.log.Error("failed to invalidate user catalog", zap.Error(err))
|
|
}
|
|
|
|
// Invalidate download data
|
|
if err := m.download.InvalidateUserDownloads(ctx, userID); err != nil {
|
|
m.log.Error("failed to invalidate user downloads", zap.Error(err))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WarmupCache pre-populates cache with common data
|
|
func (m *Manager) WarmupCache(ctx context.Context) error {
|
|
m.log.Info("warming up cache")
|
|
|
|
// Add warmup logic here as needed
|
|
// For example, pre-cache popular catalog sections
|
|
|
|
return nil
|
|
}
|
|
|
|
// HealthCheck performs a comprehensive health check
|
|
func (m *Manager) HealthCheck(ctx context.Context) error {
|
|
// Check basic connectivity
|
|
if err := m.Ping(ctx); err != nil {
|
|
return fmt.Errorf("ping failed: %w", err)
|
|
}
|
|
|
|
// Test write operation
|
|
testKey := "health:check"
|
|
testValue := map[string]interface{}{
|
|
"timestamp": time.Now().UTC(),
|
|
"test": true,
|
|
}
|
|
|
|
if err := m.service.Set(ctx, testKey, testValue, 10*time.Second); err != nil {
|
|
return fmt.Errorf("write test failed: %w", err)
|
|
}
|
|
|
|
// Test read operation
|
|
var readValue map[string]interface{}
|
|
if err := m.service.Get(ctx, testKey, &readValue); err != nil {
|
|
return fmt.Errorf("read test failed: %w", err)
|
|
}
|
|
|
|
// Cleanup test key
|
|
if err := m.service.Delete(ctx, testKey); err != nil {
|
|
m.log.Warn("failed to cleanup health check key", zap.Error(err))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetKeysByPattern retrieves all keys matching a pattern
|
|
func (m *Manager) GetKeysByPattern(ctx context.Context, pattern string) ([]string, error) {
|
|
return m.service.Keys(ctx, pattern)
|
|
}
|
|
|
|
// DeleteKeysByPattern deletes all keys matching a pattern
|
|
func (m *Manager) DeleteKeysByPattern(ctx context.Context, pattern string) error {
|
|
keys, err := m.service.Keys(ctx, pattern)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(keys) == 0 {
|
|
return nil
|
|
}
|
|
|
|
m.log.Info("deleting keys by pattern",
|
|
zap.String("pattern", pattern),
|
|
zap.Int("count", len(keys)))
|
|
|
|
return m.service.Delete(ctx, keys...)
|
|
}
|