+1

Rails - so sánh where vs select khi thao tác trên memory và performance.

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í:

  1. Bộ nhớ được cấp cho biến.
  2. 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:

  1. user: Tổng thời gian của người dùng
  2. system: Thời gian CPU hệ thống
  3. total: Tổng của người dùng và hệ thống
  4. 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

Viblo
Let's register a Viblo Account to get more interesting posts.