Originally published at blog.whynext.app.
Our AWS bill came in over budget two months in a row. Against a monthly cap of $550, actual spend landed at $585, then $553. Not huge amounts, but the trend 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: RDS $208, ECS Fargate $84, load balancer $52, WAF $49, public IP $41, ElastiCache $35, S3 $20. 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 $22 a month, 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 $22 wasn't waste - it was the premium for insurance 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.
Top comments (0)