Computer >> Máy Tính >  >> Lập trình >> Ruby

Khắc phục sự cố Hiệu suất ActiveRecord

ActiveRecord là tính năng kỳ diệu nhất của Ruby on Rails. Chúng tôi thường không cần phải lo lắng về hoạt động bên trong của nó, nhưng khi chúng tôi làm vậy, đây là cách AppSignal có thể giúp chúng tôi biết những gì đang diễn ra.

ActiveRecord là gì?

Để nói về ActiveRecord, trước tiên chúng ta cần nghĩ đến các framework, cụ thể là về các framework MVC. MVC là viết tắt của Model-View-Controller và nó là một mẫu thiết kế phần mềm phổ biến cho các ứng dụng web và đồ họa.

Các khuôn khổ MVC bao gồm:

  • Mô hình :xử lý logic nghiệp vụ và tính ổn định của dữ liệu.
  • Xem :điều khiển lớp bản trình bày và vẽ giao diện người dùng.
  • Bộ điều khiển :gắn kết mọi thứ với nhau.

ActiveRecord là mô hình thành phần trong khuôn khổ Ruby in Rails. Nó giới thiệu một lớp trừu tượng giữa mã và dữ liệu, vì vậy chúng tôi không phải tự viết mã SQL. Mỗi mô hình được ánh xạ tới một bảng và cung cấp các phương pháp khác nhau để thực hiện các hoạt động CRUD (Tạo, Đọc, Cập nhật và Xóa).

Giám sát ActiveRecord Với AppSignal

Sự trừu tượng cảm thấy kỳ diệu - chúng giúp chúng ta bỏ qua những chi tiết mà chúng ta không cần biết và tập trung vào nhiệm vụ trước mắt. Nhưng khi mọi thứ không hoạt động như mong đợi, sự phức tạp mà chúng thêm vào có thể khiến việc xác định nguyên nhân gốc rễ trở nên khó khăn hơn. AppSignal có thể cung cấp cho chúng tôi bảng phân tích chi tiết về những gì đang thực sự xảy ra trong Rails.

Biểu đồ thời gian phản hồi

Hãy cùng tìm hiểu vấn đề đầu tiên. Khắc phục sự cố về hiệu suất là một quá trình lặp đi lặp lại. Sau khi bạn báo cáo ứng dụng Rails của mình cho AppSignal, hãy chuyển đến Sự cố > Hiệu suất .

Biểu đồ Thời gian phản hồi sẽ hiển thị phần trăm thời gian phản hồi cho mỗi vùng tên. Các sự kiện ActiveRecord được tự động gán cho không gian tên yêu cầu hoặc công việc nền mà các truy vấn được thực thi.

Tiếp theo, hãy nhìn vào biểu đồ Nhóm sự kiện. Nó cho biết lượng thời gian được tiêu thụ theo danh mục. Kiểm tra lượng thời gian tương đối được sử dụng bởi active_record . Kiểm tra việc sử dụng trong tất cả các không gian tên của bạn.

Biểu đồ sẽ ngay lập tức cho chúng ta biết nơi chúng ta nên tập trung nỗ lực tối ưu hóa mã của mình.

Khi bạn đang ở trong trang tổng quan về biểu đồ hiệu suất, hãy kiểm tra thời gian phản hồi và thông lượng để đảm bảo không có bất kỳ hoạt động nào cao hơn bình thường trên ứng dụng của bạn.

Trang tổng quan truy vấn chậm

Bây giờ chúng tôi đã xác định được rằng vấn đề là liên kết dữ liệu, hãy xem liệu chúng tôi có thể phóng to để xác định nguyên nhân gốc rễ hay không.

Mở Cải thiện > Truy vấn chậm bảng điều khiển. Trang này hiển thị danh sách các truy vấn SQL được xếp hạng theo mức độ ảnh hưởng đến thời gian tổng thể. Tất cả các truy vấn bắt nguồn từ ActiveRecord được hiển thị dưới dạng sql.active_record sự kiện.

Hãy thử nhấp vào truy vấn trên cùng để xem chi tiết của nó. Trang tổng quan hiển thị thời lượng trung bình và văn bản truy vấn.

Cuộn bên dưới sẽ hiển thị cho bạn thời gian phản hồi của truy vấn trong vài giờ qua và hành động ban đầu.

Bạn có thể thấy rằng một số hành động có một sự cố liên quan. Điều này có nghĩa là AppSignal đã tạo ra một sự cố về hiệu suất trong khi truy vấn đang chạy, nhưng điều đó không nhất thiết có nghĩa là ActiveRecord là nguyên nhân của nó.

Trang tổng quan về Đo lường Hiệu suất

Sự cố đo lường hiệu suất được mở khi AppSignal ghi lại một điểm cuối hoặc công việc nền mới.

Sự cố nằm trên Hiệu suất > Danh sách sự cố bảng điều khiển.

Trang sự cố hiển thị thời gian đã trôi qua và số lượng phân bổ cho từng thành phần MVC. Sự cố ActiveRecord sẽ thể hiện thời lượng dài trong active_record danh mục.

