কম্পিউটার টিউটোরিয়াল

রেইলস ভিউ ক্যাশিং মাস্টার করুন: একটি কম্প্রিহেনসিভ গাইড

ক্যাশিং হলো একটি জেনারেল টার্ম যার অর্থ হলো কোনো কোডের রেজাল্ট স্টোর করা যাতে আমরা পরে দ্রুত সেটা রিট্রিভ করতে পারি। এটি আমাদের অনুমতি দেয়, উদাহরণস্বরূপ, ডেটাবেসকে বারবার হিট করা থেকে বাঁচতে যাতে ডেটা যা বিরলেই পরিবর্তিত হয় সেটা পেতে। যদিও জেনারেল কনসেপ্ট সব টাইপের ক্যাশিং-এর জন্য একই, রেইলস আমাদের বিভিন্ন সাহায্য প্রদান করে নির্ভর করে আমরা কী ক্যাশ করার চেষ্টা করছি।

রেইলস ডেভেলপারদের জন্য, ক্যাশিং-এর সাধারণ ফর্মগুলো অন্তর্ভুক্ত করে মেমোইজেশন, লো-লেভেল ক্যাশিং (এই ক্যাশিং সিরিজের পূর্ববর্তী অংশগুলোতে কভার করা হয়েছে), এবং ভিউ ক্যাশিং, যা আমরা এখানে কভার করব।

রুবি অন রেইলস কীভাবে ভিউ রেন্ডার করে

প্রথমে, কিছু কিছুটা কনফিউজিং টার্মিনোলজি কভার করি। রেইলস কমিউনিটি যা "ভিউ" বলে সেটা হলো ফাইলগুলো যেগুলো আপনার app/views ডিরেক্টরিতে থাকে। সাধারণত, এগুলো .html.erb ফাইল, যদিও অন্যান্য অপশনও রয়েছে (অর্থাৎ, প্লেন .html, .js.erb, বা ফাইল যেগুলো অন্যান্য প্রিপ্রসেসর ব্যবহার করে, যেমন slim এবং haml)। অনেক অন্যান্য ওয়েব ফ্রেমওয়ার্কে, এই ফাইলগুলোকে "টেমপ্লেট" বলা হয়, যা আমার মতে তাদের ব্যবহার আরও ভালোভাবে বর্ণনা করে।

যখন একটি রেইলস অ্যাপ্লিকেশন একটি GET রিকুয়েস্ট পায়, এটি একটি নির্দিষ্ট কন্ট্রোলার অ্যাকশনে রাউট হয়, উদাহরণস্বরূপ, UsersController#index। অ্যাকশনটি তারপর দায়িত্বवान ডেটাবেস থেকে যেকোনো প্রয়োজনীয় ইনফরমেশন গेदার করে এবং সেটা ভিউ/টেমপ্লেট ফাইল রেন্ডার করতে পাস করে। এই পয়েন্টে, আমরা "ভিউ লেয়ার"-এ এন্টার করছি।

সাধারণত, আপনার ভিউ (বা টেমপ্লেট) হবে হার্ডকোডেড HTML মার্কআপ এবং ডাইনামিক রুবি কোডের মিশ্রণ:

#app/views/users/index.html.erb <div class='user-list'> <% @users.each do |user| %> <div class='user-name'><%= user.name %></div> <% end %> </div> 

ফাইলে রুবি কোড এক্সিকিউট করতে হয় ভিউ রেন্ডার করার জন্য (erb-এর কেসে যেটা <% %> ট্যাগে থাকে)। পেজ ১০০ বার রিফ্রেশ করলে, @users.each... ১০০ বার এক্সিকিউট হবে। একই কথা যেকোনো ইনক্লুডেড প্যারশিয়ালের জন্যও সত্য; প্রসেসরকে প্যারশিয়াল html.erb ফাইল লোড করতে হয়, এর ভেতরের সকল রুবি কোড এক্সিকিউট করতে হয়, এবং রেজাল্টগুলো একটি সিংগল HTML ফাইলে কমবাইন করতে হয় রিকোয়েস্টারের কাছে পাঠাতে।

