Skip to main content

From Heroku to Render at 2,800 RPS: What a Rails Migration's Latency Trade-Off Reveals Before You Self-Host

11 min readDora NodaDora Noda
Share
On this page

On February 6, 2026, Heroku entered sustaining engineering mode: no new features, no new Enterprise contracts, stability and security patches only. For every team still running production on dynos, the migration question stopped being hypothetical. And the default answer — the one with a dedicated migration page, a documented dyno-to-service mapping, and even an AI agent skill that automates the move — is Render.

Most migration guides answer the easy question: how do you move? This post answers the harder one: what trade-offs replace your Heroku bill once you land? We use a concrete reference workload — a Rails API serving 2,800 requests per second — and account for latency, queueing, deploys, and incident fate across three destinations: staying on a frozen Heroku, moving to Render, and skipping the vendor move entirely for a self-hosted fleet on owned hardware.

One honesty note before the numbers. There is no single public report that measured all three platforms on the same 2,800-RPS Rails app, so this post does not pretend otherwise. The platform behaviors below are documented facts with sources; the sizing and cost math is an explicit model with stated assumptions. Treat it as a decision framework, then confirm it with your own load test.

The reference workload: 2,800 RPS of Rails

2,800 requests per second sounds abstract until you convert it: roughly 168,000 requests per minute, or about 242 million requests per day. That is a successful SaaS API or a busy consumer backend — big enough that platform defaults (routing, timeouts, connection caps) visibly shape latency, but small enough that a single team can still operate it.

Our reference app is a typical Rails 8 API on Puma: JSON endpoints averaging around 50ms of server time, a Postgres primary with one follower, Sidekiq workers chewing through background jobs, and Redis for caching and queues. Sustained load, not a five-minute spike. Everything below assumes this shape; if your app is WebSocket-heavy, GPU-bound, or serves huge file downloads, some rows of the table change, and we flag where.

The short version: all three destinations in one table

Heroku (2026, frozen)RenderSelf-hosted fleet (Cluster API + Hetzner-class hardware)
Request routingRandom per-dyno routing, documentedRegional load balancer in front of your instancesYour ingress: least-conn, locality-aware, whatever you configure
Hard request timeout30 seconds (H12)100 minutesWhatever your ingress and app agree on
RegionsAWS US + EU onlyOne region per service, from a short US/EU/Singapore listAnywhere you can rack or rent a machine
Queueing signalFirst-class: router queue + X-Request-Start timingsApp-level metrics you instrument yourselfSame — yours to instrument, plus node-pressure signals
Deploysgit push, preboot zero-downtime, instant rollbackgit push, zero-downtime with health checks, previewsWhatever your pipeline is: Kamal, Argo, git push on your own PaaS
Workers and cronWorker dynos + SchedulerBackground workers + cron jobsSidekiq anywhere; cron as a controller, not a vendor feature
Incident fateShared with every tenant; platform frozen, limits permanentShared with every tenant; vendor controls the fix timelineYours alone: no noisy neighbors, no surprise maintenance, but no vendor to page
Modeled cost at 2,800 RPS~$3,000–$13,000/mo (mid ~$6,000)~$1,000–$5,000/mo (mid ~$2,200)~$150–$400/mo infra + engineer time

The rest of this post unpacks each row. If you only remember one thing, make it this: Render wins the bill decisively over Heroku at this scale, but it keeps the two properties that bite hardest at 2,800 RPS — single-vendor routing you don't control and multi-tenant incident fate. Self-hosting is the only move that changes those; it just charges you in engineer time instead of dollars.

The bill that starts the conversation

Nobody migrates at 2,800 RPS for fun. They migrate because the Heroku invoice crossed a line. Current Heroku pricing bands run roughly $7–$500 per dyno per month, $5 to several thousand for Postgres, and $15 and up for Redis — and a frozen platform means those numbers only move in one direction over time.

