Cache System Troubleshooting
This guide helps you diagnose and resolve common issues with the cache system.
Common Issues
Missing Cache Data
Symptoms
- Cache data is not available when expected
hasCacheData()returns false for keys that should be populated- Empty or null values when accessing cache keys
Possible Causes
- Cache Handler Not Registered
// ❌ Problem: Cache handler not registered in service provider
// ServiceProvider.php is missing registration
// ✅ Solution: Register the handler in your service provider
public function register(): void
{
$this->app->tag(
[
ProductRelatedItemsCacheHandler::class,
ProductPricingCacheHandler::class,
],
'cache.handlers'
);
}
- Events Not Being Dispatched
// ❌ Problem: Events not being dispatched
public function update(array $data)
{
$this->product->update($data);
// Missing event dispatch
}
// ✅ Solution: Dispatch events after model updates
public function update(array $data)
{
$this->product->update($data);
event(new ProductUpdatedEvent($this->product));
}
- Incorrect Event Configuration
// ❌ Problem: Incorrect event configuration
new CacheConfigDTO(
key: 'related_products',
relatedEvents: [
ProductCreatedEvent::class, // Missing ProductUpdatedEvent
],
// Other config...
)
// ✅ Solution: Include all relevant events
new CacheConfigDTO(
key: 'related_products',
relatedEvents: [
ProductCreatedEvent::class,
ProductUpdatedEvent::class,
ProductCategoryChangedEvent::class,
],
// Other config...
)
- Cache Handler Errors
Check your logs for exceptions in cache handlers. Common errors include:
- Database query exceptions
- Missing dependencies
- Logic errors in data transformation
Diagnostic Commands
# Check if cache data exists for a specific model
php artisan cache:check --model=Product --id=123
# View cache statistics for a model
php artisan cache:stats --model=Product --id=123
# Debug cache events
php artisan cache:debug-events --model=Product --watch
Type Casting Errors
Symptoms
- Exceptions when accessing cache data
- Unexpected data types
TypeErrorexceptions
Possible Causes
- Incorrect Cast Type
// ❌ Problem: Incorrect cast type
new CacheConfigDTO(
key: 'view_count',
// other config...
cast: CacheCastEnum::STRING, // Should be INTEGER
)
// ✅ Solution: Use appropriate cast type
new CacheConfigDTO(
key: 'view_count',
// other config...
cast: CacheCastEnum::INTEGER,
)
- Inconsistent Data Format
// ❌ Problem: Inconsistent data format in handler
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
// Sometimes returns string, sometimes integer
$count = $this->getCount($eventDto->getEntityId());
return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $count)
]);
}
// ✅ Solution: Ensure consistent data type
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$count = $this->getCount($eventDto->getEntityId());
// Explicitly cast to integer
$count = (int) $count;
return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $count)
]);
}
- Invalid Date Format
// ❌ Problem: Invalid date format
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$lastActivity = '2023-01-01'; // String date
return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $lastActivity)
]);
}
// ✅ Solution: Use Carbon instances for dates
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$lastActivity = Carbon::parse('2023-01-01');
return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $lastActivity)
]);
}
Diagnostic Commands
# Validate cache data types
php artisan cache:validate --model=Product --id=123
# Debug cast issues
php artisan cache:debug-cast --model=Product --id=123 --key=view_count
Performance Issues
Symptoms
- Slow response times
- High CPU usage
- Database connection pool exhaustion
- Queue backlog
Possible Causes
- Inefficient Queries in Cache Handlers
// ❌ Problem: Inefficient queries
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// N+1 query problem
$relatedProducts = Product::where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})->get();
foreach ($relatedProducts as $product) {
// This loads each product's category in a separate query
$categories[] = $product->category;
}
// Process data...
}
// ✅ Solution: Optimize queries
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// Efficient query with eager loading
$relatedProducts = Product::with('category')
->where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})
->get();
// Process data...
}
- Too Many Synchronous Cache Updates
// ❌ Problem: Too many synchronous updates
new CacheConfigDTO(
key: 'expensive_computation',
// other config...
mode: CacheModeEnum::SYNC, // Blocking operation
)
// ✅ Solution: Use async mode for expensive operations
new CacheConfigDTO(
key: 'expensive_computation',
// other config...
mode: CacheModeEnum::ASYNC, // Non-blocking
)
- Large Data Sets in Cache
// ❌ Problem: Caching too much data
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// Too much data
$allRelatedProducts = Product::with('category', 'tags', 'reviews', 'images')
->where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})
->get();
return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $allRelatedProducts)
]);
}
// ✅ Solution: Limit data and select only needed fields
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// Limited data with specific fields
$relatedProducts = Product::select(['id', 'name', 'price', 'image_url'])
->where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})
->limit(5)
->get();
return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $relatedProducts)
]);
}
Diagnostic Commands
# Profile cache handler performance
php artisan cache:profile --handler=ProductRelatedItemsCacheHandler
# Monitor cache generation time
php artisan cache:monitor --model=Product --watch
# Analyze cache size
php artisan cache:analyze-size --model=Product --limit=20
Field Protection Errors
Symptoms
CacheFieldAccessExceptionexceptions- Unable to update cache data
- Cache data not persisting to database
Possible Causes
- Direct Access to Protected Field
// ❌ Problem: Direct access to cache field
$product->cache = ['related_items' => $items]; // Will throw exception
// ✅ Solution: Use the cache system API
event(new ProductCacheUpdateEvent($product, 'related_items', $items));
- Missing HasCacheField Trait
// ❌ Problem: Missing HasCacheField trait
class Product extends Model
{
// Missing trait
protected $casts = [
'cache' => 'json',
];
}
// ✅ Solution: Add HasCacheField trait
class Product extends Model
{
use HasCacheField;
protected $casts = [
'cache' => 'json',
];
}
- Incorrect Cache Field Name
// ❌ Problem: Incorrect cache field name
class Product extends Model
{
use HasCacheField;
// Missing cache field name configuration
protected $casts = [
'data_cache' => 'json', // Different from default 'cache'
];
}
// ✅ Solution: Configure custom cache field name
class Product extends Model
{
use HasCacheField;
protected $cacheField = 'data_cache';
protected $casts = [
'data_cache' => 'json',
];
}
Diagnostic Commands
# Check field protection status
php artisan cache:check-protection --model=Product
# List models with HasCacheField trait
php artisan cache:list-models
# Debug field access
php artisan cache:debug-access --model=Product --id=123
Async Processing Issues
Symptoms
- Cache not updating in async mode
- Queue jobs failing
- Inconsistent cache state
Possible Causes
- Queue Worker Not Running
# ✅ Solution: Start queue workers
php artisan queue:work --queue=cache
- Incorrect Queue Configuration
// ❌ Problem: Missing queue configuration
// config/queue.php missing cache queue
// ✅ Solution: Configure cache queue
// config/queue.php
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => 90,
'block_for' => null,
],
],
// .env
QUEUE_CONNECTION=redis
REDIS_QUEUE=cache
- Exception in Async Handler
Check your logs for exceptions in async cache handlers. Common issues include:
- Database connection issues in queue workers
- Timeout issues for long-running operations
- Memory limits exceeded
Diagnostic Commands
# Monitor queue status
php artisan queue:monitor
# Debug failed cache jobs
php artisan cache:debug-queue
# Retry failed jobs
php artisan queue:retry --queue=cache
Debugging Tools
Cache Inspection
View Cache Contents
// Inspect cache data for a model
$product = Product::find(123);
dd($product->getCacheData());
// Check if specific cache key exists
if ($product->hasCacheData('related_products')) {
// Cache exists
}
// Get cache statistics
$stats = $product->getCacheStatistics();
dd($stats);
Cache Debug Commands
# View all cache keys for a model
php artisan cache:keys --model=Product --id=123
# Dump cache data
php artisan cache:dump --model=Product --id=123 --key=related_products
# Clear cache for testing
php artisan cache:clear --model=Product --id=123
Event Debugging
Monitor Cache Events
// Add this to AppServiceProvider for local development
public function boot()
{
if (app()->environment('local')) {
Event::listen('*', function ($eventName, array $data) {
if (str_contains($eventName, 'Cache')) {
\Log::debug('Cache Event: ' . $eventName, [
'data' => $data
]);
}
});
}
}
Event Debug Commands
# Monitor cache events in real-time
php artisan cache:events --watch
# Trigger cache generation for testing
php artisan cache:generate --model=Product --id=123
Query Debugging
Monitor Database Queries
// Add this to AppServiceProvider for local development
public function boot()
{
if (app()->environment('local')) {
DB::listen(function ($query) {
if (str_contains($query->sql, 'cache')) {
\Log::debug('Cache Query: ' . $query->sql, [
'bindings' => $query->bindings,
'time' => $query->time
]);
}
});
}
}
Advanced Troubleshooting
Race Conditions
Race conditions can occur when multiple processes try to update the cache simultaneously.
Symptoms
- Inconsistent cache data
- Cache updates being overwritten
- Partial cache updates
Solutions
- Use Database Transactions
// ✅ Solution: Use transactions for cache updates
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$entityId = $eventDto->getEntityId();
DB::transaction(function () use ($entityId) {
$model = Product::lockForUpdate()->find($entityId);
if (!$model) {
return new CacheHandlerResultCollection();
}
// Process and update cache
// ...
});
}
- Implement Optimistic Locking
// ✅ Solution: Use optimistic locking
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$entityId = $eventDto->getEntityId();
$model = Product::find($entityId);
if (!$model) {
return new CacheHandlerResultCollection();
}
$currentVersion = $model->cache_version ?? 0;
// Process cache data
$cacheData = $this->generateCacheData($model);
// Update with version check
$updated = DB::table('products')
->where('id', $entityId)
->where('cache_version', $currentVersion)
->update([
'cache' => json_encode($cacheData),
'cache_version' => $currentVersion + 1
]);
if (!$updated) {
// Handle version conflict
$this->logger->warning('Cache update conflict detected', [
'entity_id' => $entityId,
'version' => $currentVersion
]);
// Retry or notify
}
}
Memory Issues
Symptoms
- PHP memory limit errors
- Queue worker crashes
- Slow performance with large datasets
Solutions
- Chunk Large Datasets
// ✅ Solution: Process data in chunks
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$categoryId = $eventDto->getEntityId();
$results = new CacheHandlerResultCollection();
// Process in chunks
Product::where('category_id', $categoryId)
->chunkById(100, function ($products) use (&$results) {
foreach ($products as $product) {
$cacheData = $this->processProduct($product);
$results->add(new CacheHandlerResult($product->id, $cacheData));
}
});
return $results;
}
- Optimize Memory Usage
// ✅ Solution: Optimize memory usage
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();
// Use cursor for memory-efficient iteration
$relatedProducts = Product::where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})
->select(['id', 'name', 'price']) // Select only needed fields
->cursor();
$results = [];
foreach ($relatedProducts as $product) {
$results[] = [
'id' => $product->id,
'name' => $product->name,
'price' => $product->price,
];
// Clear entity from memory
$product = null;
}
return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $results)
]);
}
Circular Dependencies
Symptoms
- Infinite loops in cache updates
- Excessive event firing
- High CPU usage
Solutions
- Detect and Break Circular References
// ✅ Solution: Track and prevent circular updates
class CircularReferenceTracker
{
private static $processing = [];
public static function isProcessing(string $key): bool
{
return isset(self::$processing[$key]);
}
public static function startProcessing(string $key): void
{
self::$processing[$key] = true;
}
public static function endProcessing(string $key): void
{
unset(self::$processing[$key]);
}
}
// In cache handler
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$entityId = $eventDto->getEntityId();
$cacheKey = $eventDto->getCacheKey();
$trackingKey = "{$entityId}:{$cacheKey}";
// Check for circular reference
if (CircularReferenceTracker::isProcessing($trackingKey)) {
$this->logger->warning('Circular reference detected', [
'entity_id' => $entityId,
'cache_key' => $cacheKey
]);
return new CacheHandlerResultCollection();
}
CircularReferenceTracker::startProcessing($trackingKey);
try {
// Process cache
// ...
return $results;
} finally {
CircularReferenceTracker::endProcessing($trackingKey);
}
}
- Implement Event Debouncing
// ✅ Solution: Debounce cache events
class DebouncedCacheEventDispatcher
{
private static $pendingEvents = [];
private static $dispatchScheduled = false;
public static function dispatch(string $eventClass, $entity): void
{
$entityId = $entity->id;
$key = "{$eventClass}:{$entityId}";
self::$pendingEvents[$key] = [
'class' => $eventClass,
'entity' => $entity,
];
if (!self::$dispatchScheduled) {
self::$dispatchScheduled = true;
// Schedule dispatch at end of request
app()->terminating(function () {
self::dispatchPending();
});
}
}
private static function dispatchPending(): void
{
foreach (self::$pendingEvents as $event) {
event(new $event['class']($event['entity']));
}
self::$pendingEvents = [];
self::$dispatchScheduled = false;
}
}
// Usage
DebouncedCacheEventDispatcher::dispatch(ProductUpdatedEvent::class, $product);
Common Error Messages
"CacheFieldAccessException: Direct access to cache field is not allowed"
This error occurs when trying to access the cache field directly.
Solution: Use the cache system API instead of direct access.
// ❌ Don't do this
$product->cache = ['data' => $value];
// ✅ Do this instead
event(new ProductCacheUpdateEvent($product, 'data', $value));
"TypeError: Cannot access offset on value of type null"
This error occurs when trying to access a cache key that doesn't exist or is null.
Solution: Always check if cache data exists before accessing it.
// ❌ Don't do this
$relatedProducts = $product->related_products['data'];
// ✅ Do this instead
if ($product->hasCacheData('related_products')) {
$relatedProducts = $product->related_products['data'] ?? [];
} else {
$relatedProducts = [];
}
"No handler found for cache key: [key_name]"
This error occurs when no cache handler is registered for a specific cache key.
Solution: Register the appropriate handler in your service provider.
// ✅ Register handler in service provider
public function register(): void
{
$this->app->tag(
[
ProductRelatedItemsCacheHandler::class,
],
'cache.handlers'
);
}
"Queue connection [connection] not configured"
This error occurs when the queue connection for async cache processing is not configured.
Solution: Configure the queue connection in config/queue.php and .env.
// ✅ Configure queue in .env
QUEUE_CONNECTION=redis
REDIS_QUEUE=cache
Performance Monitoring
Cache Generation Time
Monitor the time taken to generate cache data:
// Add timing to cache handlers
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$startTime = microtime(true);
// Process cache...
$endTime = microtime(true);
$duration = round(($endTime - $startTime) * 1000, 2);
$this->logger->info('Cache generation completed', [
'entity_id' => $eventDto->getEntityId(),
'cache_key' => $eventDto->getCacheKey(),
'duration_ms' => $duration
]);
return $results;
}
Cache Hit Ratio
Track cache hit ratio to measure effectiveness:
// Implement cache hit tracking
class CacheMetrics
{
public static function recordAccess(string $model, string $key, bool $hit): void
{
$metrics = cache()->get('cache_metrics', []);
if (!isset($metrics[$model][$key])) {
$metrics[$model][$key] = [
'hits' => 0,
'misses' => 0,
];
}
if ($hit) {
$metrics[$model][$key]['hits']++;
} else {
$metrics[$model][$key]['misses']++;
}
cache()->put('cache_metrics', $metrics, now()->addDay());
}
public static function getHitRatio(string $model, string $key): float
{
$metrics = cache()->get('cache_metrics', []);
if (!isset($metrics[$model][$key])) {
return 0;
}
$hits = $metrics[$model][$key]['hits'];
$misses = $metrics[$model][$key]['misses'];
$total = $hits + $misses;
return $total > 0 ? round(($hits / $total) * 100, 2) : 0;
}
}
// Usage in model
public function getCacheData(string $key = null)
{
$hasData = $this->hasCacheData($key);
CacheMetrics::recordAccess(get_class($this), $key, $hasData);
return parent::getCacheData($key);
}
Next Steps
After troubleshooting, consider:
- Best Practices - Improve your implementation
- Implementation Guide - Review implementation details
- Cache System Overview - Understand the big picture