Caching Recipes
This page covers the advanced caching surface: distributed coordination, advanced tagging, the edge/CDN seam, and production tuning.
Distributed Caching
Basic Distributed Setup
use Glueful\Cache\DistributedCacheService;
// Configuration
$config = [
'nodes' => [
['id' => 'cache-01', 'driver' => 'redis', 'host' => '192.168.1.10', 'port' => 6379, 'weight' => 100],
['id' => 'cache-02', 'driver' => 'redis', 'host' => '192.168.1.11', 'port' => 6379, 'weight' => 100],
['id' => 'cache-03', 'driver' => 'redis', 'host' => '192.168.1.12', 'port' => 6379, 'weight' => 50],
],
'replication' => 'consistent-hashing',
'failover' => true,
'health' => [
'health_check' => [
'interval' => 30,
'timeout' => 5,
'failure_threshold' => 3,
],
],
];
// Create distributed cache ($primaryCache is a CacheStore, e.g. app($context, CacheStore::class))
$distributedCache = new DistributedCacheService($primaryCache, $config, $context);
// Use like normal cache - operations are distributed automatically
$distributedCache->set('user:123', $userData, 3600);
$userData = $distributedCache->get('user:123');
Replication Strategies
// Built-in strategies: consistent-hashing (default), full-replication,
// primary-replica, key-pattern-sharding
$distributedCache->setReplicationStrategy('consistent-hashing');
$distributedCache->setReplicationStrategy('primary-replica');
// Per-strategy configuration (passed to the constructor)
$config['strategies'] = [
'consistent-hashing' => ['virtualNodes' => 150, 'replicas' => 2],
'primary-replica' => ['maxReplicas' => 2],
'key-pattern-sharding' => [
'patterns' => [/* pattern => selector */],
'fallback' => 'consistent-hashing',
],
];
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->getHealthyNodesForKey('user:123');
$allNodes = $nodeManager->getAllNodes();
Construct the service yourself: the container's DistributedCacheService is built with an empty config. config/cache.php has no distributed block (1.91 removed it, along with enable_tags and tags_store, because nothing read them), so keep the node list in your own config and pass it in.
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($context); // 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 (cache.prefix); every builder below adds it too
$key = CacheHelper::key('user:123'); // glueful:user:123
$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:<sha256 of the IP>
$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
CacheHelper::remember(), rememberWithStampedeProtection(), rememberWithEarlyExpiration() and the cache.stampede_protection config are covered in Features → Caching → Stampede Protection.
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
// The Redis factory (CacheFactory::createRedisConnection(), shared with the Redis
// lock store) reads only host, port, password, database and timeout;
// serializer/compression/persistent/pool options are not supported.
return [
'stores' => [
'redis' => [
'driver' => 'redis',
'host' => env('REDIS_HOST'),
'port' => env('REDIS_PORT'),
'password' => env('REDIS_PASSWORD'),
'database' => 0,
'timeout' => 1.0, // Faster timeout (the factory caps the connect timeout at 1s)
]
],
'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();
// Shape is driver-specific; for Redis: memory.used, performance.total_commands, ...
return [
'hit_ratio' => $this->calculateHitRatio($stats), // your own calculation
'memory_used' => $stats['memory']['used'] ?? 'unknown',
'total_commands' => $stats['performance']['total_commands'] ?? 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['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
}
}
Hit Ratio from Cache Events
Instead of computing a hit ratio from driver stats, count the CacheHitEvent / CacheMissEvent the container store fires from get() and remember(). The events are only built when a listener exists, and a listener failure never breaks the read:
use Glueful\Cache\CacheStore;
use Glueful\Events\Attributes\AsListener;
use Glueful\Events\Cache\{CacheHitEvent, CacheMissEvent};
final class CacheHitRatioRecorder
{
public function __construct(private readonly CacheStore $cache) {}
#[AsListener(CacheHitEvent::class)]
public function onHit(CacheHitEvent $event): void
{
$this->cache->increment('metrics:cache:hits');
}
#[AsListener(CacheMissEvent::class)]
public function onMiss(CacheMissEvent $event): void
{
$this->cache->increment('metrics:cache:misses');
}
}
// boot(): subscribe() autowires the class; registering it in services() is optional
app($context, EventService::class)->subscribe(CacheHitRatioRecorder::class);
increment() doesn't fire events, so recording a metric never counts as a hit or miss itself. Only the container's cache.store fires these events: it is an EventDispatchingCacheStore wrapping the configured driver, so an instanceof RedisCacheDriver-style check against it fails (call getInnerStore() for the driver; driver-only methods are forwarded). Stores you build with CacheFactory::create() are bare drivers. See Events → Attribute Listeners.
Cache Partitioning Strategy
class CachePartitioningService
{
private array $caches = [];
public function __construct(ApplicationContext $context)
{
// Partition cache by data type; the first argument forces a driver.
// These are bare drivers: they don't fire CacheHit/CacheMiss events.
$this->caches = [
'users' => CacheFactory::create('redis', $context), // User data on Redis
'sessions' => CacheFactory::create('redis', $context), // Session data on Redis
'static' => CacheFactory::create('file', $context), // Static content on file
'temp' => CacheFactory::create('memcached', $context) // 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);
}
}
Routing Recipes
Advanced routing — precedence, caching behavior, performance internals, and the framework's built-in endpoint patterns.
Queue Infrastructure
Advanced queue operations — driver internals, built-in tasks, batches, failed-job analysis, glueful/queue-ops supervision and autoscaling, and production HA.