Practical Steps to Reduce Heroku Costs

Jeff Morhous headshot

Jeff Morhous

@JeffMorhous

It’s no secret that Heroku gets expensive fast. People love Heroku for its rich developer experience and long history of taking the infrastructure burden off of developers. Still, if you’re fortunate enough to have enough usage, you’ll quickly find that keeping up with your compute requirements can quickly get out of control.

If you resonate with that at all, you’re in the right place! There’s quite a lot you can do to reduce costs on Heroku. From choosing an appropriate dyno to making optimizations in your background job processing, we’ll cover a wide-reaching range of strategies here. Let’s dig in!

Use smaller dynos (and more of them!)

Choosing which Heroku dyno to use is higher stakes than it might seem. As you scale and need more compute to keep up with demand, you might be tempted to just upgrade to a higher dyno tier. Standard-1X would become Standard-2X, then you might even get into the Performance tiers.

Heroku Dyno Tiers

This strategy will technically work, but it’s not optimal for costs. The cost of running ten Standard-2X dynos is roughly the same as 1 Perf-L dyno but using smaller dynos lets you autoscale with great precision.

In general, it’s better to use small dynos, and to use more of them!

This isn’t always straightforward though. If you can get your application into standard dynos this is good advice, but the Perf-M dyno is usually a bad deal. If you’re always using at least two Perf-M dynos, you may as well go straight to the Perf-L dyno to get more than twice the compute for exactly twice the cost.

If you are having problems with running your application in a smaller-tier dyno, there are usually some things you can do to reduce your actual compute needs. Let’s get into that next.

Reduce your compute needs

The thing that usually pushes a web or background job service into needing a more expensive Heroku dyno is memory. Many web apps need to reduce their memory footprint before even thinking about reducing the memory in their dyno.

There are three main strategies to reduce memory in your Rails apps:

Reduce your Puma concurrency

For web dynos on Heroku, one quick way to reduce your memory requirements is to reduce your Puma concurrency. Because of Heroku’s random routing, it’s important that each of your web dynos can handle more than one request at a time. While more than one puma process running at a time is important, reducing the number will directly reduce your memory usage.

You can set WEB_CONCURRENCY=<your-number> when you execute Puma, which many do in their Procfile.

Reduce your Sidekiq threads

The same strategy can be applied to your worker dynos. Background jobs aren’t controlled by Puma of course. You can control your Sidekiq concurrency with the RAILS_MAX_THREADS environment variable, which can also be controlled in your Procfile.

You can set RAILS_MAX_THREADS on a per-process level to have a different setting between Puma and SIdekiq processes.

Quarantine memory-expensive Sidekiq jobs

Sometimes you can’t reduce memory on your Sidekiq jobs because one particular job or set of jobs is more memory-hungry than the others. You can actually split your background jobs into queues based on their memory requirements. Then, you can assign the memory-hungry queue a different worker, and you can use 2 smaller dynos for those queues rather than one large dyno.

Using 2 Sidekiq queues

This gives you more precision to choose appropriate resources for the sets of background jobs. This is also somewhat straightforward to manage in your Procfile:

web: bundle exec puma -C config/puma.rb
worker_default: bundle exec sidekiq -q default
worker_memory_hungry: bundle exec sidekiq -q memory_hungry

Use a managed database service

Using a dedicated provider for a managed database service will likely result in cost savings over using Heroku. Moving your data stores (your database and even Redis) outside of Heroku to somewhere like Crunchy or TimescaleDB will not only result in some cost savings but also a better experience.

You’ll see here that for ~16GB of RAM, you’d pay Heroku a maximum of $400 compared to Crunchy’s $280!

Crunchy vs Heroku Postgres pricing

Judoscale leans on this principle ourselves! We use TimescaleDB and have been really happy with that decision.

Autoscaling to further reduce costs

Autoscaling has two main benefits. It helps you by:

  • Adding dynos when you need it to keep up with demand
  • Reducing dynos when you’re not using them to save you money

To be able to meet your peak demand without any autoscaling would require you to over-provision your dynos in type, quantity, or both. Using an autoscaler to adjust the quantity of your dynos makes sure you can keep up with traffic without overspending.

Saving money with Heroku autoscaling

Autoscaling is one of the most effective cost-saving strategies for Heroku applications, especially if you increase the precision of the autoscaling by using small dynos.

✅ Tip

Using as small a dyno as you can get away with increases the precision of autoscaling. It’s better to add or remove 1GB of RAM at a time than 4GB at a time because small increments ensure you don’t pay for more than you need.

Autoscaling is such a super powerful costs-savings tool for Heroku that it’s worth optimizing for. The built-in Heroku autoscaler isn’t perfect though.

Judoscale is a Heroku add-on that autoscales faster than the built-in autoscaler. Judoscale scales based on queue time instead of response time, which lets you respond to load changes with greater precision. It’s also available for Standard dynos while the Heroku autoscaler is limited to Performance dynos. Here’s how to get started.

Heroku’s autoscaler also only works for web dynos, even though autoscaling background job workers is where most of the cost savings is often hiding. Judoscale lets you scale web and worker dynos to unlock even more hidden savings.

What if Heroku is still too expensive?

It’s possible that you’ve tried each of the things in this article and Heroku is still too expensive for your project. You might consider another PaaS offering like Render, Railway, or Fly.io. Pricing on each of these platforms isn’t always cheaper than Heroku, but each has unique pricing models that might be a better fit for your app.

You may also consider using a managed cloud offering, an in-between offering that gives you some of the convenience of a platform with some of the cost-savings of managing your own cloud resources. This isn’t my favorite choice though, because it feels a bit like you’re getting the worst of both worlds.

Kamal can be a good option for developers who are comfortable managing their own infrastructure and want to make the repeatable parts like deployments smoother. This won’t be as smooth as Heroku though - to some extent you get the developer experience you pay for!