Cache Stampede প্রিভেন্ট করার উপায়

Cache Stampede: ধরা যাক, একটি প্রোডাক্ট পেইজের ডেটার সাইজ ১০ KB। এই ডেটা ক্যাশে রাখা হয়েছে। এক্সপায়ার টাইম ১০ মিনিটি। এই প্রোডাক্ট পেইজে সব সময় ১ মিলিয়ন ইউজার একটিভ থাকে। যেহেতু ১০ মিনিট পর পর ক্যাশ এক্সপায়ার হয়ে যায়, তখন ১ মিলিয়ন রিকুয়েস্ট সরাসরি ডেটাবেজে চলে যায় এবং ডেটাবেজ সার্ভারে অতিরিক্ত লোড সৃষ্টি করে। এতে সার্ভার স্লো হয়ে যায়। এই পরিস্থিতিকে Cache Stampede বলে।

Cache Stampede প্রিভেন্ট করার কয়েকটি ওয়ে আছে। আমি কয়েকটি মেথড শেয়ার করছি।

Cache Locking:

ক্যাশ এক্সপায়ার হয়ে গেলে আমরা লক সেট করতে পারি। লকিং এর জন্য শুধুমাত্র একটি রিকুয়েস্ট ক্যাশ রিজেনারেট করবে। বাকি রিকুয়েস্টগুলো ক্যাশ রি-বিল্ডের জন্য অপেক্ষা করতে হবে।

$cacheKey = 'product_123';
$cacheValue = Cache::get($cacheKey);

if (!$cacheValue) {
    // Attempt to lock the cache key
    $lock = Cache::lock($cacheKey . '_lock', 10); // lock for 10 seconds

    if ($lock->get()) {
        try {
            // Rebuild cache only if lock is acquired
            $cacheValue = generateProductData(); // Your cache regeneration logic
            Cache::put($cacheKey, $cacheValue, 3600); // cache for 1 hour
        } finally {
            $lock->release();
        }
    } else {
        // Wait and retry (or return cached data if available after a while)
        $cacheValue = Cache::get($cacheKey);
    }
}

Preloading Cache (Warm Cache):

এটির মাধ্যমে ক্যাশ এক্সপায়ার হওয়ার ১/২ মিনিট আগে ক্যাশ রি-জেনারেট করা। এছাড়া আমরা যদি কোন ইভেন্ট জানি যেমন প্রতিদিন রাত ১০ টায় একটি পেইজে অনেক ইউজার আসে, তাহলে আমরা আগে থেকেই সেই ডেটা ক্যাশ করে রাখতে পারি।

class PreloadCacheJob extends Job
{
    public function handle()
    {
        // Fetch the data from the database
        $data = Data::all();

        // Preload the cache with the fresh data, setting the cache for 60minutes
        Cache::put('some_data_key', $data, now()->addMinutes(60));
    }
}

// Run the cache preloading job at the 58th minute of every hour
$schedule->job(new PreloadCacheJob)->hourlyAt(58);

Background Cache Refresh:

ক্যাশ এক্সপায়ার হয়ে গেলে, ইউজারদের ওয়েট না করিয়ে, ফ্রেশ ডেটা ক্যাশে এড করার জন্য একটি জব রান করা এবং ইউজারদের সাময়িকভাবে পুরাতন ডেটা দেখানো।

public function getData()
{
    $cacheKey = 'your_data_key';

    // Check if data is available in the cache
    if (Cache::has($cacheKey)) {
        return Cache::get($cacheKey);
    }

    // If cache miss, dispatch the job to refresh the cache
    RefreshCacheJob::dispatch($cacheKey);

    // Optionally, return the old or stale cache until the job finishes
    return response()->json(['status' => 'Cache refreshing in background'], 202);
}

// Job
class RefreshCacheJob extends Job
{
    protected $key;
    
    public function __construct($key)
    {
        $this->key = $key;
    }

    public function handle()
    {
        // Fetch data (or whatever needs to be cached)
        $data = YourModel::all(); // Replace with your actual data fetching logic

        // Store the data in cache
        Cache::put($this->key, $data, now()->addMinutes(10));    }
}

Add Jitter:

জিটার এড করা হলে, প্রতিটি রিকোয়েস্টের TTL (Time To Live) কিছুটা ভিন্ন হবে, যার ফলে ক্যাশ এক্সপায়ারের সময়গুলি একটি ছোট সময়কালে ভাগ হয়ে যাবে। এর ফলে সিস্টেমটি প্রতিটি ব্যবহারকারী রিকোয়েস্টের জন্য একটি নতুন ক্যাশ এন্ট্রি তৈরি করবে।

public function getProductDetails($productId)
{
    // Get the current session ID
    $sessionId = session()->getId();

    // Define a base TTL of 60 minutes
    $ttl = 60 * 60; // 60 minutes

    // Add jitter (random between 1 and 5 minutes)
    $jitter = rand(60, 300); // Random time in seconds (1 to 5 minutes)
    
    // Adjust the TTL with jitter
    $adjustedTtl = $ttl + $jitter;

    // Generate a unique cache key based on user ID and product ID
    $cacheKey = "session_{$sessionId}:product_{$productId}";

    // Attempt to get the product from cache
    $product = Cache::remember($cacheKey, $adjustedTtl, function () use ($productId) {
        // Cache miss - Fetch from database
        return Product::find($productId);
    });

    return response()->json($product);
}

এটির একটি সমস্যা হচ্ছে ক্যাশ সাইজ বেড়ে যাবে। তাহলে, মোট ক্যাশ সাইজ হবে: 10 KB * 1M = 10 GB

এমন পরিস্থিতিতে আপনি কোনটি প্রেফার করেন?