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ụ:
1 2 3 4 | shop_ids<span class="token punctuation">.</span>map <span class="token keyword">do</span> <span class="token operator">|</span>shop_id<span class="token operator">|</span> products<span class="token punctuation">.</span>select <span class="token punctuation">{</span> <span class="token operator">|</span>p<span class="token operator">|</span> p<span class="token punctuation">.</span>shop_id <span class="token operator">==</span> shop_id <span class="token punctuation">}</span><span class="token punctuation">.</span>first <span class="token keyword">end</span> |
Code sẽ chạy nhanh hơn nếu viết thế này:
1 2 3 4 | shop_ids<span class="token punctuation">.</span>map <span class="token keyword">do</span> <span class="token operator">|</span>shop_id<span class="token operator">|</span> products<span class="token punctuation">.</span><span class="token function">where</span><span class="token punctuation">(</span>shop_id<span class="token punctuation">:</span> shop_id<span class="token punctuation">)</span><span class="token punctuation">.</span>first <span class="token keyword">end</span> |
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ụ:
1 2 3 4 | 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.
1 2 3 4 | <span class="token constant">Order</span><span class="token punctuation">.</span><span class="token function">where</span><span class="token punctuation">(</span>number<span class="token punctuation">:</span> <span class="token string">'R545612547'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>pluck <span class="token symbol">:id</span> <span class="token comment"># SQL (0.8ms) SELECT `orders`.`id` FROM `orders` WHERE `orders`.`number` = 'R545612547' ORDER BY orders.created_at DESC</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span> |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token constant">Benchmark</span><span class="token punctuation">.</span>ips <span class="token keyword">do</span> <span class="token operator">|</span>x<span class="token operator">|</span> x<span class="token punctuation">.</span><span class="token function">report</span><span class="token punctuation">(</span><span class="token string">"SQL sum"</span><span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token constant">Loan</span><span class="token punctuation">.</span><span class="token function">sum</span><span class="token punctuation">(</span><span class="token symbol">:balance</span><span class="token punctuation">)</span> <span class="token keyword">end</span> x<span class="token punctuation">.</span><span class="token function">report</span><span class="token punctuation">(</span><span class="token string">"Ruby sum"</span><span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token constant">Loan</span><span class="token punctuation">.</span><span class="token function">sum</span><span class="token punctuation">(</span><span class="token operator">&</span><span class="token symbol">:balance</span><span class="token punctuation">)</span> <span class="token comment"># Same as: Loan.all.map { |loan| loan.balance }.sum</span> <span class="token keyword">end</span> x<span class="token punctuation">.</span>compare<span class="token operator">!</span> <span class="token keyword">end</span> <span class="token comment"># Comparison:</span> <span class="token comment"># SQL sum: 7.89 i/s</span> <span class="token comment"># Ruby sum: 0.03 i/s - 209.85x slower</span> |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token constant">Benchmark</span><span class="token punctuation">.</span>ips <span class="token keyword">do</span> <span class="token operator">|</span>x<span class="token operator">|</span> x<span class="token punctuation">.</span><span class="token function">report</span><span class="token punctuation">(</span><span class="token string">"SQL max"</span><span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token constant">Loan</span><span class="token punctuation">.</span><span class="token function">maximum</span><span class="token punctuation">(</span><span class="token symbol">:amount</span><span class="token punctuation">)</span> <span class="token keyword">end</span> x<span class="token punctuation">.</span><span class="token function">report</span><span class="token punctuation">(</span><span class="token string">"Ruby max"</span><span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token constant">Loan</span><span class="token punctuation">.</span><span class="token function">pluck</span><span class="token punctuation">(</span><span class="token symbol">:amount</span><span class="token punctuation">)</span><span class="token punctuation">.</span>max <span class="token keyword">end</span> x<span class="token punctuation">.</span>compare<span class="token operator">!</span> <span class="token keyword">end</span> <span class="token comment"># Comparison:</span> <span class="token comment"># SQL max: 541.9 i/s</span> <span class="token comment"># Ruby max: 0.5 i/s - 1113.47x slower</span> |
Dùng ActiveRecord::Calculations#minimum
thay Enumerable#min
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token constant">Benchmark</span><span class="token punctuation">.</span>ips <span class="token keyword">do</span> <span class="token operator">|</span>x<span class="token operator">|</span> x<span class="token punctuation">.</span><span class="token function">report</span><span class="token punctuation">(</span><span class="token string">"SQL min"</span><span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token constant">Loan</span><span class="token punctuation">.</span><span class="token function">minimum</span><span class="token punctuation">(</span><span class="token symbol">:amount</span><span class="token punctuation">)</span> <span class="token keyword">end</span> x<span class="token punctuation">.</span><span class="token function">report</span><span class="token punctuation">(</span><span class="token string">"Ruby min"</span><span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token constant">Loan</span><span class="token punctuation">.</span><span class="token function">pluck</span><span class="token punctuation">(</span><span class="token symbol">:amount</span><span class="token punctuation">)</span><span class="token punctuation">.</span>min <span class="token keyword">end</span> x<span class="token punctuation">.</span>compare<span class="token operator">!</span> <span class="token keyword">end</span> <span class="token comment"># Comparison:</span> <span class="token comment"># SQL min: 533.3 i/s</span> <span class="token comment"># Ruby min: 0.5 i/s - 1017.21x slower</span> |
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.
1 2 3 4 | <span class="token constant">Product</span><span class="token punctuation">.</span>all<span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>product<span class="token operator">|</span> product<span class="token punctuation">.</span><span class="token function">update_column</span><span class="token punctuation">(</span><span class="token symbol">:stock</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span> <span class="token keyword">end</span> |
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):
1 2 3 4 | <span class="token constant">Product</span><span class="token punctuation">.</span>find_each <span class="token keyword">do</span> <span class="token operator">|</span>product<span class="token operator">|</span> product<span class="token punctuation">.</span><span class="token function">update_column</span><span class="token punctuation">(</span><span class="token symbol">:stock</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span> <span class="token keyword">end</span> |
Để custom số lượng record:
1 2 3 4 | <span class="token constant">Product</span><span class="token punctuation">.</span><span class="token function">find_each</span><span class="token punctuation">(</span>batch_size<span class="token punctuation">:</span> <span class="token number">200</span><span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token operator">|</span>product<span class="token operator">|</span> product<span class="token punctuation">.</span><span class="token function">update_column</span><span class="token punctuation">(</span><span class="token symbol">:stock</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span> <span class="token keyword">end</span> |
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.
1 2 3 4 | <span class="token constant">Product</span><span class="token punctuation">.</span><span class="token function">find_each</span><span class="token punctuation">(</span>batch_size<span class="token punctuation">:</span> <span class="token number">200</span><span class="token punctuation">,</span> start<span class="token punctuation">:</span> <span class="token number">5000</span><span class="token punctuation">)</span> <span class="token keyword">do</span> <span class="token operator">|</span>product<span class="token operator">|</span> product<span class="token punctuation">.</span><span class="token function">update_column</span><span class="token punctuation">(</span><span class="token symbol">:stock</span><span class="token punctuation">,</span> <span class="token number">50</span><span class="token punctuation">)</span> <span class="token keyword">end</span> |
Tham khảo: https://www.fastruby.io/blog/performance/rails/writing-fast-rails.html