Cookbook

Caching Recipes

Advanced caching — distributed coordination, advanced tagging, stampede protection, edge/CDN seam, and production tuning.

This page covers the advanced caching surface: distributed coordination, advanced tagging, stampede protection, the edge/CDN seam, and production tuning.

Distributed Caching

Basic Distributed Setup

use Glueful\Cache\DistributedCacheService;

// Configuration
$config = [
    'nodes' => [
        ['host' => '192.168.1.10', 'port' => 6379, 'weight' => 100],
        ['host' => '192.168.1.11', 'port' => 6379, 'weight' => 100],
        ['host' => '192.168.1.12', 'port' => 6379, 'weight' => 50]
    ],
    'replication' => 'consistent-hashing',
    'failover' => true,
    'health' => [
        'check_interval' => 30,
        'timeout' => 5,
        'max_failures' => 3
    ]
];

// Create distributed cache
$distributedCache = new DistributedCacheService($primaryCache, $config);

// Use like normal cache - operations are distributed automatically
$distributedCache->set('user:123', $userData, 3600);
$userData = $distributedCache->get('user:123');

Replication Strategies

// Consistent hashing (default)
$distributedCache->setReplicationStrategy('consistent-hashing');

// Round-robin distribution
$distributedCache->setReplicationStrategy('round-robin');

// Custom strategy configuration
$config['strategies'] = [
    'consistent-hashing' => [
        'virtual_nodes' => 150,
        'hash_function' => 'md5'
    ],
    'round-robin' => [
        'sticky_sessions' => true
    ]
];

Health Monitoring and Failover

// Enable automatic failover
$distributedCache->setFailoverEnabled(true);

// Check failover status
if ($distributedCache->isFailoverEnabled()) {
    // Failover is active - unhealthy nodes are automatically excluded
}

// Get node manager for advanced operations
$nodeManager = $distributedCache->getNodeManager();
$healthyNodes = $nodeManager->getHealthyNodes();
$allNodes = $nodeManager->getAllNodes();

Note: Enable or disable failover with $distributedCache->setFailoverEnabled(true|false). Replication strategies and health monitoring options are provided via the $config passed to the constructor.

Cache Tagging System

Basic Tagging

use Glueful\Cache\CacheTaggingService;

// Enable tagging
CacheTaggingService::enable();

// Tag cache entries
CacheTaggingService::tagCache('user:123', ['users', 'user_data']);
CacheTaggingService::tagCache('user:123:permissions', ['users', 'permissions']);
CacheTaggingService::tagCache('role:admin', ['roles', 'permissions']);

// Get tags for a key
$tags = CacheTaggingService::getKeyTags('user:123');

// Get keys for a tag
$userKeys = CacheTaggingService::getTaggedKeys('users');

Tag-based Invalidation

// Invalidate by single tag
$result = CacheTaggingService::invalidateByTag('users');
/*
[
    'status' => 'completed',
    'tag' => 'users',
    'invalidated' => ['user:123', 'user:456'],
    'failed' => [],
    'success_count' => 2,
    'failure_count' => 0
]
*/

// Invalidate by multiple tags
$result = CacheTaggingService::invalidateByTags(['users', 'permissions']);

// Invalidate by predefined category
$result = CacheTaggingService::invalidateRelated('config');

Predefined Tag Categories

// Built-in categories
$categories = CacheTaggingService::getPredefinedTags();
/*
[
    'config' => ['app_config', 'database_config', 'cache_config'],
    'permissions' => ['user_permissions', 'role_permissions', 'permission_definitions'],
    'roles' => ['user_roles', 'role_definitions', 'role_hierarchy'],
    'users' => ['user_data', 'user_sessions', 'user_profiles'],
    'auth' => ['jwt_tokens', 'session_data', 'auth_cache'],
    'api' => ['api_routes', 'api_definitions', 'api_metadata'],
    'files' => ['file_metadata', 'upload_cache', 'image_cache'],
    'notifications' => ['notification_templates', 'notification_preferences', 'notification_queue']
]
*/

// Add custom category
CacheTaggingService::addPredefinedTag('products', ['product_data', 'product_images', 'product_cache']);

