DEV Community

Rey Kingers
Rey Kingers

Posted on

AWS Billing Is a Game You Didn't Know You Were Playing

I spent the last few months building calculators. Not the fun kind — the kind where you stare at a cloud bill for three hours trying to figure out which line item is the one quietly draining your bank account.

The short version of what I learned: AWS (and GCP, and Azure) pricing pages are technically accurate and deeply misleading at the same time. They show you the number that makes the service look cheap, then bill you on a completely different dimension that isn't even on the same page.

Here are 10 ways I've seen real teams get burned — with the actual numbers.


1. Your NAT Gateway Costs More Than Your Compute

A NAT Gateway is basically a managed box that lets stuff in your private subnet talk to the internet. AWS charges $0.045/hr for it. That looks like thirty-two bucks a month. Whatever.

But here's what the pricing page buries three paragraphs down: you pay that per Availability Zone. The standard "well-architected" setup puts one in every AZ. Three AZs = $98.55/month before a single byte of traffic. Your database isn't even running yet and you're down a hundred bucks.

Then there's the data processing fee: $0.045 for every GB that goes through it. Then there's cross-AZ traffic at $0.02/GB — and if your app server in us-east-1a talks to a NAT Gateway in us-east-1b to reach an external API, AWS counts that traffic twice. Once leaving the app server's AZ, once leaving the NAT GW's AZ.

A mid-sized SaaS I modeled was paying $450/month in NAT Gateway charges. Their actual compute bill for the environment? $380.

If you're mostly pulling from S3 or DynamoDB: VPC Gateway Endpoints are free. Set them up. If you're pulling container images from ECR or shipping logs to CloudWatch: Interface Endpoints cost money but they're 78% cheaper on data processing than NAT GW. And if you really just need a box that does NAT, an EC2 instance with amazon-linux-amzn2-ami-nat costs like $10-20/month and handles the same traffic.

I broke down three real AWS bills where NAT Gateway was the #2 line item behind compute: full walkthrough with calculator →


2. That Lambda Cost $10.50 on Paper and $427 on Your Credit Card

I see this one constantly. Someone builds a serverless API, plugs the numbers into the AWS calculator, gets back $10.50/month, and ships it. A month later the bill is $427 and they think there's a bug.

There isn't. Five things the calculator didn't ask about:

Cold starts in a VPC. When your Lambda sits in a VPC (which almost any real app does, because your database is in one), a cold start takes 300-800ms. You're billed for that time. Worse, while a request is waiting on a cold start, new requests queue up, Lambda scales out, and suddenly you're running 3× the concurrent executions your steady-state math predicted.

CloudWatch Logs never expire by default. Lambda logs 2 KB per invocation. At 100 requests/second, that's 5.2 GB/month of log data. Ingest is $0.50/GB. After 90 days the cumulative storage + ingest cost exceeds the Lambda compute cost. For a 256MB/500ms function, your logging bill passes your function bill at around 25 KB of logging per invocation. I've seen teams set the retention to 7 days and cut their CloudWatch bill by 90% overnight.

Step Functions will wreck you. Each state transition costs $0.000025. A 10-step workflow processing 1M executions/month: $250/month in Step Functions vs $20/month in Lambda compute. The orchestration costs 12.5× what it's orchestrating.

Also: cross-AZ data transfer between Lambda and RDS/ElastiCache is billed as regular egress. And provisioned concurrency costs 30% more per GB-second than on-demand.

My general rule after modeling a bunch of these: Lambda wins at low, spiky traffic. Once you're above ~50 requests/second steady-state, do the math on a reserved EC2 instance. Sometimes the boring option is $800/month cheaper.

More details and a calculator you can plug your own numbers into: Lambda cost deep-dive →


3. Datadog's Pricing Model Is Designed to Make One Mistake Cost You $5,000

Observability tools now eat 15-25% of infrastructure spend. At a few places I've talked to, the Datadog bill was literally bigger than the AWS bill.

