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

ActiveRecord পারফরম্যান্স অপ্টিমাইজেশন: একটি বাস্তব গাইড

ActiveRecord হলো Ruby on Rails-এর সবচোয়ে ম্যাজিক্যাল ফিচার। আমরা সাধারণত এর আতরিক কাজকাজ নিয়ে চিন্তা করি না, কিন্তু когда আমরা করি, তখন AppSignal কীভাবে আমাদের হুডের নিচে কী হচ্ছে সেটা জানাতে সাহায্য করে সেটা এখানে।

ActiveRecord কী?

ActiveRecord সম্পর্কে কথা বলতে, আমাদের প্রথমে ফ্রেমওয়ার্ক সম্পর্কে চিন্তা করতে হবে, নির্দিষ্টভাবে MVC ফ্রেমওয়ার্কের ওপর। MVC মানে Model-View-Controller, এবং এটি গ্রাফিক্যাল এবং ওয়েব অ্যাপ্লিকেশনের জনপ্রিয় একটি সফটওয়্যার ডিজাইন প্যাটার্ন।

MVC ফ্রেমওয়ার্কগুলো নিয়ে গঠিত:

  • Model: বিজনেস লজিক এবং ডেটা পারসিস্টেন্স হ্যান্ডেল করে।
  • View: প্রেজেন্টেশন লেয়ার চালায় এবং ইউজার ইন্টারফেস ড্র করবে।
  • Controller: সব কিছু একসাথে বান্ধে।

ActiveRecord হলো Ruby on Rails ফ্রেমওয়ার্কে model কম্পোনেন্ট। এটি কোড এবং ডেটার মাঝে একটি অ্যাবস্ট্রাকশন লেয়ার আনে, যাতে আমাদের নিজেরা SQL কোড লিখতে হয় না। প্রতিটি মডেল এক টেবিলে ম্যাপ করা থাকে এবং CRUD অপারেশন (Create, Read, Update and Delete) পারফর্ম করার জন্য বিভিন্ন মেথড প্রদান করে।

AppSignal দিয়ে ActiveRecord মোনিটরিং

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

রেসপন্স টাইম গ্রাফ (The Response Time Graph)

চলুন সমস্যায় এক নজর দিই। পারফরম্যান্স ইস্যু ট্রাবলশুটিং একটি ইটেরেটিভ প্রসেস। একবার আপনার রেইলস অ্যাপ্লিকেশন AppSignal-এ রিপোর্ট করার लगे, Incidents → Performance-এ যান।

রেসপন্স টাইম গ্রাফ প্রতিটি নেমস্পেসের জন্য রেসপন্স টাইম পারসেন্টাইল দেখাবে। ActiveRecord ইভেন্টগুলো অটোমেটিক্যালি সেই নেমস্পেসে অ্যাসাইন করা হয় যেখানে কোয়েরিগুলো এক্সিকিউট হয়েছে।

পরবর্তী, Event Group গ্রাফে নজর দিন। এটি ক্যাটাগরি অনুযায়ী কতটুকু টাইম কনসাম হয়েছে সেটা দেখাবে। চেক করুন active_record কতটুকু রিলেটিভ টাইম স্পেন্ড করেছে। আপনার সব নেমস্পেসে ইউজেজ চেক করুন।

গ্রাফটি তুরন্ত আপনাকে বলবে আমাদের কোড-অপ্টিমাইজেশনের প্রচেষ্টা কোথায় ফোকাস করা উচিত।

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

স্লো কোয়েরি ড্যাশবোর্ড (The Slow Queries Dashboard)

এখন আমরা চিনতে পারলাম সমস্যা ডেটা-বাউন্ড, চলুন দেখি রুট কজা নির্ধারণ করতে জুম ইন করতে পারি কিনা।