// Invalidate entire category
CacheTaggingService::invalidateRelated('users');

Tag Management

// Get tagging statistics
$stats = CacheTaggingService::getTagStats();
/*
[
    'total_tags' => 12,
    'total_tagged_keys' => 45,
    'tags' => [
        'users' => ['key_count' => 15, 'keys' => ['user:123', 'user:456', ...]],
        'permissions' => ['key_count' => 8, 'keys' => ['perm:admin', ...]]
    ]
]
*/

// Cleanup orphaned tags and keys
$cleanup = CacheTaggingService::cleanup();
/*
[
    'status' => 'completed',
    'cleaned_keys' => ['user:789'], // Keys that no longer exist in cache
    'cleaned_tags' => ['temp_tag'], // Tags with no associated keys
    'key_count' => 1,
    'tag_count' => 1
]
*/

Cache Helper Utilities

Safe Cache Operations

use Glueful\Helpers\CacheHelper;

// Create cache instance with graceful fallback
$cache = CacheHelper::createCacheInstance(); // Returns null if cache unavailable

// Check cache health
if (CacheHelper::isCacheHealthy($cache)) {
    // Cache is available and responding
}

// Safe cache operations with fallback
$result = CacheHelper::safeExecute(
    $cache,
    fn($cache) => $cache->get('user:123'),
    $defaultUserData // Fallback if cache operation fails
);

Key Management

// Automatic key prefixing
$key = CacheHelper::key('user:123'); // Adds configured prefix
$keys = CacheHelper::keys(['user:123', 'user:456']); // Prefix multiple keys

// Specialized key generators
$userKey = CacheHelper::userKey('123', 'profile'); // user:123:profile
$sessionKey = CacheHelper::sessionKey('abc123', 'data'); // session:data:abc123
$rateLimitKey = CacheHelper::rateLimitKey('192.168.1.1', 'api'); // rate_limit:api:192.168.1.1
$permissionKey = CacheHelper::permissionKey('user123', 'admin'); // permissions:user123:admin
$configKey = CacheHelper::configKey('database'); // config:database

// Remove prefix from keys
$baseKey = CacheHelper::unprefix('prefix:user:123'); // user:123

Cache Stampede Protection

Basic Stampede Protection

use Glueful\Helpers\CacheHelper;

// Simple remember with stampede protection
$userData = CacheHelper::remember(
    $cache,
    'expensive:user:123',
    function() {
        // This expensive operation will only run once
        // even if multiple processes request it simultaneously
        return $this->expensiveUserDataCalculation(123);
    },
    3600, // TTL
    true  // Enable stampede protection (uses config thresholds)
);

Advanced Stampede Protection

// Advanced stampede protection with custom settings
$userData = CacheHelper::rememberWithStampedeProtection(
    $cache,
    'expensive:calculation',
    function() {
        return $this->performExpensiveCalculation();
    },
    3600,   // Cache TTL
    60,     // Lock TTL
    30,     // Max wait time for lock
    100000  // Retry interval in microseconds (0.1s)
);

Early Expiration with Background Refresh

// Early expiration detection with background refresh
$configData = CacheHelper::rememberWithEarlyExpiration(
    $cache,
    'app:config',
    function() {
        return $this->loadConfiguration();
    },
    3600,  // Cache TTL (1 hour)
    0.8,   // Refresh at 80% of TTL (48 minutes)
    60     // Lock TTL for background refresh
);

// When cache is at 80% of its TTL:
// 1. Return current cached value immediately
// 2. Trigger background refresh asynchronously
// 3. Next request gets refreshed value

Configuration-based Stampede Protection

// Configure stampede protection globally
// config/cache.php
return [
    'stampede_protection' => [
        'enabled' => true,
        'lock_ttl' => 60,
        'max_wait_time' => 30,
        'retry_interval' => 100000,
        'early_expiration' => [
            'enabled' => true,
            'threshold' => 0.8 // Refresh at 80% of TTL
        ]
    ]
];

// Use with automatic configuration
$result = CacheHelper::remember($cache, $key, $callback, $ttl);
// Automatically uses configured stampede protection settings

