Add redis support for distributed caching

This commit is contained in:
2025-10-19 01:05:41 +01:00
parent e45b06c86d
commit 92a058da83
238 changed files with 78024 additions and 124 deletions
+270 -39
View File
@@ -3,6 +3,9 @@ package traefikoidc
import (
"sync"
"time"
"github.com/lukaszraczylo/traefikoidc/internal/cache/backends"
"github.com/lukaszraczylo/traefikoidc/internal/cache/resilience"
)
// UniversalCacheManager manages all cache instances using the universal cache
@@ -34,25 +37,217 @@ func GetUniversalCacheManager(logger *Logger) *UniversalCacheManager {
logger: logger,
}
// Initialize token cache - CRITICAL FIX: Reduced from 5000 to 1000
universalCacheManager.tokenCache = NewUniversalCache(UniversalCacheConfig{
// Initialize with default in-memory backends
initializeDefaultCaches(universalCacheManager, logger)
})
return universalCacheManager
}
// GetUniversalCacheManagerWithConfig returns the singleton universal cache manager with Redis configuration
func GetUniversalCacheManagerWithConfig(logger *Logger, redisConfig *RedisConfig) *UniversalCacheManager {
universalCacheManagerOnce.Do(func() {
if logger == nil {
logger = GetSingletonNoOpLogger()
}
universalCacheManager = &UniversalCacheManager{
logger: logger,
}
if redisConfig != nil && redisConfig.Enabled {
logger.Infof("Initializing cache manager with Redis backend: %s", redisConfig.Address)
initializeCachesWithRedis(universalCacheManager, logger, redisConfig)
} else {
logger.Info("Initializing cache manager with memory-only backend")
initializeDefaultCaches(universalCacheManager, logger)
}
})
return universalCacheManager
}
// initializeDefaultCaches initializes caches with memory-only backends
func initializeDefaultCaches(manager *UniversalCacheManager, logger *Logger) {
// Initialize token cache - CRITICAL FIX: Reduced from 5000 to 1000
manager.tokenCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeToken,
MaxSize: 1000, // CRITICAL FIX: Reduced from 5000 to 1000 items
MaxMemoryBytes: 5 * 1024 * 1024, // CRITICAL FIX: Added 5MB memory limit
DefaultTTL: 1 * time.Hour,
Logger: logger,
})
// Initialize blacklist cache
manager.blacklistCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeToken,
MaxSize: 1000,
DefaultTTL: 24 * time.Hour,
Logger: logger,
})
// Initialize metadata cache with grace periods
manager.metadataCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeMetadata,
MaxSize: 100,
DefaultTTL: 1 * time.Hour,
MetadataConfig: &MetadataCacheConfig{
GracePeriod: 5 * time.Minute,
ExtendedGracePeriod: 15 * time.Minute,
MaxGracePeriod: 30 * time.Minute,
SecurityCriticalMaxGracePeriod: 15 * time.Minute,
SecurityCriticalFields: []string{
"jwks_uri",
"token_endpoint",
"authorization_endpoint",
"issuer",
},
},
Logger: logger,
})
// Initialize JWK cache
manager.jwkCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeJWK,
MaxSize: 200,
DefaultTTL: 1 * time.Hour,
Logger: logger,
})
// Initialize session cache - CRITICAL FIX: Reduced from 10000 to 2000
manager.sessionCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeSession,
MaxSize: 2000, // CRITICAL FIX: Reduced from 10000 to 2000 items
MaxMemoryBytes: 5 * 1024 * 1024, // CRITICAL FIX: Added 5MB memory limit
DefaultTTL: 30 * time.Minute,
Logger: logger,
})
// Initialize introspection cache for OAuth 2.0 Token Introspection (RFC 7662)
manager.introspectionCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeToken, // Use token cache type for introspection results
MaxSize: 1000, // Cache up to 1000 introspection results
DefaultTTL: 5 * time.Minute, // Short TTL for security (introspect frequently)
Logger: logger,
})
// Initialize token type cache for performance optimization
manager.tokenTypeCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeToken, // Use token cache type for token type detection
MaxSize: 2000, // Cache up to 2000 token type detections
DefaultTTL: 5 * time.Minute, // 5 minute TTL for token type detection
Logger: logger,
})
}
// initializeCachesWithRedis initializes caches with Redis/Hybrid backends based on configuration
func initializeCachesWithRedis(manager *UniversalCacheManager, logger *Logger, redisConfig *RedisConfig) {
// Apply defaults to Redis config
redisConfig.ApplyDefaults()
// Create Redis backend
redisBackendConfig := &backends.Config{
Type: backends.BackendTypeRedis,
RedisAddr: redisConfig.Address,
RedisPassword: redisConfig.Password,
RedisDB: redisConfig.DB,
RedisPrefix: redisConfig.KeyPrefix,
PoolSize: redisConfig.PoolSize,
EnableMetrics: true,
}
var redisBackend backends.CacheBackend
var err error
// Create Redis backend with resilience features if enabled
redisBackend, err = backends.NewRedisBackend(redisBackendConfig)
if err != nil {
logger.Errorf("Failed to create Redis backend: %v. Falling back to memory-only mode.", err)
initializeDefaultCaches(manager, logger)
return
}
// Wrap with circuit breaker if enabled
if redisConfig.EnableCircuitBreaker {
cbConfig := resilience.DefaultCircuitBreakerConfig()
cbConfig.MaxFailures = redisConfig.CircuitBreakerThreshold
cbConfig.Timeout = time.Duration(redisConfig.CircuitBreakerTimeout) * time.Second
cbConfig.OnStateChange = func(from, to resilience.State) {
logger.Infof("Circuit breaker state changed from %s to %s", from, to)
}
redisBackend = resilience.NewCircuitBreakerBackend(redisBackend, cbConfig)
logger.Info("Redis backend wrapped with circuit breaker")
}
// Wrap with health checker if enabled
if redisConfig.EnableHealthCheck {
hcConfig := &resilience.HealthCheckConfig{
CheckInterval: time.Duration(redisConfig.HealthCheckInterval) * time.Second,
Timeout: 5 * time.Second,
HealthyThreshold: 2,
UnhealthyThreshold: 3,
OnStatusChange: func(from, to resilience.HealthStatus) {
logger.Infof("Redis backend health status changed from %s to %s", from, to)
},
}
redisBackend = resilience.NewHealthCheckBackend(redisBackend, hcConfig)
logger.Info("Redis backend wrapped with health checker")
}
// Decide which backend to use based on cache mode
var createBackend func(cacheType CacheType) backends.CacheBackend
switch redisConfig.CacheMode {
case "redis":
// Redis-only mode
createBackend = func(cacheType CacheType) backends.CacheBackend {
return redisBackend
}
logger.Info("Using Redis-only cache backend")
case "hybrid":
// Hybrid mode is not currently supported due to interface incompatibilities
// Fall back to Redis-only mode
logger.Info("Hybrid mode not currently supported, using Redis-only mode")
createBackend = func(cacheType CacheType) backends.CacheBackend {
return redisBackend
}
default:
// Memory-only mode (fallback)
logger.Infof("Invalid cache mode: %s. Using memory-only mode.", redisConfig.CacheMode)
initializeDefaultCaches(manager, logger)
return
}
// Initialize token cache with backend
manager.tokenCache = NewUniversalCacheWithBackend(
UniversalCacheConfig{
Type: CacheTypeToken,
MaxSize: 1000, // CRITICAL FIX: Reduced from 5000 to 1000 items
MaxMemoryBytes: 5 * 1024 * 1024, // CRITICAL FIX: Added 5MB memory limit
MaxSize: 1000,
MaxMemoryBytes: 5 * 1024 * 1024,
DefaultTTL: 1 * time.Hour,
Logger: logger,
})
},
createBackend(CacheTypeToken),
)
// Initialize blacklist cache
universalCacheManager.blacklistCache = NewUniversalCache(UniversalCacheConfig{
// Initialize blacklist cache (CRITICAL - must be consistent across replicas)
manager.blacklistCache = NewUniversalCacheWithBackend(
UniversalCacheConfig{
Type: CacheTypeToken,
MaxSize: 1000,
DefaultTTL: 24 * time.Hour,
Logger: logger,
})
},
createBackend("blacklist"),
)
// Initialize metadata cache with grace periods
universalCacheManager.metadataCache = NewUniversalCache(UniversalCacheConfig{
// Initialize metadata cache
manager.metadataCache = NewUniversalCacheWithBackend(
UniversalCacheConfig{
Type: CacheTypeMetadata,
MaxSize: 100,
DefaultTTL: 1 * time.Hour,
@@ -69,43 +264,50 @@ func GetUniversalCacheManager(logger *Logger) *UniversalCacheManager {
},
},
Logger: logger,
})
},
createBackend(CacheTypeMetadata),
)
// Initialize JWK cache
universalCacheManager.jwkCache = NewUniversalCache(UniversalCacheConfig{
// Initialize JWK cache
manager.jwkCache = NewUniversalCacheWithBackend(
UniversalCacheConfig{
Type: CacheTypeJWK,
MaxSize: 200,
DefaultTTL: 1 * time.Hour,
Logger: logger,
})
},
createBackend(CacheTypeJWK),
)
// Initialize session cache - CRITICAL FIX: Reduced from 10000 to 2000
universalCacheManager.sessionCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeSession,
MaxSize: 2000, // CRITICAL FIX: Reduced from 10000 to 2000 items
MaxMemoryBytes: 5 * 1024 * 1024, // CRITICAL FIX: Added 5MB memory limit
DefaultTTL: 30 * time.Minute,
Logger: logger,
})
// Initialize introspection cache for OAuth 2.0 Token Introspection (RFC 7662)
universalCacheManager.introspectionCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeToken, // Use token cache type for introspection results
MaxSize: 1000, // Cache up to 1000 introspection results
DefaultTTL: 5 * time.Minute, // Short TTL for security (introspect frequently)
Logger: logger,
})
// Initialize token type cache for performance optimization
universalCacheManager.tokenTypeCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeToken, // Use token cache type for token type detection
MaxSize: 2000, // Cache up to 2000 token type detections
DefaultTTL: 5 * time.Minute, // 5 minute TTL for token type detection
Logger: logger,
})
// Session cache stays memory-only (high volume, local state)
manager.sessionCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeSession,
MaxSize: 2000,
MaxMemoryBytes: 5 * 1024 * 1024,
DefaultTTL: 30 * time.Minute,
Logger: logger,
})
return universalCacheManager
// Introspection cache uses backend for sharing results
manager.introspectionCache = NewUniversalCacheWithBackend(
UniversalCacheConfig{
Type: CacheTypeToken,
MaxSize: 1000,
DefaultTTL: 5 * time.Minute,
Logger: logger,
},
createBackend(CacheTypeToken),
)
// Token type cache stays memory-only (local optimization)
manager.tokenTypeCache = NewUniversalCache(UniversalCacheConfig{
Type: CacheTypeToken,
MaxSize: 2000,
DefaultTTL: 5 * time.Minute,
Logger: logger,
})
logger.Infof("Cache manager initialized with %s backend configuration", redisConfig.CacheMode)
}
// GetTokenCache returns the token cache
@@ -174,6 +376,35 @@ func (m *UniversalCacheManager) Close() error {
return nil
}
// InitializeCacheManagerFromConfig initializes the cache manager with configuration
// This should be called early in the application startup with the loaded configuration
func InitializeCacheManagerFromConfig(config *Config) *UniversalCacheManager {
logger := NewLogger(config.LogLevel)
// Initialize Redis config if not present
if config.Redis == nil {
config.Redis = &RedisConfig{}
}
// Apply environment variable fallbacks for fields not set in config
// This allows env vars to be used as optional overrides only when
// the config field is not explicitly set through Traefik
config.Redis.ApplyEnvFallbacks()
// Apply defaults after env fallbacks
config.Redis.ApplyDefaults()
// Log cache backend selection
if config.Redis != nil && config.Redis.Enabled {
logger.Infof("Initializing cache backend with Redis: mode=%s, address=%s",
config.Redis.CacheMode, config.Redis.Address)
} else {
logger.Info("Initializing cache backend with memory-only mode")
}
return GetUniversalCacheManagerWithConfig(logger, config.Redis)
}
// ResetUniversalCacheManagerForTesting resets the singleton for testing purposes only
// This should only be called in test code to ensure proper cleanup between tests
func ResetUniversalCacheManagerForTesting() {