پرش به مطلب اصلی

عیب‌یابی سیستم کش

این راهنما به شما کمک می‌کند تا مسائل رایج سیستم کش را تشخیص و حل کنید.

مسائل رایج

داده‌های کش گم شده

علائم

  • داده‌های کش زمانی که انتظار می‌رود در دسترس نیستند
  • hasCacheData() برای کلیدهایی که باید پر باشند false برمی‌گرداند
  • مقادیر خالی یا null هنگام دسترسی به کلیدهای کش

علل احتمالی

  1. کنترل‌کننده کش ثبت نشده
// ❌ مشکل: کنترل‌کننده کش در service provider ثبت نشده
// ServiceProvider.php ثبت را از دست داده

// ✅ راه‌حل: کنترل‌کننده را در service provider خود ثبت کنید
public function register(): void
{
$this->app->tag(
[
ProductRelatedItemsCacheHandler::class,
ProductPricingCacheHandler::class,
],
'cache.handlers'
);
}
  1. رویدادها ارسال نمی‌شوند
// ❌ مشکل: رویدادها ارسال نمی‌شوند
public function update(array $data)
{
$this->product->update($data);
// ارسال رویداد گم شده
}

// ✅ راه‌حل: پس از به‌روزرسانی مدل رویدادها را ارسال کنید
public function update(array $data)
{
$this->product->update($data);
event(new ProductUpdatedEvent($this->product));
}
  1. پیکربندی رویداد نادرست
// ❌ مشکل: پیکربندی رویداد نادرست
new CacheConfigDTO(
key: 'related_products',
relatedEvents: [
ProductCreatedEvent::class, // ProductUpdatedEvent گم شده
],
// سایر پیکربندی‌ها...
)

// ✅ راه‌حل: تمام رویدادهای مرتبط را شامل کنید
new CacheConfigDTO(
key: 'related_products',
relatedEvents: [
ProductCreatedEvent::class,
ProductUpdatedEvent::class,
ProductCategoryChangedEvent::class,
],
// سایر پیکربندی‌ها...
)
  1. خطاهای کنترل‌کننده کش

لاگ‌های خود را برای استثناها در کنترل‌کننده‌های کش بررسی کنید. خطاهای رایج شامل:

  • استثناهای پرس‌وجوی پایگاه داده
  • وابستگی‌های گم شده
  • خطاهای منطقی در تبدیل داده

دستورات تشخیصی

# بررسی وجود داده کش برای یک مدل خاص
php artisan cache:check --model=Product --id=123

# مشاهده آمار کش برای یک مدل
php artisan cache:stats --model=Product --id=123

# دیباگ رویدادهای کش
php artisan cache:debug-events --model=Product --watch

خطاهای تبدیل نوع

علائم

  • استثناها هنگام دسترسی به داده‌های کش
  • انواع داده غیرمنتظره
  • استثناهای TypeError

علل احتمالی

  1. نوع تبدیل نادرست
// ❌ مشکل: نوع تبدیل نادرست
new CacheConfigDTO(
key: 'view_count',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::STRING, // باید INTEGER باشد
)

// ✅ راه‌حل: از نوع تبدیل مناسب استفاده کنید
new CacheConfigDTO(
key: 'view_count',
// سایر پیکربندی‌ها...
cast: CacheCastEnum::INTEGER,
)
  1. فرمت داده ناسازگار
// ❌ مشکل: فرمت داده ناسازگار در کنترل‌کننده
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
// گاهی رشته، گاهی عدد صحیح برمی‌گرداند
$count = $this->getCount($eventDto->getEntityId());

return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $count)
]);
}

// ✅ راه‌حل: اطمینان از نوع داده سازگار
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$count = $this->getCount($eventDto->getEntityId());

// تبدیل صریح به عدد صحیح
$count = (int) $count;

return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $count)
]);
}
  1. فرمت تاریخ نامعتبر
// ❌ مشکل: فرمت تاریخ نامعتبر
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$lastActivity = '2023-01-01'; // تاریخ رشته‌ای

return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $lastActivity)
]);
}

// ✅ راه‌حل: از نمونه‌های Carbon برای تاریخ‌ها استفاده کنید
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$lastActivity = Carbon::parse('2023-01-01');

return new CacheHandlerResultCollection([
new CacheHandlerResult($eventDto->getEntityId(), $lastActivity)
]);
}

دستورات تشخیصی

# اعتبارسنجی انواع داده کش
php artisan cache:validate --model=Product --id=123

# دیباگ مسائل تبدیل
php artisan cache:debug-cast --model=Product --id=123 --key=view_count

مسائل عملکرد

