Why NextWhy Next
Back to posts
💻 Dev13 min read

The Discount You Weren't Claiming: Why Spot and Graviton Are Cheap

Our AWS bill came in over budget two months running. Hunting for resources to cut, I found the real lever wasn't 'how much you use' but 'how you buy it.' Why Spot and Graviton are cheap, and why one line item that looks like waste - RDS Proxy - should stay on: a story about the 'why' behind every number on a cloud bill.

TL;DR

When someone tells you to cut the cloud bill, your hand reaches for 'what to turn off' first - but if the resources are already optimized, the only lever left is how you pay. Fargate Spot is AWS clearing out spare capacity on the condition it can reclaim it 'within 2 minutes,' so it's 70% cheaper (and belongs in staging, where that condition is fine). Graviton is AWS's own ARM chip with no middleman markup, so it's 20% cheaper (and, since there's no catch, it belongs in production too). On the flip side, some costs that look like waste - like RDS Proxy - are actually insurance against connection surges. Reading a bill isn't about shrinking numbers; it's about understanding why each number is there - claim the discounts whose conditions you can live with, and keep the costs that prevent incidents.

On this page

Our AWS bill came in over budget two months in a row. The overage was only a single-digit percentage - not a big number in absolute terms, but the direction was up. I went resource by resource looking for something to cut, and the conclusion was unexpected: there was almost nothing left to cut. The real lever wasn't "how much you use" - it was "how you buy it." What I learned along the way: every number on a bill has a reason, and you need to know that reason to decide what to turn off and what to keep.

The resources were already lean

I started with usage itself. But this part had already had a lot of work put into it. The NAT gateway was gone in favor of public subnets, logs were kept for only 7 days, tracing was sampled at 1%, and the observability dashboard was squeezed to fit inside the free tier. The database and Redis were already running on cheaper ARM instances, S3 had automatic tiering, and the bastion host only spun up on demand. Pretty much everything that could be squeezed out at the resource level already had been.

Breaking down the June bill line by line: the database (RDS) alone was the biggest line, at roughly 40% of the total, followed by compute (ECS Fargate) at about 17%, then the load balancer and WAF at around 10% each, with public IP, cache (ElastiCache), and S3 trailing in single-digit percentages. Shrinking the instances further from here would have been a step backward. The database had a history of being scaled up once already due to memory pressure, and the WAF was deliberately kept around to validate rules before production rollout. The only lever left wasn't the resources - it was how we paid for them. Buy the same resources, just more cheaply.

Why Spot is cheap

The first thing that stood out was that we were buying compute purely at on-demand list price. AWS has a much cheaper channel for the exact same servers: Fargate Spot. It runs about 70% cheaper than on-demand.

There's a reason it's that cheap. AWS builds out its data centers to handle peak demand. Under normal conditions, a lot of that capacity sits idle. Idle capacity earns zero revenue, so AWS sells it off cheap on one condition: "if a full-price customer shows up, you get bumped within 2 minutes." It's the same logic as an airline selling off empty seats right before departure - a seat that flies empty earns nothing, so selling it with strings attached still beats selling nothing at all.

The question is whether you can live with that condition - "you get bumped if asked." In production, that's a problem. If a task gets reclaimed 2 minutes after a user connects, the service wobbles, even if briefly. Staging is different: there are no real users, and a few minutes of downtime just mildly inconveniences one QA engineer. So we moved staging's API onto Spot. Performance is identical to on-demand, since it runs on the exact same hardware - same vCPU, same memory. The only thing that changes is the availability condition: "can be reclaimed" - and that's a condition staging is happy to accept. Once we made the switch, staging tasks started running fine on Spot.

Why Graviton is cheap

The second lever was the compute chip itself. The database and Redis were already on ARM instances, but the Fargate tasks running the application itself were still stuck on x86. Moving to Graviton, AWS's own ARM chip, cuts the cost of the same performance by about 20%.

The reason this one is cheap is different in kind from Spot. With a standard instance, AWS is buying CPUs from Intel or AMD, so the price we pay includes the chip maker's margin. Graviton is a chip AWS designed itself, so that middleman markup disappears. On top of that, the ARM architecture does the same work with less power and less heat, which lowers the data center's electricity and cooling costs too. From AWS's side, the cost basis is lower, so selling it 20% cheaper is still profitable - and it's also a strategy to pull customers into AWS's own chip ecosystem by pricing it low.

