Should You Switch From Heroku Postgres to StackHero?

Vendor lock-in is a big factor in many decisions — you know it, I know it, and, perhaps most critically, vendors know it.

One interesting result of this is surprisingly asymmetric pricing. A vendor may have very reasonable and attractive pricing in one dimension or product category, and much less reasonable pricing in another. Often, enterprises accept the more expensive pricing because of lock-in — either because staying in the ecosystem is easier, or because it's the only technologically feasible option.

In this article, we're going to discuss the feasibility of switching from Heroku's first-party PostgreSQL hosting to StackHero. StackHero's hosting is somewhat of a "second party" solution: while it's not as integrated as Heroku's own offering, it's still available on their marketplace, works with their CLI, and gets billed alongside their other services.

First, let's discuss the different axes of comparison — RAM, CPU, disk, and finally latency. There are quite a few different points you might end up at for each axis, so we'll discuss how you can properly size your instances if you're not already sure what size is best.

RAM

For many workloads, RAM is one of the most important factors. If possible, it's best practice to fit your entire working data set in RAM. You may not need to fit all of your database in RAM at once — likely, some data is infrequently accessed.

Modern RAM is approximately 1,000 times faster than a fast modern NVMe drive, which means that if we can keep the need to swap to and from disk to a minimum, we will experience much, much faster performance.

However, once you can fit your working set in RAM, you will experience relatively little performance benefit from adding additional RAM. At that point, you may be better off spending more on additional CPU power.

Some workloads, though — many enterprise analytics jobs, for example — cannot reasonably fit into readily accessible RAM, and are generally more likely to be CPU bound. In these cases, you may be better off spending budget on sufficient CPU and/or ensuring you'll have sufficient IOPS, that is, sufficient hard drive speed.

Heroku's price per GB of RAM varies a lot. It also varies a lot by tier, though since these offer very different products, that's not surprising. For Heroku, the cheapest per GB of RAM is Standard-9, offering 768 GB at $5,800 per month. That's $7.55 per GB of RAM, whereas Heroku's Standard-3 charges $26.67. Lest you think Heroku is simply offering a volume discount, Standard-0 is only half that, at $12.50 per GB month. The next tier, Premium, varies between $14.32 and $50.00.

On the other hand, StackHero's pricing is considerably more linear. After the first two "hobby" plans, they are all between $6.25 and $7.38 per GB per month.

CPU

You see a similar story when you price by vCPU: Heroku has a less obvious relationship between performance and value, though it's worth noting that Heroku doesn't disclose the amount of CPU power you get with most plans. Only the Performance plans explicitly list vCPU as a metric. For those, the price per vCPU per month varies from $75 (4G-Performance) to $250 (16G-Performance).

For their non-Hobby plans, StackHero's vCPU price varies between $24.75 (Startup 50GB) and $33.32 (Enterprise 2400GB) — as before, much more linear.

Importantly, though, note that a vCPU from StackHero is not guaranteed to equal a Heroku vCPU exactly. Heroku, for that matter, is often assumed to have a vCPU exactly equal to an Amazon EC2 vCPU — but never actually claims that in their marketing materials.

Based on our testing, StackHero vCPUs have something like a ~1.4x advantage in single-core speed.

In any event, it's worth testing their actual performance in your environment. Strictly speaking, it's also possible that either one will make changes in the future that will degrade their performance, though that's possible with most shared providers.

To get that number, we ran the client from a Heroku dyno in the us region against six provisioned Postgres databases: three Heroku plans and three StackHero plans. The single-connection test used a deliberately server-side query, SELECT count(*) FROM generate_series(1, 2000000), then subtracted each target's SELECT 1 round-trip time from the result. That made the comparison much less sensitive to the extra network hop between the dyno and the database.

That test is not perfect, however. The Heroku logs showed the query spilling about 28 MB to temporary files, so the number is better understood as "single backend CPU plus a fixed spill cost" than as pure CPU. StackHero's server logs were not visible through Heroku, so we could not confirm whether the StackHero targets spilled in exactly the same way. The PostgreSQL major versions were also different: Heroku was on 17.9 or 17.10, while StackHero was on 18.3.

Target vCPU PG version ping p50 (ms) cpu_100ms p50 (ms) compute-only (ms)
heroku-essential-0 2† 17.9 0.59 264.97 264.38
heroku-standard-0 2† 17.10 0.77 316.40 315.63
heroku-standard-2 2† 17.10 0.78 312.44 311.66
stackhero-hobby-10 1 18.3 2.70 245.01 242.31
stackhero-startup-50 2 18.3 2.69 224.98 222.29
stackhero-enterprise-200 8 18.3 2.78 245.46 242.68

† Heroku does not publish a vCPU count for these plans on their marketing site; the technical documentation does list CPU counts, but underneath a banner informing you its subject to change.

We also ran a concurrency test, because per-core speed is only half the story. Each target opened multiple simultaneous connections, each running the same CPU-heavy query, with concurrency expressed relative to that plan's published vCPU count. For Heroku classic-tier plans where the plan API did not publish vCPU, we labeled the value as an assumption and used Heroku's classic-tier documentation as the fallback.

This is the test that tells us whether the advertised cores are actually usable by concurrent Postgres backends. StackHero's plans behaved like the spec sheet would lead you to expect: the 2-vCPU plan almost doubled from one to two active connections and then flattened, while the 8-vCPU plan doubled from four to eight and then flattened. Heroku's Standard-0 and Standard-2 did not show the same scaling at the low end; they stayed around 3 ops/s from one through four concurrent connections.

