Ruby on Rails Performance Optimization: From Slow SQL to 50ms Response Times (2026)
Master Rails performance optimization: eliminate N+1 queries, configure PostgreSQL partial indexes, master Russian Doll caching with Solid Cache, tune jemalloc memory allocation, and achieve 50ms response times.
Heads up: some links in this post are affiliate links. If you sign up through them, we may earn a small commission at no cost to you. We only recommend tools we'd use on our own client projects.
The Anatomy of a Slow Rails Request
When developers complain that "Rails is slow," profiling almost always reveals the same root causes: unindexed database lookups, hundreds of queries executed in loops (N+1s), massive Active Record object allocations that trigger Ruby garbage collection (GC) pauses, and complete absence of view fragment caching.
Below is the exact systematic methodology we use at TechVinta to profile and optimize high-throughput production Rails applications processing millions of monthly requests.
1. Eliminating N+1 Queries with Active Record & strict_loading
The classic N+1 problem occurs when fetching a collection of parent records and then querying child associations individually inside a loop:
# ❌ SLOW: Generates 1 query for listings + 100 queries for users + 100 queries for reviews
@listings = Listing.where(status: :active).limit(100)
# In view: listing.user.name, listing.reviews.count (201 total SQL queries!)
# ✅ FAST: Eager loads associations using 3 efficient queries
@listings = Listing.where(status: :active)
.includes(:user, :reviews)
.limit(100)
To permanently prevent N+1 queries from reaching production, enable Rails 7/8 strict_loading in development and test environments:
# In config/environments/development.rb
config.active_record.strict_loading_by_default = true
config.active_record.action_on_strict_loading_violation = :raise
2. PostgreSQL Index Tuning: Compound, Partial, and GIN Indexes
Adding a standard single-column index on foreign keys is not enough for complex dashboard queries. You need compound and partial indexes matching your exact WHERE and ORDER BY clauses:
# Add a partial compound index for active published listings
class AddOptimizedIndexToListings < ActiveRecord::Migration[7.1]
disable_ddl_transaction!
def change
add_index :listings, [:category_id, :created_at],
where: "status = 'active' AND published = true",
algorithm: :concurrently,
name: "idx_active_listings_by_category_created"
end
end
3. High-Performance Russian Doll Caching with Solid Cache
Rails 8 introduces Solid Cache, storing cache fragments in PostgreSQL/MySQL (on NVMe SSDs) rather than expensive RAM-constrained Redis instances. Russian Doll caching nests child fragment caches inside parent caches:
<% cache ["listings_index", @listings.map(&:cache_key_with_version)] do %>
<% @listings.each do |listing| %>
<% cache listing do %>
<%= listing.title %>
<%= listing.price_formatted %>
<%= listing.user.name %>
<% end %>
<% end %>
<% end %>
4. Cutting Memory Footprint by 40% with jemalloc
Standard glibc malloc in Linux suffers from severe heap memory fragmentation when Ruby allocates and frees thousands of string and object buffers. Switching the system memory allocator to jemalloc yields an immediate 20–40% drop in RAM consumption and speeds up garbage collection cycles:
# Install jemalloc on Debian/Ubuntu
apt-get install -y libjemalloc2
# Set in your production environment or Dockerfile
ENV LD_PRELOAD="/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
ENV MALLOC_CONF="dirty_decay_ms:1000,narenas:2,background_thread:true"
Is Your Rails Application Slow or Resource Heavy?
Our senior Rails architects conduct full-stack performance audits, optimize SQL queries, eliminate memory bloat, and configure production caching to achieve sub-50ms response times.
Hire Senior Rails Developers →Frequently Asked Questions
What is the difference between includes, preload, and eager_load in Rails?preload always runs separate queries for associations. eager_load forces a single query with a LEFT OUTER JOIN. includes intelligently chooses between the two depending on whether you reference the associated table in a where clause.
How does Solid Cache compare to Redis for Rails caching?
Redis stores cache keys in RAM, making large cache stores expensive ($100+/mo). Solid Cache stores cache fragments in NVMe SSD storage on your database, enabling 10x larger cache capacities (gigabytes of HTML fragments) for a fraction of the cost with negligible latency difference (1ms vs 0.2ms).
What tool should I use to profile slow Rails endpoints?
We recommend rack-mini-profiler in development to inspect SQL query counts and execution times directly in the browser, and pg_stat_statements in production PostgreSQL to identify the top 5 slowest database queries by cumulative time.
How do I fix memory leaks in Ruby on Rails?
The vast majority of Rails "memory leaks" are actually memory fragmentation. Installing jemalloc, avoiding holding large ActiveRecord arrays in memory (use find_each or in_batches instead of all.each), and streaming large file downloads with send_data solves 95% of memory issues.