Active Model Serializer - Increasing Render Performance Active Model Serializer - Increasing Render Performance ruby-on-rails ruby-on-rails

Active Model Serializer - Increasing Render Performance


You're running into a n+1 queries type problem. Unfortunately includes isn't really helping you here because it only helps you with one level of the association - it avoids separate fetches of the children of the top level comment, but not grand children or great grandchildren.

You could probably optimise the user lookup by maintaining your own cache of user id to user objects (the rails cache caches the raw data but will be reinstantiating the objects over and over again), but to make this substantially faster you need to change how you load the comments.

If you are using a database that supports it (such as postgresql), then recursive queries ar an option (see https://hashrocket.com/blog/posts/recursive-sql-in-activerecord for a worked example). I don't know this scales as the tree gets deeper and deeper.

If recursive queries aren't an option, then there are a few approaches that involve changing what you store.

One is the materialised path patten. For example say that the root comment has id 1, a child has id 101 and one of its children has id 426. That last comment's path is 1/101/426. All of its siblings have paths starting with 1/101/. This means you can use like queries (with wildcards at the end) to find subtrees quickly. The ancestry gem implements this. If comments get moved then you need to rewrite the paths of all the comments children (and grand children etc.), but that may not be relevant to your use case. Very deep trees are problematic I think.

Another is the nested set pattern. The core idea is that the parent node stores the minimum and maximum id of all of its children and their children's children etc. This allows retrieving of all these children in one go. The flip side is that inserts and updates require rewriting a lot of this data (more so than with materialised path). There have been various gems that implement this over the years (a current one seems to be awesome_nested_set).

Also worth checking that you have got the right indexes to support your queries - unless there are really quite a lot of comments with a given parent, 20-30ms seems quite a long time for one query.


Change your ActiveRecord query to this

parent_comments = Comment.where(parent_comment_id: nil).includes(:user, children_comments: :user)

It will get rid of N + 1 queries.