DEV Community

David Backeus
David Backeus

Posted on

Cut your Rails boot times on Heroku in half with a single command

TLDR;

heroku labs:enable build-in-app-dir -a <APP_NAME>

Now enjoy your Rails app booting ~twice as fast after your next deploy 🙌

But why?

One of the tradeoffs with majestic monoliths is that the larger they grow, the more code has to be loaded and interpreted at boot time. In massive code-bases, such as Shopify's and Github's, booting can take more than a minute (see eg. Upgrading Github to Ruby 2.7).

Particularly requiring files (eg. require "foo") tends to become a bottleneck due to Ruby having to iterate through all possible load paths to lookup matching files. This gets time consuming when it’s applied to thousands of files.

To alleviate this problem, the engineers at Shopify created bootsnap, a gem which automatically detects and caches exact load paths to make those require calls fast (see Bootsnap: Optimizing Ruby App Boot Time for details).

The bootsnap gem has been a default Rails gem since version 5.2 which was released back in 2018 and has been improving boot times in most environments ever since. But what about on Heroku?

Bootsnap on Heroku

As it turns out due to a quirk in how Heroku's Ruby buildpack initially builds the application in a /tmp folder before moving the results over to /app, the cache generated by bootsnap gets invalidated and can't be used at all. But after enabling the build-in-app-dir labs feature the entire build process takes place inside of /app which allows bootsnap to work as intended.

The feature is enabled via the heroku CLI tool:
heroku labs:enable build-in-app-dir -a <APP_NAME>

At Mynewsdesk this reduced the boot times of our 17 year old Rails codebase from ~16 seconds to ~8 seconds. The improvement is particularly noticeable when booting one off dynos via eg. heroku console or heroku run rails db:migrate.

There is a Github issue on this topic in the heroku-ruby-buildpack repo with some success stories in the comment section and a hint that build-in-app-dir may become the default behaviour in the future.

So far there have been no reports of issues after enabling this feature but you might want to try this on your staging environments first before enabling it in production to be sure. If any issues do turn up, don't forget to share your experience in the Github issue.

This is the first post in a planned series of tips and tricks for using Heroku efficiently. Feel free to follow me here on dev.to or @dbackeus on Twitter to get notified when new posts are published.

Top comments (6)

Collapse
 
andresbecker profile image
Andres Becker

Good post, kudos!

Collapse
 
martinstreicher profile image
Martin Streicher

How did you benchmark your boot time on Heroku? Are there any metrics in the Heroku dashboard?

Collapse
 
dbackeus profile image
David Backeus • Edited

Heroku doesn't provide any help in this regard. The approach I used was to heroku run bash to enter a bash shell and then run rails runner to load the Rails app via time NEW_RELIC_AGENT_ENABLED=false rails runner 'puts "done"'. The first run provides the number you want to pay attention to. For subsequent runs the bootsnap cache will be in place whether you enabled the lab feature or not.

Note that I explicitly turned off the NewRelic agent via ENV since I found that otherwise NewRelic adds ~4s of irrelevant boot time noise (ie. it would not be present when running rails console).

Collapse
 
metaskills profile image
Ken Collins

Bootsnap is a dream. It work really well for Rails on Lambda too. lamby.custominktech.com/docs/cold_...

Collapse
 
obromios profile image
Chris Drane

The Bootsnap documentation states that you may need to purge the tmp/cache/bootsnap folder from time to time. Is this necessary on Heroku?