کش با Redis در Laravel می‌تواند زمان پاسخ را از چندصد میلی‌ثانیه به چند میلی‌ثانیه برساند، اما بخش ساده همین خواندن سریع است. سؤال دشوار این است: چه زمانی داده کش‌شده دیگر معتبر نیست؟ اگر برای این سؤال جواب نداریم، سرعت را با خطای پنهان عوض کرده‌ایم.

اول مشخص کنید چرا کش می‌کنید

هدف می‌تواند کاهش فشار دیتابیس، کوتاه‌کردن زمان API بیرونی یا جلوگیری از محاسبه تکراری باشد. هر هدف سیاست متفاوتی می‌خواهد. داده تنظیمات که روزی یک‌بار عوض می‌شود با قیمت و موجودی یک فروشگاه یکسان نیست.

قبل از کش baseline بگیرید. شاید یک index یا حذف N+1 مسئله را ساده‌تر حل کند. کش نباید لایه‌ای برای پنهان‌کردن query بد باشد.

کلید قابل فهم و بدون تداخل بسازید

یک الگوی ثابت مانند product:v2:{id}:{locale} هم debugging را آسان می‌کند و هم هنگام تغییر شکل داده امکان version bump می‌دهد. در سیستم چندمستاجری، tenant باید بخشی از کلید باشد؛ فراموش‌کردن آن می‌تواند داده یک مشتری را به مشتری دیگر نشان دهد.

$key = "tenant:{$tenantId}:product:v2:{$productId}:fa";

$product = Cache::remember($key, now()->addMinutes(15), function () use ($productId) {
    return Product::query()->with('category')->findOrFail($productId);
});

TTL را از تحمل کهنگی داده انتخاب کنید

عدد ۲۴ ساعت به‌خاطر hit rate خوب انتخاب مناسبی نیست. بپرسید اگر کاربر داده ۱۰ دقیقه قبل را ببیند چه اتفاقی می‌افتد؟ برای مقاله شاید بی‌اهمیت باشد؛ برای موجودی ممکن است سفارش اشتباه بسازد. کمی jitter به TTL اضافه کنید تا هزاران کلید هم‌زمان منقضی نشوند.

سه الگوی invalidation

  1. فقط TTL: ساده و مناسب داده‌ای که کهنگی محدود آن پذیرفتنی است.
  2. حذف هنگام تغییر: بعد از update موفق، کلیدهای مرتبط پاک می‌شوند.
  3. version namespace: با تغییر بزرگ، نسخه کلید عوض می‌شود و داده قبلی طبیعی منقضی می‌شود.

Cache tag جذاب است، اما به driver و هزینه نگهداری مجموعه کلیدها توجه کنید. گاهی ثبت مستقیم کلیدهای محدود شفاف‌تر است.

جلوی cache stampede را بگیرید

وقتی یک کلید محبوب منقضی می‌شود، صدها request ممکن است هم‌زمان query سنگین را اجرا کنند. lock باعث می‌شود فقط یک process داده را بازسازی کند. راه دیگر stale-while-revalidate است: برای مدت کوتاه پاسخ قبلی را می‌دهیم و یک worker نسخه تازه را می‌سازد.

Redis همیشه در دسترس نیست

برای cache معمولی، برنامه می‌تواند در زمان قطعی از دیتابیس بخواند؛ البته باید مراقب هجوم ناگهانی به دیتابیس بود. اما اگر Redis محل session، lock یا queue است، outage رفتار دیگری دارد. timeout کوتاه، alert و runbook بازیابی بخشی از طراحی‌اند.

داده حیاتی را فقط در cache نگه ندارید. cache باید قابل بازسازی باشد و دیتابیس یا سرویس اصلی منبع حقیقت بماند.

چه چیزهایی را اندازه بگیریم؟

  • hit و miss به تفکیک گروه کلید
  • حافظه و eviction
  • زمان پاسخ Redis و خطاهای اتصال
  • زمان ساخت مقدار در miss
  • اثر واقعی روی query و p95 endpoint

یک سیاست کوچک، بهتر از کش پراکنده

در ابتدای کار، دو یا سه مسیر پرهزینه را انتخاب کنید و برای هرکدام owner، key، TTL، invalidation و fallback را مستند کنید. این الگو بعداً به استاندارد تیم تبدیل می‌شود. اگر مشکل هنوز مشخص نیست، ابتدا راهنمای عیب‌یابی Laravel و MySQL را اجرا کنید یا از ممیزی کارایی شروع کنید.

پرسش‌های متداول

چه داده‌ای را نباید کش کرد؟

داده بسیار متغیر یا حساس به تازگی که سیاست invalidation روشنی ندارد، معمولاً گزینه خوبی برای شروع نیست.

TTL بلند همیشه بهتر است؟

خیر. TTL باید از میزان تغییر داده و تحمل محصول نسبت به کهنگی بیاید، نه از میل به hit rate بالاتر.