تخطَّ إلى المحتوى

حين يجعل التخزين المؤقت تطبيق Laravel أبطأ

التخزين المؤقت أول ما يُلجأ إليه وآخر ما يُقاس. أربعة أنماط تضيف زمن استجابة بدل أن تزيله، والسؤال الجدير بالطرح قبل أي منها.

قراءة 4 دقيقة

كل تطبيق بطيء يحصل في النهاية على تخزين مؤقت. إنه التدخل صاحب أفضل حكاية: العمل غالٍ، فأدِّه مرة واحتفظ بالجواب. وكثيرًا ما ينجح، فالصفحة التي كانت تستغرق ثانيتين تستغرق الآن مئتي جزء من الألف.

وكثيرًا بما يكفي ليستحق الكتابة، لا ينجح. وهذه هي الأشكال التي يتخذها حين يذهب في الاتجاه الآخر.

استدعاء تخزين واحد لكل صف

النسخة الأشيع، والأصعب رؤيةً لأن كل قطعة منها على حدة صحيحة.

foreach ($orders as $order) {
    $rate = Cache::remember("fx.{$order->currency}", 3600, fn () =>
        $this->rates->for($order->currency)
    );
}

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

لم يفشل التخزين هنا. بل وُضع داخل حلقة، فحوّل عملية غالية واحدة إلى مئات العمليات الرخيصة ثم جمعها. خزّن المجموعة بدل العنصر، أو اقرأ المجموعة المتمايزة مرة واحدة قبل أن تبدأ الحلقة.

تخزين شيء أرخص من البحث عنه

انتقاء بمفتاح أساسي مفهرس على جدول دافئ أقل بكثير من جزء من الألف من الثانية. ورحلة Redis عبر الشبكة مقاربة، وأحيانًا أبطأ، وصرت الآن تملك نظامين حيث يكفي واحد - إضافةً إلى الإبطال.

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

التدافع

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

يوفّر Laravel الجواب:

$report = Cache::lock('report:monthly', 10)->block(5, function () {
    return Cache::remember('report:monthly', 3600, fn () => $this->build());
});

طلب واحد يعيد البناء؛ والبقية ينتظرون ثم يقرؤون ما كتبه. والبديل - والأفضل حيث يُقبل بعض القِدَم - ألا تدع المفتاح ينتهي تحت الحركة أصلًا: حدّثه من أمر مجدول ودع القرّاء يجدون شيئًا دائمًا.

إبطال يكلّف أكثر من القراءات

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

والفخ نفسه يأتي مع وسوم أوسع من اللازم. وسمٌ يغطي كل مفتاح لمستأجر مريحٌ حتى يضطر تفريغه إلى مسح فضاء مفاتيح كبير، وعندها يصير الإبطال المسار البطيء ويعمل عند كل كتابة.

السؤال الذي يأتي أولًا

ليس ماذا ينبغي أن نخزّن بل لماذا هذا بطيء.

التخزين أمام N+1 يجعل N+1 أرخص تنفيذًا، وسيظل موجودًا، وسيظل يعمل - الآن بنظام ثانٍ في اللعبة ومشكلة صحة ملحقة. والتخزين أمام فهرس مفقود وسيلة لعدم إضافة الفهرس. وليست أي من الحالتين غير مشروعة كحلّ مؤقت، لكن الاثنتين ينبغي أن تُكتبا كذلك، لأن الحل المؤقت الذي لم يكتبه أحد يصير معمارية في السنة التالية.

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

ماذا تقيس

ثلاثة أرقام، كلها رخيصة الحصول ونادرًا ما يُنظر إليها:

نسبة الإصابة لكل بادئة مفتاح. تحت الثمانين بالمئة وكلفة الإخفاق تأكل المكاسب. وتحت الخمسين أنت تدفع ثمن تخزين لا تملكه عمليًا.

استدعاءات التخزين لكل طلب. واحد تخزينٌ مؤقت. وأربعون حلقةٌ لم تلاحظها بعد.

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

غالبًا كان الاستعلام الكامن تحتها هو المشكلة من البداية، وN+1 هو شكلها المعتاد. وإن كان الأمر مسار الطلب كله لا استعلامًا واحدًا، فعمل الأداء يبدأ بقياس. أما التخزين المؤقت فيأتي لاحقًا، أو لا يأتي.

أسئلة ذات صلة

أليس Redis سريعًا بما يكفي ليصير هذا بلا معنى؟
قراءة Redis رحلة ذهاب وإياب عبر الشبكة: سريعة لا مجانية، والكلفة لكل استدعاء لا لكل صفحة. استبدل استعلامًا واحدًا بثماني مللي ثانية بأربعين قراءة تخزين بـ0.4 مللي ثانية وستكون قد أبطأت الصفحة بينما تقول كل رسومك البيانية إن التخزين سليم.
كيف أعرف أن تخزينًا مؤقتًا يستحق مكانه؟
قِس الصفحة معه وبدونه، على بيانات بشكل الإنتاج، وانظر إلى نسبة الإصابة. التخزين تحت الثمانين بالمئة تقريبًا يدفع كلفة الإخفاق كثيرًا بما يكفي لإلغاء المكاسب، وما تحت الخمسين يكلّفك صراحةً.
ما تدافع التخزين المؤقت؟
طلبات كثيرة تجد المفتاح نفسه منتهيًا في اللحظة نفسها فتعيد حسابه كلها دفعة واحدة. الاستعلام الغالي الذي خزّنته كي لا يعمل كثيرًا يعمل الآن مئة مرة في ثانية، تحت الحمل الذي جعله غاليًا.
هل تستحق وسوم التخزين المؤقت الاستخدام؟
هي أنظف طريقة لإبطال مجموعة مفاتيح وتحتاج مخزنًا يدعمها - Redis أو Memcached، لا ملفًا ولا قاعدة بيانات. والكلفة أن تفريغ وسم يمسح، فوسمٌ يغطي عددًا هائلًا من المفاتيح يجعل الإبطال هو العملية البطيئة بدل القراءة.

← العودة إلى كل المقالات

اتصل بنا+1 848 272 7583واتساب+90 850 308 5436البريدinfo@codefacture.comصفحة التواصل