The meanest trap in their pricing is custom metric cardinality. A custom metric costs $0.10/month. Sounds fine. But the "metric" is defined by the unique combination of metric name + tag values. If you tag http.request.duration with customer_id, and you have 1,000 customers — congrats, that's 1,000 custom metrics = $100/month. For one metric.

This gets out of hand fast. Someone on your team adds session_id as a tag while debugging a sticky session issue. Forgets about it. That tag generates, say, 50,000 unique values over a month. $5,000/month on the next invoice for a tag that was never supposed to be permanent.

The other Datadog reality check: at 200 hosts with logs + metrics + APM, you're looking at roughly $36,000/month. A self-hosted Grafana stack (Loki + Mimir + Tempo) on a few reserved instances can run the same telemetry for $4,900/month. The breakeven is around 70-100 hosts. Below that, pay the SaaS tax and sleep better. Above that, self-host.

Also: if you're using Splunk, 90%+ of your stored data is never queried after the first week. Ship the old stuff to S3 and query it with Athena when you actually need it.

I modeled the self-host vs SaaS decision at 50, 200, and 500 hosts: observability TCO breakdown →


4. Your Storage Costs 31× More Than You Think Because Egress Is the Real Price

S3 Standard: $0.023/GB. Cloudflare R2: $0.015/GB. The difference looks like $8 per terabyte. Who cares?

Now add what actually happens to your data: it leaves the cloud. Every time someone downloads a file, views an image, or pulls a container image. Egress from S3 to the internet is $0.09/GB.

10 TB stored, serving 50 TB/month to users:

  • R2: $150/month (storage only, zero egress)
  • S3: $4,730/month ($230 storage + $4,500 egress)

That's not a typo. 31× difference. And S3 is one of the cheaper hyperscalers on egress — GCP and Azure are worse.

The thing nobody tells you about cold storage tiers: moving 100 TB to Glacier Deep Archive saves ~$2,000/month in storage. But if you ever need that data back, retrieval costs $2,000-$10,000 depending on how fast you want it. Cold storage isn't cheaper storage. It's a bet that you'll never need to read it. If you lose the bet, it costs more than Standard.

I ran the numbers across S3, R2, B2, Wasabi, GCS, and Azure Blob for four different workload profiles: storage cost comparison →


5. Your Load Balancer Bill Is Based on Whatever Dimension You're Not Watching

ALB and NLB both bill by LCU — Load Balancer Capacity Unit. You pay for the maximum of four dimensions, not the sum.

An ALB at 2 Gbps with basically no new connections: 2 LCU. The bandwidth dimension is the one that binds, so you pay 2 LCU. Fine.

Now imagine a WebSocket-heavy app. Same 2 Gbps, but also 50,000 new connections per second. On ALB, the new-connection dimension caps at 25 connections per LCU — so it needs 2,000 LCU. On NLB, it's 800 flows per LCU — so it needs 63. NLB is 4.6× cheaper for connection-heavy workloads.

The one that catches people completely off guard: ALB rule evaluations. The first 10 rules on your listener are free. After that, you pay 1 LCU per 1,000 rule evaluations per second. If you have 50 host-based routing rules and 2,000 requests/second coming in: (50 - 10) × 2,000 = 80,000 rule evals/second = 80 LCU. That's about $467/month just in rule evaluation LCU — potentially more than your bandwidth and connections combined.

If you're running one ALB per microservice because "that's how the Terraform module was set up," you're also paying the $16.43/month idle fee per ALB. Consolidate them behind host-based routing. The first 10 rules are free.

I modeled five different traffic profiles (API, WebSocket, gRPC, microservice mesh, static site) and identified which dimension binds for each: ALB vs NLB cost comparison →


6. A 95% CDN Cache Hit Rate Is Actually Kind of Terrible

Your CDN dashboard shows 95% hit rate. Green bar. Job done.

Here's the problem: you don't pay for hits, you pay for misses. Because misses go to origin. At 95% hit, 5% of requests miss. At 99% hit, 1% miss. That's not a 4-percentage-point improvement — you just cut your origin traffic by 80%. The miss ratio is the number that costs you money.