Target vCPU Concurrency × vCPU ops/s wall (s)
heroku-essential-0 2† 1 0.5 1.5 13.46
heroku-essential-0 2† 2 1.0 3.7 10.91
heroku-essential-0 2† 4 2.0 4.3 18.64
stackhero-hobby-10 1 1 1.0 4.0 4.95
stackhero-hobby-10 1 2 2.0 4.0 10.01
heroku-standard-0 2† 1 0.5 3.1 6.50
heroku-standard-0 2† 2 1.0 2.8 14.32
heroku-standard-0 2† 4 2.0 2.9 27.30
stackhero-startup-50 2 1 0.5 4.2 4.70
stackhero-startup-50 2 2 1.0 8.2 4.90
stackhero-startup-50 2 4 2.0 8.3 9.68
heroku-standard-2 2† 1 0.5 3.2 6.34
heroku-standard-2 2† 2 1.0 3.0 13.45
heroku-standard-2 2† 4 2.0 2.9 27.64
stackhero-enterprise-200 8 4 0.5 14.6 5.47
stackhero-enterprise-200 8 8 1.0 29.7 5.38
stackhero-enterprise-200 8 16 2.0 30.0 10.68

To check whether Heroku's classic tier eventually scales up, we also did an ad-hoc run against heroku-standard-6. It behaved like a larger machine: throughput doubled cleanly from concurrency 1 through 8, then flattened around 24 ops/s. This is slower than you might expect from the advertised 16 vCPUs in Heroku's technical documentation.

Concurrency ops/s
1 3.0
2 6.0
4 12.0
8 23.8
16 23.7
32 23.5

In the future, we'll run some more testing.

Disk Space

For disk space, Heroku starts at $0.78/GB/mo for the Standard tier and ends up at $1.46/GB/mo for the Standard-10 offering. Heroku's cheapest plans in each tier are more expensive — $5.00/GB/mo for Essential-9, $3.13 for Premium-9. However, that's likely minimum-cost related, and for most non-hobby use that's not a factor.

Outside of those ranges, Heroku's $/GB jumps up and down. Premium is cheapest at Premium-2 ($1.37/GB/mo), then most expensive at Premium-7 or Premium-10 ($2.93/GB/mo).

Just as with our other two factors, it seems that Heroku's efficiency per dollar is going to depend on exactly where you are in the pricing band.

Disk IOPS

For IOPS, we could not measure raw storage-device throughput directly, since neither provider gives shell access to the underlying disk. Instead, we used the application-visible proxy that usually matters more for web apps: how many single-row INSERT plus COMMIT operations the database can sustain under concurrent client connections.

This write test includes network round-trip time and WAL fsync time, so it is not a pure disk benchmark. It's closer to "committed writes per second as an application would experience them." At matching vCPU-relative concurrency levels, Heroku's Standard tiers were ahead on this test: at 4× each plan's own 2-vCPU baseline, heroku-standard-0 reached about 2,955 writes per second, while stackhero-startup-50 reached about 2,475. StackHero's larger 8-vCPU Enterprise 200GB plan scaled higher overall, peaking around 6,735 writes per second at 24 concurrent writers before dropping at 32.

In addition to the throughput difference, the shape of the curve matters. A plan that keeps gaining throughput as concurrency rises still has write headroom; a plan that flattens or falls has found its practical commit-throughput ceiling for that workload.

Latency

For latency, we ran a separate warm-connection round-trip probe from the same Heroku dyno. Each database connection first ran a throwaway SELECT 1 to discard connection setup and TLS handshake cost, then ran 30 more SELECT 1 calls and recorded the minimum and median. The minimum is useful here because it is dominated by distance rather than momentary scheduler noise or server load.

Here are the results:

Target min p50
heroku-standard-0 0.58 ms 0.63 ms
heroku-standard-2 0.85 ms 1.04 ms
heroku-essential-0 1.07 ms 1.35 ms
stackhero-startup-50 1.96 ms 2.01 ms
stackhero-enterprise-200 2.80 ms 2.83 ms
stackhero-hobby-10 2.82 ms 2.92 ms

Neither Heroku nor StackHero guarantees the exact location of their services, although Heroku at least guarantees it will be in the same AWS region. As a result, you are not guaranteed to get the exact same results we did.

As you can see, for this particular case, where we deployed a test dyno to the us-east-1 AWS region, StackHero was consistently 1.5 ms to 2 ms slower. Presumably, this indicates that StackHero is deployed nearby — close enough that the benchmark is not accidentally measuring a cross-country or transatlantic database hop, but not quite as close to the dyno as Heroku Postgres is. A us-east to us-west round trip would be closer to 60 ms, and Europe would be higher still, so these 2–3 ms floors strongly suggest same-region placement.

StackHero also appears to use a different architecture than Heroku, seeming to use an ingress IP which routes to the physical server based on the port number. It's conceivable that a significant portion of the difference could be due to latency induced by that architecture.

Conclusion

Although we don't cover it here, note that the StackHero and Heroku plans differ on more than the above factors. Heroku's plans have a different interface and offer different guarantees.

Additionally, Heroku is a subsidiary of Salesforce, a US-based company, whereas StackHero is a French company. In some cases, this may make some compliance tasks easier or harder. Both services offer server availability in the US and in the EU.

postgresql heroku stackhero database benchmarks cost-optimization