আজ আমরা ডイブ করব কিছু কৌশলে যেগুলো ব্যবহার করে Ruby on Rails অ্যাপ্লিকেশনকে বিশাল ইউজার বেসের জন্য স্কেল করা যায়।
অ্যাপ্লিকেশন স্কেল করার একটি közvetlen উপায়: বেশি টাকা খরচ করা। এটা অদ্ভুত কাজ করে — আরও কয়েকটা সার্ভার যোগ করুন, ডেটাবেস সার্ভার আপগ্রেড করুন, এবং voilà, অনেক পারফরম্যান্স ইস্যুই poof হয়ে যায়!
কিন্তু অনেক সময় সার্ভার যোগ না করেও অ্যাপ্লিকেশন স্কেল করা সম্ভব। আজ সেটাই আলোচনা করব।
আপনার Rails অ্যাপ্লিকেশনের জন্য AppSignal ব্যবহার করুন
স্কেলিং এবং পারফরম্যান্স অপ্টিমাইজেশনে যাওয়ার আগে, আপনাকে প্রথমে শনাক্ত করতে হবে কি আপনাকে এটা করতে হবে, আপনার অ্যাপ্লিকেশনে বটলнеক কোথায়, এবং কী রিসোর্স স্কেল করা যায়।
একটি সহজ উপায়: Ruby-এর জন্য AppSignal-এর পারফরম্যান্স মনিটরিং এবং মেট্রিক্স ব্যবহার করুন।
পারফরম্যান্স ড্যাশবোর্ড আপনাকে পিনপয়েন্ট করতে সাহায্য করে কোন কন্ট্রোলার অ্যাকশন এবং ব্যাকগ্রাউন্ড জব গড়ে স্লো।
উদাহরণস্বরূপ, ActiveRecord-এর জন্য পারফরম্যান্স ড্যাশবোর্ড কীভাবে দেখতে পারে:
এটি যেকোনো স্কেলিং যাত্রার জন্য একটি ভালো স্টার্টিং পয়েন্ট — চাহে আপনি আরও সার্ভার যোগ করুন বা কোড অপ্টিমাইজ করে পারফরম্যান্স উন্নত করুন।
Ruby on Rails-এ Caching
Caching আপনাকে একই জিনিস বার বার কম্পিউট করতে বন্ধ করে দেয়।
উদাহরণ: আপনি একটি সোশ্যাল মিডিয়া প্ল্যাটফর্ম চালান এবং একটি খুব জনপ্রিয় পোস্ট আছে। Caching আপনাকে সেটা প্রতিটি ইউজারের জন্য রেন্ডার করার জন্য খরচ হওয়া সব CPU সাইকেল ফেরত দিতে পারে। এবং এটা ক্যাশিং কী কী করতে পারে তার কেবল একটি অংশ।
View Caching
ভিউ রেন্ডারিং কখনও কখনও এক্সপেনসিভ অপারেশন, বিশেষ করে যখন ভিউতে অনেক ডেটা রেন্ডার করতে হয়। অপারেশন এক্সপেনসিভ না হলেও, প্রি-রেন্ডার্ড ভিউ ব্যবহার করলে সেম ভিউ মিলিয়নবার রেন্ডার করার চেয়ে অনেক পারফরম্যান্স মিয়ে।
Rails এটি আউট-অফ-দ-বক্স সাপোর্ট করে cache ভিউ হেল্পার দিয়ে। উদাহরণস্বরূপ, পোস্টের লিস্ট রেন্ডার করার সময় প্রতিটি পোস্ট ক্যাশ করা:
<%= render partial: 'post', collection: @posts, cached: true %>
Rails অটোমেটিক্যালি প্রতিটি পোস্ট ক্যাশ করে একটি স্পেসিফিক কীতে যা HTML টেমপ্লেট কন্টেন্ট, পোস্ট ID, এবং আপডেট টাইমস্ট্যাম্পের ওপর নির্ভর করে।
এই টেকনিক সম্পর্কে আরো জানতে 'Fragment caching in Rails' এবং 'Rails collection caching' পোস্ট পড়ুন।
একটা কথা মনে রাখুন: ক্যাশ কীগুলো নেস্টেড টেমপ্লেট কন্টেন্ট ইনক্লুড করে না। তাই যদি আপনি এক লেভেলের চেয়ে গভীরে ক্যাশ কল নেস্ট করেন, তবে stale results হতে পারে। 'Russian doll caching in Rails' পড়ুন।
Response Caching
ভিউ/ফ্র্যাগমেন্ট ক্যাশিংর পাশাপাশি, আপনি GET রিকোয়েস্টের ফুল রেসপন্সও ক্যাশ করতে পারেন। এটি ব্রাউজারের পাঠানো If-None-Match এবং If-Modified-Since হেডার দিয়ে সাপোর্টেড।
If-None-Matchহেডার থাকলে, সার্ভার304 Not Modifiedরেসপন্স দিতে পারে কোনো কন্টেন্ট ছাড়া যদি রেসপন্সে কোনো পরিবর্তন না থাকে। সার্ভার-কম্পিউটেডEtagহেডারের ভ্যালুর সাথে কম্পেয়ার করা হয়।If-Modified-Sinceহেডার থাকেIf-None-Matchছাড়া, তবে সার্ভার304 Not Modifiedদিতে পারে (যদি রেসপন্স সেটা ডেটার পর থেকে পরিবর্তন না হয়)।
Rails কন্ট্রোলার অ্যাকশনে সহজে করতে দেয়:
def show
@post = Post.find(params[:id])
fresh_when etag: @post, last_modified: @post.updated_at
end
Rails সব রিকোয়ার্ড হেডার পাঠাবে ক্যাশিং সাপোর্টের জন্য, ইনকামিং হেডার হ্যান্ডেল করবে, এবং ৩০৪ রেসপন্স দেবে যদি ডেটা চেঞ্জ না হয়। সার্ভার ফুল ভিউ রেন্ডার করা বাদ দেবে যদি কিছু চেঞ্জ না হয়।
Value Caching
অবশেষে, raw valuesও ক্যাশ করা যায় (যেকোনো কিছু যা ক্যাশ স্টোরে সিরিয়ালাইজ করা যায়)। এটি সাধারণত রিসোর্স-ইনটেনসিভ বা স্লো অপারেশনের রেজাল্ট ক্যাশ করতে ব্যবহার হয় যাতে সেগুলো আবার পেরফর্ম না করতে হয়।
কোন ভ্যালু ক্যাশিংয়ের ফায়দা পাবে সেটা অ্যাপ্লিকেশনের ওপর নির্ভরশীল, কিন্তু সাধারণত আপনার slowest events দেখলে সঠিক দিক নির্দেশ করবে।
কী ক্যাশ করতে হবে সেটা শনাক্ত হলে Rails API খুব সিম্পল:
Rails.cache.fetch("cache_key_with_version") do
perform_the_slow_computation
end
উপরের কোড perform_the_slow_computation একবার করবে এবং ভ্যালু ক্যাশ করবে cache_key_with_version কীতে। পরের বার একই কোড কল হলে Rails প্রথমে চেক করবে ক্যাশড ভ্যালু আছে কিনা এবং সেটা ব্যবহার করবে perform_the_slow_computation আবার ট্রিগার করার বদলে।
এই ক্যাশিং স্ট্র্যাটেজির সবচেয়ে গুরুত্বপূর্ণ অংশ: একটি ভালো ক্যাশ কী কম্পিউট করা যা ভ্যালু কম্পিউটেশনে ব্যবহৃত সব ইনপুটের ওপর নির্ভরশীল। এটি নিশ্চিত করতে যে stale value ব্যবহার করছি না।
Cache Stores
এখন যেহেতু আমরা জানি কী ক্যাশ করতে হয় এবং Rails কী টেকনিক দিয়ে ক্যাশে স্টোর করে, পরবর্তী লজিক্যাল প্রশ্ন: আমরা এই ডেটা কোথায় ক্যাশ করব? Rails বেশ কয়েকটি বিল্ট-ইন ক্যাশ স্টোর অ্যাডাপ্টার দিয়ে থাকে। প্রোডাকশন ইউজ কেসের জন্য সবচেয়ে জনপ্রিয়: Redis এবং Memcached। আরও অপশন: file store এবং memory store। সম্পূর্ণ আলোচনা 'Rails' built-in cache stores: an overview' পোস্টে।
File এবং memory stores ডেভেলপমেন্ট ইউজের জন্য great — দ্রুত শুরু করতে। কিন্তু প্রোডাকশনের জন্য সাধারণত unsuitable, বিশেষ করে যদি ডিস্ট্রিবিউটেড সেটআপে মাল্টিপল সার্ভার থাকে। Redis এবং memcached উভয়ই প্রোডাকশন উপযোগী। কোনটি ব্যবহার করবেন সেটা সাধারণত অ্যাপ্লিকেশনের ওপর নির্ভর করে।
Ruby on Rails-এ Background Workers
বেশির অ্যাপ্লিকেশনের ব্যাকগ্রাউন্ড জব লাগে মেইলার, রেগুলার ক্লিন-আপ, বা যেকোনো টাইম-কনসামিং অপারেশনের জন্য যেটা ইউজার প্রেজেন্স দরকার নয়। চান্সেস আপনার ইতিমধ্যে ব্যাকগ্রাউন্ড ওয়ার্কার সেটআপ আছে।
যখনই আপনি কন্ট্রোলার অ্যাকশনে ১ সেকেন্ডের বেশি টাইম লাগানো কিছু করছেন, চেক করুন কি সেটা ব্যাকগ্রাউন্ড ওয়ার্কারে সরানো যায়। এটা হতে পারে ইউজার-ফেসিং অপারেশন (বড় টেবিলে ডেটা সার্চ) বা API মেথড (বড় অমাউন্ট ডেটা ingest করা)।
Example Implementation
Rails Active Job ফ্রেমওয়ার্ক দিয়ে কাস্টম জব রান করা যায়। কমপ্লেক্স ফিল্টারিং লজিক ব্যাকগ্রাউন্ড জবে সরিয়ে দেখি।
প্রথমে ব্যাকগ্রাউন্ড জব তৈরি করুন:
class HugeDatasetJob < ApplicationJob
queue_as :default
def perform(user_id)
user = User.find(user_id)
# complex filtering logic here
results = HeavyComputation.call(user)
# Turbo দিয়ে রেজাল্ট ডেলিভার
Turbo::StreamsChannel.broadcast_replace_to(
[user, :huge_datasets],
target: "huge_datasets",
partial: "huge_datasets/results",
locals: { results: results }
)
end
end
Controller-এ জব রান করান:
def huge_datasets
HugeDatasetJob.perform_later(current_user.id)
# loading indicator রেন্ডার করবে
end
Template-এ turbo_stream_from দিয়ে সাবস্ক্রাইব করুন:
<%= turbo_stream_from current_user, :huge_datasets %>
<div id="huge_datasets">
<%= render partial: "loading" %>
</div>
জব থেকে রেজাল্ট ডেলিভার:
# Job-এ (উপরে দেখানো হয়েছে)
Turbo::StreamsChannel.broadcast_replace_to(...)
এখানে গুরুত্বপূর্ণ হলো notify_completed মেথড (ব্রডকাস্ট রিপ্লেস)। এটি Turbo::StreamsChannel ব্যবহার করে [user, :huge_datasets] নোটিফিকেশন স্ট্রিমে রিপ্লেস ইভেন্ট ব্রডকাস্ট করে যা ভিউ থেকে সাবস্ক্রাইব করা হয়েছিল।
এটাই কন্ট্রোলার থেকে ব্যাকগ্রাউন্ড জবে কমপ্লেক্স অপারেশন সরানোর জন্য প্রয়োজনীয় সব। মূল সুবিধা: ব্যাকগ্রাউন্ড ওয়ার্কার স্বতন্ত্রভাবে স্কেল করা যায় ওয়েব সার্ভার থেকে। এতে ওয়েব সার্ভার সাইডে রিসোর্স ফ্রি হয়। ইউজারের জন্য এই ইন্টারফেস বেশি রেসপন্সিভ লাগে কারণ দ্রুত রেসপন্স দেয়া যায় এবং রেজাল্ট ইনক্রিমেন্টালি ডেলিভার করা যায়।
নোট: ব্যাকগ্রাউন্ড জব ওয়ার্কার বেছে নিতে হেল্প লাগলে 'Delayed Job vs. Sidekiq: Which Is Better?' পড়ুন।
Ruby on Rails অ্যাপ্লিকেশনে Database স্কেলিং
এই পোস্টে আলোচনা করার শেষ স্কেলেবল রিসোর্স: ডেটাবেস। ডেটাবেস অধিকাংশ অ্যাপ্লিকেশনের কোর। ডেটা এবং সার্ভার সংখ্যা বড়ার সাথে সাথে ডেটাবেস লোড ফিল করতে থাকে।
ডেটাবেস স্কেল করার সবচেয়ে সহজ পথ: ডেটাবেস সার্ভারে আরও প্রসেসিং পাওয়ার এবং মেমোরি যোগ করা। কিন্তু ওয়েব সার্ভার স্কেল করার বিপরীতে, ডেটাবেসের সাথে এটি সাধারণত খুব স্লো অপারেশন, বিশেষ করে যদি হাই স্টোরেজ থাকে।
দ্বিতীয় অপশন: হরিজন্টালি স্কেল করা মাল্টিপল ডেটাবেস বা শার্ডিং দিয়ে। 'Multiple Databases with Active Record' পড়ুন।
এখানে আমরা ফোকাস করব PostgreSQL পারফরম্যান্স অপ্টিমাইজেশনে।
PostgreSQL-এ Time-Consuming Queries খুঁজুন
প্রথমে সবচেয়ে টাইম-কনসামিং কোয়েরিগুলো শনাক্ত করতে হবে। pg_stat_statements টেবিল কোয়েরি করে যা সার্ভারে এক্সিকিউট হওয়া সব SQL স্টেটমেন্টের পরিসংখ্যান ধারণ করে। Top 100 highest run time queries:
SELECT query, calls, mean_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 100;
এটি কোয়েরি, কল সংখ্যা, এবং গড় রান টাইম রিটার্ন করবে। যেগুলো ফাস্টার হতে পারতো সেগুলো খুঁজে বের করুন এবং কেন স্লো ছিল সেটা অ্যানালাইজ করুন।
EXPLAIN বা EXPLAIN ANALYZE রান করুন কোয়েরি প্ল্যান এবং আকচুয়াল এক্সিকিউশন ডিটেইলস দেখতে।
রেজাল্টে সবচেয়ে গুরুত্বপূর্ণ: Seq Scan — যা ইন্ডিকেট করে Postgres সব রিকোর্ড সিকুয়েনশিয়ালি থ্রু করতে হচ্ছে কোয়েরি রান করতে। যদি এটা হয়, ফিল্টার করা কলামগুলোর ইনডেক্স যোগ করে সিকুয়েনশিয়াল স্ক্যান বাইপাস করুন।
সর্বাধিক Sequential Scans-वाले Tables
আমার পছন্দের আরেকটি কোয়েরি: টেবিলের ওপর টোটাল সিকুয়েনশিয়াল স্ক্যান সংখ্যা খুঁজে বের করা:
SELECT relname, seq_scan
FROM pg_stat_user_tables
ORDER BY seq_scan DESC;
যদি খুব বড় টেবিল (high row count) এবং এই রেজাল্ট থেকে high count দেখেন, তবে সমস্যা আছে। সেটার সব কোয়েরি চেক করুন, Seq Scan করা যাওয়া কোয়েরিগুলো খুঁজে বের করুন, এবং ইনডেক্স যোগ করে সেটা কমিয়ে আনুন।
Index Usage
ইন্ডেক্স ইউজেজ পরিসংখ্যান খুঁজে বের করতে:
SELECT relname, idx_scan, seq_scan,
(idx_scan::float / (idx_scan + seq_scan)) * 100 AS index_usage_pct
FROM pg_stat_user_tables
WHERE idx_scan + seq_scan > 0
ORDER BY index_usage_pct;
এটি প্রতিটি টেবিলের ইন্ডেক্স ইউজেজ পেরসেন্টেজ রিটার্ন করবে। কম নম্বর মানে সেটাতে কিছু ইনডেক্স মিসিং।
Wrap Up
এই পোস্টে আমরা এক্সপ্লোর করেছি Ruby on Rails অ্যাপ্লিকেশন স্কেল করার কয়েকটি কৌশল: ক্যাশিং, ব্যাকগ্রাউন্ড ওয়ার্কার, এবং PostgreSQL ডেটাবেস পারফরম্যান্স অপ্টিমাইজেশন।
Rails অনেক লেয়ারের পারফরম্যান্স অপ্টিমাইজেশন অ্যাপ্লিকেশনে যোগ করা খুব সহজ করে দিয়েছে।
স্কেলেবিলিটির সবচেয়ে গুরুত্বপূর্ণ বিবেচনা: অ্যাপ্লিকেশনে বটলনেক শনাক্ত করা قبلে আমরা সেগুলোর ওপর অ্যাক্ট নিতে পারি। একটি ভালো পারফরম্যান্স মনিটরিং টুল সাহায্য করতে পারে। দরকার হলে AppSignal for Ruby চেক আউট করুন।
Happy coding!
P.S. Ruby Magic পোস্টগুলি প্রকাশ होते সাথে সাথে পেতে আমাদের Ruby Magic নিউজলেটার সাবস্ক্রাইব করুন!