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

রাশিয়ান ডল ক্যাশিং দিয়ে দ্রুত রেলস অ্যাপ: পারফরম্যান্স অপ্টিমাইজেশন

আজ আমরা রাশিয়ান ডল ক্যাশিং-এ গোতা লাগাব যা রেলসের বিল্ট-ইন ফ্র্যাগমেন্ট ক্যাশিং-এর বাইরে ক্যাশিং আরও উন্নত করার একটি تاکটিক।

ফ্র্যাগমেন্ট ক্যাশিং

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

👋 এবং আপনি যদি ক্যাশিংয়ের বাইরে পারফরম্যান্স নিয়ে পড়তে চান, আমাদের রুবি পারফরম্যান্স মনিটরিং চেকলিস্ট দেখুন।

এটা ভালো স্পিড বুস্ট দেয়, বিশেষ করে কমপ্লেক্স ভিউ বা অনেক রেন্ডার্ড পার্শিয়াল वाले ভিউর জন্য, কিন্তু আমরা এটাকে কিছুটা আরও উন্নত করতে পারি একটি পদ্ধতি בשם রাশিয়ান ডল ক্যাশিং দিয়ে।

এই ক্যাশিং পদ্ধতি ব্যবহার করলে, ভিউ ফ্র্যাগমেন্টগুলো একে অপরের ভেতরে রাখা হয়, ঠিক যেই "ম্যাট্রোশকা" গুড়িয়ার মতো যেনে এই কৌশলটির নামकरण হয়েছে। ক্যাশড ফ্র্যাগমেন্টগুলো ছোট ছোট টুকরোতে ভাগ করে, আউটার ক্যাশ দ্রুত রেন্ডার হয় যখন এর নেস্টেড ফ্র্যাগমেন্টের যেকোনো একটি পরিবর্তন হয়।

একটি রাশিয়ান ডল ক্যাশিং উদাহরণ

উদাহরণ হিসেবে, একটি স্টোর ব্যবহার করি যা প্রোডাক্ট বিক্রি করে। প্রতিটি প্রোডাক্টের একাধিক ভ্যারিয়েন্ট থাকতে পারে, যাতে এক আইটেমের একাধিক রঙ বিক্রি করা যায়। ইনডেক্সে, আমরা বিক্রির জন্য উপলব্ধ প্রতিটি প্রোডাক্ট এবং তার সব ভ্যারিয়েন্ট দেখাব।

প্রোডাক্ট ইনডেক্সে, আমরা প্রতিটি প্রোডাক্ট পার্শিয়ালকে একটি cache ব্লকে র‍্যাপ করেছি। আমরা ক্যাশ কী বানাতে product অবজেক্ট ব্যবহার করছি, যেটি ক্যাশড ফ্র্যাগমেন্ট ইনভ্যালিডেট করতে ব্যবহৃত হয়। এটা অবজেক্টের আইডি, এর updated_at তারিখ, এবং টেমপ্লেট ট্রির একটি ডাইজেস্ট নিয়ে গঠিত, তাই অটোমেটিক্যালি stale ধরা পড়ে যদি অবজেক্ট পরিবর্তন হয়, বা টেমপ্লেটের কন্টেন্ট পরিবর্তন হয়।

# app/views/products/index.html.erb
<h1>Products</h1>
<% @products.each do |product| %>
  <% cache product do %>
    <%= render product %>
  <% end %>
<% end %>

টিপ: আমরা ক্লэрিটির জন্য পুরো ব্লক লিখছি, কিন্তু আপনি <%= render partial: 'products/product', collection: @products, cached: true %> ব্যবহার করে প্রতিটি প্রোডাক্ট ক্যাশ ব্লকে রেন্ডার করতে পারেন।

প্রোডাক্টস পার্শিয়ালে, আমরা প্রোডাক্টের প্রতিটি ভ্যারিয়েন্টের জন্য একটি রো রেন্ডার করি।

# app/views/products/_product.html.erb
<article>
  <h1><%= product.title %></h1>
  <ul>
    <% product.variants.each do |variant| %>
      <%= render variant %>
    <% end %>
  </ul>
</article>

ক্যাশ ইনভ্যালিডেশন

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

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

এটা ফিক্স করার উপায় হলো নিশ্চিত করা যে প্রোডাক্টটি বদলায় যখন এর যেকোনো ভ্যারিয়েন্টে কিছু পরিবর্তন হয়। সেটি করার জন্য, আমরা প্রোডাক্টের updated_at অ্যাট্রিবিউট আপডেট করব যখনই সেটি হয়। যেহেতু এটা খুব সাধারণ, belongs_to (এবং এক্টিভমডেলের অন্যান্য রিলেশন মেথড) এর জন্য একটি আর্গুমেন্ট আছে, :touch, যা আটোমেটিক্যালি প্যারেন্ট অবজেক্টের updated_at আপডেট করে দেবে।

class Variant < ApplicationRecord
  belongs_to :product, touch: true
end

নেস্টেড ফ্র্যাগমেন্ট

এখন যেহেতু আমরা নিশ্চিত করেছি প্রোডাক্ট ফ্র্যাগমেন্ট আপডেট হয় যখন তাদের ভ্যারিয়েন্ট পরিবর্তন হয়, ভ্যারিয়েন্টগুলোও ক্যাশ করার 시간이 এসেছে। আগের মতো, আমরা প্রতিটির চারপাশে একটি cache ব্লক যোগ করব।

