Tìm N+1 với gem prosopite và rspec

Đăng bởi Lưu Đại vào ngày 21-04-2023

1. Gem Prosopite 

- Tại sao lại không sử dụng gem Bullet? 

Gem Load Error is: Bullet does not support active_record 7.0.3.1 yet
tại lúc mình viết bài này gem bullet không hỗ trợ cho phiên bản Rails dự án mình (7.0.3.1)
- Gem prosopite cũng giống như gem Bullet dùng để tìm kiếm N+1 trong dự án. Gem này tìm N+1 theo log của dự án vì thế nếu có lỗi N+1 tiềm ẩn nhưng dữ liệu lại chỉ có 1 bản ghi con + 1 bản ghi cha thì nó cũng không tìm ra được đâu. 
- Gem prosopite có thể dùng để tìm N+1 khi chạy Unit test, tuy nhiên để nó tìm được hết các lỗi N+1 thì mình cũng phải viết đủ case unit test cho nó. 

2. N+1 query

- N+1 gồm 2 phần: 1 câu query cha và theo sau bởi N câu query con. Mặc định Rails sử dụng lazy load lúc nào cần và cần cái gì thì nó sẽ tạo truy vấn lấy đúng dữ liệu đó ra thôi, không lấy thừa. Như vậy nếu lấy N bản ghi con thì nó sẽ tạo N câu query để truy vấn (do tại 1 thời điểm nó chỉ cần đúng 1 bản ghi con).
- Lazy load không phải lúc nào cũng xấu, ví dụ có bản ghi cha ta cần phải lấy bản ghi con có bản ghi cha lại không cần lấy bản ghi con hoặc chỉ lấy 1 phần bản ghi con (10 bản ghi / 3000 bản ghi). Những lúc như thế này lazy load sẽ giúp ta tiết kiệm nhiều thời gian truy vấn hơn.
 

3. Tìm N+1 query thông qua gem prosopite và Rspec

- Gem prosopite có 2 mode là chỉ warning khi  gặp N+1 và raise exception khi gặp N+1. Warning thì nó sẽ báo log đỏ trong log. Để quản lý chuyển qua lại 2 mode này thì mình dùng 1 biến môi trường STRICT_N_PLUS_ONE. Khi mình set giá trị cho biến này thì nó sẽ raise exception khi gặp N+1 còn ngược lại thì không. 
- Để gem check N+1 cho các request thông thường, mình thêm before_action để Prosopite chạy mỗi khi bắt đầu request 
  if Rails.env.development?
    before_action { Prosopite.scan }
    after_action { Prosopite.finish }
  end
- Config mode warning / raise exception trong config/enviroments/development.rb
  config.after_initialize do
    Prosopite.rails_logger = true
    Prosopite.raise = ENV['STRICT_N_PLUS_ONE'] || false
  end
- Config tương tự cho môi trường test config/enviroments/test.rb
  config.after_initialize do
    Prosopite.rails_logger = true
    Prosopite.raise = ENV['STRICT_N_PLUS_ONE'] || false
  end
- Config thêm 1 xíu trong spec/spec_helper.rb (cũng tương tự với ý tưởng before_action trên). Chỉ khác ở đây mình gọi cho mỗi test case (it do ... end) được chạy 
  config.before(:each) do
    Prosopite.scan if ENV['STRICT_N_PLUS_ONE']
  end

  config.after(:each) do
    Prosopite.finish if ENV['STRICT_N_PLUS_ONE']
  end