Ruby has a memory problem (part 1)

I was recently asked “What is your favorite thing about Ruby, and what is your least favorite?”

For the former, I replied “Ruby is easy to rapidly prototype. You can get something up and running very quickly.”, and for the latter, “Ruby is very memory intensive”

Maybe you’ve noticed. If your on Heroku, perhaps you’ve seen in the logs R14 — Memory Quota Exceeded . You think “Ok, I’ll just scale the dynos so we have more memory to work with” 💸💸💸💸 Ruby apps on Heroku get expensive quickly due to this common issue.

Or maybe its all hidden to you. A page loads slowly and its a mystery. You fix N+1’s but its still slow. What gives? While there is no ‘silver bullet’, I’m going to say the closest thing is tackling ways to mitigate memory bloat in your Ruby application.

In this first post (I’ll do a part 2 on HOW to tackle memory issues) we are going to look at a couple simple ways to find memory issues.

First up, the easy route: if you don’t want to re-invent the wheel and use a fantastic tool that already exists out there, enter Skylight. It’s like a more readable New Relic, but hyper-focused on your app’s performance, and nothing else. It’s an incredible tool and last I checked, very affordable.

“Your Rails app is probably humming along just fine most of the time. Still, your users probably have the occasionally painfully slow request seemingly at random. While unexplained slowdowns can happen for many reasons, the most common root cause is excessive object allocations.” — Skylight

The most applicable feature here would be the “allocations mode” in Skylight (read up on allocations if you’d like).

Notice the “Avg. Allocations” at the top-center

This view will break down where the intensive memory allocations are talking place, pinpointing down to a specific trace (the largest colored bar is the largest problem).

Take it for a spin for free for the first month. Highly recommended.

If you want to dive in and do more of the sleuthing yourself 🕵️, since Ruby 2.1 we have ObjectSpace, built right into the core library. This basically can measure what the module name suggests: look at the space an object takes up, hence “ObjectSpace”. It does more, but at its root, that’s what it’s for. I won’t do a deep-dive here, but here’s a few examples of what you can look at:

ObjectSpace.memsize_of(Array.new)
=> 40

Above, we see a simple Array object takes up 40 bytes in memory.

ObjectSpace.memsize_of(Array.new(10_000, Hash.new))
=> 80040

Simple, but useful.

You could also peek into your ruby files, “starting” allocation tracing anywhere within the file via ObjectSpace.trace_object_allocations_start

ObjectSpace.trace_object_allocations_start

class MyApp
def perform
"foobar"
end
end

o = MyApp.new.perform
ObjectSpace.allocation_sourcefile(o) #=> "example.rb"
ObjectSpace.allocation_sourceline(o) #=> 6
ObjectSpace.allocation_generation(o) #=> 1
ObjectSpace.allocation_class_path(o) #=> "MyApp"
ObjectSpace.allocation_method_id(o) #=> :perform

Example above taken from ObjectSpace’s developer, here.

To make working with ObjectSpace easier (especially with passing in your code in a block and getting readable results), check out this gem.

Every object in Ruby takes up space, and sometimes it gets built up before Ruby’s Garbage Collector can ‘take out the trash’ if you will, causing memory bloat.

I hope this little article gets you wanting to dive deeper into learning about how Ruby handles memory, and ways to pinpoint issues. I’ll do a part 2 where we can look at common causes of memory bloat in Ruby and some approaches to fix it.

Full Stack Software Developer