علائم

  • زمان پاسخ کند
  • استفاده بالای CPU
  • تمام شدن pool اتصال پایگاه داده
  • تجمع صف

علل احتمالی

  1. پرس‌وجوهای ناکارآمد در کنترل‌کننده‌های کش
// ❌ مشکل: پرس‌وجوهای ناکارآمد
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();

// مشکل N+1 query
$relatedProducts = Product::where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})->get();

foreach ($relatedProducts as $product) {
// این هر دسته‌بندی محصول را در پرس‌وجوی جداگانه بارگذاری می‌کند
$categories[] = $product->category;
}

// پردازش داده...
}

// ✅ راه‌حل: پرس‌وجوها را بهینه‌سازی کنید
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();

// پرس‌وجوی کارآمد با eager loading
$relatedProducts = Product::with('category')
->where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})
->get();

// پردازش داده...
}
  1. تعداد زیاد به‌روزرسانی‌های همزمان کش
// ❌ مشکل: تعداد زیاد به‌روزرسانی‌های همزمان
new CacheConfigDTO(
key: 'expensive_computation',
// سایر پیکربندی‌ها...
mode: CacheModeEnum::SYNC, // عملیات مسدودکننده
)

// ✅ راه‌حل: از حالت ناهمزمان برای عملیات پرهزینه استفاده کنید
new CacheConfigDTO(
key: 'expensive_computation',
// سایر پیکربندی‌ها...
mode: CacheModeEnum::ASYNC, // غیرمسدودکننده
)
  1. مجموعه داده‌های بزرگ در کش
// ❌ مشکل: کش کردن داده‌های بیش از حد
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();

// داده‌های بیش از حد
$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)
]);
}

// ✅ راه‌حل: محدود کردن داده‌ها و انتخاب فقط فیلدهای مورد نیاز
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();

// داده‌های محدود با فیلدهای مشخص
$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)
]);
}

دستورات تشخیصی

# پروفایل عملکرد کنترل‌کننده کش
php artisan cache:profile --handler=ProductRelatedItemsCacheHandler

# نظارت بر زمان تولید کش
php artisan cache:monitor --model=Product --watch

# تحلیل اندازه کش
php artisan cache:analyze-size --model=Product --limit=20

خطاهای محافظت فیلد

علائم

  • استثناهای CacheFieldAccessException
  • عدم توانایی به‌روزرسانی داده‌های کش
  • عدم ماندگاری داده‌های کش در پایگاه داده

علل احتمالی

  1. دسترسی مستقیم به فیلد محافظت شده
// ❌ مشکل: دسترسی مستقیم به فیلد کش
$product->cache = ['related_items' => $items]; // استثنا پرتاب می‌کند

// ✅ راه‌حل: از API سیستم کش استفاده کنید
event(new ProductCacheUpdateEvent($product, 'related_items', $items));
  1. trait HasCacheField گم شده
// ❌ مشکل: trait HasCacheField گم شده
class Product extends Model
{
// trait گم شده

protected $casts = [
'cache' => 'json',
];
}

// ✅ راه‌حل: trait HasCacheField را اضافه کنید
class Product extends Model
{
use HasCacheField;

protected $casts = [
'cache' => 'json',
];
}
  1. نام فیلد کش نادرست
// ❌ مشکل: نام فیلد کش نادرست
class Product extends Model
{
use HasCacheField;

// پیکربندی نام فیلد کش گم شده

protected $casts = [
'data_cache' => 'json', // متفاوت از پیش‌فرض 'cache'
];
}

// ✅ راه‌حل: پیکربندی نام فیلد کش سفارشی
class Product extends Model
{
use HasCacheField;

protected $cacheField = 'data_cache';

protected $casts = [
'data_cache' => 'json',
];
}

دستورات تشخیصی

# بررسی وضعیت محافظت فیلد
php artisan cache:check-protection --model=Product

# لیست مدل‌های دارای trait HasCacheField
php artisan cache:list-models

# دیباگ دسترسی فیلد
php artisan cache:debug-access --model=Product --id=123

مسائل پردازش ناهمزمان

علائم

  • عدم به‌روزرسانی کش در حالت ناهمزمان
  • شکست کارهای صف
  • وضعیت ناسازگار کش

علل احتمالی

  1. عدم اجرای queue worker
# ✅ راه‌حل: راه‌اندازی queue workerها
php artisan queue:work --queue=cache
  1. پیکربندی نادرست صف
// ❌ مشکل: پیکربندی صف گم شده
// config/queue.php فاقد صف کش

// ✅ راه‌حل: پیکربندی صف کش
// 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. استثنا در کنترل‌کننده ناهمزمان