Edge Caching and CDN Integration

Requires the glueful/cdn extension

Core ships only the seam: response caching emits surrogate keys against the Glueful\Cache\Contracts\EdgeCacheInterface, bound by default to the no-op NullEdgeCache. To actually emit edge headers and purge a CDN, install glueful/cdn — it binds Glueful\Extensions\Cdn\EdgeCachePurger to the same interface and adds the cache:purge command. The purge API (purgeUrl(), purgeByTag(), purgeAll()) and cache.edge / EDGE_CACHE_* configuration are documented with that extension. Resolve the binding via the container rather than constructing it directly:

$edge = app($context, \Glueful\Cache\Contracts\EdgeCacheInterface::class);
// no-op without glueful/cdn; real CDN purges once the extension is installed

Production Optimization

High-Performance Configuration

// config/cache.php - Production optimizations
return [
    'stores' => [
        'redis' => [
            'driver' => 'redis',
            'host' => env('REDIS_HOST'),
            'port' => env('REDIS_PORT'),
            'password' => env('REDIS_PASSWORD'),
            'database' => 0,
            'timeout' => 1.0, // Faster timeout for production
            'options' => [
                'serializer' => 'igbinary', // Faster serialization
                'compression' => 'lz4',     // Fast compression
                'persistent' => true,       // Persistent connections
                'tcp_keepalive' => 1       // Keep connections alive
            ],
            'pool' => [
                'min_connections' => 5,
                'max_connections' => 20,
                'idle_timeout' => 300
            ]
        ]
    ],
    
    'stampede_protection' => [
        'enabled' => true,
        'lock_ttl' => 30,        // Shorter locks in production
        'max_wait_time' => 10,   // Less waiting time
        'early_expiration' => [
            'enabled' => true,
            'threshold' => 0.85  // Earlier refresh
        ]
    ]
];

Monitoring and Alerting

class CacheMonitoringService
{
    private CacheStore $cache;
    
    public function getHealthMetrics(): array
    {
        $stats = $this->cache->getStats();
        
        return [
            'hit_ratio' => $this->calculateHitRatio($stats),
            'memory_usage' => $stats['memory_usage'] ?? 0,
            'connection_count' => $stats['connections'] ?? 0,
            'operations_per_second' => $stats['ops_per_sec'] ?? 0,
            'avg_response_time' => $this->measureResponseTime()
        ];
    }
    
    public function checkAlerts(): array
    {
        $metrics = $this->getHealthMetrics();
        $alerts = [];
        
        if ($metrics['hit_ratio'] < 0.85) {
            $alerts[] = 'Cache hit ratio below 85%';
        }
        
        if ($metrics['memory_usage'] > 0.9) {
            $alerts[] = 'Cache memory usage above 90%';
        }
        
        if ($metrics['avg_response_time'] > 10) {
            $alerts[] = 'Cache response time above 10ms';
        }
        
        return $alerts;
    }
    
    private function measureResponseTime(): float
    {
        $start = microtime(true);
        $this->cache->get('health_check_' . uniqid());
        return (microtime(true) - $start) * 1000; // ms
    }
}

Cache Partitioning Strategy

class CachePartitioningService
{
    private array $caches = [];
    
    public function __construct()
    {
        // Partition cache by data type for better performance
        $this->caches = [
            'users' => CacheFactory::create('redis'), // User data on Redis
            'sessions' => CacheFactory::create('redis'), // Session data on Redis
            'static' => CacheFactory::create('file'),    // Static content on file
            'temp' => CacheFactory::create('memcached')  // Temporary data on Memcached
        ];
    }
    
    public function getCache(string $partition): CacheStore
    {
        return $this->caches[$partition] ?? $this->caches['users'];
    }
    
    public function set(string $partition, string $key, mixed $value, int $ttl = 3600): bool
    {
        return $this->getCache($partition)->set($key, $value, $ttl);
    }
    
    public function get(string $partition, string $key, mixed $default = null): mixed
    {
        return $this->getCache($partition)->get($key, $default);
    }
}