Improve → Slow Queries ড্যাশবোর্ড খুলুন। এই পেজে ওভারঅল টাইমের ওপর ইম্প্যাক্ট অনুযায়ী র‍্যাঙ্ক করা SQL কোয়েরির লিস্ট দেখাবে। সকল ActiveRecord-উদ্ভব কোয়েরি sql.active_record ইভেন্ট হিসেবে দেখানো হয়।

সর্বোচ্চ কোয়েরিতে ক্লিক করে তার ডিটেইল দেখুন। ড্যাশবোর্ড গড় ডুরেশন এবং কোয়েরি টেক্সট দেখাবে।

নিচে স্ক্রোল করলে কোয়েরির রেসপন্স টাইম শেষ কয়েক ঘন্টা এবং ওরিজিনেটিং অ্যাকশন দেখাবে।

আপনি খুঁজে পেতে পারেন কিছু অ্যাকশনের সাথে একটি asosciated ইন্সিডেন্ট রয়েছে। এর মানে AppSignal কোয়েরি চলাকালীন একটি পারফরম্যান্স ইন্সিডেন্ট তৈরি করেছিল, কিন্তু এটা অবশ্যই বোঝায় না যে ActiveRecordই কজা।

পারফরম্যান্স মেজারমেন্ট ড্যাশবোর্ড (Performance Measurements Dashboard)

পারফরম্যান্স মেজারমেন্ট ইন্সিডেন্ট তখন খুলে যখন AppSignal একটি নতুন এন্ডপয়েন্ট বা ব্যাকগ্রাউন্ড জব রেকর্ড করে।

ইন্সিডেন্টগুলো Performance → Issue List ড্যাশবোর্ডে লোকেটেড।

ইন্সিডেন্ট পেজে MVC কম্পোনেন্টগুলোর প্রতিটির জন্য এলাপসড টাইম এবং নম্বর অফ এলোকেশন দেখাবে। ActiveRecord সমস্যা active_record ক্যাটাগরিতে লং ডুরেশন দিয়ে এক্সহিবিট করবে।

ইভেন্ট টাইমলাইন দেখাবে ইভেন্টটি কীভাবে সময়ের সাথে প্রগ্রেস করেছে।

ActiveRecord ইস্যু খুঁজে বের করা (Finding ActiveRecord Issues)

এই সেকশনে, আমরা দেখব AppSignal কীভাবে কিছু কমন ActiveRecord ইস্যু শনাক্ত করতে সাহায্য করে।

প্রাসঙ্গিক কলাম সিলেক্ট করা (Selecting the Relevant Columns)

ডাটাবেসের জ্ঞান বলে: আমাদের সবসময় কাজের জন্য প্রয়োজনীয় কলামগুলো রিট্রিভ করা উচিত। উদাহরণস্বরূপ, SELECT * FROM people এর বদলে SELECT first_name, surname, birthdate FROM people করা উচিত। এটা সব ঠিক আছে, কিন্তু রেইলসে এটা কীভাবে করব?

ডিফল্টভাবে, ActiveRecord সব কলাম রিট্রিভ করে।