لاگ‌های خود را برای استثناها در کنترل‌کننده‌های کش ناهمزمان بررسی کنید. مسائل رایج شامل:

  • مشکلات اتصال پایگاه داده در queue workerها
  • مسائل زمان اجرا برای عملیات طولانی
  • محدودیت‌های حافظه فراتر رفته

دستورات تشخیصی

# نظارت بر وضعیت صف
php artisan queue:monitor

# دیباگ کارهای کش شکست خورده
php artisan cache:debug-queue

# تلاش مجدد کارهای شکست خورده
php artisan queue:retry --queue=cache

ابزارهای دیباگ

بررسی کش

مشاهده محتویات کش

// بررسی داده‌های کش برای یک مدل
$product = Product::find(123);
dd($product->getCacheData());

// بررسی وجود کلید کش مشخص
if ($product->hasCacheData('related_products')) {
// کش وجود دارد
}

// دریافت آمار کش
$stats = $product->getCacheStatistics();
dd($stats);

دستورات دیباگ کش

# مشاهده تمام کلیدهای کش برای یک مدل
php artisan cache:keys --model=Product --id=123

# نمایش داده‌های کش
php artisan cache:dump --model=Product --id=123 --key=related_products

# پاک کردن کش برای تست
php artisan cache:clear --model=Product --id=123

دیباگ رویداد

نظارت بر رویدادهای کش

// این را برای توسعه محلی به AppServiceProvider اضافه کنید
public function boot()
{
if (app()->environment('local')) {
Event::listen('*', function ($eventName, array $data) {
if (str_contains($eventName, 'Cache')) {
\Log::debug('رویداد کش: ' . $eventName, [
'data' => $data
]);
}
});
}
}

دستورات دیباگ رویداد

# نظارت بر رویدادهای کش در زمان واقعی
php artisan cache:events --watch

# راه‌اندازی تولید کش برای تست
php artisan cache:generate --model=Product --id=123

عیب‌یابی پیشرفته

شرایط مسابقه (Race Conditions)

شرایط مسابقه زمانی رخ می‌دهند که چندین فرآیند همزمان سعی در به‌روزرسانی کش دارند.

علائم

  • داده‌های کش ناسازگار
  • بازنویسی به‌روزرسانی‌های کش
  • به‌روزرسانی‌های جزئی کش

راه‌حل‌ها

  1. استفاده از تراکنش‌های پایگاه داده
// ✅ راه‌حل: از تراکنش‌ها برای به‌روزرسانی کش استفاده کنید
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$entityId = $eventDto->getEntityId();

DB::transaction(function () use ($entityId) {
$model = Product::lockForUpdate()->find($entityId);

if (!$model) {
return new CacheHandlerResultCollection();
}

// پردازش و به‌روزرسانی کش
// ...
});
}
  1. پیاده‌سازی قفل‌گذاری خوش‌بینانه
// ✅ راه‌حل: استفاده از قفل‌گذاری خوش‌بینانه
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$entityId = $eventDto->getEntityId();
$model = Product::find($entityId);

if (!$model) {
return new CacheHandlerResultCollection();
}

$currentVersion = $model->cache_version ?? 0;

// پردازش داده‌های کش
$cacheData = $this->generateCacheData($model);

// به‌روزرسانی با بررسی نسخه
$updated = DB::table('products')
->where('id', $entityId)
->where('cache_version', $currentVersion)
->update([
'cache' => json_encode($cacheData),
'cache_version' => $currentVersion + 1
]);

if (!$updated) {
// مدیریت تضاد نسخه
$this->logger->warning('تضاد به‌روزرسانی کش تشخیص داده شد', [
'entity_id' => $entityId,
'version' => $currentVersion
]);

// تلاش مجدد یا اطلاع‌رسانی
}
}

مسائل حافظه

علائم

  • خطاهای محدودیت حافظه PHP
  • کرش queue worker ها
  • عملکرد کند با مجموعه داده‌های بزرگ

راه‌حل‌ها

  1. تکه‌تکه کردن مجموعه داده‌های بزرگ
// ✅ راه‌حل: پردازش داده‌ها به صورت تکه‌ای
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$categoryId = $eventDto->getEntityId();

$results = new CacheHandlerResultCollection();

// پردازش به صورت تکه‌ای
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. بهینه‌سازی استفاده از حافظه
// ✅ راه‌حل: بهینه‌سازی استفاده از حافظه
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$productId = $eventDto->getEntityId();