Dòng thời gian của sự kiện cho biết sự kiện diễn ra như thế nào theo thời gian.

Tìm các vấn đề về ActiveRecord

Trong phần này, chúng ta sẽ xem cách AppSignal có thể giúp chúng ta xác định một số vấn đề ActiveError phổ biến.

Chọn các cột có liên quan

Cơ sở dữ liệu khôn ngoan nói rằng chúng ta nên luôn truy xuất các cột cần thiết cho công việc. Ví dụ:thay vì SELECT * FROM people chúng ta nên SELECT first_name, surname, birthdate FROM people . Đó là tất cả tốt và tốt, nhưng làm thế nào để chúng tôi làm điều đó trên Rails?

Theo mặc định, ActiveRecord truy xuất tất cả các cột.

Person.all.each {
      # process data
}

May mắn thay, chúng tôi có select phương pháp để chọn và chọn các cột bắt buộc:

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

Có vẻ như tôi đang bị ám ảnh về những chi tiết nhỏ. Nhưng trên các bảng rộng, việc chọn tất cả các cột chỉ là lãng phí. Bạn sẽ nhận thấy rằng khi điều này xảy ra, ActiveRecord phân bổ phần lớn bộ nhớ:

N + 1 sự cố

Sự cố N + 1 xảy ra khi ứng dụng lấy một tập hợp các bản ghi từ cơ sở dữ liệu và lặp qua nó. Điều này khiến ứng dụng thực hiện N + 1 truy vấn, trong đó N là số hàng thu được ban đầu. Như bạn có thể tưởng tượng, mô hình này có tỷ lệ kém khi bảng lớn lên. Đây là một vấn đề tai hại đến nỗi AppSignal đặc biệt cảnh báo bạn về nó:

N + 1 sự cố thường xuất hiện với các mô hình liên quan. Hãy tưởng tượng chúng ta có một mô hình Người:

class Person < ApplicationRecord
    has_many :addresses
end

Mỗi người có thể có nhiều địa chỉ:

Địa chỉ lớp
class Address < ApplicationRecord
    belongs_to :person
end

Cách truy xuất dữ liệu đơn giản nhất dẫn đến vấn đề N + 1:

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

Bạn có thể thấy trong AppSignal rằng ứng dụng đang chạy SELECT mỗi người:

Cách khắc phục cho trường hợp cụ thể này rất đơn giản:sử dụng bao gồm, lệnh cho ActiveRecord tối ưu hóa truy vấn cho các cột cần thiết:

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

Bây giờ chúng tôi có hai truy vấn thay vì 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, . . .)

Thực thi các truy vấn cần thiết ít nhất trên mỗi bảng

Điều này đôi khi bị nhầm lẫn với vấn đề N + 1, nhưng nó hơi khác một chút. Khi chúng tôi truy vấn một bảng, chúng tôi nên truy xuất tất cả dữ liệu mà chúng tôi nghĩ rằng chúng tôi sẽ cần để giảm thiểu các thao tác đọc. Tuy nhiên, có rất nhiều mã trông có vẻ vô tội kích hoạt các truy vấn thừa. Ví dụ:hãy xem cách count luôn dẫn đến SELECT COUNT(*) truy vấn trong dạng xem sau:

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

Bây giờ ActiveRecord thực hiện hai truy vấn:

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

Trong AppSignal, dấu hiệu bạn sẽ nhận thấy là có hai active_record sự kiện trên cùng một bảng:

Thực tế là chúng tôi không cần hai truy vấn; chúng tôi đã có tất cả dữ liệu chúng tôi cần trong bộ nhớ. Trong trường hợp này, giải pháp là hoán đổi count với size :

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

Bây giờ chúng ta có một SELECT duy nhất , như nó phải là:

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

Một giải pháp khác là sử dụng tải trước để lưu dữ liệu vào bộ nhớ đệm.

Tính toán dữ liệu tổng hợp trong đường dẫn

Tổng hợp được sử dụng để tính toán một giá trị dựa trên một tập hợp dữ liệu. Cơ sở dữ liệu rất tốt khi làm việc với các tập dữ liệu lớn. Đây là những gì họ làm và chúng tôi sử dụng chúng để làm gì. Mặt khác, việc sử dụng Rails để tổng hợp không mở rộng quy mô vì nó yêu cầu lấy tất cả các bản ghi từ cơ sở dữ liệu, lưu giữ chúng trong bộ nhớ và sau đó tính toán bằng cách sử dụng mã cấp cao.

Chúng tôi đang tổng hợp trong Rails bất cứ khi nào chúng tôi sử dụng các hàm Ruby như max , min hoặc sum qua các phần tử ActiveRecord hoặc các bảng liệt kê khác.

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

May mắn thay, các mô hình ActiveRecord bao gồm các phương thức cụ thể ánh xạ tới các hàm tổng hợp trong cơ sở dữ liệu. Ví dụ:truy vấn sau ánh xạ tới SELECT SUM(number) FROM ... , chạy nhanh hơn và rẻ hơn nhiều so với ví dụ trước:

