mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-06-05 22:44:17 +00:00
Add sharded cache and prevention of CPU spikes / locks (#96)
* Add sharded cache and prevention of CPU spikes / locks * Add dynamic client registration with oidc provider * Fix race condition introduced during the sharded cache implementation. * Add page for traefikoidc.
This commit is contained in:
+125
-29
@@ -1,11 +1,14 @@
|
||||
package traefikoidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UniversalCacheManager manages all cache instances using the universal cache
|
||||
// It runs a single consolidated cleanup goroutine for all caches, reducing
|
||||
// goroutine count and CPU overhead compared to per-cache cleanup routines.
|
||||
type UniversalCacheManager struct {
|
||||
tokenCache *UniversalCache
|
||||
blacklistCache *UniversalCache
|
||||
@@ -16,6 +19,12 @@ type UniversalCacheManager struct {
|
||||
tokenTypeCache *UniversalCache // Cache for token type detection results
|
||||
mu sync.RWMutex
|
||||
logger *Logger
|
||||
|
||||
// Consolidated cleanup management
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
cleanupStarted bool
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -30,25 +39,34 @@ func GetUniversalCacheManager(logger *Logger) *UniversalCacheManager {
|
||||
logger = GetSingletonNoOpLogger()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
universalCacheManager = &UniversalCacheManager{
|
||||
logger: logger,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
// Initialize all caches with SkipAutoCleanup=true to prevent 7 separate cleanup goroutines
|
||||
// Instead, we use a single consolidated cleanup routine managed by this manager
|
||||
|
||||
// Initialize token cache - CRITICAL FIX: Reduced from 5000 to 1000
|
||||
universalCacheManager.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,
|
||||
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,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Initialize blacklist cache
|
||||
universalCacheManager.blacklistCache = NewUniversalCache(UniversalCacheConfig{
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000,
|
||||
DefaultTTL: 24 * time.Hour,
|
||||
Logger: logger,
|
||||
Type: CacheTypeToken,
|
||||
MaxSize: 1000,
|
||||
DefaultTTL: 24 * time.Hour,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Initialize metadata cache with grace periods
|
||||
@@ -68,46 +86,115 @@ func GetUniversalCacheManager(logger *Logger) *UniversalCacheManager {
|
||||
"issuer",
|
||||
},
|
||||
},
|
||||
Logger: logger,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Initialize JWK cache
|
||||
universalCacheManager.jwkCache = NewUniversalCache(UniversalCacheConfig{
|
||||
Type: CacheTypeJWK,
|
||||
MaxSize: 200,
|
||||
DefaultTTL: 1 * time.Hour,
|
||||
Logger: logger,
|
||||
Type: CacheTypeJWK,
|
||||
MaxSize: 200,
|
||||
DefaultTTL: 1 * time.Hour,
|
||||
Logger: logger,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// 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,
|
||||
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,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// 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,
|
||||
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,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// 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,
|
||||
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,
|
||||
SkipAutoCleanup: true, // Managed cleanup
|
||||
})
|
||||
|
||||
// Start single consolidated cleanup goroutine for all caches
|
||||
// This replaces 7 individual cleanup goroutines with 1
|
||||
universalCacheManager.startConsolidatedCleanup()
|
||||
})
|
||||
|
||||
return universalCacheManager
|
||||
}
|
||||
|
||||
// startConsolidatedCleanup starts a single cleanup goroutine for all caches
|
||||
// This reduces goroutine count from 7 to 1 and consolidates cleanup operations
|
||||
func (m *UniversalCacheManager) startConsolidatedCleanup() {
|
||||
m.mu.Lock()
|
||||
if m.cleanupStarted {
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
m.cleanupStarted = true
|
||||
m.mu.Unlock()
|
||||
|
||||
m.wg.Add(1)
|
||||
go func() {
|
||||
defer m.wg.Done()
|
||||
|
||||
// Use 5-minute interval for consolidated cleanup
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
m.performConsolidatedCleanup()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
m.logger.Info("UniversalCacheManager: Started consolidated cleanup routine for all caches")
|
||||
}
|
||||
|
||||
// performConsolidatedCleanup runs cleanup on all caches in sequence
|
||||
// This is more efficient than parallel cleanup as it reduces lock contention
|
||||
func (m *UniversalCacheManager) performConsolidatedCleanup() {
|
||||
m.mu.RLock()
|
||||
caches := []*UniversalCache{
|
||||
m.tokenCache,
|
||||
m.blacklistCache,
|
||||
m.metadataCache,
|
||||
m.jwkCache,
|
||||
m.sessionCache,
|
||||
m.introspectionCache,
|
||||
m.tokenTypeCache,
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
|
||||
totalCleaned := 0
|
||||
for _, cache := range caches {
|
||||
if cache != nil {
|
||||
// Each cache.Cleanup() is self-contained and handles its own locking
|
||||
cache.Cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
if totalCleaned > 0 {
|
||||
m.logger.Debugf("UniversalCacheManager: Consolidated cleanup completed for all caches")
|
||||
}
|
||||
}
|
||||
|
||||
// GetTokenCache returns the token cache
|
||||
func (m *UniversalCacheManager) GetTokenCache() *UniversalCache {
|
||||
m.mu.RLock()
|
||||
@@ -157,8 +244,16 @@ func (m *UniversalCacheManager) GetTokenTypeCache() *UniversalCache {
|
||||
return m.tokenTypeCache
|
||||
}
|
||||
|
||||
// Close shuts down all caches
|
||||
// Close shuts down all caches and the consolidated cleanup routine
|
||||
func (m *UniversalCacheManager) Close() error {
|
||||
// Stop the consolidated cleanup routine first
|
||||
if m.cancel != nil {
|
||||
m.cancel()
|
||||
}
|
||||
|
||||
// Wait for cleanup routine to finish
|
||||
m.wg.Wait()
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
@@ -170,7 +265,8 @@ func (m *UniversalCacheManager) Close() error {
|
||||
}
|
||||
}
|
||||
|
||||
m.logger.Info("UniversalCacheManager: Closed all caches")
|
||||
m.cleanupStarted = false
|
||||
m.logger.Info("UniversalCacheManager: Closed all caches and cleanup routine")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user