Tips cải thiện hiệu năng code Rails
Bài đăng này đã không được cập nhật trong 4 năm
Dùng where
thay select
Khi thực hiện nhiều tính toán, càng load ít dữ liệu vào bộ nhớ càng tốt, luôn luôn dùng SQL query thay cho object method.
Ví dụ:
shop_ids.map do |shop_id|
products.select { |p| p.shop_id == shop_id }.first
end
Code sẽ chạy nhanh hơn nếu viết thế này:
shop_ids.map do |shop_id|
products.where(shop_id: shop_id).first
end
Dùng pluck
thay map
Nếu chỉ quan tâm một vài giá trị mỗi hàng, nên dùng pluck thay map
Ví dụ:
Order.where(number: 'R545612547').map &:id
# Order Load (5.0ms) SELECT `orders`.* FROM `orders` WHERE `orders`.`number` = 'R545612547' ORDER BY orders.created_at DESC
=> [1]
Map sẽ load tất cả dữ liệu vào bộ nhớ trước, sau đó thực hiện lấy id, pluck nhanh hơn vì nó không phải load dữ liệu vào trước.
Order.where(number: 'R545612547').pluck :id
# SQL (0.8ms) SELECT `orders`.`id` FROM `orders` WHERE `orders`.`number` = 'R545612547' ORDER BY orders.created_at DESC
=> [1]
Dùng ActiveRecord::Calculations#sum
thay Enumerable#sum
Thông thường khi thực hiện tính toán đôi khi chúng ta sử dụng Enumerable::sum
để tính tổng, đây là một sai lầm thường gặp bởi vì ActiveRecord::Calculations
đã cho ta cách để tính mà không cần load một đống dữ liệu vào bộ nhớ trước. Nếu bạn muốn thưc hiện tính toán theo cách của Rails, ActiveRecord::Calculations
là lựa chọn tốt nhất để sử dụng.
Benchmark.ips do |x|
x.report("SQL sum") do
Loan.sum(:balance)
end
x.report("Ruby sum") do
Loan.sum(&:balance)
# Same as: Loan.all.map { |loan| loan.balance }.sum
end
x.compare!
end
# Comparison:
# SQL sum: 7.89 i/s
# Ruby sum: 0.03 i/s - 209.85x slower
Dùng ActiveRecord::Calculations#maximum
thay Enumerable#max
Như đã giải thích ở trên, ta nên dùng ActiveRecord::Calculations
để thực thiện việc tính toán
Benchmark.ips do |x|
x.report("SQL max") do
Loan.maximum(:amount)
end
x.report("Ruby max") do
Loan.pluck(:amount).max
end
x.compare!
end
# Comparison:
# SQL max: 541.9 i/s
# Ruby max: 0.5 i/s - 1113.47x slower
Dùng ActiveRecord::Calculations#minimum
thay Enumerable#min
Benchmark.ips do |x|
x.report("SQL min") do
Loan.minimum(:amount)
end
x.report("Ruby min") do
Loan.pluck(:amount).min
end
x.compare!
end
# Comparison:
# SQL min: 533.3 i/s
# Ruby min: 0.5 i/s - 1017.21x slower
Dùng Model.find_each
thay Model.all.each
Một sai lầm rất hay gặp đó là dùng ActiveRecord::Scoping::Named::ClassMethods#all
+ ActiveRecord::Result#each
để lặp qua một bảng có hàng ngàn record.
Product.all.each do |product|
product.update_column(:stock, 50)
end
Method all
sẽ load tất cả dữ liệu vào bộ nhớ trước, điều này có thể dẫn đến nhiều vấn đề về bộ nhớ. Để cho rõ hơn, thì vấn để ở đây không phải là method all
mà là số lượng records nó load ra.
Để xử lý, ta load records vào từng batch (mặc định là 1000 record):
Product.find_each do |product|
product.update_column(:stock, 50)
end
Để custom số lượng record:
Product.find_each(batch_size: 200) do |product|
product.update_column(:stock, 50)
end
Có thể chỉ định điểm bắt đầu của batch, điều này hữu ích nếu có nhiều worker xử lý trong cùng 1 queue, bạn có thể cho 1 worker xử lý records từ id 1-5000, và worker còn lại xử lý record có id từ 500 trở đi.
Product.find_each(batch_size: 200, start: 5000) do |product|
product.update_column(:stock, 50)
end
Tham khảo: https://www.fastruby.io/blog/performance/rails/writing-fast-rails.html
All rights reserved