Here are some tips to help you improve your performance in Rails. We have a wide range of items, from those that are ready to use to those that take some time to improve.
We hope it helps improve the performance of the app you are developing.
If N + 1 is occurring anyway, try to eliminate it. In most cases, that should be the bottleneck for your app's performance.
books_controller.rb
class BooksController < ApplicationController
def index
@book = Book.all
end
end
erb:index.html.erb
<% @book.each do |book|
<%= book.title %>
<%= book.user.name %> #Here N+1 is happening
<% end %>
In the above code example, N + 1 is occurring when reading the user associated with the book.
Let's rewrite the controller as follows.
books_controller.rb
class BooksController < ApplicationController
def index
@book = Book.includes(:user)
end
end
There is a high possibility that N + 1 is occurring in the place where all models have been acquired in model.all
, and all is often not used.
It is also recommended to install the gem bullet
in the app to detect N + 1.
https://github.com/flyerhzm/bullet
Also, in the above example, ʻincludes is used, but it would be nice to be able to use
preload and ʻeager_load
properly.
Differences between ActiveRecord joins, preloads, includes and eager_load
If you use count, you will issue SQL.
Therefore, if you want to check the number of models, use size
instead.
It's a case that tends to happen unexpectedly, but it's easy because you just replace count
with size
.
** When using count **
user = User.all
user.count
#A SELECT statement using the count function is issued
(4.6ms) SELECT COUNT(*) FROM `users`
=> 100
** When using size **
user = User.all
user.size
#SQL query is not issued
=> 100
Like count
, ʻexist?will issue sql if used on a model object. If you want to check if it exists, use
present?` Instead.
*** When using exit? ***
user = User.where(deleted: true)
user.exist?
#SQL is issued
User Load (5.4ms) SELECT `users`.* FROM `users` WHERE `users`.`deleted` = TRUE
=> []
*** When using present? ***
user = User.where(deleted: true)
user.present?
#SQL is not issued
=> []
It's a pattern like getting all users with ʻall` and turning it with each. It's a pattern that is common in batch processing.
User.all.each do |user|
#Code that does something using the user's object
end
As I mentioned in the N + 1 tips, once all comes into the process, suspect the code.
In the above implementation, after expanding all User cases to memory, each process is performed by each, so memory consumption becomes heavy.
Use find_each instead of all.
User.find_each do |user|
#Code that does something using the user's object
end
find_each fetches 1000 records at a time and processes the retrieved records one by one.
After acquiring 1000 items, the next 1000 items will be acquired again, and so on.
By the way, if you want to specify the number of record expansions, use its sister method, find_in_batches. You can specify the number of records in the method argument.
Although not as much as the N + 1 problem and each pattern mentioned above, this is also an implementation that tends to be done if you are not careful.
user_names = User.all.map(&:name)
The map-based approach described above creates unnecessary Active Record objects and does not perform very well.
Since Active Record objects wrap a huge number of modules and methods, they are expensive to generate and consume memory on their own.
If there's a way you don't have to create an Active Record object, think about it.
user_names = User.pluck(:name)
By using pluck, we were able to avoid unnecessary object creation.
You can also use the cache or make the process asynchronous in the place where the performance deteriorates.
** Use cache ** Consider introducing Redis, etc. It might be a good idea to consider reading master data. However, if you do not carefully consider the introduction location, it tends to cause bugs.
** Make processing asynchronous **
Gem's sidekiq
and delayed job
make heavy processing asynchronous.
I often see how to make the mail sending process asynchronous.
How was that. So far, we have introduced tips that can easily improve performance just by being aware of it when coding with Rails.
If there are other ways to do this, or if there is something wrong here, please let me know in the comments section lol
Recommended Posts