Rails - so sánh where vs select khi thao tác trên memory và performance.
This post hasn't been updated for 6 years
Bài toán
Tôi đã viết một file seed trong đó nó cần phải được thiết lập để cập nhật chi tiết liên quan đến service_id trong một bảng. Task hiện tại của tôi là cần tìm trên 10.000 services và sau đó cập nhật các bản ghi.
Viết một cách theo như tư duy truyền thống thì tôi có thể lấy tất cả các services lưu vào một biến và sau đó lặp qua từng bản ghi đẻ thao tác(Cách này đương nhiên là tồi tệ nếu số lượng bản ghi lớn nhưng tôi vẵn muốn thử chạy để xem nó tệ như thế nào)
services = Service.all
services.each do |service|
#Do something on service
end
Để hiểu và so sánh rõ hơn về sự tác động thì tôi muốn nhìn và đánh giá trên 2 tiêu chí:
- Bộ nhớ được cấp cho biến.
- Thời gian thực thi câu lênh SQL
Tiêu chí 1
Có một mudule khá là hay trong Ruby để kiểm tra bộ nhớ được sử dụng bởi một biến đó là Object Space
. Để sử dụng được nó bên trong Rails Console hãy làm như bên dưới.
require 'objspace'
Module này bao gôm rất nhiều điều hay và đáng quan tâm để thực sự nghiên cứu một đối tượng, nhưng trước hết hãy tập trung vào vấn đề đang được xem xét ở trên. Phương thức memsize_of
giúp chúng ta biết kích thước của một đối tượng trên memory và đơn vị là byte.
ObjectSpace.memsize_of(services)
#=> 120632
Như ví dụ trên là biến services
đang chưa tất cả thông tin của service
, vì vậy tôi quyết định thử sửa và chỉ chọn id
của service bằng cách sử dụng select
method của Rails query method(ActiveRecord).
services_id = Service.select(:id).all
Tôi đã dự đoán rằng cách làm này sẽ làm giảm size đi và nhỏ hơn rất nhiều so với cách trên, nhưng chắc tôi đã nhầm. Bên dưới là kết quả
ObjectSpace.memsize_of(services_id)
#=> 120632
Điều tôi không ngờ tới là kích thước của 2 đối tượng theo 2 cách trên là chính xác như nhau. Như vậy ActiveRecord
đã xử lý cả 2 truy vấn là giống nhau trên cả các đối tượng và thuộc tính.
Bây giờ, tôi nghĩ rằng thử tối ưu hóa vấn đề này thì hãy sử dụng method where
trong ActiveRecord
. Tôi đã biết rõ số lượng bản ghi services được lấy ở bên trên nên sẽ thực hiện truy vấn với đúng số bản ghi đó.
services_where = Service.where(:id => 1..11000)
Và hãy đừng ngạc nhiên với kết quả bên dưới
ObjectSpace.memsize_of(services_where)
#=> 264
Theo các ví dụ trên và tìm hiểu thì tôi đã phần nào hiểu được hành vi của ActiveRecord
, phương thức where
trả về một ActiveRecord Relation
có đặc tính Lazy loading
. Nói một cách đơn giản hơn là truy vấn đó thực tế không được thực hiện cho đến khi người dùng sử dụng nó, và đó là một đặc điểm nội bật giúp chung ta tiết kiệm bộ nhớ quý báu của mình.
Tiêu chí 2
Bây giờ để nghiên cứu thao tác SQL (SQL impact) của những cách tương tự để có được dữ liệu, tôi đã đọc và chuẩn bị để sử dụng module Benchmark
mà Ruby cung cấp. Benchmark module có phương thức measure
sẽ thống kê tất cả các loại thời gian cho chúng ta.
Benchmark.measure { "a"*1_000_000}
#=> 1.166667 0.050000 1.216667 ( 0571355 )
Dòng bên dưới là kết quả thực tế khi thực hiện đoạn mã trên và hiển thị 4 loại khác nhau(user/system/total/real). Ý nghĩa của 4 loại này như bên dưới:
- user: Tổng thời gian của người dùng
- system: Thời gian CPU hệ thống
- total: Tổng của người dùng và hệ thống
- real: Thời gian thực tế cần để xử lý cho hoạt động này.
Benchmark.measure { "a"*1_000_000}
user system total (user + system) real
#=> 1.166667 0.050000 1.216667 ( 0.571355 )
Bây giờ tôi sẽ chạy và phân tích áp dụng với ví dụ giống như kiểm tra bộ nhớ ở trên.
Benchmark.measure { Service.all }
Service Load (206.2ms) SELECT `services`.* FROM `services`
#=> 0.490000 0.040000 0.530000 ( 0.552428 )
Benchmark.measure { Service.select(:id).all }
Service Load (5.7ms) SELECT id FROM `services`
#=> 0.060000 0.000000 0.060000 ( 0.063805 )
Benchmark.measure { Service.where(:id => 1..11000) }
#=> 0.000000 0.000000 0.000000 ( 0.000088 )
Okay, nhìn vào kết quả trên chúng ta có thể thấy thời gian giảm đang kể theo thứ tự từ trên xuống dưới theo (lấy tất cả/ sử dụng select/ sử dụng where). Và cuối cùng là khẳng định thêm where
method đã không thực hiện bất kỳ truy vấn nào đến database mà trong nó ẩn chưa cơ chế lazy loading .
Trên đây là một so sánh nhỏ về select
method và where
method dựa vào 2 tiêu chí như bên trên. Benchmark modelues như là một công cụ rất hữu ích cho việc so sánh này, tôi hy vọng bạn đọc có thể áp dụng nó vào các bài so sánh khác và cùng viết ra để mọi người tham khảo. Hy vọng bài so sanh sẽ giúp ích cho mọi người. Thanks all!
All Rights Reserved