Debugging Common Memory Issues in Rails Applications
A deep dive into identifying and fixing memory leaks in Ruby on Rails applications.
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!