Caching Recipes
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
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);
}
}