How to determine acceptable memory baseline for my application?
Hey everyone, I have a Rails app deployed on Render's "Starter" plan which only includes 512MB of memory. My Rails app is a very simple blog that doesn't do anything crazy (as far as I know) with regards to common memory issues. I'm not iterating over thousands of objects in memory, etc.
I got 130 unique monthly page views last month, so it's also not a high-traffic site at all.
I've been using ScoutAPM and rack-mini-profiler
to chase down some issues, but frankly, I haven't noticed any improvement. My app is usually killed every 12 hours or so. Last night, it was actually fine and has been running around 340MB for the last ~18 hours. I'm pleasantly surprised, but also kinda frustrated that I don't understand why anything would've changed.
So I was curious—is there a way to benchmark or estimate what the memory usage of a Rails app should be? I'd love to have a target to aim for. But for example, yesterday I wasn't even sure if getting my memory below 512MB was a reasonable goal (despite really feeling like it should be a reasonable goal!)
Does anyone have insight about how to get good metrics around what your memory usage should reasonably be?
In general, I think a Rails app tends to use 400-600MB of RAM. Gems and different configurations can affect the usage so there's not really a specific number to aim for.
One thing you can do is enable jemalloc and that often reduces memory usage by a good percentage as it's more efficient than the default malloc.
https://community.render.com/t/how-to-use-jemalloc-in-ruby-web-service/1183
Thanks for the rapid response, Chris!
I think my biggest desire is to avoid unrealistic expectations. For example, if getting my memory usage below 250MB is not realistic, knowing that is very helpful so I don't get frustrated by my own (unrealistic) expectations. But if my memory usage was 800MB and getting to 500MB was realistic, then I'd know to keep trying to optimize further.
I believe that the phosphor-icons gem contributed to a non-trivial amount of memory usage. I have yet to confirm, but removing that (along with a few other changes I unscientifically made at the exact same time) has gotten my baseline memory for the past few hours down to that 340MB number. It hasn't been OOM killed for over 18 hours now, which is progress!
I installed jemalloc a couple days ago and was bummed to see that it hadn't really affected my memory at all, as far as I could tell. The process would still be killed every 12 hours or so.
All this to say, I've been reading and learning a lot about memory management in Ruby, but the one answer I couldn't find is "How do I know if my baseline is already good enough?" Or put another way, "How do I know if I just need a bigger box?" Which I believe you've answered. It sounds like 512MB doesn't leave much room for a non-optimized Rails app to run free with its hair down.
It's always trade-offs. For example, an icons gem is probably going to load them into memory so rendering icons is faster by not loading them from disk every time.
Reducing memory usage sometimes means making things slower so don't look at it as an arbitrary goal. You may want to keep it low so your server costs are low, but in production paying for more RAM to achieve more throughput is often worth the trade-off.
Wow, I really wish I fully understood what change fixed it, but removing the icon library, changing my ruby version to 3.3.2, and then removing a hash that I was creating within a loop in one of my partials have all combined to lower my resting app memory usage from ~480MB to 190MB. 🤯
I may put some of those changes back just to see if I can isolate which change finally dealt the killing blow!
That's awesome. Curious to hear what change(s) made the biggest difference. That's a huge improvement.
Here's a fun little chart. Some of this just makes no sense to me.
- Here's where I start playing around, deploying a lot, to try to see what sticks. Nothing really moves the needle as far as I can tell.
- Here's the first time I see any noticeable improvement.. The commit? Adding the
Scout::APM
module to my Rack configuration. This really feels like a red herring because... why would that matter at all? - This is strange too—I did nothing here. No deploys, people are still hitting my site at a rate of 10s of visits per day, hardly anything. Yet the memory usage just randomly drops down to nearly half?
- I just reverted my commit where I changed from Ruby 3.3.2, back to Ruby 3.2.2 because I had read somewhere that Ruby 3.3 had some noticeable memory improvements. I suspect that's one of the main things that would've made such a profound difference. However, I made those changes near the pink/purple lines right above #2 there—notice it didn't look like a huge win.
I realized I may not have let my Ruby version change cook long enough to see the difference. So I've reverted them to see if I can get a longer time scale to view. Then I'll systematically start adding/reverting things to see if I can definitively say what did it.
Man, I'm so confused. Here's another fun chart from last night.
- This was when I put back my Ruby 3.2 changes. You'll notice right off the bat, its memory usage is way higher than Ruby 3.3 before it.
- At 10pm, the server was OOM killed, which caused it to restart, but it immediately started trending right back up.
- For some unknown reason, around 2am after using 511MB (1MB shy of getting OOM killed), it drops all the way down to ~190MB usage which is right around what Ruby 3.3 was using.
I have no idea what's going on here. 😂 Just let the server run long enough and eventually its memory usage falls off a cliff?