Sizing math first, with explicit assumptions. Take one web unit (a dyno, an instance, a box) and ask how many requests per second of typical Rails API traffic it absorbs:

  • Slow endpoints (~50 RPS/unit): heavy serialization, N+1s you haven't found yet. 2,800 RPS needs ~56 units.
  • Typical endpoints (~100–150 RPS/unit): healthy Puma with threads busy. Needs ~19–28 units.
  • Fast endpoints (~200 RPS/unit): cached reads, tuned queries. Needs ~14 units.

Now price the mid-case (~20–25 units) per platform:

  • Heroku: 20 Performance-M dynos at $250 each is $5,000 before data. Add a production Postgres tier and Redis and you land around $5,500–$6,500/month, with the slow/fast range spanning roughly $3,000 to $13,000.
  • Render: 20 Pro-class instances at roughly $85 each is $1,700 before data. Add managed Postgres and Key Value and you land around $2,000–$2,500/month, with a range of roughly $1,000 to $5,000. Render's own Heroku comparison docs lean on exactly this gap, and independent 2026 cost guides confirm the ordering across compute, database, and Redis.
  • Self-hosted on Hetzner-class hardware: six to eight 4-vCPU/8-GB nodes at roughly $20 each is under $200; add load balancing, backups, and object storage and infrastructure lands around $150–$400/month. Operators who have run both sides of this comparison consistently report the crossover where VPS economics beat managed PaaS at roughly $500–$1,000/month of PaaS spend — a line our reference workload crosses several times over.

The honest footnote: the self-hosted column omits the scarcest input, which is engineer attention. A managed bill is money; a fleet is a part-time job. The checklist at the end prices that job explicitly instead of hand-waving it.

Latency and queueing: what actually changes

Cost opens the conversation, but latency decides whether the migration was worth it. Two platform behaviors dominate Rails tail latency, and they differ on every platform in the table.

Routing: random vs balanced vs yours. Heroku's router famously picks a random dyno per request rather than the least-loaded one — documented behavior, not folklore, and the root cause behind a decade of "add dynos, tail latency barely moves" stories. At 2,800 RPS across 20+ dynos, random routing guarantees that some requests land on a dyno whose Puma threads are all busy while another dyno idles.

Render puts a regional load balancer in front of your instances, which behaves like balancing rather than dice — a genuine structural improvement for tail latency. Self-hosted, your ingress (Envoy, HAProxy, Gateway API) can do least-connection, locality-aware, even header-based routing; the ceiling is your own config, not a vendor's design decision from 2011.

Timeouts: 30 seconds vs 100 minutes vs none. Heroku's router kills any request past 30 seconds with an H12, a limit unchanged since the Cedar stack and now frozen permanently with the rest of the platform. If your API has endpoints that legitimately take longer — report generation, bulk exports, slow upstream calls — Heroku forces you to re-architect around the limit (background job plus polling) whether or not that fits your product. Render documents a 100-minute HTTP request timeout, which effectively removes the ceiling for anything request-shaped. Self-hosted, there is no ceiling except the ones you set.

Queueing math at 2,800 RPS. Little's Law is unforgiving: 2,800 RPS at 50ms mean service time means ~140 requests in flight at every instant, before bursts. With Puma at 5 threads per process, that's 28 saturated processes just to stand still — which is why the "typical" sizing row above needs 20+ units plus headroom for deploys and traffic bursts. Heroku at least gives you a first-class queueing signal: router logs report queue depth and connect time per request, and X-Request-Start lets middleware measure time-to-service precisely. On Render and self-hosted, you instrument this yourself (request-start headers, Puma stats, queue-time middleware). Teams migrating off Heroku should plan to rebuild that dashboard on day one, not discover its absence during the first incident.

