Introduction

Memory issues can cripple a Ruby on Rails application, causing slow performance, unexpected crashes, and high server costs. Common symptoms include:

❌ Increasing memory usage over time
Slow responses due to excessive garbage collection (GC)
❌ Frequent Heroku or AWS memory limits exceeded errors
Out-of-memory (OOM) crashes in production

In this guide, we’ll explore:
Common memory leaks in Rails
How to detect memory bloat using profiling tools
Fixing leaks with best practices and optimizations


1. Understanding Memory Issues in Ruby on Rails

Rails applications are prone to memory bloat due to:

🔹 Long-lived objects – Unreleased ActiveRecord objects, class variables, or large data structures
🔹 Inefficient caching – Retaining too much data in Redis, Memcached, or Rails.cache
🔹 Unoptimized database queries – Large ActiveRecord collections loaded into memory
🔹 Improper garbage collection (GC) settings – Default Ruby GC may not work well for large applications


2. Identifying Memory Leaks

Before fixing memory issues, we need to identify leaks using profiling tools.

🔍 Checking Memory Usage

Use ps to monitor running Rails processes:

ps -o pid,rss,command -p $(pgrep -f puma)
  • rss (Resident Set Size) shows memory usage in KB.
  • If usage keeps increasing over time, you might have a leak.
🔍 Detecting Memory Growth with derailed_benchmarks

This gem helps track memory usage:

gem install derailed

Run:

bundle exec derailed exec perf:mem

This reports which gems are consuming the most memory.

🔍 Using memory_profiler for Detailed Insights

This gem provides line-by-line memory allocation:

gem install memory_profiler

Run in Rails console:

require 'memory_profiler'
report = MemoryProfiler.report do
User.all.to_a # Example query
end
report.pretty_print

Look for objects that stay in memory longer than expected.

🔍 Monitoring Garbage Collection (GC)

Enable GC logging to see if it’s running too frequently:

GC::Profiler.enable

Run your app and then check logs:

puts GC::Profiler.report

If GC is triggering too often, your app may have excessive object allocations.


3. Fixing Common Memory Leaks

Once you’ve identified problem areas, use these techniques to optimize memory usage.

🛠️ 1. Reduce Large ActiveRecord Queries

Loading too much data into memory can cause bloat. Avoid:

users = User.all.to_a # Loads everything into memory! ❌

Instead, use:

User.find_each(batch_size: 1000) do |user|
process(user)
end

This keeps memory usage low by loading records in batches.

🛠️ 2. Avoid Retaining Objects in Memory

Beware of class variables storing large objects:

class UserCache
@@users = User.all # ❌ This stays in memory forever!
end

Instead, use Rails.cache:

Rails.cache.fetch("users", expires_in: 10.minutes) { User.all }
🛠️ 3. Use Explicit Object Cleanup

If working with large data sets, explicitly free memory:

def process_large_data
users = User.limit(10000).to_a
users.each { |user| process(user) }
users = nil # Free memory immediately
GC.start # Trigger manual garbage collection
end
🛠️ 4. Optimize Caching Mechanisms

Cache invalidation issues can cause memory leaks if stale objects aren’t removed.
Use low expiration times for cache keys:

Rails.cache.write("recent_users", users, expires_in: 5.minutes)
🛠️ 5. Tune Ruby Garbage Collection

Adjusting GC settings can improve performance for high-memory apps.
Modify config/puma.rb (or Unicorn config):

before_fork do
GC.disable
end
after_fork do
GC.enable
GC.compact # Reduce memory fragmentation
end
🛠️ 6. Use Jemalloc for Memory Management

Jemalloc is a more efficient memory allocator than the default glibc.
On Linux, install:

sudo apt install libjemalloc-dev

Run Rails with:

LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so rails s

This reduces memory fragmentation and improves GC efficiency.


4. Best Practices for Memory Optimization

🔹 Avoid long-lived objects – Free memory where possible
🔹 Use database-level pagination instead of loading all records
🔹 Optimize background jobs – Avoid large in-memory jobs
🔹 Set caching expiration to prevent memory bloating
🔹 Use profiling tools regularly – Run memory audits in production


Conclusion

Memory issues can silently degrade Rails performance over time. By:
Profiling memory usage regularly
Optimizing ActiveRecord queries
Configuring garbage collection efficiently
Using caching and background jobs effectively

You can prevent memory leaks, reduce server costs, and improve response times. 🚀

Do you have a favorite debugging tool? Let me know in the comments!