البرمجة متوسط 10 دقيقة

كيفية تخزين استعلامات قاعدة البيانات المكلفة مؤقتاً في Laravel

لكل رحلة إلى قاعدة البيانات تكلفة. للبيانات التي نادراً ما تتغير — الإعدادات، الفئات، المنشورات الشائعة، قوائم التنقل — تشغيل نفس الاستعلام في كل طلب هدر محض. طبقة التخزين المؤقت تتيح لك دفع تلك التكلفة مرة واحدة وتقديم النتيجة من الذاكرة في كل طلب لاحق.

واجهة التخزين المؤقت في Laravel مستقلة عن برنامج التشغيل: نفس الكود يعمل سواء استخدمت الملفات أو قاعدة البيانات أو Redis. هذا الدليل يغطي الأنماط العملية: متى تخزن، كيف تسمّي المفاتيح، وكيف تبقي التخزين المؤقت متسقاً عند تغيير البيانات.

الخطوات

  1. 1

    تحديد ما يستحق التخزين المؤقت

    ليس كل استعلام يجب تخزينه مؤقتاً. المكان المثالي هو البيانات التي تُقرأ كثيراً ونادراً ما تتغير. مثل إعدادات الموقع، فئات التنقل، محتوى الصفحة الرئيسية المميز، أو الإحصاءات المجمّعة. تجنب تخزين أي شيء يجب أن يكون فورياً — حالات الطلبات، مستويات المخزون، أو بيانات الجلسة لكل مستخدم.

    اعتبر أيضاً تكلفة الاستعلام. بحث بسيط في users عبر المفتاح الأساسي سريع أصلاً. أما تقرير بـ multi-join مع GROUP BY على ملايين الصفوف فهو مرشح ممتاز.

  2. 2

    تخزين استعلام باستخدام Cache::remember

    Cache::remember() هو المحرك الأساسي. يتحقق من التخزين المؤقت أولاً؛ إن وُجد المفتاح يعيد القيمة المخزنة، وإلا ينفّذ الـ closure ويخزن النتيجة ثم يعيدها. مدة الصلاحية TTL بالثواني.

    php
    use Illuminate\Support\Facades\Cache;
    use Illuminate\Support\Facades\DB;
    
    $categories = Cache::remember('categories.all', 3600, function () {
        return DB::table('categories')
            ->where('active', true)
            ->orderBy('name')
            ->get();
    });
    
    // Eloquent version
    $settings = Cache::remember('settings.all', 86400, fn () =>
        Setting::all()->keyBy('key')
    );
  3. 3

    اختيار مفاتيح تخزين واضحة

    مفتاح التخزين هو الشيء الوحيد الذي يفصل بين قيمتين مخزنتين. التصادمات صامتة وصعبة التصحيح. اتبع نمط تسمية ثابتاً: اسم.مؤهل أو اسم:id:مؤهل. أدرج دائماً العوامل التي تؤثر على النتيجة — اللغة، دور المستخدم، الفلاتر المطبقة.

    php
    // Bad — collides across locales
    Cache::remember('posts', 3600, fn () => Post::published()->get());
    
    // Good — locale-scoped
    $locale = app()->getLocale();
    Cache::remember("posts.featured.{$locale}", 3600, fn () =>
        Post::published()->featured()->with('author')->get()
    );
    
    // Good — user-scoped
    Cache::remember("user.{$user->id}.dashboard", 600, fn () =>
        Dashboard::buildFor($user)
    );
  4. 4

    استخدام الـ cache() helper للاختصار

    الـ helper العام cache() هو اختصار للعمليات الشائعة. هو مطابق للـ facade داخلياً — استخدم أيهما تفضل، لكن كن ثابتاً داخل المشروع.

    php
    // Remember
    cache()->remember('key', 3600, fn () => expensiveQuery());
    
    // Store forever
    cache()->rememberForever('key', fn () => expensiveQuery());
    
    // Get with default
    $value = cache('key', 'default');
    
    // Put
    cache(['key' => $value], now()->addHour());
    
    // Forget
    cache()->forget('key');
  5. 5

    إبطال التخزين عبر Model Observers

    التخزين المؤقت القديم أسوأ من عدمه. عند حفظ نموذج أو حذفه، يجب تنظيف إدخالات التخزين المرتبطة به. الـ model observers هي أنظف مكان لذلك — المنطق يعيش بجانب النموذج وينطلق تلقائياً في كل مسار كتابة.

    php
    <?php
    
    namespace App\Observers;
    
    use App\Models\Category;
    use Illuminate\Support\Facades\Cache;
    
    class CategoryObserver
    {
        public function saved(Category $category): void
        {
            Cache::forget('categories.all');
            Cache::forget("categories.all.en");
            Cache::forget("categories.all.ar");
        }
    
        public function deleted(Category $category): void
        {
            $this->saved($category);
        }
    }
    
    // Register in AppServiceProvider or via #[ObservedBy] attribute (Laravel 10+)
    // Category::observe(CategoryObserver::class);
  6. 6

    اختيار برنامج تشغيل التخزين المناسب

    Laravel يدعم عدة برامج تشغيل. File مناسب للتطبيقات الصغيرة والتطوير المحلي — لا خدمات إضافية. Database مفيد حين لا تستطيع تثبيت Redis لكنك تحتاج التخزين يبقى بعد إعادة التشغيل. Redis هو المعيار في الإنتاج: قراءات دون ميلي ثانية، عمليات ذرية، وإدارة TTL مدمجة. اضبط برنامج التشغيل في .env.

    bash
    # .env
    CACHE_STORE=redis     # redis | database | file | array
    REDIS_HOST=127.0.0.1
    REDIS_PORT=6379
    
    # For database driver, create the table first:
    php artisan cache:table
    php artisan migrate
  7. 7

    مسح التخزين المؤقت عند النشر

    نشر كود جديد قد يغير ما يجب أن يعيده الاستعلام المخزن — أعمدة جديدة، علاقات مختلفة، منطق أعمال محدّث. امسح تخزين التطبيق دائماً كجزء من سكريبت النشر حتى يعيد الطلب الأول بعد النشر بناء قيم جديدة.

    bash
    # In your deploy script (after git pull)
    php artisan cache:clear
    php artisan optimize
    php artisan view:cache
  8. 8

    قياس الأداء قبل التخزين وبعده

    لا تخزّن بشكل أعمى. استخدم microtime() للتأكد أنك فعلاً توفر وقتاً، أو فعّل لوحة استعلامات Telescope لاكتشاف مشاكل N+1 والاستعلامات البطيئة قبل اللجوء للتخزين. التخزين يخفي المشاكل — التحليل يجدها.

    php
    // Quick benchmark in a controller or tinker
    $start = microtime(true);
    
    $result = Cache::remember('expensive.query', 600, fn () =>
        HeavyReport::generate()
    );
    
    $ms = round((microtime(true) - $start) * 1000, 2);
    logger("Cache query took {$ms}ms");
    
    // Or use DB::listen() to log all queries during a request
    DB::listen(fn($q) => logger($q->sql, ['time' => $q->time . 'ms']));

