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)
Good post, kudos!
How did you benchmark your boot time on Heroku? Are there any metrics in the Heroku dashboard?
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 runrails runner
to load the Rails app viatime 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
).Bootsnap is a dream. It work really well for Rails on Lambda too. lamby.custominktech.com/docs/cold_...
The Bootsnap documentation states that you may need to purge the tmp/cache/bootsnap folder from time to time. Is this necessary on Heroku?