কম্পিউটার

রুবির লুকানো রত্ন:বুলেট

একটি ডাটাবেস হল অনেক অ্যাপ্লিকেশনের কেন্দ্রবিন্দু, এবং এতে সমস্যা হলে কার্যক্ষমতার গুরুতর সমস্যা হতে পারে।

ActiveRecord এবং Mongoid-এর মতো ORMগুলি আমাদেরকে বিমূর্ত বাস্তবায়নে সাহায্য করে এবং দ্রুত কোড সরবরাহ করে, কিন্তু কখনও কখনও, আমরা হুডের নীচে কোন প্রশ্নগুলি চলছে তা পরীক্ষা করতে ভুলে যাই৷

বুলেট রত্ন আমাদের কিছু সুপরিচিত ডাটাবেস-সম্পর্কিত সমস্যা সনাক্ত করতে সাহায্য করে:

  1. "N+1 ক্যোয়ারী":যখন অ্যাপ্লিকেশনটি একটি তালিকার প্রতিটি আইটেম লোড করার জন্য একটি ক্যোয়ারী চালায়
  2. "অব্যবহৃত আগ্রহী লোডিং":যখন অ্যাপ্লিকেশন ডেটা লোড করে, সাধারণত N+1 প্রশ্নগুলি এড়াতে, কিন্তু এটি ব্যবহার করে না
  3. "মিসিং কাউন্টার ক্যাশে":যখন অ্যাপ্লিকেশানটিকে সংশ্লিষ্ট আইটেমগুলির সংখ্যা পেতে গণনা প্রশ্নগুলি চালানোর প্রয়োজন হয়

এই পোস্টে, আমি দেখাতে যাচ্ছি:

  • কিভাবে bullet কনফিগার করবেন একটি রুবি প্রকল্পে রত্ন,
  • আগে উল্লিখিত প্রতিটি সমস্যার উদাহরণ,
  • কিভাবে bullet প্রতিটি সনাক্ত করে,
  • কীভাবে প্রতিটি সমস্যার সমাধান করবেন, এবং
  • কিভাবে bullet সংহত করবেন অ্যাপসিগন্যাল সহ।

আমি এই পোস্টের জন্য তৈরি করা একটি প্রকল্প থেকে কিছু উদাহরণ ব্যবহার করব৷

রুবি প্রকল্পে কিভাবে বুলেট কনফিগার করবেন

প্রথমে, Gemfile-এ রত্নটি যোগ করুন .

আমরা প্রদত্ত সমস্ত পরিবেশে এটি যোগ করতে পারি, আমরা এটিকে সক্ষম বা নিষ্ক্রিয় করতে পারি এবং প্রতিটিতে একটি ভিন্ন পদ্ধতি ব্যবহার করতে পারি:

gem 'bullet'

এর পরে, এটি কনফিগার করা প্রয়োজন৷

আপনি যদি একটি রেল প্রকল্পে থাকেন, তাহলে আপনি স্বয়ংক্রিয়ভাবে কনফিগারেশন কোড তৈরি করতে নিম্নলিখিত কমান্ডটি চালাতে পারেন:

bundle exec rails g bullet:install

আপনি যদি একটি নন-রেলস প্রকল্পে থাকেন, তাহলে আপনি ম্যানুয়ালি যোগ করতে পারেন, উদাহরণস্বরূপ, spec_helper.rb-এ নিম্নলিখিত কোড যোগ করে অ্যাপ্লিকেশনের কোড লোড করার পরে:

Bullet.enable        = true
Bullet.bullet_logger = true
Bullet.raise         = true

এবং অ্যাপ্লিকেশনের কোড লোড করার পরে প্রধান ফাইলে নিম্নলিখিত কোড যোগ করুন:

Bullet.enable = true

আমি এই পোস্টে কনফিগারেশন সম্পর্কে আরও বিশদ ভাগ করতে যাচ্ছি। আপনি যদি সেগুলি দেখতে চান তবে বুলেটের README পৃষ্ঠায় যান৷