Six things that silently tank your hit rate:

  • Query string spam. Every ?utm_source=linkedin&t=1738432000 looks like a unique URL to the cache. Strip marketing params at the CDN layer.
  • AI crawlers. GPTBot and ClaudeBot hit URLs with near-100% uniqueness. Every request is a cache miss. Block them, rate-limit them, or serve them cached versions aggressively.
  • Vary headers gone wild. If your origin sends Vary: User-Agent, congrats, every browser variant gets its own cache slot.
  • Cookies. A Set-Cookie header or a session cookie on static assets forces every request through to origin.
  • Short TTLs. Default TTL on CloudFront is 24 hours. Most teams never change it. For content-hashed assets (main.a3f2b1c.js), set it to a year.
  • Invalidation cascades. Running /* invalidation on every deploy warms the cache from zero. Invalidate specific paths or use versioned URLs.

The single biggest lever: origin shield. CloudFront calls it Regional Edge Cache, Cloudflare calls it Argo Tiered Cache. It's an intermediate cache layer between edge POPs and your origin. At 95% edge hit + 90% shield hit, origin sees only 0.5% of traffic. It adds a few cents per GB and pays for itself almost immediately at any real scale.

Here's the full breakdown with a calculator that shows how much each leak is costing you: CDN cache economics →


7. Your LLM API Bill Is 3× the Sticker Because You're Not Caching Your System Prompt

The GPT-4o pricing page says $2.50 per million input tokens. You do the napkin math: 10,000 requests/day, 2,000-token system prompt. That's $18.25/day. Reasonable.

The bill says $54.

Here's why: output tokens cost 4× more than input tokens ($10/M vs $2.50/M). Your napkin math only accounted for input. And every provider's tokenizer counts the same text differently — the same 10,000-character document can be 2,350 tokens on Anthropic and 2,800 on DeepSeek. At a million requests a month, tokenizer variance alone is a four-figure line item.

But the real money is in prompt caching — that almost nobody configures. Anthropic gives 90% off cached input tokens. OpenAI gives 50% off. Your system prompt is the same on every request. Your few-shot examples are the same. Your RAG context might be the same for an entire user session. All of that qualifies for caching. A workload with 40% cache-hit rate on Claude Opus saves about $6,500/month compared to sending everything uncached.

Batch processing is another 50% off at both OpenAI and Anthropic if you can tolerate a 24-hour turnaround. Stack caching + batching, and an "expensive" model like Claude Opus starts looking cheaper than the uncached "cheap" model you were using before.

If you're shipping LLM API calls at any real volume, plug your numbers into this. The caching lever alone can be 5 figures: LLM API pricing calculator →


8. "Managed PostgreSQL" Means Completely Different Things on Different Clouds

AWS RDS, Aurora, Google Cloud SQL, and Azure DB all say they run "managed PostgreSQL." The Postgres part is the same. Everything about the bill is different.

Multi-AZ: RDS, Cloud SQL, and Azure DB all provision a full standby instance and charge you for it. Double compute. Aurora doesn't — the standby shares the cluster's storage layer. At 8 vCPU, this one difference is about $500/month.

io1 IOPS: A 200 GB RDS volume with 12,000 provisioned IOPS: $40/month for the storage, $780/month for the IOPS. I've seen teams provision io1 because "the database is important" without ever checking if they're actually saturating gp3's baseline IOPS. Check CloudWatch first. Most OLTP workloads don't need io1.

Backups: RDS charges for retained automated backups beyond the first snapshot. A 2 TB database with 35-day retention: roughly $646/month in backup storage. Aurora, Cloud SQL, and Azure DB include automated backups in the base price. RDS is literally the only one that charges for this.

Reservations: A 3-year commitment saves 60% — but only on compute. If your bill is 50% storage + IOPS + backups, that "60% savings" is actually 30% on the total. Still worth doing, but not the magic number the pricing page implies.

Interactive calculator with all four providers, six instance tiers, and commitment discounts: managed DB cost comparison →


9. A 3-Year Reserved Instance Is Not a Discount — It's a Bet

AWS offers "up to 40% off" with a 3-year commitment. Framed as a discount. It's actually a 36-month cash advance where you're betting that spot instance prices won't get cheaper over the same period.

Here's the one number that matters: spot interruption rate. At current spot pricing with a 2-minute interruption warning and 10-minute recovery, the breakeven with a 3-year RI is roughly 2.5 interruptions per 1,000 instance-hours. If your actual interruption rate is lower, spot wins. Higher, RI wins.

For CPU instances (m6i, c6i, r6i), spot interruption rates are under 1%. The spot pool is deep. You'd need to be catastrophically unlucky to lose money on spot vs. an RI.

For GPU instances (p4d, p5), interruption rates are 5-20%. There aren't enough GPUs on the planet, so when a big customer places a large on-demand order, AWS reclaims spot capacity and your training job gets a 2-minute warning. If your training framework can checkpoint and resume fast, spot still wins. If it can't, you need the RI.

What most teams land on: cover the floor with RIs or a Savings Plan (the instances that run 24/7 no matter what), put the burst capacity on spot, and keep a small on-demand buffer. If your autoscaling group never drops below 6 instances, buy 6 RIs. Everything above that rides on spot.

I built a calculator that models the breakeven surface for any instance type: RI vs spot breakeven →


10. Every Kafka Partition Eats 0.5 MB of RAM Before You Send a Single Message

This one kills me because it's so easy to avoid and so painful to fix.

A Kafka partition costs ~0.5 MB of broker heap just to exist — metadata, index structures, leader state. If you create 5,000 partitions because "better parallelism," that's 2.5 GB of RAM gone before your first message. At 20,000 partitions on a 64 GB broker, half your heap is partition metadata.

And it doesn't stop at RAM. Each partition segment is an open file handle (fd exhaustion). Partition reassignment during a broker failure is O(partitions) work for the controller (controller overload). If you have too many partitions relative to your throughput, the producer batch size per partition is too small for batching to kick in (throughput collapses). And a broker restart with 10,000+ partitions takes 5-15 minutes while it reads all the index files.

The fix: size partitions for throughput, not "future scale." A single partition can handle 10-25 MB/s easily. Partition count = peak throughput ÷ per-partition target. Adding partitions later is a config change. Removing them is a migration.

Calculator that shows the RAM impact of your partition count: Kafka partition budget →


Look, I Didn't Set Out to Become the "Cloud Bill Guy"

I started building these calculators because my friends kept sending me their AWS bills with "can you look at this, this can't be right." It was right. Every time.

After the tenth one, I noticed the same thing keeps happening: you get a bill. You see a number that seems crazy. You dig in. You find a line item you didn't know existed. You Google it. The pricing page doesn't explain it clearly. You end up on the third page of a documentation article from 2023 that finally tells you the actual billing rule.

I just wanted to save people the last three steps.

Everything I linked above is a real page with a calculator you can use — plug in your own numbers, see what your bill should look like, compare it to what you're actually paying. Nothing leaves your browser. No signup. No "talk to sales." Just the math.

If your cloud bill has a line item you can't explain and you want me to model it: drop it in the comments. I'll dig into it and if it's interesting, I'll build a calculator for it and put it on the site.

Seriously. Weird bill line items are my hobby now. Send them.

Top comments (1)

Collapse
 
scale-zone profile image
Trigops

This resonates. The hardest part of AWS billing isn't the math, it's that the bill is a lagging indicator of decisions made weeks earlier by people who've moved on to other things. A dev spun up an instance for a spike test in March, the spike test ended, the instance didn't. Calculators help you understand what happened, but the real fix is closer to the source: catching the idle window while it's happening instead of reconstructing it later from a CUR export. Curious what your calculators surfaced most often, compute sitting idle, oversized instance classes, or cross-AZ data transfer nobody accounted for? Those three usually explain most of the surprise.