Skip to main content

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

  1. 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'
);
}
  1. 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));
}
  1. 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...
)
  1. 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
  • TypeError exceptions

Possible Causes

  1. 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,
)
  1. 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)
]);
}
  1. 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

  1. 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...
}
  1. 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
)
  1. 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

  • CacheFieldAccessException exceptions
  • Unable to update cache data
  • Cache data not persisting to database

Possible Causes

  1. 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));
  1. 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',
];
}
  1. 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

  1. Queue Worker Not Running
# ✅ Solution: Start queue workers
php artisan queue:work --queue=cache
  1. 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
  1. 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

  1. 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
// ...
});
}
  1. 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

  1. 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;
}
  1. 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

  1. 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);
}
}
  1. 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:

  1. Best Practices - Improve your implementation
  2. Implementation Guide - Review implementation details
  3. Cache System Overview - Understand the big picture