In pursuit of the best value US cloud provider

In pursuit of the best value US cloud provider Background
13 min read

At ServiceStack, we have been using AWS for hosting for over 10 years. It has served us well, but it suffers from complex pricing and possibility of bill shock due to its fractured pay-as-you-go design.

Thankfully, more and more companies are providing simpler offerings for hosting needs, and AWS themselves launched Lightsail as their answer to market demands for simple hosting options that package everything you need for basic hosting.

These simpler hosting options tend to bundle several things together as one fixed monthly price. A VM with a specific compute and memory allocation, as well as data transfer, and storage.

Looking at different US offerings

Something we wanted to do was to host our live demo applications on a US based host. We were using Hetzner dedicated servers in the past for non-latency sensitive use cases like our build server and Gist.Cafe (our interactive playground for multiple platforms) but we also wanted our demo applications to be snappy for US users.

DigitalOcean provides "Droplets" with this fixed pricing model with a nice and simple interface. Their pricing was quite good and we realized we could run all 20+ of our demo applications on a single droplet for $40/month.

For deployment, we also like to keep things as simple as we can, whilst keeping portability. Since all our projects are public and on GitHub, we use GitHub Actions heavily along with a pattern that deploys our applications using Docker Compose via SSH. Each application runs in its own container behind an NGINX proxy with a side car that handles renewing LetsEncrypt certificates. Below is an example of this pattern with Blazor and Litestream.

A nice side effect of this approach is moving servers is relatively painless. We change the DNS entry for the application to point to our new server, update the GitHub Action Secrets if needed and run our Release workflow.

A minute or so later, the application is back running again. Since their were 20+ of these repositories we took advantage of the GitHub Organization Secrets so we only needed to update values in one place, and running the workflows again can also be done programmatically through the GitHub CLI.

DigitalOcean Price Increase

In June of 2022, we got a notification that prices for droplets would be increasing, and for our droplet it would be going from $40 to $48. While this is a small amount of money, it prompted us to have a wider look into this market.

Something we try to do at ServiceStack is to not only provide a comprehensive .NET Framework for building API first systems, but also seek out great value hosting options we can recommend in this ever change space which we're happy to share, like this blog post, that might be useful to our users and others.

Not everyone builds massively distributed systems, and as hardware performance increases, and platforms like .NET are becoming even more optimized, a setup with just a server or two can manage larger loads and use cases.

Our research and evaluations ended up right back at Hetzner but this time with their Cloud offering. For less than $15 USD per month, you can get a 4 vCPU, 8GB RAM, 160GB storage and 20TB of data transfer hosted in the US.

We found this was by far the cheapest offering for a simple fixed monthly hosting, and looked to compare how well it performed against the more traditional cloud hosting setups.

Litestream and SQLite

Our demo applications use SQLite as a simple way to host the database storage and application together, taking advantage of SQLite's embedded nature. We were also testing out Litestream as a possible solution to the lack of data backups and safety when using SQLite for more production like workloads.

Litestream runs as a separate process and watches your SQLite file for changes and replicates them to storage options like AWS S3, Azure Blob storage and SFTP. We created several templates to make this easier and provide a way to bake in automated disaster recovery using Litestream when used with GitHub Actions and our SSH with Docker Compose deployment.

With some basic load testing, we noticed that SQLite performed pretty well without any effort on our part, and decided we should see how this compares to the commonly suggested hosting patterns provided by the large cloud providers of AWS and Azure.

We used the recommended "Production" setups provided by AWS RDS and Azure SQL Database wizards along with 2 vCPU application server to provide the basis on our comparison. The reason we chose to use the suggested defaults from these providers was to illustrate the power of defaults when offered by market leaders. When compared to a simple SQLite setup, and providers that offer fixed monthly pricing like Hetzner and DigitalOcean, which is often enough to small companies selling Business to Business (B2B) solutions, AWS and Azure recommended "Production" environments can look extremely over priced.