টেস্টে বুলেট ব্যবহার করা

পূর্বে প্রস্তাবিত কনফিগারেশনের সাথে, বুলেট পরীক্ষায় কার্যকর করা খারাপ প্রশ্নগুলি সনাক্ত করবে এবং তাদের জন্য ব্যতিক্রমগুলি উত্থাপন করবে৷

এখন, কিছু উদাহরণ দেখা যাক।

N+1 কোয়েরি সনাক্ত করা হচ্ছে

একটি index দেওয়া হয়েছে নিম্নরূপ কর্ম:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

এবং এই মত একটি দৃশ্য:

# app/views/posts/index.html.erb
 
<h1>Posts</h1>
 
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Comments</th>
    </tr>
  </thead>
 
  <tbody>
    <% @posts.each do |post| %>
    <tr>
      <td><%= post.name %></td>
      <td><%= post.comments.map(&:name) %></td>
    </tr>
    <% end %>
  </tbody>
</table>

bullet একটি সমন্বিত পরীক্ষা চালানোর সময় একটি "N+1" সনাক্ত করতে একটি ত্রুটি উত্থাপন করবে যা ভিউ এবং কন্ট্রোলার থেকে কোড নির্বাহ করে, উদাহরণস্বরূপ, নিম্নরূপ একটি অনুরোধ স্পেস ব্যবহার করে:

# spec/requests/posts_request_spec.rb
require 'rails_helper'
 
RSpec.describe "Posts", type: :request do
  describe "GET /index" do
    it 'lists all posts' do
      post1 = Post.create!
      post2 = Post.create!
 
      get '/posts'
 
      expect(response.status).to eq(200)
    end
  end
end

এই ক্ষেত্রে, এটি এই ব্যতিক্রম বাড়াবে:

Failures:

  1) Posts GET /index lists all posts
     Failure/Error: get '/posts'

     Bullet::Notification::UnoptimizedQueryError:
       user: fabioperrella
       GET /posts
       USE eager loading detected
         Post => [:comments]
         Add to your query: .includes([:comments])
       Call stack
         /Users/fabioperrella/projects/bullet-test/app/views/posts/index.html.erb:17:in `map'
         ...
     # ./spec/requests/posts_controller_spec.rb:9:in `block (3 levels) in <top (required)>'

এটি ঘটে কারণ ভিউটি post.comments.map(&:name)-এ প্রতিটি মন্তব্যের নাম লোড করার জন্য একটি ক্যোয়ারী চালাচ্ছে :

Processing by PostsController#index as HTML
  Post Load (0.4ms)  SELECT "posts".* FROM "posts"
  ↳ app/views/posts/index.html.erb:14
  Comment Load (0.0ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 1]]
  ↳ app/views/posts/index.html.erb:17:in `map'
  Comment Load (0.1ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 2]]

এটি ঠিক করতে, আমরা কেবল ত্রুটি বার্তায় নির্দেশনা অনুসরণ করতে পারি এবং .includes([:comments]) যোগ করতে পারি ক্যোয়ারীতে:

-@posts = Post.all
+@posts = Post.all.includes([:comments])

এটি ActiveRecord কে শুধুমাত্র 1টি প্রশ্নের সাথে সমস্ত মন্তব্য লোড করার নির্দেশ দেবে৷

Processing by PostsController#index as HTML
  Post Load (0.2ms)  SELECT "posts".* FROM "posts"
  ↳ app/views/posts/index.html.erb:14
  Comment Load (0.0ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" IN (?, ?)  [["post_id", 1], ["post_id", 2]]
  ↳ app/views/posts/index.html.erb:14

যাইহোক, bullet নিচের মত একটি কন্ট্রোলার পরীক্ষায় একটি ব্যতিক্রম উত্থাপন করবে না, কারণ কন্ট্রোলার পরীক্ষাগুলি ডিফল্টরূপে ভিউ রেন্ডার করে না, তাই N+1 ক্যোয়ারী ট্রিগার করা হবে না৷

দ্রষ্টব্য:Rails 5:

থেকে কন্ট্রোলার পরীক্ষা নিরুৎসাহিত করা হয়
# spec/controllers/posts_controller_spec.rb
require 'rails_helper'
 
RSpec.describe PostsController do
  describe 'GET index' do
    it 'lists all posts' do
      post1 = Post.create!
      post2 = Post.create!
 
      get :index
 
      expect(response.status).to eq(200)
    end
  end
end

বুলেট একটি "N+1" সনাক্ত করতে পারবে না এমন একটি পরীক্ষার আরেকটি উদাহরণ হল একটি ভিউ পরীক্ষা কারণ, এই ক্ষেত্রে, এটি ডাটাবেসে N+1 কোয়েরি চালাবে না:

# spec/views/posts/index.html.erb_spec.rb
require 'rails_helper'
 
describe "posts/index.html.erb" do
  it 'lists all posts' do
    post1 = Post.create!(name: 'post1')
    post2 = Post.create!(name: 'post2')
 
    assign(:posts, [post1, post2])
 
    render
 
    expect(rendered).to include('post1')
    expect(rendered).to include('post2')
  end
end

পরীক্ষায় একটি N+1 শনাক্ত করার আরও সুযোগ পাওয়ার জন্য একটি টিপ

আমি প্রতিটি কন্ট্রোলার অ্যাকশনের জন্য কমপক্ষে 1টি অনুরোধ স্পেক তৈরি করার পরামর্শ দিচ্ছি, শুধুমাত্র এটি সঠিক HTTP স্থিতি প্রদান করে কিনা তা পরীক্ষা করার জন্য, তারপর bullet এই মতামতগুলি রেন্ডার করার সময় প্রশ্নগুলি দেখবে৷

অব্যবহৃত এগার লোডিং সনাক্ত করা হচ্ছে

নিচে দেওয়া basic_index কর্ম:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def basic_index
    @posts = Post.all.includes(:comments)
  end
end

এবং নিম্নলিখিত basic_index দেখুন:

# app/views/posts/basic_index.html.erb
 
<h1>Posts</h1>
 
<table>
  <thead>
    <tr>
      <th>Name</th>
    </tr>
  </thead>
 
  <tbody>
    <% @posts.each do |post| %>
    <tr>
      <td><%= post.name %></td>
    </tr>
    <% end %>
  </tbody>
</table>

যখন আমরা নিম্নলিখিত পরীক্ষা চালাই:

# spec/requests/posts_request_spec.rb
require 'rails_helper'
 
RSpec.describe "Posts", type: :request do
  describe "GET /basic_index" do
    it 'lists all posts' do
      post1 = Post.create!
      post2 = Post.create!
 
      get '/posts/basic_index'
 
      expect(response.status).to eq(200)
    end
  end
end

বুলেট নিম্নলিখিত ত্রুটি বাড়াবে:

  1) Posts GET /basic_index lists all posts
     Failure/Error: get '/posts/basic_index'

     Bullet::Notification::UnoptimizedQueryError:
       user: fabioperrella
       GET /posts/basic_index
       AVOID eager loading detected
         Post => [:comments]
         Remove from your query: .includes([:comments])
       Call stack
         /Users/fabioperrella/projects/bullet-test/spec/requests/posts_request_spec.rb:20:in `block (3 levels) in <top (required)>'

এটি ঘটে কারণ এই দৃশ্যের জন্য মন্তব্যের তালিকা লোড করার প্রয়োজন নেই৷

সমস্যা সমাধানের জন্য, আমরা উপরের ত্রুটির নির্দেশনা অনুসরণ করতে পারি এবং প্রশ্নটি সরিয়ে দিতে পারি .includes([:comments]) :

-@posts = Post.all.includes(:comments)
+@posts = Post.all

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

অনুপস্থিত কাউন্টার ক্যাশে সনাক্ত করা হচ্ছে

এই মত একটি নিয়ামক দেওয়া হয়েছে:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index_with_counter
    @posts = Post.all
  end
end

এবং এই মত একটি দৃশ্য:

# app/views/posts/index_with_counter.html.erb
 
<h1>Posts</h1>
 
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Number of comments</th>
    </tr>
  </thead>
 
  <tbody>
    <% @posts.each do |post| %>
    <tr>
      <td><%= post.name %></td>
      <td><%= post.comments.size %></td>
    </tr>
    <% end %>
  </tbody>
</table>

যদি আমরা নিম্নলিখিত অনুরোধ স্পেস চালাই:

describe "GET /index_with_counter" do
  it 'lists all posts' do
    post1 = Post.create!
    post2 = Post.create!
 
    get '/posts/index_with_counter'
 
    expect(response.status).to eq(200)
  end
end

bullet নিম্নলিখিত ত্রুটি উত্থাপন করবে:

1) Posts GET /index_with_counter lists all posts
  Failure/Error: get '/posts/index_with_counter'

  Bullet::Notification::UnoptimizedQueryError:
    user: fabioperrella
    GET /posts/index_with_counter
    Need Counter Cache
      Post => [:comments]
  # ./spec/requests/posts_request_spec.rb:31:in `block (3 levels) in <top (required)>'

এটি ঘটে কারণ এই ভিউটি post.comments.size-এ মন্তব্যের সংখ্যা গণনা করার জন্য 1টি কোয়েরি চালাচ্ছে প্রতিটি পোস্টের জন্য।

Processing by PostsController#index_with_counter as HTML
  ↳ app/views/posts/index_with_counter.html.erb:14
  Post Load (0.4ms)  SELECT "posts".* FROM "posts"
  ↳ app/views/posts/index_with_counter.html.erb:14
   (0.4ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 1]]
  ↳ app/views/posts/index_with_counter.html.erb:17
   (0.1ms)  SELECT COUNT(*) FROM "comments" WHERE "comments"."post_id" = ?  [["post_id", 2]]

এটি ঠিক করার জন্য, আমরা একটি কাউন্টার ক্যাশে তৈরি করতে পারি, যা কিছুটা জটিল হতে পারে, বিশেষ করে যদি উৎপাদন ডাটাবেসে ডেটা থাকে৷

একটি কাউন্টার ক্যাশে একটি কলাম যা আমরা একটি টেবিলে যোগ করতে পারি, যেটি ActiveRecord স্বয়ংক্রিয়ভাবে আপডেট হবে যখন আমরা সংশ্লিষ্ট মডেলগুলি সন্নিবেশ এবং মুছে ফেলি। এই পোস্টে আরো বিস্তারিত আছে. কাউন্টার ক্যাশে কীভাবে তৈরি এবং সিঙ্ক করতে হয় তা জানতে আমি এটি পড়ার পরামর্শ দিই৷

বিকাশে বুলেট ব্যবহার করা

কখনও কখনও, পরীক্ষাগুলি পূর্বে উল্লিখিত সমস্যাগুলি সনাক্ত করতে পারে না, উদাহরণস্বরূপ, যদি পরীক্ষার কভারেজ কম হয়, তাই bullet সক্ষম করা সম্ভব অন্য পরিবেশে বিভিন্ন পন্থা ব্যবহার করে।

উন্নয়ন পরিবেশে, আমরা নিম্নলিখিত কনফিগারেশনগুলি সক্রিয় করতে পারি:

Bullet.alert         = true

তারপর, এটি ব্রাউজারে এইরকম সতর্কতা দেখাবে:

Bullet.add_footer    = true

এটি ত্রুটি সহ পৃষ্ঠায় একটি ফুটার যোগ করবে:

ব্রাউজারের কনসোলে লগ ইন করার জন্য ত্রুটিগুলি সক্ষম করাও সম্ভব:

Bullet.console    = true

এটি এই মত একটি ত্রুটি যোগ করবে:

অ্যাপসিগন্যালের সাথে স্টেজিং-এ বুলেট ব্যবহার করা

মঞ্চায়নে পরিবেশ, আমরা চাই না যে এই ত্রুটি বার্তাগুলি শেষ-ব্যবহারকারীদের দেখানো হোক, তবে অ্যাপ্লিকেশনটিতে পূর্বে উল্লেখিত সমস্যাগুলির মধ্যে একটি হতে শুরু করে কিনা তা জেনে রাখা ভাল হবে৷

একই সময়ে, bullet কর্মক্ষমতা হ্রাস করতে পারে এবং অ্যাপ্লিকেশনে মেমরি খরচ বাড়াতে পারে, তাই এটি শুধুমাত্র অস্থায়ীভাবে স্টেজিং-এ সক্ষম করা ভাল , কিন্তু এটিকে উৎপাদন-এ সক্ষম করবেন না .

মঞ্চায়ন অনুমান করা হচ্ছে পরিবেশ উৎপাদন হিসাবে একই কনফিগারেশন ফাইল ব্যবহার করছে পরিবেশ, যা তাদের মধ্যে পার্থক্য কমাতে একটি ভাল অভ্যাস, আমরা bullet সক্ষম বা নিষ্ক্রিয় করতে একটি পরিবেশ পরিবর্তনশীল ব্যবহার করতে পারি নিম্নরূপ:

# config/environments/production.rb
config.after_initialize do
  Bullet.enabled   = ENV.fetch('BULLET_ENABLED', false)
  Bullet.appsignal = true
end

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

তারপর, যদি bullet দ্বারা একটি সমস্যা সনাক্ত করা হয় , এটি এইরকম একটি ত্রুটির ঘটনা তৈরি করবে:

এই ত্রুটিটি ইউনিফর্ম_নোটিফায়ার রত্ন দ্বারা উত্থাপিত হয়েছে যা bullet থেকে বের করা হয়েছিল .

দুর্ভাগ্যবশত, ত্রুটি বার্তাটি পর্যাপ্ত তথ্য দেখায় না, তবে আমি এটিকে উন্নত করার জন্য একটি পুল অনুরোধ পাঠিয়েছি!

উপসংহার

bullet রত্ন হল একটি দুর্দান্ত সরঞ্জাম যা আমাদের এমন সমস্যাগুলি সনাক্ত করতে সাহায্য করতে পারে যা অ্যাপ্লিকেশনগুলিতে কর্মক্ষমতা হ্রাস করবে৷

প্রোডাকশনে যাওয়ার আগে এই সমস্যাগুলি শনাক্ত করার আরও বেশি সম্ভাবনার জন্য আগে যেমন উল্লেখ করা হয়েছে, ভাল পরীক্ষার কভারেজ রাখার চেষ্টা করুন৷

একটি অতিরিক্ত টিপ হিসাবে, আপনি যদি ডাটাবেসের সাথে সম্পর্কিত পারফরম্যান্স সমস্যার বিরুদ্ধে আরও বেশি সুরক্ষিত থাকতে চান, তাহলে wt-activerecord-index-spy রত্নটি দেখুন, যা সঠিক সূচী ব্যবহার করে না এমন প্রশ্নগুলি সনাক্ত করতে সহায়তা করে৷

পি.এস. আপনি যদি রুবি ম্যাজিক পোস্টগুলি প্রেস থেকে বের হওয়ার সাথে সাথে পড়তে চান তবে আমাদের রুবি ম্যাজিক নিউজলেটারে সাবস্ক্রাইব করুন এবং একটি পোস্ট মিস করবেন না!


  1. Rails লুকানো রত্ন:ActiveSupport StringInquirer

  2. 7 দুর্দান্ত রুবি রত্ন বেশিরভাগ লোকেরা শুনেননি

  3. রুবি 2.6-এ 9টি নতুন বৈশিষ্ট্য

  4. Windows 11 টিপস এবং লুকানো রত্ন আপনার জানা উচিত