When Your Heroku App Slows Down: Bigger Dynos or More Dynos?
Heroku gives you two scaling levers — bigger dynos or more dynos — and they solve different problems. Here's how to tell which one your app actually needs, and the database bottleneck neither one fixes.
17 Aug 2026, 05:14 UTC

Your Heroku app is getting slow under load. The instinct is to throw resources at it — but Heroku gives you two very different levers, and pulling the wrong one wastes money without fixing the problem. You can scale vertically (upgrade to a bigger dyno type) or horizontally (run more dynos). The useful takeaway: horizontal scaling improves availability and request throughput, while vertical scaling mostly helps memory-hungry or CPU-bound single processes — and neither one fixes a saturated database.
What a dyno actually is
A dyno is an isolated Linux container running your application code and its dependencies. Two properties drive every scaling decision you'll make:
- Ephemeral filesystem. Anything written to local disk disappears when the dyno restarts or cycles (which happens at least daily). Uploads, session files, and caches on disk are not durable.
- No shared memory between dynos. Two web dynos know nothing about each other. Any state they both need — sessions, rate-limit counters, job queues — must live in an external store like Postgres or Redis.
Standard dyno types (e.g., Standard-1X, Standard-2X) share a multi-tenant host, so your neighbors can affect performance. Performance dynos (Performance-M, Performance-L) get dedicated CPU and RAM. That distinction matters when you decide which direction to scale.
Vertical scaling: one bigger container
Vertical scaling means changing the dyno type, for example from Standard-1X (512 MB RAM) to Standard-2X (1 GB) or a Performance dyno. You do this in the dashboard or with the CLI:
heroku ps:type web=standard-2x --app your-app-name
This is the right move when a single process is the bottleneck: your app needs more than 512 MB of memory (you're seeing R14 memory quota errors), or a single request does heavy CPU work like image processing. It's also the only way to get dedicated resources — moving to Performance dynos removes multi-tenant noise.
The limit: one dyno is still one dyno. If it crashes or is cycling, your app is down. And a bigger dyno doesn't help if your problem is handling 500 concurrent requests that each take 200 ms — that's a concurrency problem, not a size problem.
Horizontal scaling: more containers behind the router
Horizontal scaling adds dyno instances. The Heroku Router distributes incoming HTTP requests across all running web dynos using a roughly random/round-robin style distribution:
heroku ps:scale web=3 --app your-app-name
heroku ps --app your-app-name # confirm three web dynos are up
Run these from any machine with the Heroku CLI installed and authenticated; you need a collaborator or owner role on the app. Scaling is near-instant and reversible (web=1), so the risk is low — the main cost is billing.
This buys you two things. First, throughput: three dynos can serve roughly three times the concurrent requests (assuming your language/runtime handles concurrency per dyno — check your web server's worker/thread configuration, e.g., Puma threads or Gunicorn workers, since a single-threaded server wastes a multi-core dyno). Second, availability: with two or more dynos, a single dyno crash or restart doesn't take the app offline.
Horizontal scaling only works if your app is stateless. Before adding dynos, verify: sessions are in cookies, Redis, or the database — not in-process memory; file uploads go to object storage like S3; and any in-memory caching is treated as a per-dyno optimization, not a source of truth.
The bottleneck neither lever fixes
Here's the trade-off that bites people: scaling web dynos multiplies the load on your database. Each dyno opens its own connections, and Heroku Postgres plans have connection limits. Go from 1 to 10 dynos with 5 connections each and you may blow past a 20-connection limit, producing errors that look like app slowness.
Before scaling, check the dashboard Metrics tab: if router response times are high but dyno CPU and memory are low, the bottleneck is downstream — usually the database or an external API. In that case, add connection pooling (e.g., PgBouncer via a buildpack or server-side pooling on supported plans), tune slow queries, or upgrade the database — not the dynos.
Also consider worker dynos for long-running tasks. Anything that takes more than a few hundred milliseconds — sending email, generating reports, calling slow APIs — should be pushed to a background queue and processed by a worker dyno, keeping web dynos free to answer requests quickly.
A practical decision path
- Open the Metrics tab. Memory at quota or R14 errors? Scale vertically (bigger type).
- CPU pegged across all dynos with queuing requests? Scale horizontally (more dynos) — after confirming statelessness.
- Low dyno utilization but slow responses? Look at the database and external services; dyno scaling won't help.
- Need consistent performance without noisy neighbors? Move to Performance dynos.
Verify any change by watching response time percentiles and dyno load in Metrics for a day of real traffic, not just the first five minutes. Start with web=2 for availability alone — it's the cheapest reliability upgrade Heroku offers — and let measured load, not guesswork, drive you from there.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.