<article>
  <h1><%= product.title %></h1>
  <ul>
    <% product.variants.each do |variant| %>
      <% cache(variant) do %>
        <%= render variant %>
      <% end %>
    <% end %>
  </ul>
</article>

টিপ: আমরা ক্লэрিটির জন্য পুরো ব্লক লিখছি, কিন্তু আপনি <%= render partial: 'variants/variant', collection: product.variants, cached: true %> ব্যবহার করে প্রতিটি ভ্যারিয়েন্ট ক্যাশ ব্লকে রেন্ডার করতে পারেন।

একটি কোল্ড ক্যাশে (আপনি rake tmp:cache:clear চালিয়ে ক্যাশ ক্লিয়ার করতে পারেন), প্রথম রিকোয়েস্ট প্রতিটি প্রোডাক্ট পার্শিয়াল রেন্ডার করবে।

এখন পেজ রিকোয়েস্ট করলে (ডেভেলপমেন্টে ক্যাশ অন করার জন্য rails dev:cache চালানো মতে ভুলবেন না), প্রতিটি প্রোডাক্ট পার্শিয়াল একটি পার্শিয়াল হিসেবে ক্যাশ হবে, এবং দ্বিতীয় রিকোয়েস্টে ক্যাশড ফ্র্যাগমেন্ট রিটার্ন হবে।

Started GET "/products" for 127.0.0.1 at 2018-03-30 14:51:38 +0200
Processing by ProductsController#index as HTML
Rendering products/index.html.erb within layouts/application
Product Load (0.2ms) SELECT "products".* FROM "products"
Variant Load (0.9ms) SELECT "variants".* FROM "variants" WHERE "variants"."product_id" IN (27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51)
Rendered variants/_variant.html.erb (0.5ms)
Rendered variants/_variant.html.erb (0.1ms)
...
Rendered products/_product.html.erb (44.8ms) [cache miss]
...
Rendered products/_product.html.erb (46.2ms) [cache miss]
Rendered products/index.html.erb within layouts/application (1378.6ms)
Completed 200 OK in 1414ms (Views: 1410.5ms | ActiveRecord: 1.1ms)

Started GET "/products" for 127.0.0.1 at 2018-03-30 14:51:41 +0200
Processing by ProductsController#index as HTML
Rendering products/index.html.erb within layouts/application
Product Load (0.3ms) SELECT "products".* FROM "products"
Variant Load (12.7ms) SELECT "variants".* FROM "variants" WHERE "variants"."product_id" IN (27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51)
Rendered products/index.html.erb within layouts/application (48.1ms)
Completed 200 OK in 76ms (Views: 59.0ms | ActiveRecord: 13.0ms)

Voila: রাশিয়ান ডল ম্যাজিক

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

প্রোডাক্ট পার্শিয়ালে প্রোডাক্টের প্রতিটি ভ্যারিয়েন্ট অন্তর্ভুক্ত। পরিবর্তন করা ভ্যারিয়েন্টের ক্যাশড ফ্র্যাগমেন্ট stale, তাই সেটি রি-জেনারেট করতে হবে, কিন্তু অন্য ভ্যারিয়েন্টগুলো পরিবর্তন হয়নি, তাই তাদের ক্যাশড ফ্র্যাগমেন্ট পুনরায় ব্যবহার হয়। লগে আমরা দেখি ভ্যারিয়েন্ট এবং প্রোডাক্ট পার্শিয়াল দুটোই একবার রেন্ডার হয়েছে।

Started GET "/products" for 127.0.0.1 at 2018-03-30 14:52:04 +0200
Processing by ProductsController#index as HTML
Rendering products/index.html.erb within layouts/application
Product Load (0.3ms) SELECT "products".* FROM "products"
Variant Load (1.2ms) SELECT "variants".* FROM "variants" WHERE "variants"."product_id" IN (27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51)
Rendered variants/_variant.html.erb (0.5ms)
Rendered products/_product.html.erb (13.3ms) [cache miss]
Rendered products/index.html.erb within layouts/application (45.9ms)
Completed 200 OK in 78ms (Views: 73.5ms | ActiveRecord: 1.5ms)

ফলাফল

এভাবে ক্যাশ ফ্র্যাগমেন্ট নেস্ট করলে, ভিউ почти কখনোই পুরোপুরি রেন্ডার হয় না, যদি ক্যাশ সম্পূর্ণ খালি না হয়। এমনকি ডেটা পরিবর্তন হলে, রেন্ডার্ড পেজের অধিকাংশ część সরাসরি ক্যাশ থেকে সার্ভ হয়।

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

  1. Ruby নিউজ ব্রিফ – Q3 ২০২১: মূল আপডেট ও ট্রেন্ডস

  2. Hotwire দিয়ে Rails অ্যাপস দ্রুত করুন: JavaScript ছাড়া সার্ভার-সাইড রেন্ডারিং

  3. রুবি অন রেইলস AWS Lambda-তে ডিপ্লয়: ধাপে ধাপে গাইড

  4. Angular এবং Rails 5 ইন্টিগ্রেশন: একটি স্মুথ ফুল-স্ট্যাক পদ্ধতি