DEV Community

nico_lrx
nico_lrx

Posted on • Edited on

How to Easily Avoid Hitting the Memory Limit on Heroku

Heroku Memory Leak

Heroku is a great tool to launch a new app easily. However, dynos can quickly reach their limit if your app is memory intensive. It was the case of my Rails app and I started to spend way more money than expected!

If you have already optimized memory following those Heroku tips, it's time to find another solution to avoid spending too much money.

How to restart Heroku dynos periodically

One efficient workaround is to restart dynos when the memory limit is close. Unfortunately, you will have to pass the whole day (and night!) in front of your computer if your app is leaking memory!

The solution is to create a scheduled task to restart the dynos consuming too much memory before they hit the limit.

Write a task in your app

First, add the platform-api gem to your Rails app:

gem 'platform-api'

bundle install
Enter fullscreen mode Exit fullscreen mode

Create an OAuth token using the heroku-oauth toolbelt plugin:

$ heroku plugins:install heroku-cli-oauth
$ heroku authorizations:create -d "Platform API example token"
Created OAuth authorization.
  ID:          2f01aac0-e9d3-4773-af4e-3e510aa006ca
  Description: Platform API example token
  Scope:       global
  Token:       e7dd6ad7-3c6a-411e-a2be-c9fe52ac7ed2
Enter fullscreen mode Exit fullscreen mode

Then, create a scheduler.rake file in the lib/tasks folder of your Rails app.

  require 'platform-api'

  task :restart_dyno => :environment do
    puts "task restart_worker is on"
    heroku = PlatformAPI.connect_oauth(YOUR OAUTH TOKEN)
    heroku.dyno.restart("APP_NAME", "web.1")
  end
Enter fullscreen mode Exit fullscreen mode

You can specify which dyno you want to restart by passing its name. According to the documentation, you can also restart all dynos at the same time:

  require 'platform-api'

  task :restart_dyno => :environment do
    puts "task restart_worker is on"
    heroku = PlatformAPI.connect_oauth(YOUR OAUTH TOKEN)
    heroku.dyno.restart_all("APP_NAME")
  end
Enter fullscreen mode Exit fullscreen mode

Schedule your task

In order to perform a task on a repetitive basis, you will have to install the Heroku Scheduler add-on.

The Scheduler comes with a frequency limitation: you can only choose between "Daily", "Hourly" or "Every 10 minutes".

In case those frequencies are not ideal regarding your memory consumption, you can add a condition in your task to match the frequency you want. For example, if you want to restart your dyno twice a day:

  require 'platform-api'

  task :restart_dyno => :environment do
    t = Time.now.strftime('%H')
    if t == "07" or t == "14"
        puts "task restart_worker is on"
        heroku = PlatformAPI.connect_oauth(YOUR OAUTH TOKEN)
        heroku.dyno.restart("APP_NAME", "web.1")
    end
  end
Enter fullscreen mode Exit fullscreen mode

Once you have defined your frequency, add a new job to the Scheduler: rake restart_dyno.

Heroku Memory Leak

Now, you will have your dynos restarting automatically before hitting the memory limit. If you have any questions, feel free to contact me on Twitter.

Top comments (8)

Collapse
 
briankephart profile image
Brian Kephart

Thanks for this post! That gem looks handy.

For those running Rails & Puma, the gem puma_worker_killer can do this as well. The nice thing about that one is it will restart your dynos sequentially to avoid downtime.

I once made a pull request to puma_worker_killer to use the Heroku log drain to measure memory use and restart if it gets too high. Sadly, it didn’t get merged. 😢

Collapse
 
nicolrx profile image
nico_lrx

It's my first post here (and one of my first technical post ever), please let me know if you have any feedback ;)

Collapse
 
ben profile image
Ben Halpern

This is just the kind of post I would have loved to have found while dealing with this issue myself.

For further on this subject, this talk is great:

Collapse
 
nicolrx profile image
nico_lrx

Thanks Ben!

Collapse
 
codebrotha profile image
Tineyi Takawira

This just saved me so many headaches! Thanks!

Collapse
 
nicolrx profile image
nico_lrx

I'm glad it's useful!

Collapse
 
codebrotha profile image
Tineyi Takawira • Edited

I finally implemented this.

By the way t = Time.now.day.strftime('%H') should be t = Time.now.strftime('%H').

Thanks again!

Thread Thread
 
nicolrx profile image
nico_lrx

Thanks, I corrected it.