TimeString
TimeString

Reputation: 1798

Will has_many :through impose huge performance impact in RoR?

Let's consider the following relationships (3 models):

class Author < ActiveRecord::Base
    has_many :authorships
    has_many :papers, :through => :authorships
end
class Paper < ActiveRecord::Base
    has_many :authorships, inverse_of: :paper, :dependent => :destroy
    has_many :authors, :through => :authorships
    accepts_nested_attributes_for :authorships, :allow_destroy => true
end
class Authorship < ActiveRecord::Base
    belongs_to :paper
    belongs_to :author
    validate: rank, presence: true  # position of the author
end

As you can see, I use a lot has_many and also :through. When I browse paper.html.erb page which basically shows all the papers and corresponding authors, it slows down the system a lot. Right now I have roughly 400 papers and 2000 authorships, and it takes 3 seconds to load the page, plus, from the log I saw RoR made a lot of SQL queries, the majority of the queries are to retrieve authors. I was wondering this is a typical thing in RoR. Thanks for your help.

Upvotes: 2

Views: 1000

Answers (1)

FaithoftheFallen
FaithoftheFallen

Reputation: 510

This sounds like more of an optimization problem. To answer your original question, has_many :through by itself does NOT cause any noticeable difference. You would have the exact same problem if you only only had two tables for Paper and Authorship.

Based on what you reported about your logs, I assume your code looks something like this:

# Controller:
@papers = Paper.all

# View:
<% @papers.each do |paper| %>
    <%= paper.authors.join(', ') %>
    <%# other code... %>
<% end %>

What rails does is for every time you call paper.authors, it will run a query to fetch the authors associated with that paper. If you have 400 papers, rails will run 400 queries. This is called the n+1 query problem, where n queries are run for each record, along with the first query to get the initial records. Rails has a nice guide on how to tackle this issue: http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

You basically want to avoid running a query every time you want to get the authors for a paper. You could instead load your papers in the controller like this: @papers = Paper.includes(:author).all. This would load all the papers along with their associated authors and only run 2 queries: one for all the papers, and another for all the authors associated with those papers.

However, be careful with overusing .includes because it could also slow down your loading time once you have too many records. Rails also takes time to create each Paper and each Author object in memory; it would currently be creating 2,400 objects based on your database records. After you get to a certain number of records, you should look into adding pagination or search functionality instead of loading tens of thousands of records on a single page.

Upvotes: 2

Related Questions