From zero to a million users: one startup's cloud bill
A fitness app goes from a design document to a million monthly users in two years. Its architects project the cloud bill before writing code, choose serverless for a small team and a spiky load, find their real bill three times the projection at 100,000 users, fix it without touching a feature, and at a million users run the numbers on leaving serverless and decide, mostly, not to. Each step uses one technique from Module 22a.
1. The situation
Fitloop lets people book live fitness classes and watch short workout videos. Six engineers, seed funding, a launch in four months. Traffic will be spiky: most bookings happen in the ten minutes before each hour, when classes start, and video viewing peaks in the evening. Nobody on the team has run Kubernetes.
2. What mattered
- Time to market and a team too small to run infrastructure.
- Elasticity for the on-the-hour booking spikes (Module 05).
- Unit cost: investors asked for gross margin per subscriber, so cost per monthly active user (MAU) became an architecture characteristic from day one.
3. Estimating before building
The design doc's cost section listed the drivers per MAU: 900 API requests, 30 MB of stored data, and 400 MB of video watched. It multiplied them by users and list prices, added a fixed floor, and compared three ways to run the API using the framework below.
| Workload | Usually cheapest in total | Why |
|---|---|---|
| Spiky or modest request traffic, small team | Serverless functions | Nothing runs while idle; almost no operations work |
| Steady, high request traffic | Containers (managed Kubernetes or a simpler container service) | Per-request pricing overtakes instance time plus the people to run it |
| Many services and an existing platform team | Managed Kubernetes | One platform, dense packing of workloads, one way to deploy |
| Long-running steady compute (databases, ML serving) | Instances with a one- or three-year commitment | Lowest unit price, predictable load |
| Interruptible batch work (transcoding, reports) | Spot capacity | Up to 90% off on-demand prices, for work that can be retried |
The compute lab in Module 22a runs the same comparison with numbers.
4. The decision
ADR-003: Serverless API, managed PostgreSQL, object storage for video
Status: Accepted
Context: 6 engineers, launch in 4 months, spiky traffic (on-the-hour bookings),
no Kubernetes experience. Projection: ~$700/month at 1,000 MAU falling to
~$0.05/MAU at 100,000 MAU.
Decision: API on API Gateway + Lambda; PostgreSQL on a managed service with a
standby; videos in S3, transcoded on upload. Every resource tagged by feature.
Consequences:
+ No idle cost, no servers or clusters to run, scales with the booking spikes
- Per-request pricing: cost grows linearly with traffic
- Functions in the VPC (to reach the database) send other traffic via NAT
Compliance: monthly cost review per feature tag; alert if cost per MAU rises
more than 20% month on month
Revisit: sustained traffic above 1,000 requests/s, or Lambda + API Gateway over
25% of the bill
5. How it works: the bill at 100,000 users
Eighteen months after launch, Fitloop has 100,000 MAU. The projection said about $5,200 a month. The bill says $14,800. The alert fires; the tags show where the money goes.
6. The numbers
| Monthly, 100,000 MAU | Projected | Actual | After fixes |
|---|---|---|---|
| API (Gateway + Lambda) | $180 | $190 | $190 |
| Database (managed, with standby) | $900 | $900 | $900 |
| Video sent to viewers | $3,600 | $3,600 | $1,900 (CDN, cached) |
| NAT gateway processing | not projected | $2,700 | $150 (S3 gateway endpoint) |
| Video transcoding on Lambda | $150 | $2,900 | $450 (spot batch) |
| Logs | $100 | $1,400 | $250 (sampled, 7-day debug) |
| Staging and test copies | $270 | $3,110 | $900 (off nights and weekends) |
| Total | ≈ $5,200 | ≈ $14,800 | ≈ $4,740 |
| Per MAU | $0.052 | $0.148 | $0.047 |
None of the surprises was a pricing error. Each was an architecture-shaped cost: a network path (functions reaching S3 through NAT, at $0.045 per GB, instead of through a free gateway endpoint), a workload on the wrong compute model (transcoding, long and steady per job, on per-millisecond functions sized at 10 GB), a default (debug logs kept 90 days), and an environment nobody turned off.
7. At a million users
At 1,000,000 MAU, and with a new social feed that raised requests per user from 900 to about 2,600 a month, the API averages about 1,000 requests a second, with peaks of 4,000 on the hour: the ADR's revisit trigger. API Gateway and Lambda now cost about $5,300 a month (2.6 billion requests at about $2.03 per million). The team, now 22 engineers, ran the comparison:
- Containers on managed Kubernetes would cost about $900 a month in nodes for the API, plus the share of a platform engineer to run the cluster, about $10,000 a month. Not cheaper in total at this traffic.
- A Compute Savings Plan, which also applies to Lambda and Fargate, lowered the steady part of the function bill with no code change.
- The feed-ranking job, which runs continuously, moved to containers on spot capacity: steady and interruptible, the opposite of what functions are good at.
The API stayed serverless, with a new revisit trigger at 3,000 requests a second sustained. Cost per MAU at a million users: $0.03, tracked monthly against the projection.
8. Lessons
- Project the bill in the design doc. Drivers per user times price, plus a floor. The projection was wrong by 3x, and still useful: it made the surprises visible the month they appeared (Module 22a).
- Tag from day one. Without per-feature tags, "$14,800" has no owner and no fix.
- Runaway costs are architecture-shaped. Network paths, compute models, retention defaults and idle environments, not unit prices (Where bills run away).
- Count the people before leaving serverless. At 1,000 requests a second, the cluster was cheaper in infrastructure and more expensive in total. Move the steady workloads; keep the spiky ones.
- Put revisit triggers in the ADR. "Above 1,000 requests a second" turned a vague worry into a scheduled decision (Module 27).
9. Your turn
- Project (15 min). Fill in the cost projection template for a product you know at launch, 10x and 100x. Which line dominates at 100x?
- Find the NAT (5 min). Which of your services reach cloud storage or third parties through a NAT gateway?
- Set the trigger (5 min). At what traffic, or what share of the bill, would you revisit your compute model?
- "How would you estimate the cost of this system?" List the drivers per user (requests, storage, data out), multiply by users and unit prices at launch, 10x and 100x, and add the fixed floor for availability. Then name the line that dominates at scale and the design choice that cuts it.
10. Check yourself
Where did Fitloop's $2,700 NAT gateway line come from?
Why did the API stay serverless at a million users?
Which compute model suits a continuously running, interruptible feed-ranking job?
11. Sources and further reading
| Source | Why |
|---|---|
| AWS Lambda pricing and API Gateway pricing | Per-request and per-GB-second prices. |
| Amazon VPC pricing and gateway endpoints | NAT gateway processing charges; gateway endpoints at no additional charge. |
| Compute Savings Plans | Commitments that apply to EC2, Lambda and Fargate. |
| EC2 Spot Instances | Up to 90% off on-demand, for interruptible work. |