Advanced Rails Performance Optimization and ActiveRecord Techniques — Ruby Deep Dive[10]
Discussing some performance optimization and active record techniques specific to rails.
Database Query Optimization Techniques
Proper Indexing Strategies
Indexes are crucial for speeding up database queries. They help the database find rows quickly without scanning the entire table.
# Migration to add indexes
class AddIndexesToUsers < ActiveRecord::Migration[6.1]
def change
add_index :users, :email, unique: true
add_index :orders, [:user_id, :created_at]
end
end
Using EXPLAIN to Analyze Queries
The EXPLAIN
statement helps understand how a query is executed and identify bottlenecks.
# Analyzing a complex query
query = User.joins(:orders).where(orders: { status: 'completed' })
puts query.explain
Query Caching and Key-Based Expiration
Caching results of expensive queries can significantly improve performance.
# Caching a query result
class User < ApplicationRecord
def self.cached_active_users
Rails.cache.fetch('active_users', expires_in: 12.hours) do
where(active: true).to_a
end
end
end
Solving the N+1 Query Problem
N+1 queries occur when an application makes N+1 queries to the database instead of a single join query.
Eager Loading with includes, preload, and eager_load
# Using includes to prevent N+1 queries
users = User.includes(:posts).all
users.each do |user|
puts user.posts.count
end
Bullet Gem for Detecting N+1 Queries
The Bullet gem helps identify N+1 queries during development.
# Gemfile
gem 'bullet'
# config/environments/development.rb
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
end
Application-Level Caching Strategies
Russian Doll Caching
Russian Doll Caching involves nesting fragments within other cached fragments.
<% cache @post do %>
<%= render @post %>
<% cache @post.comments do %>
<%= render @post.comments %>
<% end %>
<% end %>
Low-Level Caching with Rails.cache
Low-level caching can be used for caching any piece of data.
# Using Rails.cache for low-level caching
Rails.cache.fetch("expensive_query", expires_in: 1.hour) do
User.where(active: true).to_a
end
HTTP Caching Headers
Setting HTTP caching headers can reduce the load on your servers.
# Setting cache headers in a controller
class ProductsController < ApplicationController
def index
@products = Product.all
expires_in 5.minutes, public: true
end
end
Advanced ActiveRecord Techniques
Complex Associations and Polymorphic Relationships
Complex associations allow for more flexible data models.
# Polymorphic association example
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Event < ApplicationRecord
has_many :comments, as: :commentable
end
Custom SQL and Arel for Complex Queries
When ActiveRecord’s query methods aren’t enough, you can write custom SQL or use Arel.
# Custom SQL query
users = User.find_by_sql("SELECT * FROM users WHERE created_at > ?", 1.week.ago)
# Using Arel for complex queries
users = User.arel_table
active_users = users[:active].eq(true)
recent_users = users[:created_at].gt(1.week.ago)
query = User.where(active_users.and(recent_users))
Optimizing Bulk Operations (insert_all, upsert_all)
Bulk operations can significantly reduce the number of database transactions.
# Bulk insert using insert_all
User.insert_all([
{ name: "Alice", created_at: Time.now, updated_at: Time.now },
{ name: "Bob", created_at: Time.now, updated_at: Time.now }
])
# Upsert using upsert_all
User.upsert_all([
{ id: 1, name: "Alice", updated_at: Time.now },
{ id: 2, name: "Bob", updated_at: Time.now }
])
Using Database Views with ActiveRecord
Database views can simplify complex queries and make them reusable.
# Migration to create a view
class CreateActiveUsersView < ActiveRecord::Migration[6.1]
def change
create_view :active_users, "SELECT * FROM users WHERE active = true"
end
end
# Using the view in ActiveRecord
class ActiveUser < ApplicationRecord
self.primary_key = :id
def readonly?
true
end
end
active_users = ActiveUser.all
Handling Soft Deletes Effectively
Soft deletes allow you to mark records as deleted without removing them from the database.
# Using the Paranoia gem for soft deletes
class User < ApplicationRecord
acts_as_paranoid
end
# Soft deleting a user
user = User.find(1)
user.destroy
# Finding only non-deleted users
User.without_deleted
# Finding deleted users
User.only_deleted
This article provides practical techniques and examples to optimize Rails performance and make the most of ActiveRecord’s advanced features. Each technique includes real-world code snippets, ensuring you can apply these strategies effectively in your Rails applications.