One of the main reasons managed database solutions are chosen is the fact that they take care of automated backups and restore if things go wrong. There are other nice features that definitely have a lot of value, but managed disaster recovery is probably the most commonly cited one I've come across for why services like RDS are chosen during early development.

Litestream provides this kind of data safety and disaster recovery functionality by targeting cost effective and robust storage solutions like AWS S3 and other cloud provided object stores, and making the backup process close to real-time, and accessible via their CLI. And the embedded nature of SQLite removes the uncertainty of the process of upgrading your database.

The Test

To get a clearer idea how each of these hosting options perform with a fairly modest workload, we used a Gatling test to simulate a user logging into our sample Bookings application, browsing around and creating a booking.

These series of steps had 2 write requests and 8 read, separated by 2 seconds per step. We then setup a Gatling simulation that ramped up adding new users to our system from 5 per second to 15 per second, to add a growing number of users over 10 minutes, then sustained over another 10 minutes.

AWS Gatling Result.
Azure Gatling Result.
Hetzner Gatling Result.

All 3 setups could handle this rate of requests without issue, and though the "Recommended" AWS and Azure setups would have more headroom, the price difference is far too large to ignore, especially as the difference is paid every month. The requests throughput of that this test illustrated ~100rps can suit many many use cases, and SQLite is really only limited by its single writer design. We did previous tests of upto 250rps on the same Hetzner Cloud instance with SQLite, but this was starting to reach the maximum throughput, again purely to do with the single writer limitation.

Previous test result price comparison without AWS using Provisioned IOPS.

This level of throughput is enough to service many kinds of businesses with a drastically more simple system to manage, with large cost savings. Also, with the use of an ORM like OrmLite, switching to another database provider can be migrated if and when the traditional offerings like Postgres are needed.

The Setups

The original setup for tests we did in June didn't default to provisioned IOPs for AWS, but when repeating the tests AWS costs blow out due to this feature being enabled by default.

Without provisioned IOPs, it drops to around $132/month as an estimated cost. The $300/month default feature for a "Production" database is very hard for AWS to justify, and I think more of a sign of their poor performing GP2 storage option. Although this will only impact very "chatty" types of applications that need higher IOPs throughput, the difference in performance from RDS vs providers like DigitalOcean and Hetzner can be quite stark.

AWS RDS now defaults to provisioned IOPs for a Production setup, drastically increasing costs.
AWS (DB) AWS (App) Azure (DB) Azure (App) DigitalOcean Hetzner Cloud
vCPU 2 2 4 2 4 4
Memory (GB) 8 4 10 8 8 8
Storage (GB) 100 (provisioned) 16 32 30 160 160
Cost $442 $34 $373 $70 $48 $15

The above specs were provided as "Production" defaults when using a single database instance. Azure SQL Database defaults to costing $373, during the load test, the database CPU hit ~25%.

Azure SQL database without tuning performs poorly for cost, likely due to lack of indexes
AWS (DB) AWS (App) Azure (DB) Azure (App) Hetzner Cloud
Max CPU % 8 35 25 45 40

This is without any tuning on any of the databases, so while you like more performance out of the recommended setups, it is still clear SQLite performs well by default, and it is well worth considering not only Hetzner Cloud for value for money, but if your use can only needs a single host with SQLite.

Hetzner Cloud

While we were primarily looking for one of the lowest cost options with simplified pricing, Hetzner Cloud pleasantly surprised us with a few features the larger providers could learn from.

Hetzner Cloud Pricing.

Creating a new instance is fast

Most of the time if will be ready to remote to before you can open your terminal. Not sure if this is due to some kind of pre-creation process on Hetzner part during the creation screen, but everything is very responsive. In my testing from the time the "Create" button was clicked, my SSH commands would succeed within 20 seconds.

Live Graphs