Connections and regions. At this throughput, database connection math matters as much as request math: 28 Puma processes at 5 threads each want up to 140 Postgres connections before workers and followers enter the picture, so poolers (PgBouncer) or transaction pooling stop being optional on every platform. Regions are the other quiet constraint: Heroku offers US and EU on AWS; Render pins each service to one region from a short list (US regions, Frankfurt, Singapore), so a globally distributed audience still funnels through one geography per service — independent synthetic monitoring has measured Render averaging ~451ms with 99.89% uptime from distant probes, a datapoint about geography more than quality. Only self-hosting lets you place capacity next to every audience, at the cost of operating every placement.

Deploys, day-2 operations, and incident fate

Day one is git push on all three platforms. Day two is where they diverge.

Deploys. Heroku's pipeline — push, build, preboot zero-downtime release, one-command rollback — remains the gold standard for simplicity, and Render matches its shape: push-to-deploy, zero-downtime releases gated on health checks, preview environments per pull request, and instant rollbacks. The real-world Rails migration writeups from 2026 (one detailed May walkthrough ran Sidekiq on both Heroku and Render hostnames in parallel during cutover) confirm the mechanical move is measured in hours, not weeks. Self-hosted deploys are a spectrum: Kamal gives you push-to-deploy on your own boxes with minimal moving parts, while a Cluster API fleet with GitOps gives you declarative everything at the cost of real platform engineering.

Workers and scheduled jobs. Sidekiq, GoodJob, cron — every Rails app has them, and every platform expression differs slightly. Heroku splits them into worker dynos plus Scheduler; Render into background workers plus cron jobs; self-hosted into whatever your orchestrator schedules. Budget migration time for the translation layer (Procfile to render.yaml to manifests), not just the web tier.

Incident fate is the row most guides skip. On any multi-tenant PaaS, your uptime is the vendor's uptime: their auth outage is your deploy freeze, their control-plane incident is your incident, and their fix timeline is your status page. Heroku's sustaining-mode announcement makes this concrete in a new way — the limits you have today are the limits you will always have, because there is no roadmap left to change them.

Render is under active development, which cuts both ways: the platform improves, but you ride every change on shared infrastructure. Self-hosting inverts the bargain: nobody else's incident can take you down, and nobody else fixes yours at 3am. At 2,800 RPS — where ten minutes of downtime is 1.7 million failed requests — that inversion deserves a line in the decision, not a footnote.

The decision checklist: stop at Render or keep going to self-hosted?

With the rows unpacked, the decision compresses to six questions. Answer honestly; the pattern they form is the answer.

  1. Does anything in your traffic exceed 30 seconds per request? If yes, Heroku is already disqualified and Render's 100-minute ceiling is sufficient — no need to self-host for this row alone.
  2. Is your audience in more than one geography? If p99 latency from a single region fails your SLO, Render only partially helps (one region per service). Multi-region is the strongest technical argument for self-hosting on this list.
  3. Is your modeled PaaS bill above ~$1,000/month? Past the VPS-economics crossover, every additional unit of growth widens the gap. At our reference workload's ~$2,200 Render mid-case vs ~$250 infra, the annual delta funds serious engineering time.
  4. Do you have (or can you hire) half an engineer for platform work? Self-hosting at this scale is backups, upgrades, on-call, and CVE patching. If the answer is no, Render's premium is cheap insurance, full stop.
  5. Can you tolerate shared incident fate? Regulated workloads, strict error budgets, or past trauma from a vendor outage all push toward owning the blast radius.
  6. How fast must this happen? Heroku-to-Render is a weeks-long project with a documented path and automated tooling. Standing up a production-grade fleet is quarters. Sustaining mode gives you time — Heroku isn't shutting down — but don't confuse "not urgent" with "never."

The most common honest outcome: migrate to Render now, keep the architecture portable, and revisit self-hosting when growth or geography forces the question. The least common honest outcome is staying on Heroku indefinitely at this scale — not because it stops working tomorrow, but because every limit you feel today is now permanent, and your bill funds a roadmap that no longer exists.


Moving off Heroku is the forcing function; where you land is the strategy. Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with a Render-compatible API that keeps the migration path open in both directions. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex