Computer >> कंप्यूटर >  >> प्रोग्रामिंग >> Ruby

रूबी के छिपे हुए रत्न:बुलेट

एक डेटाबेस कई अनुप्रयोगों का दिल है, और इसके साथ समस्या होने पर गंभीर प्रदर्शन समस्याएं हो सकती हैं।

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

मैं इस पोस्ट में कॉन्फ़िगरेशन पर अधिक विवरण साझा करने जा रहा हूं। अगर आप उन सभी को देखना चाहते हैं, तो बुलेट के रीडमी पेज पर जाएं।

परीक्षा में बुलेट का उपयोग करना

पहले सुझाए गए कॉन्फ़िगरेशन के साथ, Bullet परीक्षणों में निष्पादित खराब क्वेरी का पता लगाएगा और उनके लिए अपवाद उठाएगा।

अब, कुछ उदाहरण देखते हैं।

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 क्वेरी ट्रिगर नहीं की जाएगी।

नोट:रेल 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

एक परीक्षण का एक और उदाहरण है कि Bullet "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

यह इस तरह एक त्रुटि जोड़ देगा:

Appsignal के साथ स्टेजिंग में Bullet का उपयोग करना

मंचन . में पर्यावरण, हम नहीं चाहते कि ये त्रुटि संदेश अंतिम-उपयोगकर्ताओं को दिखाए जाएं, लेकिन यह जानना बहुत अच्छा होगा कि क्या एप्लिकेशन में पहले बताई गई समस्याओं में से एक है।

साथ ही, bullet प्रदर्शन को कम कर सकता है और एप्लिकेशन में मेमोरी खपत बढ़ा सकता है, इसलिए इसे केवल अस्थायी रूप से स्टेजिंग में सक्षम करना बेहतर है , लेकिन इसे उत्पादन . में सक्षम न करें ।

मंचन मानते हुए पर्यावरण उत्पादन . के समान कॉन्फ़िगरेशन फ़ाइल का उपयोग कर रहा है पर्यावरण, जो उनके बीच के अंतर को कम करने के लिए एक अच्छा अभ्यास है, हम bullet को सक्षम या अक्षम करने के लिए एक पर्यावरण चर का उपयोग कर सकते हैं इस प्रकार है:

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

आपके स्टेजिंग परिवेश में Bullet को मिली समस्याओं के बारे में सूचनाएं प्राप्त करने के लिए, आप उन सूचनाओं को त्रुटियों के रूप में रिपोर्ट करने के लिए AppSignal का उपयोग कर सकते हैं। आपको appsignal . की आवश्यकता होगी मणि आपके प्रोजेक्ट में स्थापित और कॉन्फ़िगर किया गया है। आप रूबी जेम डॉक्स में अधिक विवरण देख सकते हैं।

फिर, यदि bullet . द्वारा किसी समस्या का पता लगाया जाता है , यह इस तरह एक त्रुटि घटना पैदा करेगा:

यह त्रुटि यूनिफ़ॉर्म_नोटिफ़ायर रत्न द्वारा उठाई गई है जिसे bullet . से निकाला गया था ।

दुर्भाग्य से, त्रुटि संदेश पर्याप्त जानकारी नहीं दिखाता है, लेकिन मैंने इसे सुधारने के लिए एक पुल अनुरोध भेजा है!

निष्कर्ष

bullet मणि एक बेहतरीन टूल है जो हमें उन समस्याओं का पता लगाने में मदद कर सकता है जो अनुप्रयोगों में प्रदर्शन को खराब कर देंगी।

जैसा कि पहले उल्लेख किया गया है, अच्छा परीक्षण कवरेज रखने की कोशिश करें, ताकि उत्पादन में जाने से पहले इन समस्याओं का पता लगाने की अधिक संभावना हो।

एक अतिरिक्त युक्ति के रूप में, यदि आप डेटाबेस से संबंधित प्रदर्शन समस्याओं से और भी अधिक सुरक्षित रहना चाहते हैं, तो wt-activerecord-index-spy gem पर एक नज़र डालें, जो उन प्रश्नों का पता लगाने में मदद करता है जो उचित अनुक्रमणिका का उपयोग नहीं कर रहे हैं।

पी.एस. यदि आप रूबी मैजिक की पोस्ट प्रेस से बाहर होते ही पढ़ना चाहते हैं, तो हमारे रूबी मैजिक न्यूजलेटर की सदस्यता लें और एक भी पोस्ट मिस न करें!


  1. 7 महान रूबी रत्न के बारे में ज्यादातर लोगों ने नहीं सुना है

    सबसे अच्छे रूबी रत्न कौन से हैं जिनका उपयोग आप अपनी रेल परियोजनाओं में कर सकते हैं? इस लेख में आप यही पाएंगे! मैं आपको 7 रत्न देने जा रहा हूँ, लेकिन वह पुराने रत्न नहीं जिन्हें आपने लाखों बार देखा है , मैं आपके साथ कुछ रत्न साझा करने जा रहा हूँ जो बहुत उपयोगी हैं, लेकिन कम ज्ञात हैं। लेकिन इससे प

  1. रूबी में 9 नई सुविधाएँ 2.6

    रूबी का एक नया संस्करण नई सुविधाओं और प्रदर्शन में सुधार के साथ आ रहा है। क्या आप परिवर्तनों के साथ बने रहना चाहेंगे? आइए एक नज़र डालते हैं! अंतहीन रेंज रूबी 2.5 और पुराने संस्करण पहले से ही अंतहीन श्रेणी के एक रूप का समर्थन करते हैं (Float::INFINITY के साथ) ), लेकिन रूबी 2.6 इसे अगले स्तर पर ले

  1. Windows 11 युक्तियाँ और छिपे हुए रत्न जिन्हें आपको जानना चाहिए

    विंडोज 11 कई नई सुविधाओं और सुधारों के साथ संगत उपकरणों के लिए मुफ्त अपग्रेड के रूप में उपलब्ध है। एक नया पुन:डिज़ाइन किया गया प्रारंभ मेनू टास्कबार है, एंड्रॉइड ऐप समर्थन के साथ एक बेहतर Microsoft स्टोर, एकीकृत Microsoft टीम, स्नैप लेआउट, विजेट और बहुत कुछ। लेकिन रेडमंड जायंट द्वारा आधिकारिक तौर पर