Ruby on Rails Optimization Part 2

Ruby on Rails Optimization Part 2

Performance Enhancements

Hello there! Whether you're a newcomer or were captivated by the first part 👀, this blogpost will delve deeper into additional Ruby on Rails (RoR) optimizations. These strategies will not only boost your app's performance but also enhance your code's readability—truly a chef's kiss.

1. Eager Loading

A common pitfall in RoR applications is the N+1 query problem, which arises when your code lazily loads data. This leads to a scenario where, for each record retrieved, a new database query is executed to fetch associated records. RoR provides a solution to this through eager loading.

Eager Loading

Eager loading fetches the main object(s) along with their associated objects through n database query**(n represents the number of associations in the include)**. Implement eager loading with the includes method.

Example:

@users = User.includes(:posts).where(posts: { published: true })

This code efficiently fetches users and their published posts in just two queries instead of multiple, reducing the overall query count.

For scenarios requiring users to have at least one published post, scopes can be utilized:

class User < ApplicationRecord
  scope :with_published_posts, -> {
    joins(:posts).merge(Post.published).distinct
  }
  scope :comedy_writers, -> { where(genre: 'comedy') }
end

class Post < ApplicationRecord
  scope :published, -> { where(published: true) }
end

These scopes can be chained to further refine the query:

@users = User.comedy_writers.
              with_published_posts.
              includes(:posts)

To include posts with comments while eager loading:

@users = User.comedy_writers.
              with_published_posts.
              includes(posts: :comments)

Furthermore, for including only published posts:

class User < ApplicationRecord
  has_many :published_posts, -> { published }, class_name: 'Post'
  has_many :posts
end

This association allows for seamless inclusion of second-level associations:

@users = User.comedy_writers.
              with_published_posts.
              includes(:published_posts => [:comments, :likes])

2. Usingfind_in_batches

Consider the need to reindex all modified objects daily with a third-party search engine (like Algolia or Elastic) to ensure data integrity. Querying millions of records changed in the past 24 hours can be overwhelming.

find_in_batches offers an optimal solution for memory management in such cases:

Product.changed_last_day.find_in_batches do |batch|
  # Call API with your batch
end

The default batch size is 1000, but it can be customized for efficiency:

Product.changed_last_day.find_in_batches(batch_size: 500) do |batch|
  # Call API with your batch
end

Wrapping Up

Congratulations on making it through this deep dive into optimizing your Ruby on Rails applications!

We've explored two pivotal strategies that are essential for any RoR developer aiming to boost app performance and maintain clean, efficient code.

Firstly, we tackled the notorious N+1 query problem by implementing eager loading. This technique drastically reduces the number of database queries, thus enhancing your application's speed and efficiency.

Secondly, we delved into the power of find_in_batches for processing large datasets without overwhelming your server's memory. This method is particularly useful when syncing large volumes of data with third-party services or performing bulk updates.

Happy coding, and let's keep making magic with Ruby 💎!