Here's the key difference. Spot's discount comes with a condition - "accept that you might get reclaimed." Graviton's discount comes with none. It's simply a cheaper chip. So while Spot is confined to staging, Graviton carries straight through to production as-is. Performance isn't much of a worry either: Node.js gets performance per vCPU on Graviton that's on par with x86, or better. The one thing worth watching is cold start. When an app first boots, single-core speed matters, and Graviton can lag a bit behind the latest x86 chips there, adding a few seconds to startup. So we rolled it out to staging first, measured boot time, and then promoted it to production. The only pre-check needed was confirming that native modules were built for arm64.

To sum up the character of the two discounts: Spot is a discount you earn in exchange for tolerating inconvenience, so it comes with a condition (hence staging); Graviton is a discount you get by sharing in AWS's own cost advantage on its chip, so it comes with none (hence production too). With performance equal, there was no reason to keep paying x86 list price. In truth, this round of optimization wasn't really about cutting anything new - it was closer to finally claiming a discount we'd never been claiming.

Some costs shouldn't be cut

But scanning a bill also turns up traps running the other direction: line items that look wasteful but are actually insurance.

RDS Proxy was one. It was costing a fixed monthly cost, and at first it looked like a candidate for cutting. One of Proxy's features is handling secret rotation without downtime, and that rotation feature was currently turned off. If we weren't using rotation, why keep the Proxy at all?

That was a misread. Proxy's primary purpose was never rotation - it's preventing connection surges. When a deploy or a scale-out suddenly spins up more tasks, the number of connections hitting the database spikes, and the database has a hard cap on how many concurrent connections it can accept. Proxy sits in front of it, pooling and multiplexing connections so that cap never gets breached. As it happens, this particular database was an instance that had already been scaled up once due to memory pressure, and every single connection eats memory. In other words, that fixed cost wasn't waste - it was an insurance premium that keeps the database from keeling over from a connection surge every time we deploy. Turning it off wouldn't have saved money; it would have removed the safety net.

The misread traced back to a code comment. The comment describing that Proxy was written mostly from the rotation angle, which made it easy for a reader to conclude "rotation is off, so Proxy is unnecessary." So instead of cutting Proxy from the savings plan, we added a line to the comment: "Primary purpose is preventing connection surges; rotation is a secondary feature." So the next person reading the bill doesn't make the same mistake.

Every number on a bill has a reason

Tell someone to cut cloud costs, and their hand reaches for "what to turn off" first - shrink the instances, disable features, delete resources. What we learned this time was the opposite. The question to ask before cutting anything is: "why is this number here?"

Some numbers are discounts you never claimed. Like Spot and Graviton, there's a cheaper channel for buying the exact same resource, and you were simply paying list price. Each of these discounts is cheap for its own reason, and knowing that reason - whether it demands tolerating reclamation, or it's a no-strings-attached cost advantage - tells you exactly where it's safe to apply. Other numbers are, conversely, insurance you shouldn't cut. If a line item that looks like waste is actually preventing an incident, it isn't spend - it's defense.

In the end, reading a bill isn't about making the numbers smaller. It's about knowing the reason behind each one. Once you know the reason, you claim the discounts whose conditions you can live with, and you keep the costs that prevent incidents. That's all we really did here - not saving anything new, just sorting out which discounts to claim and which insurance to keep.

Frequently asked questions

Is it safe to use Fargate Spot in production?

Spot's trade-off is that a task can be reclaimed after a 2-minute warning. If your environment can absorb that interruption, it's fine for production too. If you run an HA setup with multiple tasks and losing a bit of capacity for a few minutes isn't critical, partial adoption works. But if you're running a single task or a workload sensitive to interruption, it's risky - start with staging, where interruptions are harmless.

Doesn't performance suffer when you move to Graviton (ARM)?

For most workloads it's on par or even better. Node.js in particular gets performance per vCPU on Graviton that matches x86. The one thing worth watching is cold start: single-core speed is a bit slower than the latest x86 chips when an app first boots, which can add a few seconds to startup. Measure boot time in staging first, then promote to production - the only thing to check beforehand is that your native modules are built for arm64.

Related posts