نظرة سريعة على التخزين المؤقت في ريلز

من موسوعة حسوب

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

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

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

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

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

  • أسلوب تخزين الأجزاء (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

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

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

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

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

إعدادات التكوين

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

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

ملاحظة: بدلاً من ذلك، يمكنك الاتصال بـ ActionController :: Base.cache_store خارج كتلة التكوين.

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

ActiveSupport::Cache::Store

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

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

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

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

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

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

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

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

config.cache_store = MyCacheStore.new

ActiveSupport::Cache::MemoryStore

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

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

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

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

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

ActiveSupport::Cache::FileStore

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

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

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

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

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

ActiveSupport::Cache::MemCacheStore

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

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

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

: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 على أحد المتغيرات الخاصة بكل المفاتيح. يدعم Redis 4+ الإخلاء الأقل استخدامًا (allkeys-lfu) ، وهو اختيار افتراضي ممتاز. يجب على Redis 3 وأقدم استخدام الإخلاء الأقل استخدامًا (allkeys-lru).

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

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

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

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

gem 'redis'

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

gem 'hiredis'

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

وأخيرًا، أضف التكوين في الملف config / environments / * .bb:

<nowiki>config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }</nowiki>

أكثر تعقيدًا، قد يظهر متجر Redis cache store كالتالي:

cache_servers = %w(<nowiki>redis://cache-01:6379/0</nowiki> <nowiki>redis://cache-02:6379/0</nowiki>)

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 و Arrays من القيم كمفاتيح ذاكرة التخزين المؤقت.

<nowiki>#</nowiki> This is a legal cache key

Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])

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

دعم GET الشرطي

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

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

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

class ProductsController < ApplicationController

 def show

   @product = Product.find(params[:id])

   # If the request is stale according to the given timestamp and etag value

   # (i.e. it needs to be processed again) then execute this block

   if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)

     respond_to do |wants|

       # ... normal response processing

     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

بدلاً من تجزئة الخيارات، يمكنك أيضًا ببساطة المرور في النموذج. سيستخدم Rails التابعين 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|

       # ... normal response processing

     end

   end

 end

End

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

class ProductsController < ApplicationController

 # This will automatically send back a :not_modified if the request is fresh,

 # and will render the default template (product.*) if it's stale.

 def show

   @product = Product.find(params[:id])

   fresh_when last_modified: @product.published_at.utc, etag: @product

 end

End

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

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

باستخدام هذا المساعد ، تم تعيين رأس الصفحة  last_modified على Time.new (2011، 1، 1) .utc وتنتهي صلاحية رأس الصفحة إلى 100 عام.

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

class HomeController < ApplicationController

 def index

   http_cache_forever(public: true) do

     render

   end

 end

End

ETags القوية مقابل الضعيفة

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

ETAGS الضعيفة لديها W / الرائدة في تمييزها من ETags القوية.

W/"618bbc92e2d35ea1945008b42799b0e7"  Weak ETag

"618bbc92e2d35ea1945008b42799b0e7"  Strong ETag

بعكس ETag الضعيف، يشير ETag القوي إلى أن الاستجابة يجب أن تكون متماثلة تمامًا والبايت يطابق البايت. مفيد عند تنفيذ طلبات النطاق داخل ملف فيديو أو ملف PDF كبير. بعض شبكات CDN تدعم فقط الأتصالات القوية، مثل 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"

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

من الشائع أن ترغب في اختبار استراتيجية التخزين المؤقت للتطبيق الخاص بك في وضع التطوير. يوفر Rails مهمة dev:cache  لتبديل التخزين المؤقت بسهولة على فتح/ قفل.

$ bin/rails dev:cache

Development mode is now being cached.

$ bin/rails dev:cache

Development mode is no longer being cached.

المراجع

  • مقالة DHH حول انتهاء الصلاحية المستندة إلى المفتاح.
  • Ryan Bates' Railscast على ملخص التخزين المؤقت.

مصادر