الفرق بين المراجعتين لصفحة: «Rails/caching with rails»

من موسوعة حسوب
لا ملخص تعديل
طلا ملخص تعديل
 
(2 مراجعات متوسطة بواسطة نفس المستخدم غير معروضة)
سطر 224: سطر 224:
ومع ذلك، من المهم ملاحظة أن مخازن الاستعلام المؤقتة تُنشَأ في بداية الإجراء وتدمَّر في نهاية هذا الإجراء وبالتالي تستمر فقط لمدة الإجراء. إذا كنت ترغب في تخزين نتائج طلبات البحث بطريقة أكثر ثباتًا، فيمكنك استخدام [[Rails/caching with rails#.D8.A7.D9.84.D8.AA.D8.AE.D8.B2.D9.8A.D9.86 .D8.A7.D9.84.D9.85.D8.A4.D9.82.D8.AA .D9.85.D9.86.D8.AE.D9.81.D8.B6 .D8.A7.D9.84.D9.85.D8.B3.D8.AA.D9.88.D9.89|التخزين المؤقت ذي المستوى المنخفض]].
ومع ذلك، من المهم ملاحظة أن مخازن الاستعلام المؤقتة تُنشَأ في بداية الإجراء وتدمَّر في نهاية هذا الإجراء وبالتالي تستمر فقط لمدة الإجراء. إذا كنت ترغب في تخزين نتائج طلبات البحث بطريقة أكثر ثباتًا، فيمكنك استخدام [[Rails/caching with rails#.D8.A7.D9.84.D8.AA.D8.AE.D8.B2.D9.8A.D9.86 .D8.A7.D9.84.D9.85.D8.A4.D9.82.D8.AA .D9.85.D9.86.D8.AE.D9.81.D8.B6 .D8.A7.D9.84.D9.85.D8.B3.D8.AA.D9.88.D9.89|التخزين المؤقت ذي المستوى المنخفض]].


== مخازن ذاكرة التخزين المؤقت ==
== مخازن ذاكرة التخزين المؤقتة ==
توفر Rails متاجرًا مختلفة للبيانات المخزنة مؤقتًا (بخلاف التخزين المؤقت لـ SQL و الصفحة).
توفر ريلز مخازن مختلفة للبيانات المخزنة مؤقتًا (بخلاف التخزين المؤقت لاستعلامات SQL والصفحة).


=== إعدادات التكوين ===
=== إعدادات الضبط ===
يمكنك إعداد مخزن ذاكرة التخزين المؤقت الافتراضي للتطبيق من خلال تعيين خيار التكوين config.cache_store. يمكن تمرير معاملات أخرى كوسائط إلى مُنشئ مخزن التخزين المؤقت:
يمكنك إعداد مخزن ذاكرة تخزين مؤقتة افتراضي للتطبيق من خلال تعيين ضبط الإعداد <code>config.cache_store</code>. يمكن تمرير معاملات أخرى كوسائط إلى مُنشئ مخزن التخزين المؤقت:
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
config.cache_store = :memory_store, { size: 64.megabytes }
config.cache_store = :memory_store, { size: 64.megabytes }
</syntaxhighlight>
</syntaxhighlight>


ملاحظة: بدلاً من ذلك، يمكنك الاتصال بـ ActionController :: Base.cache_store خارج كتلة التكوين.
'''ملاحظة''': بدلًا من ذلك، يمكنك استدعاء <code>ActionController::Base.cache_store</code> خارج كتلة الضبط.


يمكنك الوصول إلى ذاكرة التخزين المؤقت عن طريق استدعاء Rails.cache.
يمكنك الوصول إلى ذاكرة التخزين المؤقتة عن طريق استدعاء <code>Rails.cache</code>.


=== ActiveSupport::Cache::Store ===
=== <code>ActiveSupport::Cache::Store</code> ===
يوفر هذا الفصل الأساس للتفاعل مع ذاكرة التخزين المؤقت في Rails. هذه هي فئة مجردة ولا يمكنك استخدامها من تلقاء نفسها. بدلا من ذلك يجب عليك استخدام تنفيذ ملموس للفئة مرتبطة بمحرك التخزين. تشحن Rails مع العديد من التطبيقات الموثقة أدناه.
يوفر هذا الصنف الأساس للتفاعل مع ذاكرة التخزين المؤقت في ريلز. هذا هو صنف مجرد (abstract class) ولا يمكنك استخدامه من تلقاء نفسه. بدلًا من ذلك يجب عليك استخدام تنفيذ متماسك للصنف المرتبط بمحرك التخزين. تُشحَن ريلز مع العديد من التطبيقات الموثقة أدناه.


التوابع الرئيسية للإستدعاء هي ?read ،write ،delete ،exist و fetch.
التوابع الرئيسية للإستدعاء هي <code>read</code>، و <code>write</code>، و <code>delete</code>، و <code>?exist</code> و <code>fetch</code>.


التابع fetch يأخذ كتلة ويرجع قيمة موجودة من ذاكرة التخزين المؤقت أو تقييم الكتلة وكتابة النتيجة إلى ذاكرة التخزين المؤقت في حالة عدم وجود قيمة.
التابع <code>fetch</code> يأخذ كتلة ويعيد قيمة موجودة من ذاكرة التخزين المؤقت أو تقييم الكتلة وكتابة النتيجة إلى ذاكرة التخزين المؤقت في حالة عدم وجود قيمة.


هناك بعض الخيارات الشائعة المستخدمة بواسطة جميع تطبيقات ذاكرة التخزين المؤقت. يمكن تمرير هذه إلى المنشئ أو التوابع المختلفة للتفاعل مع الإدخالات.
هناك بعض الخيارات الشائعة المستخدمة بواسطة جميع تطبيقات ذاكرة التخزين المؤقت. يمكن تمريرها إلى المنشئ أو التوابع المختلفة للتفاعل مع المدخلات.
* :namespace - يمكن استخدام هذا الخيار لإنشاء مساحة اسم داخل مخزن ذاكرة التخزين المؤقت. من المفيد بشكل خاص إذا كان التطبيق الخاص بك يشارك ذاكرة تخزين مؤقت مع تطبيقات أخرى.
*‎<code>:namespace</code> - يمكن استخدام هذا الخيار لإنشاء مجال اسم داخل مخزن ذاكرة التخزين المؤقتة. هذا الخيار  مفيد بشكل خاص إذا كان التطبيق الخاص بك يشارك ذاكرة تخزين مؤقت مع تطبيقات أخرى.
* :compress - مفعل افتراضيًا. يضغط إدخالات ذاكرة التخزين المؤقت بحيث يمكن تخزين المزيد من البيانات في نفس مساحة الذاكرة، مما يؤدي إلى عدد أقل من عمليات الإخلاء في الذاكرة المؤقتة ومعدلات ضرب أعلى.
*<code> :compress</code> - مفعل افتراضيًا. يضغط مدخلات ذاكرة التخزين المؤقتة بحيث يمكن تخزين المزيد من البيانات في نفس مساحة الذاكرة، مما يؤدي إلى عدد أقل من عمليات الإخلاء في الذاكرة المؤقتة ومعدل أعلى من الاعتماد واللجوء إلى الذاكرة المؤقتة (أي نسبة الطّلبات المُحتفَظ بإجاباتِها في الذاكرة المؤقتة تصبح أعلى).
* :compress_threshold - افتراضياً 1 كيلو بايت. يضغط إدخالات ذاكرة التخزين المؤقت الأكبر من هذا الحد، محدد بالبايت، مضغوطة.
*‎<code>:compress_threshold</code> - افتراضيًا 1 كيلو بايت. تضغط مدخلات ذاكرة التخزين المؤقتة الأكبر من هذا الحد، محدد بالبايت.
* :expires_in - يحدد هذا الخيار وقت انتهاء الصلاحية بالثواني لإدخال ذاكرة التخزين المؤقت عندما يزال تلقائيًا من ذاكرة التخزين المؤقت.
*‎<code>:expires_in</code> - يحدد هذا الخيار وقت انتهاء الصلاحية بالثواني لمدخلة في ذاكرة التخزين المؤقتة أي متى ستُزال تلقائيًا من ذاكرة التخزين المؤقت.
* :race_condition_ttl - يستخدم هذا الخيار مع الخيار :expires_in. وسوف تمنع ظروف السباق عندما تنتهي صلاحية إدخالات ذاكرة التخزين المؤقت من خلال منع عمليات متعددة من إعادة إنشاء نفس الإدخال في نفس الوقت (المعروف أيضًا باسم تأثير كومة الكلب). يحدد هذا الخيار عدد الثواني التي يمكن إعادة استخدامها في إدخال منتهي الصلاحية أثناء إعادة إنشاء قيمة جديدة. من الممارسات الجيدة تعيين هذه القيمة إذا كنت تستخدم الخيار: expires_in.
*‎<code>:race_condition_ttl</code> - يستخدم هذا الخيار مع الخيار <code>:expires_in</code>. وسوف يمنع حدوث حالات تسابق (race conditions) عندما تنتهي صلاحية مدخلات ذاكرة التخزين المؤقتة من خلال منع عمليات متعددة من إعادة إنشاء نفس المدخلات في نفس الوقت (المعروف أيضًا باسم تأثير تناثر محتوى الذاكرة المؤقتة [cache stampede]). يحدد هذا الخيار عدد الثواني التي يمكن فيها إعادة استخدام المدخلة منتهية الصلاحية أثناء إعادة توليد قيمة جديدة. من الممارسات الجيدة تعيين هذه القيمة إذا كنت تستخدم الخيار ‎<code>:expires_in</code>.


==== مخازن ذاكرة التخزين المؤقت المخصصة ====
==== مخازن ذاكرة التخزين المؤقتة المخصصة ====
يمكنك إنشاء مخزن مؤقت مخصص خاص بك ببساطة عن طريق توسيع ActiveSupport :: Cache :: Store وتنفيذ التوابع المناسبة. بهذه الطريقة، يمكنك تبديل أي عدد من تقنيات التخزين المؤقت في تطبيق Rails.
يمكنك إنشاء مخزن مؤقت مخصص خاص بك ببساطة عن طريق توسيع <code>ActiveSupport::Cache::Store</code> وتنفيذ التوابع المناسبة. بهذه الطريقة، يمكنك تبديل أي عدد من تقنيات التخزين المؤقت في تطبيق ريلز.


لاستخدام مخزن مؤقت مخصص، ما عليك سوى تعيين مخزن ذاكرة التخزين المؤقت على نسخة جديدة للفئة المخصصة.
لاستخدام مخزن مؤقت مخصص، ما عليك سوى تعيين مخزن ذاكرة التخزين المؤقت إلى نسخة جديدة للصنف المخصصة الخاص بك.
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
config.cache_store = MyCacheStore.new
config.cache_store = MyCacheStore.new
</syntaxhighlight>
</syntaxhighlight>


=== ActiveSupport::Cache::MemoryStore ===
=== <code>ActiveSupport::Cache::MemoryStore</code> ===
يحتفظ مخزن التخزين المؤقت هذه بالإدخالات في الذاكرة في نفس عملية Ruby. يحتوي مخزن التخزين المؤقت على حجم محدد يتم تحديده عن طريق إرسال الخيار :size إلى المُهيئ (القيمة الافتراضية هي 32 ميجابايت). عندما تتجاوز ذاكرة التخزين المؤقت الحجم المخصص، سيحدث تنظيف وستزال الإدخالات الأقل استخداماً مؤخراً.
يحتفظ مخزن التخزين المؤقت هذا بالمدخلات في الذاكرة في نفس عملية [[Ruby|روبي]]. يحتوي مخزن التخزين المؤقت على حجم محدد يتم تحديده عن طريق إرسال الخيار <code>:size</code> إلى المُهيئ (القيمة الافتراضية هي 32 ميجابايت). عندما تتجاوز ذاكرة التخزين المؤقت الحجم المخصص، ستجرى عملية تنظيف وستزال المدخلات الأقل استخدامًا مؤخرًا.
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
config.cache_store = :memory_store, { size: 64.megabytes }
config.cache_store = :memory_store, { size: 64.megabytes }
</syntaxhighlight>
</syntaxhighlight>


إذا كنت تقوم بتشغيل عدة عمليات الخادم Ruby on Rails (وهي الحالة إذا كنت تستخدم Phusion Passenger أو وضع clustered)، فلن تتمكن نُسخ عملية الخادم Rails من مشاركة بيانات ذاكرة التخزين المؤقت مع بعضها البعض. مخزن التخزين المؤقت هذا غير مناسب لعمليات نشر التطبيقات الكبيرة. ومع ذلك، يمكن أن تعمل بشكل جيد للمواقع الصغيرة منخفضة الحركة مع اثنين فقط من عمليات الخادم، بالإضافة إلى بيئات التطوير والاختبار.
إذا كنت تشغِّل عدة عمليات على خادم ريلز (وهي الحالة إذا كنت تستخدم الوضع Phusion Passenger أو الوضع puma clustered)، فلن تتمكن نُسَخ عملية خادم ريلز من مشاركة بيانات ذاكرة التخزين المؤقت مع بعضها بعضًا. مخزن التخزين المؤقت هذا غير مناسب لعمليات نشر التطبيقات الكبيرة. ومع ذلك، يمكن أن تعمل بشكل جيد للمواقع الصغيرة منخفضة الحركة مع اثنين فقط من عمليات الخادم، بالإضافة إلى بيئتي التطوير والاختبار.


تتكون مشاريع Rails جديدة لاستخدام هذا التطبيق في بيئة التطوير بشكل افتراضي.
تضبط مشاريع ريلز حديثة الإنشاء هذا التطبيق في بيئة التطوير بشكل افتراضي.


نظرًا لأن العمليات لن تشارك بيانات ذاكرة التخزين المؤقت عند استخدام :memory_store، لن يكون من الممكن قراءة ذاكرة التخزين المؤقت أو كتابتها أو انتهاء صلاحيتها يدويًا عبر وحدة التحكم Rails.
'''ملاحظة''': نظرًا لأن العمليات لن تشارك بيانات ذاكرة التخزين المؤقت عند استخدام <code>:memory_store</code>، لن يكون من الممكن قراءة ذاكرة التخزين المؤقت أو الكتابة عليها أو انهاء صلاحيتها يدويًا عبر وحدة تحكم ريلز.


=== ActiveSupport::Cache::FileStore ===
=== <code>ActiveSupport::Cache::FileStore</code> ===
يستخدم مخزن التخزين المؤقت هذا نظام الملفات لتخزين الإدخالات. يجب تحديد المسار إلى الدليل حيث تُخزن ملفات المخزن عند تهيئة ذاكرة التخزين المؤقت.
يستخدم مخزن التخزين المؤقت هذا نظام الملفات لتخزين المدخلات. يجب تحديد المسار إلى المجلد حيث تُخزن ملفات المخزن عند تهيئة ذاكرة التخزين المؤقت.
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
config.cache_store = :file_store, "/path/to/cache/directory"
config.cache_store = :file_store, "/path/to/cache/directory"
سطر 279: سطر 279:
باستخدام هذا المخزن المؤقت، يمكن لعمليات الخادم المتعددة على نفس المضيف مشاركة ذاكرة تخزين مؤقت. هذا المخزن المؤقت مناسب للمواقع ذات الحركة المرورية المنخفضة أو المتوسطة التي تعرض على مضيف واحد أو اثنين. يمكن لعمليات الخادم التي تعمل على مضيفين مختلفين مشاركة ذاكرة تخزين مؤقت باستخدام نظام ملفات مشترك، لكن هذا الإعداد غير مستحسن.
باستخدام هذا المخزن المؤقت، يمكن لعمليات الخادم المتعددة على نفس المضيف مشاركة ذاكرة تخزين مؤقت. هذا المخزن المؤقت مناسب للمواقع ذات الحركة المرورية المنخفضة أو المتوسطة التي تعرض على مضيف واحد أو اثنين. يمكن لعمليات الخادم التي تعمل على مضيفين مختلفين مشاركة ذاكرة تخزين مؤقت باستخدام نظام ملفات مشترك، لكن هذا الإعداد غير مستحسن.


عندما تنمو ذاكرة التخزين المؤقت حتى يمتلئ القرص، يُوصى بمسح الإدخالات القديمة بشكل دوري.
عندما تكبر ذاكرة التخزين المؤقت حتى يمتلئ القرص، يُوصى بمسح المدخلات القديمة بشكل دوري.


هذا هو تطبيق التخزين المؤقت الافتراضي (في "# {root} / tmp / cache /") إذا لم يتوفر config.cache_store صريح.
هذا هو تنفيذ التخزين المؤقت الافتراضي (في "#{root}/tmp/cache/") إذا لم يضبط <code>config.cache_store</code> بشكل صريح.


=== ActiveSupport::Cache::MemCacheStore ===
=== <code>ActiveSupport::Cache::MemCacheStore</code> ===
يستخدم مخزن التخزين المؤقت هذا خادم ذاكرة Danga's memcached لتوفير ذاكرة تخزين مركزية لتطبيقك. يستخدم Rails جوهرة dalli المجمعة بشكل افتراضي. هذا هو مخزن ذاكرة التخزين المؤقت الأكثر شيوعًا حاليًا لمواقع الإنتاج. يمكن استخدامه لتوفير مجموعة ذاكرة تخزين مؤقت مشتركة واحدة بأداء عالٍ جدًا وتكرار.
يستخدم مخزن التخزين المؤقت هذا الخادم <code>memcached</code> الذي يخص Danga لتوفير ذاكرة تخزين مركزية لتطبيقك. يستخدم ريلز الجوهرة dalli المحزَّمة بشكل افتراضي. هذا هو مخزن ذاكرة التخزين المؤقت الأكثر شيوعًا حاليًا لمواقع الإنتاج. يمكن استخدامه لتوفير مجموعة ذاكرة تخزين مؤقت مشتركة واحدة بأداء وتوافر عالٍ جدًا.


عند تهيئة ذاكرة التخزين المؤقت، تحتاج إلى تحديد العناوين لكافة خوادم memcached في نظام المجموعة الخاص بك. إذا لم تحدد أي منها، فستفترض أن memcached يعمل على localhost على المنفذ الافتراضي، ولكن هذا ليس إعدادًا مثاليًا للمواقع الكبيرة.
عند تهيئة ذاكرة التخزين المؤقتة، تحتاج إلى تحديد العناوين لكافة خوادم <code>memcached</code> في نظام المجموعة الخاص بك. إذا لم تحدد أي منها، فستفترض أن <code>memcached</code> يعمل على مضيف محلي (localhost) على المنفذ الافتراضي، ولكن هذا ليس إعدادًا مثاليًا للمواقع الكبيرة.


تقبل التوابع write وfetch في ذاكرة التخزين المؤقت هذين الخيارين الإضافيين اللذين يستفيدان من الميزات الخاصة بـ memcached. يمكنك تحديد : raw لإرسال قيمة مباشرة إلى الخادم بدون وجود تسلسل. يجب أن تكون القيمة عبارة عن سلسلة أو رقم. يمكنك استخدام العمليات المباشرة memcached مثل increment و decrement فقط على القيم الخام. يمكنك أيضًا تحديد
يقبل التابعين <code>write</code> و <code>fetch</code> في ذاكرة التخزين المؤقتة هذين الخيارين الإضافيين اللذين يستفيدان من الميزات الخاصة بخوادم <code>memcached</code>. يمكنك تحديد <code>:raw</code> لإرسال قيمة مباشرة إلى الخادم بدون وجود تسلسل. يجب أن تكون القيمة عبارة عن سلسلة أو رقم. يمكنك استخدام عمليات <code>memcached</code>  المباشرة مثل <code>increment</code> و <code>decrement</code> فقط على قيم صرفة (raw values). يمكنك أيضًا تحديد ‎<code><nowiki>:</nowiki>unless_exist</code> إذا كنت لا تريد من الخادم <code>memcached</code> استبدال مدخلة موجودة.<syntaxhighlight lang="ruby">
 
<nowiki>:</nowiki>unless_exist إذا كنت لا تريد memcached للكتابة فوق إدخال موجود.
<syntaxhighlight lang="ruby">
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
</syntaxhighlight>
</syntaxhighlight>


=== ActiveSupport::Cache::RedisCacheStore ===
=== <code>ActiveSupport::Cache::RedisCacheStore</code> ===
يستفيد مخزن التخزين المؤقت Redis من دعم Redis للإخراج التلقائي عندما يصل إلى الحد الأقصى للذاكرة، مما يسمح له بالتصرف بشكل كبير مثل خادم ذاكرة التخزين المؤقت Memcached.
يستفيد مخزن التخزين المؤقت Redis من دعم Redis للإخراج التلقائي عندما يصل إلى الحد الأقصى للذاكرة، مما يسمح له بالتصرف بشكل كبير مثل خادم ذاكرة التخزين المؤقت Memcached.


ملاحظة النشر: لا تنتهي صلاحية Redis للمفاتيح افتراضيًا، لذا يجب توخي الحذر لاستخدام خادم التخزين المؤقت Redis المخصص. لا تملأ خادم Redis المستمر مع بيانات ذاكرة التخزين المؤقت المتطايرة! اقرأ دليل إعداد خادم التخزين المؤقت Redis بالتفصيل.
'''ملاحظة للنشر''': لا تنتهي صلاحية Redis للمفاتيح افتراضيًا، لذا يجب توخي الحذر لاستخدام خادم التخزين المؤقت Redis المخصص. لا تملأ خادم Redis المستمر مع بيانات مؤقتة متطايرة! اقرأ [https://redis.io/topics/lru-cache دليل إعداد خادم التخزين المؤقت Redis] بالتفصيل.
 
بالنسبة لخادم Redis الخاص بالذاكرة المؤقتة فقط ، قم بتعيين maxmemory-policy على أحد المتغيرات الخاصة بكل المفاتيح. يدعم Redis 4+ الإخلاء الأقل استخدامًا (allkeys-lfu) ، وهو اختيار افتراضي ممتاز. يجب على Redis 3 وأقدم استخدام الإخلاء الأقل استخدامًا (allkeys-lru).


تعيين ذاكرة التخزين المؤقت للقراءة والكتابة مُهلة منخفضة نسبيًا. غالبًا ما يكون إعادة إنشاء قيمة مخزنة مؤقتًا أسرع من الانتظار لأكثر من ثانية لاستعادتها. تتوقف مهلة القراءة والكتابة افتراضيًا على ثانية واحدة ، ولكن قد تُعين أقل إذا كانت الشبكة منخفضة التأخير باستمرار.
بالنسبة لخادم Redis الخاص بالذاكرة المؤقتة فقط، قم بتعيين <code>maxmemory-policy</code> إلى أحد المتغيرات الخاصة بكل المفاتيح. يدعم الإصدار 4 وما بعده من Redis الإخلاء الأقل استخدامًا (<code>allkeys-lfu</code>) ، وهو اختيار افتراضي ممتاز. يجب في الإصدار 3 وما دونه استخدام الإخلاء الأقل استخدامًا (<code>allkeys-lru</code>).


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


ذاكرة التخزين المؤقت يقرأ ويكتب لا يثير استثناءات. هم فقط يرجعون التشعب nil، ويتصرفون كما لو لم يكن هناك شيء في ذاكرة التخزين المؤقت. لقياس ما إذا كانت ذاكرة التخزين المؤقت تصل إلى الاستثناءات، يمكنك تقديم error_handler للإبلاغ عن خدمة تجميع الاستثناءات. يجب أن يقبل ثلاث وسائط للكلمات الرئيسية: method، تابع تخزين ذاكرة التخزين المؤقت التي كانت تسمى في الأصل؛ returning، القيمة التي أُرجعت للمستخدم، عادة nil؛ و exception، الاستثناء الذي أُنقذ.
بشكل افتراضي، لن يحاول مخزن ذاكرة التخزين المؤقت إعادة الاتصال بـ Redis إذا فشل الاتصال أثناء الطلب. إذا واجهت قطع اتصال متكررة، فقد ترغب في تمكين محاولات إعادة الاتصال.


للبدء، أضف جوهرة redis إلى Gemfile:
لا ترمي ذاكرة التخزين المؤقت يقرأ ويكتب استثناءات مطلقًا. هي تعيد فقط القيمة <code>nil</code> بدلًا من رمي الاستثناء، وتتصرف كما لو لم يكن هناك شيء في ذاكرة التخزين المؤقتة. لقياس ما إذا كانت ذاكرة التخزين المؤقتة ترمي استثناءً أم لا، يمكنك توفير التابع <code>error_handler</code> للإبلاغ عن خدمة تجميع الاستثناءات. يجب أن يقبل ذلك التابع ثلاث وسائط مسماة هي:
* <code>method</code>: تابع تخزين ذاكرة التخزين المؤقتة الذي استدعي في الأصل؛
* <code>returning</code>: القيمة التي أعيدت للمستخدم، وتكون عادةً <code>nil</code>؛
* <code>exception</code>: الاستثناء الذي أُنقذ.
للبدء، أضف الجوهرة redis إلى الملف Gemfile:
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
gem 'redis'
gem 'redis'
</syntaxhighlight>
</syntaxhighlight>


يمكنك تمكين الدعم لمكتبة الاتصال أسرع hiredis بواسطة الإضافة، إضافة غلاف روبي إلى Gemfile:
يمكنك تمكين الدعم لمكتبة الاتصال [https://github.com/redis/hiredis hiredis] الأسرع بواسطة إضافة مغلف روبي الخاص بها إلى الملف Gemfile الخاص بك:
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
gem 'hiredis'
gem 'hiredis'
</syntaxhighlight>
</syntaxhighlight>


سيتطلب مخزن التخزين المؤقت Redis تلقائيًا استخدام hiredis واستخدامه إذا كان متاحًا. هناك حاجة إلى مزيد من التكوين.
سيطلب مخزن التخزين المؤقت Redis المكتبة hiredis تلقائيًّا ويستخدمها إذا كان متاحًا. هناك حاجة إلى مزيد من الضبط.


وأخيرًا، أضف التكوين في الملف config / environments / * .bb:
وأخيرًا، أضف الضبط في الملف config/environments/*.bb:
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
<nowiki>config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }</nowiki>
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
</syntaxhighlight>
</syntaxhighlight>


أكثر تعقيدًا، قد يظهر متجر Redis cache store كالتالي:
أكثر تعقيدًا، قد يظهر مخزن Redis الإنتاجي للتخزين المؤقت كالتالي:
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
cache_servers = %w(<nowiki>redis://cache-01:6379/0</nowiki> <nowiki>redis://cache-02:6379/0</nowiki>)
cache_servers = %w(redis://cache-01:6379/0 redis://cache-02:6379/0)
 
config.cache_store = :redis_cache_store, { url: cache_servers,
config.cache_store = :redis_cache_store, { url: cache_servers,
 
 connect_timeout: 30,  # Defaults to 20 seconds
  connect_timeout: 30, # Defaults to 20 seconds
 
  read_timeout:   0.2, # Defaults to 1 second
 read_timeout:    0.2, # Defaults to 1 second
  write_timeout:   0.2, # Defaults to 1 second
 
 write_timeout:   0.2, # Defaults to 1 second
  error_handler: -> (method:, returning:, exception:) {
 
    # Report errors to Sentry as warnings
 error_handler: -> (method:, returning:, exception:) {
    Raven.capture_exception exception, level: 'warning',
 
      tags: { method: method, returning: returning }
   # Report errors to Sentry as warnings
  }
 
   Raven.capture_exception exception, level: 'warning',
 
     tags: { method: method, returning: returning }
 
 }
 
}
}
</syntaxhighlight>
</syntaxhighlight>


=== ActiveSupport::Cache::NullStore ===
=== <code>ActiveSupport::Cache::NullStore</code> ===
من المفترض استخدام تطبيق مخزن ذاكرة التخزين المؤقت هذا فقط في بيئات التطوير أو الاختبار ولا يخزن أي شيء أبدًا. قد يكون هذا مفيدًا جدًا في التطوير عندما يكون لديك رمز يتفاعل مباشرة مع Rails.cache ولكن قد يتداخل التخزين المؤقت مع إمكانية رؤية نتائج تغييرات التعليمات البرمجية. باستخدام هذا المخزن المؤقت، ستؤدي جميع عمليات fetch و read إلى فقدانها.
من المفترض استخدام تنفيذ مخزن ذاكرة التخزين المؤقت هذا فقط في بيئات التطوير أو الاختبار ولا يخزن أي شيء أبدًا. قد يكون هذا مفيدًا جدًا في التطوير عندما يكون لديك شيفرة تتفاعل مباشرةً مع <code>Rails.cache</code> ولكن قد يتداخل التخزين المؤقت مع إمكانية رؤية نتائج تغييرات التعليمات البرمجية. باستخدام هذا المخزن المؤقت، ستؤدي جميع عمليات <code>fetch</code> و <code>read</code> إلى فقدانها.
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
config.cache_store = :null_store
config.cache_store = :null_store
سطر 357: سطر 348:


== مفاتيح ذاكرة التخزين المؤقت ==
== مفاتيح ذاكرة التخزين المؤقت ==
يمكن أن تكون المفاتيح المستخدمة في ذاكرة التخزين المؤقت أي كائن يستجيب إلى cache_key أو to_param. يمكنك تنفيذ التابع cache_key في الفئات الخاصه بك إذا كنت تحتاج إلى إنشاء مفاتيح مخصصة. سيعمل Active Record على إنشاء مفاتيح استنادًا إلى اسم الفئة ومعرف السجل.
يمكن أن تكون المفاتيح المستخدمة في ذاكرة التخزين المؤقتة أي كائن يستجيب إلى <code>cache_key</code> أو <code>to_param</code>. يمكنك تنفيذ التابع <code>cache_key</code> في الأصناف الخاصه بك إذا كنت تحتاج إلى إنشاء مفاتيح مخصصة. سيعمل [[Rails/active record|Active Record]] على إنشاء مفاتيح استنادًا إلى اسم الصنف ومعرف السجل.


يمكنك استخدام Hash و Arrays من القيم كمفاتيح ذاكرة التخزين المؤقت.
يمكنك استخدام جدول Hash ومصفوفات من القيم كمفاتيح ذاكرة التخزين المؤقت.
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
<nowiki>#</nowiki> This is a legal cache key
# هذا مفتاح ذاكرة مؤقتة صالح
 
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])
</syntaxhighlight>
</syntaxhighlight>


لن تكون المفاتيح التي تستخدمها على Rails.cache هي نفسها المفاتيح المستخدمة فعليًا مع محرك التخزين. يمكن تعديلها باستخدام مساحة اسمية أو تعديلها لتلائم القيود الخلفية للتكنولوجيا. هذا يعني، على سبيل المثال، أنه لا يمكنك حفظ القيم باستخدام Rails.cache ثم حاول سحبها باستخدام جوهرة dalli. ومع ذلك، لا داعي للقلق أيضًا بشأن تجاوز حد حجم memcached أو انتهاك قواعد تركيب الجملة.
لن تكون المفاتيح التي تستخدمها مع <code>Rails.cache</code> هي نفسها المفاتيح المستخدمة فعليًا مع محرك التخزين. يمكن تعديلها باستخدام مجال اسم أو تعديلها لتلائم القيود الخلفية للتقنية. هذا يعني (technology backend constraints). على سبيل المثال، لا يمكنك حفظ القيم باستخدام <code>Rails.cache</code> وبالتالي تحاول سحبها باستخدام الجوهرة dalli. ومع ذلك، لا داعي للقلق أيضًا بشأن تجاوز حد حجم <code>memcached</code> أو انتهاك قواعد تركيب الصياغة.


== دعم GET الشرطي ==
== دعم GET الشرطي ==
تعتبر GETs المشروطة إحدى ميزات مواصفات HTTP التي توفر طريقة لخوادم الويب لإخبار المستعرضات بأن الاستجابة لطلب GET لم يتغير منذ آخر طلب ويمكن سحبها بأمان من ذاكرة التخزين المؤقت للمتصفح.
تعتبر طلبيات GET المشروطة إحدى ميزات مواصفات HTTP التي توفر طريقة لخوادم الويب لإخبار المستعرضات بأن الاستجابة لطلب GET لم يتغير منذ آخر طلب ويمكن سحبها بأمان من ذاكرة التخزين المؤقت للمتصفح.


وهي تعمل باستخدام رأس الصفحة HTTP_IF_NONE_MATCH و HTTP_IF_MODIFIED_SINCE لتمرير كل من معرف محتوى فريد وطابع زمني لآخر مرة تغير فيها المحتوى. إذا قدم المتصفح طلبًا حيث يتطابق معرّف المحتوى (الإصدار) أو آخر تعديل منذ الطابع الزمني مع إصدار الخادم، يحتاج الخادم فقط إلى إرسال رد فارغ بحالة لم تُعدل.
وهي تعمل باستخدام الترويسة <code>HTTP_IF_NONE_MATCH</code> والترويسة <code>HTTP_IF_MODIFIED_SINCE</code> لتمرير كل من معرف محتوى فريد وطابع زمني لآخر مرة تغير فيها المحتوى. إذا قدم المتصفح طلبًا حيث يطابق معرّف المحتوى (الإصدار) أو آخر تعديل منذ الطابع الزمني مع إصدار الخادم، يحتاج الخادم فقط إلى إرسال رد فارغ بحالة لم تُعدل.


يتحمل الخادم (الخاص بنا) مسؤولية البحث عن آخر طابع زمني معدّل و رأس if-none-match وتحديد ما إذا كان سيرسل الاستجابة الكاملة أم لا. مع دعم get الشرطية في Rails هذه مهمة سهلة جدًا:
يتحمل الخادم (الخاص بنا) مسؤولية البحث عن آخر طابع زمني معدّل وتحدد الترويسة <code>if-none-match</code> ما إذا كان سيرسل الاستجابة الكاملة أم لا. مع توافر دعم GET الشرطية في ريلز، هذه مهمة سهلة جدًا:
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
class ProductsController < ApplicationController
class ProductsController < ApplicationController
 
 def show
  def show
 
    @product = Product.find(params[:id])
   @product = Product.find(params[:id])
 
    # etag إن أصبح الطلب قديمًا وفقًا للطابع الزمني وقيمة
   # If the request is stale according to the given timestamp and etag value
    # أي يحتاج الطلب لمعالجته مجددًّا)، فستنفَّذ هذه الكتلة)
 
    if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)
   # (i.e. it needs to be processed again) then execute this block
      respond_to do |wants|
 
        # ... معالجة الطلب بشكل طبيعي
   if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)
      end
 
    end
     respond_to do |wants|
 
    # إن كان الطلب حديثًا (أي لم يتغير)، فلن تحتاج إلى فعل أي شيء
       # ... normal response processing
    # تتحقق عملية التصيير الافتراضية لهذا باستعمال المعاملات المستعملة
 
    # :not_modified وسيرسل تلقائيًا stale? في الاستدعاء السابق إلى
     end
    # هذا كل ما في الأمر. انتهينا
 
  end
   end
end
 
   # If the request is fresh (i.e. it's not modified) then you don't need to do
 
   # anything. The default render checks for this using the parameters
 
   # used in the previous call to stale? and will automatically send a
 
   # :not_modified. So that's it, you're done.
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


بدلاً من تجزئة الخيارات، يمكنك أيضًا ببساطة المرور في النموذج. سيستخدم Rails التابعين updated_at و cache_key_with_version لإعداد last_modified و etag:
بدلًا من [[Ruby/Hash|جدول Hash]] من الخيارات، يمكنك أيضًا ببساطة تمرير نموذج. سيستخدم ريلز التابعين <code>updated_at</code> و <code>cache_key_with_version</code> لإعداد <code>last_modified</code> و <code>etag</code>:
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
class ProductsController < ApplicationController
class ProductsController < ApplicationController
 
  def show
 def show
    @product = Product.find(params[:id])
 
   @product = Product.find(params[:id])
    if stale?(@product)
 
      respond_to do |wants|
   if stale?(@product)
        # ... معالجة الطلب بشكل طبيعي
 
      end
     respond_to do |wants|
    end
 
  end
       # ... normal response processing
end
 
     end
 
   end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


إذا لم تكن لديك أية معالجة خاصة للرد وكنت تستخدم آلية التقديم الافتراضية (بمعنى أنك لا تستخدم response_to أو call لتقديم نفسك)، فحينئذٍ يكون لديك مساعد سهل في وضع fresh_when:
إذا لم تكن لديك أية معالجة خاصة للرد وكنت تستخدم آلية التنفيذ الافتراضية (بمعنى أنك لا تستخدم <code>response_to</code> أو تستدعي <code>render</code> بنفسك)، فحينئذٍ يكون لديك مساعد سهل الاستعمال هو <code>fresh_when</code>:
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
class ProductsController < ApplicationController
class ProductsController < ApplicationController
 
 # This will automatically send back a :not_modified if the request is fresh,
  # تلقائيًاإن كان الطلب حديثًا :not_modified هذا سيعيد إرسال
 
  # إن كان الطلب قديمًا (product.*) وسيصيّر القالب الافتراضي
 # and will render the default template (product.*) if it's stale.
 
  def show
 def show
    @product = Product.find(params[:id])
 
    fresh_when last_modified: @product.published_at.utc, etag: @product
   @product = Product.find(params[:id])
  end
 
end
   fresh_when last_modified: @product.published_at.utc, etag: @product
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


في بعض الأحيان نريد تخزين الاستجابة مؤقتًا، على سبيل المثال صفحة ثابتة، لا تنتهي صلاحيتها أبدًا. ولتحقيق ذلك، يستخدم http_cache_forever المساعد ومن خلال إجراء ذلك، سيعمل المتصفح والبروكسي على تخزينها مؤقتًا إلى أجل غير مسمى.
في بعض الأحيان، نريد تخزين الاستجابة مؤقتًا، على سبيل المثال صفحة ثابتة، لا تنتهي صلاحيتها أبدًا. ولتحقيق ذلك، استخدم المساعد <code>http_cache_forever</code>؛ ومن خلال إجراء ذلك، سيعمل المتصفح والوسائط (proxies) على تخزينها مؤقتًا إلى أجل غير مسمى.


ستكون الاستجابات المخزنة مؤقتاً بشكل افتراضي خاصة، و تخزن مؤقتًا فقط على متصفح الويب للمستخدم. للسماح للبروكسي تخزين الاستجابة مؤقتًا، يمكنك تعيين public: true للإشارة إلى أنه يمكنهم عرض الاستجابة المخزنة مؤقتًا لجميع المستخدمين.
ستكون الاستجابات المخزنة مؤقتًا بشكل افتراضي خاصة، و تخزن مؤقتًا فقط على متصفح الويب للمستخدم. للسماح للوسائط (proxies) تخزين الاستجابة مؤقتًا، يمكنك تعيين <code>public: true</code> للإشارة إلى أنه يمكنهم عرض الاستجابة المخزنة مؤقتًا لجميع المستخدمين.


باستخدام هذا المساعد ، تم تعيين رأس الصفحة  last_modified على Time.new (2011، 1، 1) .utc وتنتهي صلاحية رأس الصفحة إلى 100 عام.
باستخدام هذا المساعد، تُعيَّن الترويسة <code>last_modified</code> إلى <code>Time.new(2011، 1، 1).utc</code> وتضبط الترويسة <code>expires</code> إلى 100 عام.


ملاحظة: استخدم هذا التابع بعناية نظرًا لأن المتصفح / البروكسي لن يتمكن من إبطال الاستجابة المخزنة مؤقتاً ما لم تُمحى ذاكرة التخزين المؤقت للمتصفح.
'''تحذير''': استخدم هذا التابع بعناية نظرًا لأن المتصفح / الوسيط لن يتمكن من إبطال الاستجابة المخزنة مؤقتًا ما لم تُمحى ذاكرة التخزين المؤقتة للمتصفح.
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
class HomeController < ApplicationController
class HomeController < ApplicationController
 
  def index
 def index
    http_cache_forever(public: true) do
 
      render
   http_cache_forever(public: true) do
    end
 
  end
     render
end
 
   end
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


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


ETAGS الضعيفة لديها W / الرائدة في تمييزها من ETags القوية.
تملك الترويسات ETag الضعيفة البادئة W/‎ لتمييزها عن الترويسات ETag القوية.
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="text">
W/"618bbc92e2d35ea1945008b42799b0e7" → Weak ETag
W/"618bbc92e2d35ea1945008b42799b0e7" → Weak ETag
"618bbc92e2d35ea1945008b42799b0e7" → Strong ETag
"618bbc92e2d35ea1945008b42799b0e7" → Strong ETag
</syntaxhighlight>
</syntaxhighlight>


بعكس ETag الضعيف، يشير ETag القوي إلى أن الاستجابة يجب أن تكون متماثلة تمامًا والبايت يطابق البايت. مفيد عند تنفيذ طلبات النطاق داخل ملف فيديو أو ملف PDF كبير. بعض شبكات CDN تدعم فقط الأتصالات القوية، مثل Akamai. إذا كنت في حاجة ماسة إلى إنشاء ETag قوي، فيمكن القيام بذلك على النحو التالي.
بعكس الترويسات ETag الضعيف، تشير الترويسات ETag القوية إلى أن الاستجابة يجب أن تكون متماثلة تمامًا ومتطابقة بايتًا ببايت. هذا مفيد عند تنفيذ طلبات النطاق داخل ملف فيديو أو ملف PDF كبير. بعض شبكات توززيع المحتوى (CDN) تدعم فقط الترويسات ETag القوية، مثل Akamai. إذا كنت في حاجة ماسة إلى إنشاء ترويسات ETag قوية، فيمكن القيام بذلك على النحو التالي:
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
class ProductsController < ApplicationController
class ProductsController < ApplicationController
 
  def show
 def show
    @product = Product.find(params[:id])
 
    fresh_when last_modified: @product.published_at.utc, strong_etag: @product
   @product = Product.find(params[:id])
  end
 
end
   fresh_when last_modified: @product.published_at.utc, strong_etag: @product
 
 end
 
End
</syntaxhighlight>
</syntaxhighlight>


يمكنك أيضًا تعيين ETag القوي مباشرة على الاستجابة.
يمكنك أيضًا تعيين ترويسات ETag القوية مباشرة على الاستجابة:
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"
response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"
سطر 504: سطر 457:


== التخزين المؤقت في التطوير ==
== التخزين المؤقت في التطوير ==
من الشائع أن ترغب في اختبار استراتيجية التخزين المؤقت للتطبيق الخاص بك في وضع التطوير. يوفر Rails مهمة dev:cache  لتبديل التخزين المؤقت بسهولة على فتح/ قفل.
من الشائع أن ترغب في اختبار استراتيجية التخزين المؤقت للتطبيق الخاص بك في وضع التطوير. يوفر ريلز المهمة <code>dev:cache</code> التي تخص rake لتبديل التخزين المؤقت بسهولة بين التشغيل والإيقاف (on/off).
<syntaxhighlight lang="ruby">
<syntaxhighlight lang="ruby">
$ bin/rails dev:cache
$ bin/rails dev:cache
Development mode is now being cached.
Development mode is now being cached.
$ bin/rails dev:cache
$ bin/rails dev:cache
Development mode is no longer being cached.


Development mode is no longer being cached.
</syntaxhighlight>
</syntaxhighlight>


== المراجع ==
== المراجع ==
* مقالة DHH حول انتهاء الصلاحية المستندة إلى المفتاح.
* [https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works مقالة DHH حول انتهاء الصلاحية المستندة إلى المفتاح.]
* Ryan Bates' Railscast على ملخص التخزين المؤقت.
* [http://railscasts.com/episodes/387-cache-digests Ryan Bates: القيم العددية المخلصة للتخزين المؤقت.]


== مصادر ==
== مصادر ==

المراجعة الحالية بتاريخ 08:19، 25 مارس 2019

هذا الدليل عبارة عن مدخل يطلعك على كيفية تسريع تطبيق ريلز عبر التخزين المؤقت.

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

غالبًا ما يكون التخزين المؤقت الطريقة الأكثر فاعلية لتعزيز أداء التطبيق. من خلال التخزين المؤقت، يمكن لمواقع الويب التي تعمل على خادم واحد مع قاعدة بيانات واحدة الحفاظ على تحميل الآلاف من المستخدمين المتزامنين.

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

بعد قراءة هذا الدليل، ستتعلم:

  • أسلوب تخزين الأجزاء (fragment caching) وأسلوب تخزين الدمية الروسية (Russian doll caching)
  • كيفية إدارة اعتماديات التخزين المؤقت.
  • مخازن ذاكرة التخزين المؤقت البديلة.
  • دعم GET الشرطي.

التخزين المؤقت الأساسي

هذه مقدمة إلى ثلاثة أنواع من تقنيات التخزين المؤقت: تخزين الصفحة (page)، وتخزين الإجراء (action)، وتخزين الأجزاء (fragment).

بشكل افتراضي يوفر ريلز أسلوب تخزين الأجزاء. لاستخدام أسلوب التخزين المؤقت للصفحة وللإجراء، ستحتاج إلى إضافة actionpack-page_caching و actionpack-action_caching إلى الملف Gemfile الخاص بك.

بشكل افتراضي، يُفعَّل التخزين المؤقت فقط في بيئة الإنتاج الخاصة بك. للتلاعب بالتخزين المؤقت محليًا، ستحتاج إلى تمكين التخزين المؤقت في البيئة المحلية عن طريق تعيين الضبط config.action_controller.perform_caching إلى القيمة true في الملف config/environments/*.rb:

config.action_controller.perform_caching = true

ملاحظة: سيؤثر تغيير قيمة config.action_controller.perform_caching فقط على التخزين المؤقت الذي يوفره مكون "عنصر وحدة التحكم في الإجراء". على سبيل المثال، لن يؤثر ذلك على التخزين المؤقت ذي المستوى المنخفض، الذي سنتناوله أدناه.

التخزين المؤقت للصفحة

التخزين المؤقت للصفحة هو آلية توفرها ريلز تسمح لطلب صفحة وُلِّدَت مسبقًا بواسطة خادم الويب (أي Apache أو NGINX) دون الحاجة إلى المرور عبر مكدس ريلز بأكمله. في حين أن هذا لا يمكن تطبيقه بسرعة فائقة على كل حالة (مثل الصفحات التي تحتاج إلى المصادقة). أيضًا، لأن خادم الويب يخدم ملفًا مباشرةً من نظام الملفات، إذ ستحتاج إلى تنفيذ انتهاء صلاحية ذاكرة التخزين المؤقت (cache expiration).

تنبيه: تمت إزالة التخزين المؤقت للصفحة من الإصدار 4 من ريلز. راجع الجوهرة actionpack-page_caching.

التخزين المؤقت للإجراء

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

تنبيه: تمت إزالة التخزين المؤقت للإجراء من الإصدار 4 من ريلز. راجع الجوهرة actionpack-action_caching. انظر نظرة عامة على انتهاء صلاحية ذاكرة التخزين المؤقت المستندة إلى مفتاح DHH للاطلاع على الطريقة المفضلة للتخزين حاليًا.

التخزين المؤقت للأجزاء

عادة ما تقوم تطبيقات الويب الديناميكية بإنشاء صفحات تحتوي على مجموعة متنوعة من المكونات التي لا تحتوي جميعها على نفس خصائص التخزين المؤقت. عندما تحتاج إلى تخزين أجزاء مختلفة من الصفحة مؤقتًا بشكل تنتهي صلاحيتها بشكل منفصل، يمكنك استخدام أسلوب تخزين الأجزاء مؤقتًا (Fragment Caching).

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

على سبيل المثال، إذا كنت تريد تخزين كل منتج معروض في صفحة مؤقتًا، يمكنك استخدام هذه الشيفرة:

<% @products.each do |product| %>
  <% cache product do %>
    <%= render product %>
  <% end %>
<% end %>

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

views/products/1-201505056193031061005000/bea67108094918eeba42cd4a6e786901

الرقم في المنتصف هو معرف المنتج product_id متبوعًا بقيمة الطابع الزمني في الخاصية updated_at لسجل المنتج. تستخدم ريلز قيمة الطابع الزمني للتأكد من أنها لا تخدم البيانات القديمة. إذا تغيرت قيمة updated_at، ستولد قيمة مفتاح جديد. ثم ستكتب ريلز ذاكرة تخزين مؤقت جديدة إلى هذا المفتاح، ولن تُستخدم ذاكرة التخزين المؤقتة القديمة المكتوبة على المفتاح القديم مرة أخرى. وهذا ما يسمى انتهاء الصلاحية المستندة إلى المفتاح (key-based expiration).

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

تنبيه: ستحذف مخازن ذاكرة التخزين المؤقت مثل Memcached ملفات التخزين المؤقت القديمة تلقائيًا.

إذا كنت تريد تخزين جزء مؤقتًا تحت شروط معينة، فيمكنك استخدام cache_if أو cache_unless:

<% cache_if admin?, product do %>
  <%= render product %>
<% end %>

التخزين المؤقت للمجموعة

يمكن للمساعد render أيضًا تخزين قوالب فردية مصيَّرة لمجموعة ما. في أحد الأمثلة السابقة التي تطبق each، يمكن تصيير جميع قوالب التخزين المؤقت دفعة واحدة بدلًا من تصييرها واحدة تلو الأخرى. يجرى ذلك بتمرير cached: true عند تصيير المجموعة:

<%= render partial: 'products/product', collection: @products, cached: true %>

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

أسلوب الدمية الروسية للتخزين المؤقت

قد ترغب في دمج أجزاء مخزنة مؤقتًا داخل أجزاء أخرى مخزنة مؤقتًا. وهذا ما يسمى أسلوب الدمية الروسية للتخزين المؤقت (Russian doll caching).

ميزة التخزين المؤقت بأسلوب الدمية الروسية (Russian doll) هي أنه في حالة تحديث منتج واحد، يمكن إعادة استخدام جميع الأجزاء الداخلية الأخرى عند إعادة توليد الجزء الخارجي.

كما هو موضح في القسم السابق، ستنتهي صلاحية الملف المخزن مؤقتًا إذا تغيرت القيمة updated_at للسجل الذي يعتمد عليه الملف المخزن مؤقتًا بشكل مباشر. ومع ذلك، لن تنتهي صلاحية أي ذاكرة تخزين مؤقتة تكون الجزئية (fragment) متشعبة فيها.

على سبيل المثال، خذ الواجهة التالية:

<% cache product do %>
  <%= render product.games %>
<% end %>

وهو ما يؤدي بدوره إلى عرض هذه الواجهة:

<% cache game do %>
  <%= render game %>
<% end %>

إذا تغيرت أية خاصية في game، فستُعيَّن القيمة updated_at إلى الوقت الحالي، وبالتالي تنتهي صلاحية ذاكرة التخزين المؤقت. ومع ذلك، نظرًا لأنه لن تتغيير updated_at لكائن المنتج، فلن تنتهي صلاحية ذاكرة التخزين المؤقت هذه وسيقدم تطبيقك بيانات قديمة. لإصلاح ذلك، نقوم بربط النماذج مع التابع touch:

class Product < ApplicationRecord
  has_many :games
end
 
class Game < ApplicationRecord
  belongs_to :product, touch: true
end

مع ضبط touch إلى القيمة true، فإن أي إجراء يغير updated_at لسجل game سيغيره أيضًا للمنتج product المقترن، وبذلك تنتهي صلاحية ذاكرة التخزين المؤقت.

التخزين المؤقت لأجزاء مشتركة

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

render(partial: 'hotels/hotel', collection: @hotels, cached: true)

سيُحمل ملفًا اسمه hotels/hotel.erb.

خيار آخر هو تضمين اسم الملف الكامل للجزئية المراد تصييرها:

render(partial: 'hotels/hotel.html.erb', collection: @hotels, cached: true)

سيُحمل ملفًا باسم hotels/hotel.html.erb في أي نوع من أنواع ملفات mime، على سبيل المثال، يمكنك تضمين هذه الجزئية في ملف JavaScript.

إدارة الاعتماديات

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

الاعتماديات الضمنية

يمكن اشتقاق معظم اعتماديات القالب من الإستدعاءات render في القالب نفسه. فيما يلي بعض الأمثلة على الإستدعاءات التي تجعل ActionView::Digestor تعرف كيفية فك ترميز:

render partial: "comments/comment", collection: commentable.comments
render "comments/comments"
render 'comments/comments'
render('comments/comments')
 
render "header" translates to render("comments/header")
 
render(@topic)         translates to render("topics/topic")
render(topics)         translates to render("topics/topic")
render(message.topics) translates to render("topics/topic")

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

render @project.documents.where(published: true)

إلى

render partial: "documents/document", collection: @project.documents.where(published: true)

اعتماديات صريحة

في بعض الأحيان سيكون لديك اعتماديات قالب لا يمكن اشتقاقه على الإطلاق. هذا هو الحال عادة عندما يحدث التصيير في المساعدين. إليك مثال على ذلك:

<%= render_sortable_todolists @project.todolists %>

ستحتاج إلى استخدام تنسيق تعليق خاص لاستدعاءها:

<%# Template Dependency: todolists/todolist %>
<%= render_sortable_todolists @project.todolists %>

في بعض الحالات، مثل إعداد توريث جدول فردي، قد يكون لديك مجموعة من الاعتماديات الصريحة. بدلًا من كتابة كل قالب، يمكنك استخدام محرف بديل (wildcard) لمطابقة أي قالب في المجلد:

<%# Template Dependency: events/* %>
<%= render_categorizable_events @person.events %>

بالنسبة إلى التخزين المؤقت للمجموعة، إذا لم يبدأ قالب الجزئية باستدعاء ذاكرة تخزين مؤقتة نظيفة (clean cache)، فلا يزال بإمكانك الاستفادة من التخزين المؤقت للمجموعة عن طريق إضافة تنسيق خاص للتعليق في أي مكان في النموذج، مثل:

<%# Template Collection: notification %>
<% my_helper_that_calls_cache(some_arg, notification) do %>
  <%= notification.name %>
<% end %>

الاعتماديات الخارجية

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

<%# Helper Dependency Updated: Jul 28, 2015 at 7pm %>
<%= some_helper_method(person) %>

التخزين المؤقت منخفض المستوى

تحتاج في بعض الأحيان إلى تخزين قيمة استعلام أو نتيجة معينة بدلاً من تخزين أجزاء من ذاكرة التخزين المؤقت. تعمل آلية التخزين المؤقت في ريلز بشكل رائع لتخزين أي نوع من المعلومات.

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

خذ بعين الاعتبار المثال التالي. يحتوي التطبيق على النموذج Product مع نسخة تابع تبحث عن سعر المنتج على موقع ويب منافس. ستكون البيانات التي تعاد بواسطة هذا التابع مثالية للتخزين المؤقت على مستوى منخفض:

class Product < ApplicationRecord
  def competing_price
    Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do
      Competitor::API.find_price(id)
    end
  end
end

ملاحظة: لاحظ أننا في هذا المثال استخدمنا التابع cache_key_with_version، لذا فإن مفتاح ذاكرة التخزين المؤقت الناتج سيكون شيئًا مثل المنتجات products/233-20140225082222765838000/competing_price. يولد cache_key_with_version سلسلة نصية استنادًا إلى معرف النموذج والخاصية updated_at. هذا هو عُرفٌ شائع وله فائدة إبطال ذاكرة التخزين المؤقت كلما تحدَّث المنتج. بشكل عام، عند استخدام التخزين المؤقت ذي المستوى المنخفض لمعلومات على مستوى النسخة، تحتاج إلى توليد مفتاح ذاكرة تخزين مؤقت.

تخزين تعليمات SQL مؤقتًا

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

فمثلًا:

class ProductsController < ApplicationController
 
  def index
    # نفذ تعليمة
    @products = Product.all
 
    ...
 
    # أعد تنفيذ نفس التعليمة مجددًا
    @products = Product.all
  end
 
end

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

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

مخازن ذاكرة التخزين المؤقتة

توفر ريلز مخازن مختلفة للبيانات المخزنة مؤقتًا (بخلاف التخزين المؤقت لاستعلامات SQL والصفحة).

إعدادات الضبط

يمكنك إعداد مخزن ذاكرة تخزين مؤقتة افتراضي للتطبيق من خلال تعيين ضبط الإعداد config.cache_store. يمكن تمرير معاملات أخرى كوسائط إلى مُنشئ مخزن التخزين المؤقت:

config.cache_store = :memory_store, { size: 64.megabytes }

ملاحظة: بدلًا من ذلك، يمكنك استدعاء ActionController::Base.cache_store خارج كتلة الضبط.

يمكنك الوصول إلى ذاكرة التخزين المؤقتة عن طريق استدعاء Rails.cache.

ActiveSupport::Cache::Store

يوفر هذا الصنف الأساس للتفاعل مع ذاكرة التخزين المؤقت في ريلز. هذا هو صنف مجرد (abstract class) ولا يمكنك استخدامه من تلقاء نفسه. بدلًا من ذلك يجب عليك استخدام تنفيذ متماسك للصنف المرتبط بمحرك التخزين. تُشحَن ريلز مع العديد من التطبيقات الموثقة أدناه.

التوابع الرئيسية للإستدعاء هي read، و write، و delete، و ?exist و fetch.

التابع fetch يأخذ كتلة ويعيد قيمة موجودة من ذاكرة التخزين المؤقت أو تقييم الكتلة وكتابة النتيجة إلى ذاكرة التخزين المؤقت في حالة عدم وجود قيمة.

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

  • :namespace - يمكن استخدام هذا الخيار لإنشاء مجال اسم داخل مخزن ذاكرة التخزين المؤقتة. هذا الخيار مفيد بشكل خاص إذا كان التطبيق الخاص بك يشارك ذاكرة تخزين مؤقت مع تطبيقات أخرى.
  • :compress - مفعل افتراضيًا. يضغط مدخلات ذاكرة التخزين المؤقتة بحيث يمكن تخزين المزيد من البيانات في نفس مساحة الذاكرة، مما يؤدي إلى عدد أقل من عمليات الإخلاء في الذاكرة المؤقتة ومعدل أعلى من الاعتماد واللجوء إلى الذاكرة المؤقتة (أي نسبة الطّلبات المُحتفَظ بإجاباتِها في الذاكرة المؤقتة تصبح أعلى).
  • :compress_threshold - افتراضيًا 1 كيلو بايت. تضغط مدخلات ذاكرة التخزين المؤقتة الأكبر من هذا الحد، محدد بالبايت.
  • :expires_in - يحدد هذا الخيار وقت انتهاء الصلاحية بالثواني لمدخلة في ذاكرة التخزين المؤقتة أي متى ستُزال تلقائيًا من ذاكرة التخزين المؤقت.
  • :race_condition_ttl - يستخدم هذا الخيار مع الخيار :expires_in. وسوف يمنع حدوث حالات تسابق (race conditions) عندما تنتهي صلاحية مدخلات ذاكرة التخزين المؤقتة من خلال منع عمليات متعددة من إعادة إنشاء نفس المدخلات في نفس الوقت (المعروف أيضًا باسم تأثير تناثر محتوى الذاكرة المؤقتة [cache stampede]). يحدد هذا الخيار عدد الثواني التي يمكن فيها إعادة استخدام المدخلة منتهية الصلاحية أثناء إعادة توليد قيمة جديدة. من الممارسات الجيدة تعيين هذه القيمة إذا كنت تستخدم الخيار ‎:expires_in.

مخازن ذاكرة التخزين المؤقتة المخصصة

يمكنك إنشاء مخزن مؤقت مخصص خاص بك ببساطة عن طريق توسيع ActiveSupport::Cache::Store وتنفيذ التوابع المناسبة. بهذه الطريقة، يمكنك تبديل أي عدد من تقنيات التخزين المؤقت في تطبيق ريلز.

لاستخدام مخزن مؤقت مخصص، ما عليك سوى تعيين مخزن ذاكرة التخزين المؤقت إلى نسخة جديدة للصنف المخصصة الخاص بك.

config.cache_store = MyCacheStore.new

ActiveSupport::Cache::MemoryStore

يحتفظ مخزن التخزين المؤقت هذا بالمدخلات في الذاكرة في نفس عملية روبي. يحتوي مخزن التخزين المؤقت على حجم محدد يتم تحديده عن طريق إرسال الخيار :size إلى المُهيئ (القيمة الافتراضية هي 32 ميجابايت). عندما تتجاوز ذاكرة التخزين المؤقت الحجم المخصص، ستجرى عملية تنظيف وستزال المدخلات الأقل استخدامًا مؤخرًا.

config.cache_store = :memory_store, { size: 64.megabytes }

إذا كنت تشغِّل عدة عمليات على خادم ريلز (وهي الحالة إذا كنت تستخدم الوضع Phusion Passenger أو الوضع puma clustered)، فلن تتمكن نُسَخ عملية خادم ريلز من مشاركة بيانات ذاكرة التخزين المؤقت مع بعضها بعضًا. مخزن التخزين المؤقت هذا غير مناسب لعمليات نشر التطبيقات الكبيرة. ومع ذلك، يمكن أن تعمل بشكل جيد للمواقع الصغيرة منخفضة الحركة مع اثنين فقط من عمليات الخادم، بالإضافة إلى بيئتي التطوير والاختبار.

تضبط مشاريع ريلز حديثة الإنشاء هذا التطبيق في بيئة التطوير بشكل افتراضي.

ملاحظة: نظرًا لأن العمليات لن تشارك بيانات ذاكرة التخزين المؤقت عند استخدام :memory_store، لن يكون من الممكن قراءة ذاكرة التخزين المؤقت أو الكتابة عليها أو انهاء صلاحيتها يدويًا عبر وحدة تحكم ريلز.

ActiveSupport::Cache::FileStore

يستخدم مخزن التخزين المؤقت هذا نظام الملفات لتخزين المدخلات. يجب تحديد المسار إلى المجلد حيث تُخزن ملفات المخزن عند تهيئة ذاكرة التخزين المؤقت.

config.cache_store = :file_store, "/path/to/cache/directory"

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

عندما تكبر ذاكرة التخزين المؤقت حتى يمتلئ القرص، يُوصى بمسح المدخلات القديمة بشكل دوري.

هذا هو تنفيذ التخزين المؤقت الافتراضي (في "‎#{root}/tmp/cache/‎") إذا لم يضبط config.cache_store بشكل صريح.

ActiveSupport::Cache::MemCacheStore

يستخدم مخزن التخزين المؤقت هذا الخادم memcached الذي يخص Danga لتوفير ذاكرة تخزين مركزية لتطبيقك. يستخدم ريلز الجوهرة dalli المحزَّمة بشكل افتراضي. هذا هو مخزن ذاكرة التخزين المؤقت الأكثر شيوعًا حاليًا لمواقع الإنتاج. يمكن استخدامه لتوفير مجموعة ذاكرة تخزين مؤقت مشتركة واحدة بأداء وتوافر عالٍ جدًا.

عند تهيئة ذاكرة التخزين المؤقتة، تحتاج إلى تحديد العناوين لكافة خوادم memcached في نظام المجموعة الخاص بك. إذا لم تحدد أي منها، فستفترض أن memcached يعمل على مضيف محلي (localhost) على المنفذ الافتراضي، ولكن هذا ليس إعدادًا مثاليًا للمواقع الكبيرة.

يقبل التابعين write و fetch في ذاكرة التخزين المؤقتة هذين الخيارين الإضافيين اللذين يستفيدان من الميزات الخاصة بخوادم memcached. يمكنك تحديد :raw لإرسال قيمة مباشرة إلى الخادم بدون وجود تسلسل. يجب أن تكون القيمة عبارة عن سلسلة أو رقم. يمكنك استخدام عمليات memcached المباشرة مثل increment و decrement فقط على قيم صرفة (raw values). يمكنك أيضًا تحديد ‎:unless_exist إذا كنت لا تريد من الخادم memcached استبدال مدخلة موجودة.

config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"

ActiveSupport::Cache::RedisCacheStore

يستفيد مخزن التخزين المؤقت Redis من دعم Redis للإخراج التلقائي عندما يصل إلى الحد الأقصى للذاكرة، مما يسمح له بالتصرف بشكل كبير مثل خادم ذاكرة التخزين المؤقت Memcached.

ملاحظة للنشر: لا تنتهي صلاحية Redis للمفاتيح افتراضيًا، لذا يجب توخي الحذر لاستخدام خادم التخزين المؤقت Redis المخصص. لا تملأ خادم Redis المستمر مع بيانات مؤقتة متطايرة! اقرأ دليل إعداد خادم التخزين المؤقت Redis بالتفصيل.

بالنسبة لخادم Redis الخاص بالذاكرة المؤقتة فقط، قم بتعيين maxmemory-policy إلى أحد المتغيرات الخاصة بكل المفاتيح. يدعم الإصدار 4 وما بعده من Redis الإخلاء الأقل استخدامًا (allkeys-lfu) ، وهو اختيار افتراضي ممتاز. يجب في الإصدار 3 وما دونه استخدام الإخلاء الأقل استخدامًا (allkeys-lru).

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

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

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

  • method: تابع تخزين ذاكرة التخزين المؤقتة الذي استدعي في الأصل؛
  • returning: القيمة التي أعيدت للمستخدم، وتكون عادةً nil؛
  • exception: الاستثناء الذي أُنقذ.

للبدء، أضف الجوهرة redis إلى الملف Gemfile:

gem 'redis'

يمكنك تمكين الدعم لمكتبة الاتصال hiredis الأسرع بواسطة إضافة مغلف روبي الخاص بها إلى الملف Gemfile الخاص بك:

gem 'hiredis'

سيطلب مخزن التخزين المؤقت Redis المكتبة hiredis تلقائيًّا ويستخدمها إذا كان متاحًا. هناك حاجة إلى مزيد من الضبط.

وأخيرًا، أضف الضبط في الملف config/environments/*.bb:

config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }

أكثر تعقيدًا، قد يظهر مخزن Redis الإنتاجي للتخزين المؤقت كالتالي:

cache_servers = %w(redis://cache-01:6379/0 redis://cache-02:6379/0)
config.cache_store = :redis_cache_store, { url: cache_servers,
 
  connect_timeout: 30,  # Defaults to 20 seconds
  read_timeout:    0.2, # Defaults to 1 second
  write_timeout:   0.2, # Defaults to 1 second
 
  error_handler: -> (method:, returning:, exception:) {
    # Report errors to Sentry as warnings
    Raven.capture_exception exception, level: 'warning',
      tags: { method: method, returning: returning }
  }
}

ActiveSupport::Cache::NullStore

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

config.cache_store = :null_store

مفاتيح ذاكرة التخزين المؤقت

يمكن أن تكون المفاتيح المستخدمة في ذاكرة التخزين المؤقتة أي كائن يستجيب إلى cache_key أو to_param. يمكنك تنفيذ التابع cache_key في الأصناف الخاصه بك إذا كنت تحتاج إلى إنشاء مفاتيح مخصصة. سيعمل Active Record على إنشاء مفاتيح استنادًا إلى اسم الصنف ومعرف السجل.

يمكنك استخدام جدول Hash ومصفوفات من القيم كمفاتيح ذاكرة التخزين المؤقت.

# هذا مفتاح ذاكرة مؤقتة صالح
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])

لن تكون المفاتيح التي تستخدمها مع Rails.cache هي نفسها المفاتيح المستخدمة فعليًا مع محرك التخزين. يمكن تعديلها باستخدام مجال اسم أو تعديلها لتلائم القيود الخلفية للتقنية. هذا يعني (technology backend constraints). على سبيل المثال، لا يمكنك حفظ القيم باستخدام Rails.cache وبالتالي تحاول سحبها باستخدام الجوهرة dalli. ومع ذلك، لا داعي للقلق أيضًا بشأن تجاوز حد حجم memcached أو انتهاك قواعد تركيب الصياغة.

دعم GET الشرطي

تعتبر طلبيات GET المشروطة إحدى ميزات مواصفات HTTP التي توفر طريقة لخوادم الويب لإخبار المستعرضات بأن الاستجابة لطلب GET لم يتغير منذ آخر طلب ويمكن سحبها بأمان من ذاكرة التخزين المؤقت للمتصفح.

وهي تعمل باستخدام الترويسة HTTP_IF_NONE_MATCH والترويسة HTTP_IF_MODIFIED_SINCE لتمرير كل من معرف محتوى فريد وطابع زمني لآخر مرة تغير فيها المحتوى. إذا قدم المتصفح طلبًا حيث يطابق معرّف المحتوى (الإصدار) أو آخر تعديل منذ الطابع الزمني مع إصدار الخادم، يحتاج الخادم فقط إلى إرسال رد فارغ بحالة لم تُعدل.

يتحمل الخادم (الخاص بنا) مسؤولية البحث عن آخر طابع زمني معدّل وتحدد الترويسة if-none-match ما إذا كان سيرسل الاستجابة الكاملة أم لا. مع توافر دعم GET الشرطية في ريلز، هذه مهمة سهلة جدًا:

class ProductsController < ApplicationController
 
  def show
    @product = Product.find(params[:id])
 
    # etag إن أصبح الطلب قديمًا وفقًا للطابع الزمني وقيمة
    # أي يحتاج الطلب لمعالجته مجددًّا)، فستنفَّذ هذه الكتلة)
    if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)
      respond_to do |wants|
        # ... معالجة الطلب بشكل طبيعي
      end
    end
 
    # إن كان الطلب حديثًا (أي لم يتغير)، فلن تحتاج إلى فعل أي شيء
    # تتحقق عملية التصيير الافتراضية لهذا باستعمال المعاملات المستعملة
    # :not_modified وسيرسل تلقائيًا stale? في الاستدعاء السابق إلى
    # هذا كل ما في الأمر. انتهينا
  end
end

بدلًا من جدول Hash من الخيارات، يمكنك أيضًا ببساطة تمرير نموذج. سيستخدم ريلز التابعين updated_at و cache_key_with_version لإعداد last_modified و etag:

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
 
    if stale?(@product)
      respond_to do |wants|
        # ... معالجة الطلب بشكل طبيعي
      end
    end
  end
end

إذا لم تكن لديك أية معالجة خاصة للرد وكنت تستخدم آلية التنفيذ الافتراضية (بمعنى أنك لا تستخدم response_to أو تستدعي render بنفسك)، فحينئذٍ يكون لديك مساعد سهل الاستعمال هو fresh_when:

class ProductsController < ApplicationController
 
  # تلقائيًاإن كان الطلب حديثًا :not_modified هذا سيعيد إرسال
  # إن كان الطلب قديمًا (product.*) وسيصيّر القالب الافتراضي
 
  def show
    @product = Product.find(params[:id])
    fresh_when last_modified: @product.published_at.utc, etag: @product
  end
end

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

ستكون الاستجابات المخزنة مؤقتًا بشكل افتراضي خاصة، و تخزن مؤقتًا فقط على متصفح الويب للمستخدم. للسماح للوسائط (proxies) تخزين الاستجابة مؤقتًا، يمكنك تعيين public: true للإشارة إلى أنه يمكنهم عرض الاستجابة المخزنة مؤقتًا لجميع المستخدمين.

باستخدام هذا المساعد، تُعيَّن الترويسة last_modified إلى Time.new(2011، 1، 1).utc وتضبط الترويسة expires إلى 100 عام.

تحذير: استخدم هذا التابع بعناية نظرًا لأن المتصفح / الوسيط لن يتمكن من إبطال الاستجابة المخزنة مؤقتًا ما لم تُمحى ذاكرة التخزين المؤقتة للمتصفح.

class HomeController < ApplicationController
  def index
    http_cache_forever(public: true) do
      render
    end
  end
end

الترويسات ETag القوية والضعيفة

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

تملك الترويسات ETag الضعيفة البادئة W/‎ لتمييزها عن الترويسات ETag القوية.

W/"618bbc92e2d35ea1945008b42799b0e7" → Weak ETag
"618bbc92e2d35ea1945008b42799b0e7" → Strong ETag

بعكس الترويسات ETag الضعيف، تشير الترويسات ETag القوية إلى أن الاستجابة يجب أن تكون متماثلة تمامًا ومتطابقة بايتًا ببايت. هذا مفيد عند تنفيذ طلبات النطاق داخل ملف فيديو أو ملف PDF كبير. بعض شبكات توززيع المحتوى (CDN) تدعم فقط الترويسات ETag القوية، مثل Akamai. إذا كنت في حاجة ماسة إلى إنشاء ترويسات ETag قوية، فيمكن القيام بذلك على النحو التالي:

class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
    fresh_when last_modified: @product.published_at.utc, strong_etag: @product
  end
end

يمكنك أيضًا تعيين ترويسات ETag القوية مباشرة على الاستجابة:

response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"

التخزين المؤقت في التطوير

من الشائع أن ترغب في اختبار استراتيجية التخزين المؤقت للتطبيق الخاص بك في وضع التطوير. يوفر ريلز المهمة dev:cache التي تخص rake لتبديل التخزين المؤقت بسهولة بين التشغيل والإيقاف (on/off).

$ bin/rails dev:cache
Development mode is now being cached.
$ bin/rails dev:cache
Development mode is no longer being cached.

المراجع

مصادر