# 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"

Nếu bạn cần các hàm tổng hợp phức tạp hơn hoặc kết hợp, bạn có thể cần bao gồm một chút mã SQL thô:

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

Quản lý các giao dịch lớn

Các giao dịch SQL đảm bảo cập nhật nhất quán và nguyên tử. Khi chúng tôi sử dụng một giao dịch để thực hiện thay đổi, mọi hàng đều được cập nhật thành công hoặc toàn bộ nội dung được khôi phục lại. Trong mọi trường hợp, cơ sở dữ liệu luôn duy trì ở trạng thái nhất quán.

Chúng tôi có thể nhóm một loạt các thay đổi trong một giao dịch duy nhất với 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

Có rất nhiều trường hợp hợp pháp để sử dụng các giao dịch lớn. Tuy nhiên, những điều này có nguy cơ làm chậm cơ sở dữ liệu. Bên cạnh đó, các giao dịch vượt quá ngưỡng nhất định sẽ dẫn đến việc cơ sở dữ liệu từ chối chúng.

Dấu hiệu đầu tiên của các giao dịch quá lớn là dành nhiều thời gian cho commit transaction sự kiện:

Để ngăn chặn các vấn đề về cấu hình trên chính cơ sở dữ liệu, giải pháp là chia nhỏ giao dịch thành các phần nhỏ hơn.

Giám sát Cơ sở dữ liệu

Đôi khi, mặc dù chúng tôi tìm kiếm mã, chúng tôi không thể tìm thấy bất kỳ điều gì sai trái với nó. Sau đó, có thể có sự cố trong chính cơ sở dữ liệu. Công cụ cơ sở dữ liệu rất phức tạp và nhiều thứ có thể gặp trục trặc:bộ nhớ thấp, cài đặt mặc định, chỉ mục bị thiếu, công việc sao lưu theo lịch trình bất tiện. Bức tranh không thể hoàn chỉnh trừ khi chúng tôi nhận được thông tin về máy đang chạy cơ sở dữ liệu.

Nếu chúng tôi đang chạy cơ sở dữ liệu của riêng mình, chúng tôi có thể cài đặt tác nhân độc lập để nắm bắt các chỉ số máy chủ lưu trữ. Để tìm hiểu thêm về cách sử dụng tác nhân độc lập, hãy đọc:Giám sát mọi hệ thống với StatsD và AppSignal’s Standalone Agent.

Các dấu hiệu sau đây có thể cho thấy điều gì đó đang xảy ra với cơ sở dữ liệu. Đi tới Kiểm tra > Số liệu Máy chủ lưu trữ bảng điều khiển để xem việc sử dụng tài nguyên trong máy chủ của bạn:

  • Sử dụng bộ nhớ cao :cơ sở dữ liệu cần rất nhiều bộ nhớ - nhiều hơn hầu hết các hệ thống khác - để chạy chính xác. Khi tập dữ liệu phát triển, các yêu cầu về bộ nhớ thường mở rộng theo. Thỉnh thoảng, chúng tôi cần bổ sung thêm bộ nhớ hoặc chia nhỏ tập dữ liệu giữa các máy khác nhau.
  • Hoán đổi sử dụng :lý tưởng nhất là các máy cơ sở dữ liệu không cần bộ nhớ hoán đổi. Hoán đổi giết chết hiệu suất cơ sở dữ liệu. Sự hiện diện của nó có nghĩa là có vấn đề về cấu hình hoặc bộ nhớ chuyên sâu hơn.
  • Sử dụng I / O cao :đỉnh hoạt động của đĩa có thể do các tác vụ bảo trì như lập chỉ mục lại hoặc sao lưu cơ sở dữ liệu. Làm những công việc này trong giờ cao điểm chắc chắn sẽ đạt được hiệu suất.

👋 Nếu bạn thích bài viết này, chúng tôi đã viết nhiều hơn về hiệu suất của Ruby (trên Rails), hãy xem danh sách kiểm tra giám sát hiệu suất Ruby của chúng tôi.

Kết luận

Chẩn đoán các vấn đề về hiệu suất không bao giờ là một nhiệm vụ dễ dàng. Hôm nay, chúng ta đã học cách sử dụng Appsignal để nhanh chóng xác định nguồn gốc của sự cố.

Hãy tiếp tục tìm hiểu về AppSignal và Ruby on Rails:

  • Hiệu suất ActiveRecord:N + 1 truy vấn phản vật chất
  • Đường ray nhanh chóng:Tối ưu hóa hiệu suất xem của bạn
  • Giới thiệu về các mẫu Ruby on Rails và các mẫu chống lại

Tái bút. Nếu bạn muốn đọc các bài đăng của Ruby Magic ngay khi chúng xuất hiện trên báo chí, hãy đăng ký bản tin Ruby Magic của chúng tôi và không bao giờ bỏ lỡ một bài đăng nào!