Person.all.each { # process data }

সৌভাগ্যক্রমে, আমাদের select মেথড রয়েছে প্রয়োজনীয় কলাম পিক এন্ড চুজ করার জন্য:

Person.select(:name, :address, :birthdate).each { # process data }

এটা লাগতে পারে আমি ছোট ডিটেইল নিয়ে অবসেসড হচ্ছি। কিন্তু চোড়া টেবিলে, সকল কলাম সিলেক্ট করা শুধু বর্জ্য। আপনি লক্ষ্য করবেন যখন এটা হয়, ActiveRecord বড় চান্ক মেমরি এলোকেট করে:

N+1 সমস্যা (N+1 Problems)

N+1 সমস্যা হয় যখন অ্যাপ্লিকেশন ডাটাবেস থেকে একটি রেকর্ড সেট পায় এবং তার মাধ্যমে লুপ করে। এর ফলে অ্যাপ্লিকেশন N+1 টি কোয়েরি এক্সিকিউট করে, যেখানে N হলো প্রাথমিকভাবে প্রাপ্ত সারোর সংখ্যা। আপনি কল্পনা করতে পারেন, টেবিল বড় হলে এই প্যাটার্ন খুব খারাপ স্কেল করে। এটা এতটুকু ড্যামেজিং যে AppSignal স্পেসিফিক্যালি আপনাকে সতর্ক করে:

N+1 সমস্যা সাধারণত অ্যাসোসিয়েটেড মডেলের সাথে দেখা যায়। কল্পনা করুন আমাদের একটি Person মডেল রয়েছে:

class Person < ApplicationRecord has_many :addresses end

প্রতিটি পেরসনের অনেক অ্যাড্রেস থাকতে পারে:

class Address < ApplicationRecord belongs_to :person end

ডেটা রিট্রিভ করার সবচোয়ে সিম্পল পথ N+1 সমস্যায় নিয়ে যায়:

class RelatedTablesController < ApplicationController def index Person.all.each do |person| person.addresses.each do |address| address.address end end end end

AppSignal-এ আপনি দেখবেন অ্যাপ্লিকেশন পেরসন প্রতি একটি SELECT রান করছে:

এই নির্দিষ্ট কেসের ফিক্স সিম্পল: includes ব্যবহার করুন, যা ActiveRecord-কে প্রয়োজনীয় কলামের জন্য কোয়েরি অপ্টিমাইজ করতে বলে:

class RelatedTablesController < ApplicationController def index Person.all.includes(:addresses).each do |person| person.addresses.each do |address| address.address end end end end

এখন আমাদের N+1 এর বদলে দুটি কোয়েরি আছে:

Processing by RelatedTablesController#index as HTML (0.2ms) SELECT sqlite_version(*) ↳ app/controllers/related_tables_controller.rb:12:in `index' Person Load (334.6ms) SELECT "people".* FROM "people" ↳ app/controllers/related_tables_controller.rb:12:in `index' Address Load (144.4ms) SELECT "addresses".* FROM "addresses" WHERE "addresses"."person_id" IN (1, 2, 3, . . .)

প্রতি টেবিল কমপক্ষে কোয়েরি এক্সিকিউট করা (Execute the Least Necessary Queries Per Table)

এটা কখনও কখনও N+1 সমস্যার সাথে গুলিয়ে দেওয়া হয়, কিন্তু এটা একটু আলাদা। যখন আমরা একটি টেবিল কোয়েরি করি, আমাদের সকল ডেটা রিট্রিভ করা উচিত যা আমরা মনে করি প্রয়োজন হবে, রিড অপারেশন মিনিমাইজ করার জন্য। তবে, খুব সাদামতা দেখতে এমন কোড রয়েছে যা রিডান্ড্যান্ট কোয়েরি ট্রিগার করে। উদাহরণস্বরূপ, নিচের ভিউতে count কীভাবে সবসময় একটি SELECT COUNT(*) কোয়েরি ট্রিগার করে দেখুন:

<ul> <% @people.each do |person| %> <li><%= person.name %></li> <% end %> </ul> <h2>Number of Persons: <%= @people.count %></h2>

এখন ActiveRecord দুটি কোয়েরি করে:

Rendering duplicated_table_query/index.html.erb within layouts/application (69.1ms) SELECT COUNT(*) FROM "people" WHERE "people"."name" = ? [["name", "John Waters"]] ↳ app/views/duplicated_table_query/index.html.erb:3 Person Load (14.6ms) SELECT "people".* FROM "people" WHERE "people"."name" = ? [["name", "John Waters"]] ↳ app/views/duplicated_table_query/index.html.erb:6

AppSignal-এ, আপনি লক্ষ্য করবেন সেম টেবিলে দুটি active_record ইভেন্ট রয়েছে:

বাস্তবতা হলো: আমাদের দুটি কোয়েরি দরকার নেই; আমাদের প্রয়োজনীয় সকল ডেটা ইতিমধ্যে মেমরিতে আছে। এই কেসে, সমাধান হলো count-কে size-র সাথে স্ব্যাপ করা:

<ul> <% @people.each do |person| %> <li><%= person.name %></li> <% end %> </ul> <h2>Number of Persons: <%= @people.size %></h2>

এখন আপনার কাছে একটি একক SELECT, যেন হওয়া উচিত:

Rendering duplicated_table_query/index.html.erb within layouts/application Person Load (63.2ms) SELECT "people".* FROM "people" WHERE "people"."name" = ? [["name", "Abdul Strosin"]] ↳ app/views/duplicated_table_query/index.html.erb:5

আরেকটি সমাধান হলো preload ব্যবহার করে ডেটা মেমরিতে ক্যাশ করা।

রেইলসে অ্যাগ্রিগেটেড ডেটা ক্যালকুলেট করা (Computing Aggregated Data in Rails)

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

আমরা রেইলসে অ্যাগ্রিগেশন করছি যখনই আমরা max, min, বা sum যেন রুবি ফাংশনগুলো ActiveRecord এলিমেন্ট বা অন্যান্য এনুমারেবলের ওপর রিসোর্ট করি।

class AggregatedColumnsController < ApplicationController def index @mean = Number.pluck(:number).sum() end end

সৌভাগ্যক্রমে, ActiveRecord মডেলগুলোর নির্দিষ্ট মেথডগুলো রয়েছে যা ডাটাবেসের অ্যাগ্রিগেশন ফাংশনে ম্যাপ করে। উদাহরণস্বরূপ, নিচের কোয়েরিটি SELECT SUM(number) FROM ...-এ ম্যাপ করে, যা পূর্বের উদাহরণের চেয়ে অনেক দ্রুত এবং সস্তা রান করে:

# controller class AggregatedColumnsController < ApplicationController def index @mean = Number.sum(:number) end end
Processing by AggregatedColumnsController#index as */* (2.4ms) SELECT SUM("numbers"."number") FROM "numbers"

আপনি যদি আরও কমপ্লেক্স বা কম্বাইনড অ্যাগ্রিগেশন ফাংশন দরকার হয়, তবে আপনাকে কয়েকটু র SQL কোড ইনক্লুড করতে হতে পারে:

sql = "SELECT AVG(number), STDDEV(number), VAR(number) FROM ..." @results = ActiveRecord::Base.connection.execute(sql)

বড় ট্রানজ্যাকশন ম্যানেজ করা (Managing Big Transactions)

SQL ট্রানজ্যাকশন কনসিস্টেন্ট এবং আটমিক আপডেট নিশ্চিত করে। যখন আমরা একটি ট্রানজ্যাকশন ব্যবহার করে পরিবর্তন করি, হয় প্রতিটি সারো সফলভাবে আপডেট হয়, নাহলে পুরো জিনিস রোল বাক হয়। যেকোনো কেসে, ডাটাবেস সবসময় একটি কনসিস্টেন্ট স্টেটে থাকে।

আমরা ActiveRecord::Base.transaction দিয়ে একটি ব্যাচ চেঞ্জ একটি একক ট্রানজ্যাকশনে বান্ডল করতে পারি।

class BigTransactionController < ApplicationController def index ActiveRecord::Base.transaction do (1..1000).each do Person.create(name: 'Les Claypool') end end end end

বড় ট্রানজ্যাকশন ব্যবহার করার অনেক লেজিটিম কেস রয়েছে। এগুলো, যiewe, ডাটাবেস স্লো ডাউন করার রিস্কে পড়ে। বসিডées, নির্দিষ্ট থ্রেশহোল্ড ছাড়া ট্রানজ্যাকশনগুলো ডাটাবেস দিয়ে রিজেক্ট হয়।

ট্রানজ্যাকশন খুব বড় হয়েছে তার প্রথম লক্ষণ: commit transaction ইভেন্টে প্রচুর টাইম স্পেন্ড হচ্ছে:

ডাটাবেসের নিজস্ব কনফিগারেশন ইস্যু বাদ দিলে, সমাধান হলো ট্রানজ্যাকশনকে ছোট ছোট চাঙ্কে ভাঙা।

ডাটাবেস মোনিটরিং (Monitoring The Database)

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

আমরা যদি নিজেদের ডাটাবেস রান করি, তবে স্ট্যান্ডআলোন এজেন্ট ইনস্টল করে হোস্ট মেট্রিক্স ক্যাপচার করতে পারি। কীভাবে স্ট্যান্ডআলোন এজেন্ট ব্যবহার করবেন সেটা জানতে পড়ুন: Monitoring Any System with StatsD and AppSignal's Standalone Agent.

নিচের সাইনগুলো দেখাতে পারে ডাটাবেসে কিছু হচ্ছে। Inspect → Host Metrics ড্যাশবোর্ডে যান আপনার সার্ভারের রিসোর্স ইউজেজ দেখতে:

  • হাই মেমরি ইউজেজ: ডাটাবেস অন্যান্য অধিকাংশ সিস্টেমের চেয়ে অনেক বেশি মেমরি দরকার করে সঠিকভাবে রান করার জন্য। ডেটাসেট বড় হলে, মেমরি রিকোয়ারমেন্ট সাধারণত স 그와 সাথেই স্কেল করে। আমাদের সময়ে সময়ে হয় আরও মেমরি যোগ করতে হয় বা ডেটাসেট আলাদা আলাদা মেশিনে Сп্লিট করতে হয়।
  • সওয়াপ ইউজেজ: আদর্শভাবে, ডাটাবেস মেশিনগুলোকে সওয়াপ মেমরির প্রয়োজন নয়। সওয়াপিং ডাটাবেস পারফরম্যান্স ধ্বংস করে। এর উপস্থিতি মানে আছে একটি গভীর কনফিগারেশন বা মেমরি সমস্যা।
  • হাই I/O ইউজেজ: ডিস্ক এক্টিভিটির পিক হতে পারে মেইনটেনেন্স টাস্কের কারণে যেন রি-ইন্ডেক্সিং বা ডাটাবেস ব্যাকআপ। পিক আওয়ার্সে এই টাস্কগুলো করলে অবশ্যই পারফরম্যান্স হিট করবে।

👋 আপনি যদি এই আর্টিকেল পছন্দ করেন, রুবি (অন রেইলস) পারফরম্যান্স সম্পর্কে আরও অনেক কিছু লিখেছি, আমাদের Ruby performance monitoring checklist চেক আউট করুন।

উপসংহার (Conclusion)

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

AppSignal এবং Ruby on Rails সম্পর্কে আরও শিখতে থাকি:

  • ActiveRecord পারফরম্যান্স: N+1 কোয়েরি অ্যান্টি-প্যাটার্ন
  • Rails is Fast: Optimize Your View Performance
  • Introduction to Ruby on Rails Patterns and Anti-patterns

P.S. Ruby Magic পোস্টগুলো প্রেস থেকে বের होतेই পেতে চাইলে, আমাদের Ruby Magic নিউজলেটার সাবস্ক্রাইব করুন এবং কখনো একক পোস্ট মিস করবেন না!

  1. OpenStruct-এর পারফরম্যান্স প্রভাব: কেন এটি আপনার রুবি অ্যাপকে ধীর করতে পারে

  2. 데이터베이스 최적화: 전문가 성능 튜닝 기법

  3. Redis পারফরম্যান্স সর্বাধিক করা: এক্সপার্ট ইনসাইট এবং সেরা অভ্যাস

  4. Windows 11 পারফরম্যান্স বাড়ান: ১২টি প্রমাণিত টিপস পিসি দ্রুত করার জন্য