Remove N+1 queries in your Ruby on Rails app

I.Giới thiệu

Bao giờ tự hỏi tại sao mình tải trang chậm hơn khi bạn cần tải những đoạdữ liệu lặp đi lặp lại hay lấy dữ liệu từ một tập các dữ liệu? Câu trả lời có thể là vì bạn có vấn đề với N + 1 queries, điều đó làm chậm trang web của bạn đáng kể. Nhưng N + 1 là gì và vấn đề là làm thế nào để giải quyết nó? Sau đây tôi sẽ giúp các bạn có một cái nhìn tổng quan về nó.

II. Ví dụ minh họa

N+1 query problem is a situation when you are making an extra call(s) to database when you want to get a specific associated data over and over again. For example: N+1 query trong tình huống này là khi bạn truy ván nhiều lần vào database khi bạn muốn lấy một trường cụ thể của nhiều bản ghi. Ví dụ:

1.PNG

Ở ví dụ này, chúng ta lấy ra items rồi lặp lại các records của chúng và cố gắng lấy ra items category từ database. Tưởng tượng điều này xảy ra với khoảng 50 tới 100 mục thì các model sẽ trở nên cồng kềnh.

III. Cách giải quyết thông dụng

1. Preload

Đầu tiên là sử dụng eager loading methods Preload là trường hợp mặc định của phương thức includes - nó tạo ra 2 truy vấn tách biệt, một cho truy vấn chỉnh và một cho truy vấn các dữ liệu quan hệ. Điều đó có nghĩa là chúng ta không thể thêm #where({ categories: { title: “Fruits” } }), và nó sẽ báo lỗi.

2.PNG

Example will load all items with preloaded category. Ví dụ trên sẽ load tất cả các itemse với các category được load ra

2. Includes

Thay vì preload, includes chọn để rạo ra 1 truy vấn dựa vào tình huống cụ thể - nếu bạn thêm quan hệ ở câu truy vấn "#where". Điều này sẽ tạo ra nhiều hơn truy vấn SQL phức tạp hơn:

3.PNG

Example will load only items with preloaded category which title is Fruits. You can also force #includes to make one query with #references(using LEFT OUTER JOIN) method: Ví dụ trên sẽ chỉ load các items với prreload category mà title là "Fruits" Bạn có thể dùng #includes để tạo ra một truy vấn với references(sử dụng LEFT OUTER JOIN):

4.PNG

3. Eager_load

Phương thức này giống như việc kết hợp #includes#references, nó tạo ra 1 truy vấn vLEFLEFFT OUTER JOIN:

5.PNG

Có một số N+1 xuất hiện và chúng ta khá dễ dàng để chú ý nếu bạn nhìn vào Rails console logs nhưng có một vài truy vấn chúng ta không thể nhìn thấy hay khó để cảnh báo.

IV. Cách giải quyết khác

Một vài giải pháp khác giúp bạn giải quyết vấn đề với N+1 queries:

** Dynamic condition** Ví dụ:

6.PNG

nó sẽ gọi trong một trang trong vòng cả tháng, do vậy 30 queries sẽ đếm mỗi ngày. Nó có thể được sửa lại như sau:

7.PNG

nó sẽ tạo ra instance variable với hash mà keys là ngày và giá trị là biến cố. Tất cả chỉ trong 1 query.

** Join nhiều queries thành một **

Có thể 1 vài truy vấn không thể nhóm lại nhưng có thể join trong các SQL level. Khi nó là những thứ khá phức tạp, bạn không muốn dựa vào ActiveRecord. Thay vào đó, bạn sử dụng SQL để lấy được thứ bạn cần từ database (nhưng bạn cần chú ý cẩn thận nếu sau này bạn có thay đổi kiểu database, nó có thể không hoạt động nữa). Ví dụ:

8.PNG

bạn có thể gộp lại thành 1 query như sau:

9.PNG

V. Bullet

Bullet là một gem giúp bạn phát hiện và hỗ trợ bạn sửa các truy vấn N+1 khá tốt. Nó khá dễ cài đặt và sử dụng. Bạn có thể tìm hiểu và cài đặt Bullet tại đây github.

VI. Tổng kết

Rails là một ngôn ngữ khá dễ học và sử dụng nhanh chóng để viết các trang web. Nhưng dù gì thì nó vẫn cần những truy vấn database. Vì vậy nếu bạn muốn trang cảu bạn chạy nhanh hơn thì cần chú ý tới vấn đề N+1 queries.

VII. Tài liệu tham khảo

http://blog.diatomenterprises.com/