Another part of the responsiveness is their "Live" graphs for monitoring. It is surprisingly low latency and an extremely stark difference between AWS charging extra for "Detailed" monitoring on EC2 instances. The graphs update every 3-5 seconds in the browser and look to be over a few seconds behind real-time.

Live monitoring updates every 3-5 seconds.

CloudWatch is a major value add for AWS, and Hetzner's offering is very very basic in comparison, but it is nice to see live updating stats right in your web browser, and something hopefully the other providers can also offer in the future.

Price

This is the biggest draw card by a long way. The AWS and Azure "recommended" setups are extremely expensive for the hardware and performance they offer. Yes they are mature cloud offerings with a large array of features, but their pricing scales with hardware resources. Products like Provisioned IOPs are extremely expensive, and when other cloud providers are offering far more performant and competitive storage with their instances, it can feel like AWS is using it's market share and their defaults to upsell very expensive products.

Transfer costs

It's been long known that one of the ways large cloud providers keep customers in their network is by charging excessively large and complex data egress costs. Something attractive about simplified pricing from Hetzner Cloud (and DigitalOcean to a lesser degree) is the included data transfer of 20TB a month.

Not only is AWS data transfer pricing extremely complicated (inter region vs cross region vs CloudFront vs Transit Gateway and so on), but if your application was sending a lot of data to clients, that same 20TB you get for free with a $15 server, would cost $1,791 just for data when coming from AWS. Azure pricing also confusing, and in some ways more expensive.

Defaults are powerful

Both AWS and Azure "recommended" defaults are there not because the software selected (SQL Server and Postgres) need that amount of resources just to operate, but more as an upsell. Lots of projects and applications absolutely do not need features like "Provisioned IOPs", despite GP2 storage of AWS being incredibly slow.

Performing disk speed check using the Linux utility fio an AWS EC2 instance with 100GB GP2 storage can do ~2250 IOPS and 9MB/s read, and ~750 IOPs at 3MB/s write. In contrast, Digital Ocean $48 instance, this is not even paying the extra $8/month for the faster storage can do 35.2k IOPS at 144MB/s read, and 11.8k IOPS at 48MB/s write.

Hetzner again is the stand out, with the $15 instance tests resulting in 50.8k IOPS at 207MB/s read, and 16.9k IOPS at 69MB/s write.

Read IOPS Write IOPs Read MBs Write MBs
AWS 2.3k 0.8k 9.2 MB/s 3.1 MB/s
Azure 3.0k 1.0k 12.5 MB/s 4.2 MB/s
DigitalOcean 35.2k 11.8k 144 MB/s 48.2 MB/s
Hetzner Cloud 50.5k 16.9k 207 MB/s 69.2 MB/s

All tests used the following fio command.

fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test \
--filename=test --bs=4k --iodepth=64 --size=4G --readwrite=randrw --rwmixread=75

SQLite

Part of the resurgence in popularity of using SQLite is not only the simplicity of a single server, but also as hardware is getting faster, issues surrounding limitations of a single writer are becoming less of an issue for a wider number of use cases.

Litestream's elegant solution for streaming backups to cheap replica storage is definitely adding to that popularity as well since it was a sticking point for a lot of use cases that need that simple data redundancy functionality.

Other solutions for Postgres like pgbackrest are similar, but the ease of use is another big part of what makes SQLite and Litestream a great combination. One command to watch and replicate, another to restore, and it runs completely independent of your application using the SQLite file.

Hetzner Cloud is hard to beat on price

We're going to keep testing Hetzner Cloud with new applications and use cases going into the future. While they are a very new player in the crowded Cloud Provider market, and their offerings are much more limited, the pricing is a breath of fresh air from the large three providers.

More competition in this space is a great thing, and for those that can use solutions like SQLite for their projects, checking out some of the smaller players like DigitalOcean and Hetzner Cloud is well worth your time. The early signs from Hetzner Cloud is they not only have an amazing value product, but the features they do have improve on the equivalents from likes of AWS and Azure, which is hopefully a sign of things to come from them.