স্লো ভিউ-এর কারণ কী

আপনি সম্ভবত লক্ষ্য করেছেন যে ডেভেলপমেন্টের সময় পেজ দেখার সময় রেইলস প্রচুর লগ ইনফরমেশন প্রিন্ট করে, যা দেখতে এভাবে লাগে:

Processing by PagesController#home as HTML Rendering layouts/application.html.erb Rendering pages/home.html.erb within layouts/application Rendered pages/home.html.erb within layouts/application (Duration: 4.0ms | Allocations: 1169) Rendered layouts/application.html.erb (Duration: 35.9ms | Allocations: 8587) Completed 200 OK in 68ms (Views: 40.0ms | ActiveRecord: 15.7ms | Allocations: 14307) 

শেষ লাইনটি এই স্টেজে আমাদের জন্য সবচেয়ে উপকারী। বাম থেকে ডান টাইম ফলো করলে, আমরা দেখি রেইলস ব্রাউজারে রেসপন্স রিটার্ন করতে মোট ৬৮ms লাগল, যার মধ্যে ৪০ms erb ফাইল রেন্ডার করার জন্য এবং ১৫.৭ms ActiveRecord কোয়েরি প্রসেস করার জন্য।

যদিও এটি একটি তुच্ছ উদাহরণ, এটি দেখাচ্ছে কেন আমরা ভিউ লেয়ার ক্যাশিং-এ তাকাতে চাই। এমনকি যদি আমরা ম্যাজিক্যালি ActiveRecord কোয়েরিগুলো ইনস্ট্যান্ট করতে পারতাম, আমরা erb রেন্ডার করার চেয়ে দ্বিগুণ বেশি সময় খরচ করছি।

আমাদের ভিউ রেন্ডারিং স্লো হয়ার কিছু কারণ রয়েছে; উদাহরণস্বরূপ, আমরা ভিউজের ভেতরেই এক্সপেনসিভ DB কোয়েরি কল করতে পারি বা লুপের ভেতরে অনেক কাজ করতে পারি। সবচেয়ে সাধারণ পরিস্থিতি যা আমি দেখেছি তা হলো অনেক প্যারশিয়াল রেন্ডার করা, হয়তো মাল্টিপল লেভেল নেস্টিংসহ।

একটি ইমেল ইনবক্স কল্পনা করুন, যেখানে আমাদের একটি প্যারশিয়াল থাকতে পারে যা একটি אינডিভিজুয়াল রো হ্যান্ডেল করে:

# app/views/emails/_email.html.erb <li class="email-line"> <div class="email-sender"> <%= email.from_address %> </div> <div class="email-subject"> <%= email.subject %> </div> </div> 

এবং, আমাদের মেইন ইনবক্স পেজে, আমরা প্রতিটি ইমেলের জন্য প্যারশিয়াল রেন্ডার করি:

# app/views/emails/index.html.erb ... <% @emails.each do |email| %> <%= render email %> <% end %> 

যদি আমাদের ইনবক্সে ১০০টি মেসেজ থাকে, তবে আমরা _email.html.erb প্যারশিয়াল ১০০ বার রেন্ডার করছি। আমাদের তুচ্ছ উদাহরণে, এটি চিন্তার বিষয় নয়। আমার মেশিনে, প্যারশিয়ালটি পুরো ইনডেক্স রেন্ডার করতে মাত্র ১৫ms লাগে। অবশ্যই, রিয়েল-ওয়ার্ল্ড উদাহরণগুলো আরও জটিল হবে এবং এগুলো তাদের ভেতরেও অন্যান্য প্যারশিয়াল ইনক্লুড করতে পারে; রেন্ডার টাইম বড় হওয়া কঠিন নয়। এমনকি যদি আমাদের _email প্যারশিয়াল রেন্ডার করতে শুধু ১-২ms লাগে, তবে পুরো কালেকশন করার জন্য ১০০-২০০ms লাগবে।