// استفاده از cursor برای پیمایش کارآمد حافظه
$relatedProducts = Product::where('category_id', function ($query) use ($productId) {
$query->select('category_id')
->from('products')
->where('id', $productId);
})
->select(['id', 'name', 'price']) // فقط فیلدهای مورد نیاز را انتخاب کنید
->cursor();

$results = [];

foreach ($relatedProducts as $product) {
$results[] = [
'id' => $product->id,
'name' => $product->name,
'price' => $product->price,
];

// پاک کردن موجودیت از حافظه
$product = null;
}

return new CacheHandlerResultCollection([
new CacheHandlerResult($productId, $results)
]);
}

وابستگی‌های دایره‌ای

علائم

  • حلقه‌های بی‌نهایت در به‌روزرسانی‌های کش
  • ارسال بیش از حد رویداد
  • استفاده بالای CPU

راه‌حل‌ها

  1. تشخیص و شکستن ارجاعات دایره‌ای
// ✅ راه‌حل: ردیابی و جلوگیری از ارجاعات دایره‌ای
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]);
}
}

// در کنترل‌کننده کش
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$entityId = $eventDto->getEntityId();
$cacheKey = $eventDto->getCacheKey();
$trackingKey = "{$entityId}:{$cacheKey}";

// بررسی ارجاع دایره‌ای
if (CircularReferenceTracker::isProcessing($trackingKey)) {
$this->logger->warning('ارجاع دایره‌ای تشخیص داده شد', [
'entity_id' => $entityId,
'cache_key' => $cacheKey
]);
return new CacheHandlerResultCollection();
}

CircularReferenceTracker::startProcessing($trackingKey);

try {
// پردازش کش
// ...

return $results;
} finally {
CircularReferenceTracker::endProcessing($trackingKey);
}
}
  1. پیاده‌سازی تأخیر در ارسال رویداد (Debouncing)
// ✅ راه‌حل: تأخیر در ارسال رویدادهای کش
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;

// زمان‌بندی ارسال در پایان درخواست
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;
}
}

// استفاده
DebouncedCacheEventDispatcher::dispatch(ProductUpdatedEvent::class, $product);

پیام‌های خطای رایج

"CacheFieldAccessException: دسترسی مستقیم به فیلد کش مجاز نیست"

این خطا زمانی رخ می‌دهد که سعی در دسترسی مستقیم به فیلد cache می‌کنید.

راه‌حل: به جای دسترسی مستقیم از API سیستم کش استفاده کنید.

// ❌ این کار را نکنید
$product->cache = ['data' => $value];

// ✅ این کار را انجام دهید
event(new ProductCacheUpdateEvent($product, 'data', $value));

"TypeError: نمی‌توان به offset روی مقدار نوع null دسترسی پیدا کرد"

این خطا زمانی رخ می‌دهد که سعی در دسترسی به کلید کشی که وجود ندارد یا null است می‌کنید.

راه‌حل: همیشه قبل از دسترسی وجود داده کش را بررسی کنید.

// ❌ این کار را نکنید
$relatedProducts = $product->related_products['data'];

// ✅ این کار را انجام دهید
if ($product->hasCacheData('related_products')) {
$relatedProducts = $product->related_products['data'] ?? [];
} else {
$relatedProducts = [];
}

"No handler found for cache key: [key_name]"

این خطا زمانی رخ می‌دهد که هیچ کنترل‌کننده کشی برای یک کلید کش خاص ثبت نشده است.

راه‌حل: کنترل‌کننده مناسب را در service provider خود ثبت کنید.

// ✅ ثبت کنترل‌کننده در service provider
public function register(): void
{
$this->app->tag(
[
ProductRelatedItemsCacheHandler::class,
],
'cache.handlers'
);
}

نظارت بر عملکرد

زمان تولید کش

زمان صرف شده برای تولید داده‌های کش را نظارت کنید:

// اضافه کردن زمان‌سنجی به کنترل‌کننده‌های کش
public function handle(CacheEventDTO $eventDto): CacheHandlerResultCollection
{
$startTime = microtime(true);

// پردازش کش...

$endTime = microtime(true);
$duration = round(($endTime - $startTime) * 1000, 2);

$this->logger->info('تولید کش تکمیل شد', [
'entity_id' => $eventDto->getEntityId(),
'cache_key' => $eventDto->getCacheKey(),
'duration_ms' => $duration
]);

return $results;
}

مراحل بعدی

پس از عیب‌یابی، موارد زیر را در نظر بگیرید:

  1. بهترین شیوه‌ها - پیاده‌سازی خود را بهبود دهید
  2. راهنمای پیاده‌سازی - جزئیات پیاده‌سازی را بررسی کنید
  3. نمای کلی سیستم کش - تصویر کلی را درک کنید