If your Rails app on Heroku is constantly throwing R14 memory errors and running like molasses, check your Puma config first.
🛠️ The Quick Fix
If you're on a 512MB Heroku dyno (like the Basic or Free plan), do not run multiple Puma workers. Just set your Puma config like this:
# config/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
# ❌ No workers, no preload_app!
Remove or comment out any workers
or preload_app!
calls.
😅 Quick Personal Note
I’ve been working as a developer for about 8 years — mostly on product and feature work — but I don’t touch production-level infrastructure stuff very often.
So when my Heroku app started acting up with R14 errors, I figured, “How hard could it be to fix?”
It took me months.
Not because the answer was complicated — but because it was buried in Puma and Heroku nuance I’d never needed to know until now.
🧠 What Was Happening (And Why Nothing Else Worked)
I tried everything first:
- Removing gems
- Refactoring memory-heavy code
- Profiling allocations
- Reading Stack Overflow and random blog posts
None of it helped.
Eventually, I compared the Puma config of this slow, crashing app with another Rails app I had running fine on the same dyno size.
The broken one had:
if ENV["RAILS_ENV"] == "production"
require "concurrent-ruby"
worker_count = Integer(ENV.fetch("WEB_CONCURRENCY") { Concurrent.physical_processor_count })
workers worker_count if worker_count > 1
end
That line was silently spawning multiple Puma workers — and chewing through memory like crazy.
🚨 Why This Breaks Your App on Heroku
Each Puma worker is a full process with its own memory space. If you're on a 512MB dyno and you spawn 2 workers, you're instantly in trouble.
- 2 workers × ~400–500MB each = 🚨
- Heroku starts throwing R14 (Memory Quota Exceeded) errors
- If it gets worse, R15 and crash
- App starts swapping memory to disk = slow
- Response times spike to multiple seconds
📉 The Before & After
After switching to a single Puma process with threads only:
✅ Memory Usage:
- Before: Peaked at ~1GB, avg ~723MB
- After: Stable around 250MB
✅ R14 Errors:
- Before: Endless
- After: Gone
✅ Response Time:
- Before: Regular 2–4 second spikes
- After: Consistent ~150ms
🧐 Why Puma Recommends Workers (But You Shouldn’t Use Them)
Puma’s docs and many blog posts talk about using workers + threads for performance. And that’s true — if you have the memory.
On Heroku’s Standard-1X (1GB+) or on your own VPS, clustering with workers can help throughput.
But on Heroku Free or Basic? One worker is all you can afford. Threads are your friend here.
✅ TL;DR
If you're on a 512MB dyno:
- Use threads only (5 is safe)
-
Avoid
workers
andpreload_app!
- Enjoy smooth sailing
Final Thought
I'm sharing this because I wish someone had shared it with me. Debugging memory issues in production Rails apps is hard enough — no one needs to be blindsided by a default Puma config that silently wrecks performance on Heroku.
If you're struggling with R14s or slow response times, take 5 minutes to check your puma.rb
. It might just save you months of head-scratching.