نصائح ومحاذير

  • استخدم <code>Cache::tags()</code> (مع Redis/Memcached فقط) لتجميع المفاتيح المتعلقة ببعضها وإفراغها دفعة واحدة — أنظف بكثير من إدراج كل مفتاح يدوياً في الـ observer.
  • اضبط مدة صلاحية TTL واقعية. "للأبد" نادراً ما يكون صحيحاً للبيانات المرتبطة بقاعدة بيانات. حتى ساعة من القِدم مقبولة لمعظم بيانات واجهة المستخدم.
  • إذا رمى الـ closure استثناءً، لن يخزن <code>Cache::remember()</code> النتيجة — ينتشر الخطأ. هذا السلوك الصحيح؛ لا تبتلع الاستثناءات داخل closures التخزين أبداً.
  • للمفاتيح شديدة الطلب (آلاف الزيارات في الثانية)، فكر في نهج طبقتين: متغير ثابت داخل الطلب يُفحص قبل الوصول إلى مخزن التخزين.
  • <code>php artisan tinker</code> مع <code>Cache::get('key')</code> هي أسرع طريقة لفحص ما هو مخزن فعلاً والتأكد من نجاح الإبطال.

خاتمة

التخزين المؤقت الفعّال يقوم على اختيار البيانات الصحيحة، وتسميتها بدقة، وإبقائها حديثة. اضبط هذه الأمور الثلاثة وستتمكن من خفض الحمل على قاعدة البيانات بشكل كبير دون إدخال أخطاء خفية.

#Laravel #Cache #Performance
العودة إلى جميع الأدلة

هل تحتاج مساعدة في مشروعك؟

احجز استشارة مجانية لمدة 30 دقيقة لمناقشة تحدياتك التقنية واستكشاف الحلول معًا.