সৌভাগ্যবশত, রেইলসের কিছু বিল্ট-ইন ফাংশনালিটি রয়েছে যা এই সমস্যা সমাধান করতে সহজে ক্যাশিং যোগ করতে সাহায্য করে, চাইলে শুধু __email প্যারশিয়াল, index পেজ, অথবা উভয়।

ভিউ ক্যাশিং কী

রুবি অন রেইলসে ভিউ ক্যাশিং হলো একটি ভিউ যে HTML জেনারেট করে সেটা लेकर পরবর্তীতে জন্য স্টোর করা। যদিও রেইলস এইগুলো ফাইলসিস্টেমে লেখা বা মেমরিতে রাখার সাপোর্ট দেয়, প্রোডাকশন ইউজের জন্য, আপনার幾乎 নিশ্চিতভাবেই একটি স্ট্যান্ডঅ্যালোন ক্যাশিং সার্ভার চাইবেন, যেমন Memcached বা Redis। রেইলসের memory_store ডেভেলপমেন্টের জন্য উপযোগী কিন্তু প্রসেসগুলোর মধ্যে শেয়ার করা যায়না (যেমন, মাল্টিপল সার্ভার/ডাইনো বা ফর্কিং সার্ভার, যেমন unicorn)। একইভাবে, file_store সার্ভারের জন্য লোকাল। তাই, এটি মাল্টিপল বক্সের মধ্যে শেয়ার করা যায়না, এবং এটি এক্সপায়ার্ড এন্ট্রি অটোমেটিক্যালি ডিলিট করে না, তাই আপনাকে পিরিয়ডিক্যালি Rails.cache.clear কল করতে হবে আপনার সার্ভারের ডিস্ক ফুল না হয় সেটা প্রতিরোধ করতে।

একটি ক্যাশিং স্টোর ইনেবল করা যেতে পারে আপনার এনভায়রনমেন্ট কনফিগারেশন ফাইলে (যেমন, config/environments/production.rb):

 # memory store is handy for testing # during development but not advisable # for production config.cache_store = :memory_store 

একটি ডিফল্ট ইনস্টলেশনে, আপনার development.rb-এ ইতিমধ্যে কিছু কনফিগারেশন করা থাকবে যাতে আপনি আপনার মেশিনে ক্যাশিং সহজে টগল করতে পারেন। শুধু rails dev:cache রান করুন ক্যাশিং অন এবং অফ টগল করার জন্য।

রেইলসে একটি ভিউ ক্যাশ করা ধোকার মতো সহজ, তাই পারফরম্যান্স ডিফারেন্স ইলাস্ট্রেট করার জন্য, আমি শুধু sleep(5) ব্যবহার করব একটি আর্টিফিশিয়াল ডিলে তৈরি করতে:

<% cache do %> <div> <p>Hi <%= @user.name %> <% sleep(5) %> </div> <% end %> 

এই ভিউটি প্রথমবার রেন্ডার করতে ৫ সেকেন্ড লাগবে, প্রত্যাশা অনুযায়ী। কিন্তু, দ্বিতীয়বার লোড করলে মাত্র কয়েক মিলিসেকেন্ড লাগবে কারণ cache do ব্লকের ভেতরের সবকিছু ক্যাশ থেকে ফেচ হয়।

উদাহরণ দিয়ে ভিউ ক্যাশিং যোগ করা

আসুন একটি ছোট উদাহরণ ভিউ নিই এবং আমাদের ক্যাশিং অপশনগুলো ওয়াক থ্রু করি। আমরা অনুমন করব যে এই ভিউ আসলে কিছু পারফরম্যান্স সমস্যা সৃষ্টি করছে:

# app/views/user/show.html.erb <div> Hi <%= @user.name %>! <div> <div> Here's your list of posts, you've written <%= @user.posts.count %> so far <% @user.posts.each do |post| <div><%= post.body %></div> <% end %> </div> <% sleep(5) #artificial delay %> 

এটি আমাদের একটি বেসিক স্কেলেটন দিয়েছে কাজ করার জন্য, সাথে আর্টিফিশিয়াল ৫ সেকেন্ড ডিলে। প্রথমে, আমরা পুরো show.html.erb ফাইলকে একটি cache do ব্লকে র‍্যাপ করতে পারি, পূর্বে বর্ণনা করা মত। এখন, একবার ক্যাশ ওয়ার্ম হলে, আমরা নাইস, ফাস্ট রেন্ডারিং টাইম পাই। কিন্তু এই প্ল্যানের সাথে সমস্যা দেখতে দেরি লাগে না।

প্রথম, কী হবে যদি ইউজাররা তাদের নাম চেঞ্জ করে? আমরা রেইলসকে নই বলেছি কখন আমাদের ক্যাশড পেজ এক্সপায়ার হবে, তাই ইউজার কখনো আপডেটেড ভার্সন দেখতে পাবে না। একটি সহজ সমাধান হলো শুধু @user অবজেক্টকে cache মেথডে পাস করা:

<% cache(@user) do %> <div> Hi <%= @user.name %>! </div> ... <% sleep(5) #artificial delay %> <% end %> 

এই সিরিজের পূর্ববর্তী আর্টিকেলে লো-লেভেল ক্যাশিং-এ ক্যাশ কীসের ডিটেইল কভার করা হয়েছে, তাই আমি এখানে আবার কভার করব না। এখন, জানা যথেষ্ট যে যদি আমরা একটি মডেল cache()-এ পাস করি, তবে এটি সেটি মডেলের updated_at অ্যাট্রিবিউট ব্যবহার করে ক্যাশে লুকআপের জন্য একটি কী জেনারেট করবে। অন্য কথায়, যেকোনো সময় @user আপডেট হলে, এই ক্যাশড পেজ এক্সপায়ার হবে, এবং রেইলস HTML রি-রেন্ডার করবে।

আমরা ইউজার নাম চেঞ্জ করার কেস হ্যান্ডেল করেছি, কিন্তু তাদের পোস্টগুলো কী? একটি এক্সিস্টিং পোস্ট চেঞ্জ করা বা নতুন তৈরি করা User-এর updated_at টাইমস্ট্যাম্প চেঞ্জ করবে না, তাই আমাদের ক্যাশড পেজ এক্সপায়ার হবে না। এডিশনালি, যদি ইউজাররা তাদের নাম চেঞ্জ করে, তবে আমরা সকল তাদের পোস্ট রি-রেন্ডার করব, এমনকি যদি তাদের পোস্টগুলো চেঞ্জ না হয়। উভয় সমস্যা সমাধান করার জন্য; আমরা "রাশিয়ান ডল ক্যাশিং" ব্যবহার করতে পারি (অর্থাৎ, ক্যাশের ভেতর ক্যাশ):

<% cache(@user) do %> <div> Hi <%= @user.name %>! <div> <div> Here's your list of posts, you've written <%= @user.posts.count %> so far<br> <% @user.posts.each do |post| %> <% cache(post) do %> <div><%= post.body %></div> <% end %> <% end %> </div> <% sleep(5) #artificial delay %> <% end %> 

আমরা এখন প্রতিটি ইন্ডিভিডুয়ালি রেন্ডার করা post ক্যাশ করছি (রিয়েল ওয়ার্ল্ডে, এটি সম্ভবত একটি প্যারশিয়াল হতো)। তাই, এমনকি যদি @user আপডেট হয়, আমাদের পোস্ট রি-রেন্ডার করতে হবেনা; আমরা ক্যাশড ভ্যালু ব্যবহার করতে পারি। আমাদের এখনও একটি সমস্যা রয়েছে, যদিও। যদি একটি post চেঞ্জ হয়, তবে আমরা এখনও আপডেট রেন্ডার করব না কারণ @user.update_at চেঞ্জ হয়নি, তাই cache(@user) do ব্লকের ভেতরে এক্সিকিউট হবে না।

এই সমস্যা ফিক্স করার জন্য, আমাদের Post মডেলে touch: true যোগ করতে হবে:

class Post < ApplicationRecord belongs_to :user, touch: true end 

touch: true যোগ করে, আমরা ActiveRecord-কে বলছি যে প্রতিবার একটি পোস্ট আপডেট হলে, আমরা চাই যে সেই পোস্ট যেটি "belongs to" করে সেটির updated_at টাইমস্ট্যাম্পও আপডেট হোক।

আমি এড করছি যে রেইলস একটি স্পেসিফিক হেল্পার প্রদান করে একটি কালেকশন অব প্যারশিয়াল রেন্ডার করার জন্য, যেহেতু এটি কতটা কমন:

 <%= render partial: 'posts/post', collection: @posts, cached: true %> 

যা ফাংশন্যালি ইকুইভ্যালেন্ট নিচের সাথে:

<% @posts.each do |post| %> <% cache(post) do %> <%= render post %> <% end %> <% end %> 

render partial: ... cached: true ফর্ম শুধু কম ভার্বোজ নয়, এটি আপনাকে কিছু এক্সট্রা এফিশিয়েন্সিও দেয় কারণ রেইলস ক্যাশ স্টোরে একটি মাল্টিগেট ইস্যু করতে পারে (অর্থাৎ, একটি সিংগল রাউন্ড-ট্রিপে অনেক কী/ভ্যালু পেয়ার পড়া) বরং কালেকশনের প্রতিটি আইটেমের জন্য ক্যাশ স্টোর হিট করার বদলে।

ডাইনামিক পেজ কন্টেন্ট

কিছু পেজে কিছু পরিমাণ 'ডাইনামিক' কন্টেন্ট থাকা কমন যা পেজের বাকি অংশের চেয়ে অনেক ফাস্ট রেটে চেঞ্জ হয়। এটি বিশেষ করে হোম পেজ বা ড্যাশবোর্ডে সত্য, যেখানে আপনার এক্টিভিটি/নিউজ ফিড থাকতে পারে। এইগুলোক আমাদের ক্যাশড পেজে ইনক্লুড করলে আমাদের ক্যাশ যেন ফ্রিকোয়েন্টলি ইনভ্যালিডেট হতে হয়, যা ক্যাশিং থেকে আমরা যা বেনিফিট পাই সেটা লিমিট করে।

একটি সিম্পল উদাহরণ হিসেবে, আসুন আমাদের ভিউতে বর্তমান দিন যোগ করি:

<% cache(@user) do %> <div> Hi <%= @user.name %>, hope you're having a great <%= Date.today.strftime("%A") %>! <div> ... <% end %> 

আমরা প্রতিদিন ক্যাশ ইনভ্যালিডেট করতে পারি, কিন্তু এটি स्पষ্ট কারণে খুব প্র্যাকটিক্যাল নয়। একটি অপশন হলো একটি প্লেসহোল্ডার ভ্যালু (বা এমনকি শুধু একটি খালি <span>) ব্যবহার করা এবং জাভাস্ক্রিপ্ট দিয়ে পপুলেট করা। এই ধরণের পদ্ধতিকে প্রায় "জাভাস্ক্রিপ্ট স্প্রিংকেলস" বলা হয় এবং এটি বেসক্যাম্পের পছন্দের পদ্ধতি, যেখানে রেইলসের অনেক কোর কোড ডেভেলপ হয়। রেজাল্ট হবে এভাবে:

<% cache(@user) do %> <div> Hi <%= @user.name %>, hope you're having a great <span id='greeting-day-name'>Day</span>! <div> ... <% end %> <script> // assuming you're using vanilla JS with turbolinks document.addEventListener( "turbolinks:load", function() { weekdays = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'); today = weekdays[new Date().getDay()]; document.getElementById("greeting-day-name").textContent=today; }); </script> 

অন্য একটি পদ্ধতি হলো ভিউর শুধু কিছু অংশ ক্যাশ করা। আমাদের উদাহরণে, গriteriaিং পেজের টপে আছে, তাই এর পরে যা আছে সেটা ক্যাশ করা ভাবেই সহজ:

<div> Hi <%= @user.name %>, hope you're having a great <%= Date.today.strftime("%A") %>! <div> <% cache(@user) do %> ... <% end %> 

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

সতর্কবার্তার কথা

ভিউ ক্যাশিংকে পারফরম্যান্স সমস্যার কুইক-অ্যান্ড-ইজি প্যানেসিয়া হিসেবে দেখতে সহজ। অবশ্যই, রেইলস ভিউ এবং প্যারশিয়াল ক্যাশ করা অবিশ্বাস্য সহজ করে দিয়েছে, এমনকি gdy вони গভীরভাবে নেস্টেড থাকে। এই সিরিজের প্রথম আর্টিকেলে, আমি ক্যাশিং আপনার সিস্টেমে যোগ করার সময় যেসমস্যাগুলো উঠতে পারে সেগুলো লে আউট করেছি, কিন্তু আমি মনে করি এটি বিশেষ করে ভিউ-লেভেল ক্যাশিং-এ সত্য।

কারণ হলো ভিউ, তাদের খুব প্রকৃতির মতে, একটি সিস্টেমের অন্তর্নিহিত ডেটার সাথে বেশি ইন্টারঅ্যাকশন রাখে। আপনি রেইলসে মেমোইজেশন বা লো-লেভেল ক্যাশিং অ্যাপ্লাই করলে, আপনি প্রায়শই আপনার ফাইলের বাইরে তাকাতে হয় না নির্ধারণ করতে যে ক্যাশড ভ্যালু কখন এবং কেন রিফ্রেশ হওয়া উচিত। একটি ভিউ, অন্যদিকে, একাধিক ভিন্ন মডেল কল করতে পারে, এবং স tudious প্ল্যানিং ছাড়া, এটি কঠিন হতে পারে দেখতে যে কোন মডেল ভিউর কোন অংশকে কখন রি-রেন্ডার করব।

লো-লেভেল ক্যাশিং-এর মতো, সেরা পরামর্শ হলো কোথায় এবং কখন আপনি ক্যাশিং ব্যবহার করবেন সেটা স্ট্র্যাটেজিকলি ঠিক করা। যতটুকু ক্যাশিং দরকার, ততটুকুই ব্যবহার করুন, যত কম জায়গায় পারفوর্মেন্সের একটি গ্রহণযোগ্য লেভেল অর্জন করতে।

রেইলস-এর ডিফল্ট ক্যাশিং

এই ক্যাশিং সিরিজে আমরা এখনো ম্যানুয়ালি কীভাবে ক্যাশ করবেন সেটা কভার করেছি কিন্তু এমনকি কোনো ম্যানুয়াল কনফিগারেশন ছাড়াই, ActiveRecord ইতিমধ্যে কিছু ক্যাশিং হুডের নিচে করে কোয়েরি স্পিড আপ করার জন্য (বা সেগুলো সম্পূর্ণ স্কিপ করার জন্য)। এই সিরিজের পরের আর্টিকেলে, আমরা দেখব ActiveRecord আমাদের জন্য কী ক্যাশ করছে, এবং কীভাবে, একটু কাজ করলে, আমরা এটাকে একটি "কাউন্টার ক্যাশ" রাখতে বাধ্য করতে পারি যাতে thing.children.size কোড লাইনগুলো ডেটাবেস হিট করতে হয় না আপ টু ডেট কাউন্ট পেতে।

  1. GDPR-এর সার্বিক গাইড: মূল নীতিমালা, কমপ্লায়েন্স এবং সেরা অনুশীলন

  2. স্যামসাং গ্যালкси এস৯: সম্পূর্ণ গাইড এবং মূল ফিচারগুলো

  3. Bluetooth 5: ফিচার, সুবিধা এবং কম্প্যাটিবিলিটির সমৃদ্ধ গাইড

  4. PBM ফাইল বোঝা: মূল তথ্য এবং ব্যবহার পদ্ধতি