Contents
Cost Is an Architecture Property
In a mid-size business, the AWS bill is usually one of the largest non-people costs in technology, and it tends to creep. Not because anyone is careless, but because defaults accumulate: a log group with no retention, a NAT gateway carrying traffic that could go through an endpoint, an instance sized for a peak that happened two years ago.
I treat cost the way I treat performance. It is a property of the architecture, it needs measuring, and it regresses if nobody owns it. What follows is the order in which I work through a bill. None of it is exotic, and most of it is a few days of work.
1. Move to Graviton
AWS's Arm-based Graviton processors are, in my experience, the simplest large saving available to most teams, because it is often a configuration change rather than a redesign.
- Lambda. Switching the architecture to
arm64lowers the price per GB-second (AWS quotes around 20% lower) and often improves performance. For interpreted runtimes like Node.js and Python it is usually a one-line change, as long as you have no native dependencies compiled for x86. - Containers. ECS on Fargate and EKS both support Arm. You need multi-architecture images (
docker buildx build --platform linux/amd64,linux/arm64) and base images that publish Arm variants, which almost all mainstream ones now do. - RDS and ElastiCache. Graviton instance classes (the
gfamilies such asr7gandm7g) are a straightforward instance class change during a maintenance window.
# AWS SAM: arm64 for every function in the template
Globals:
Function:
Runtime: nodejs22.x
Architectures:
- arm64
MemorySize: 512Test first. Native modules, some image processing libraries and older agents are where Arm migrations get stuck. Move one non-critical service, measure it, then do the rest.
2. Commit, but Commit to the Right Thing
Once the fleet is on the right architecture and size, commit to a baseline. The two main options behave differently.
| Option | Covers | Flexibility | When I use it |
|---|---|---|---|
| Compute Savings Plans | EC2, Fargate, Lambda | Any region, family, size or OS | Default choice for compute. Survives Graviton migrations and re-architecture. |
| EC2 Instance Savings Plans | EC2 in one family and region | Size and OS within the family | Stable, long-lived EC2 fleets. Deeper discount, less flexibility. |
| Reserved Instances (RDS, ElastiCache, OpenSearch, Redshift) | That specific service | Size flexibility within a family for some engines | Databases, which Savings Plans don't cover. |
Two rules. Commit to your floor, not your average: look at the lowest sustained hourly spend over recent months and cover that, because unused commitment is pure waste. And buy database Reserved Instances after the Graviton move, since a reservation for the old instance family won't apply to the new one.
For a business with strong seasonal peaks, like ours around Valentine's Day and Mother's Day, this matters. The peak should run on on-demand or serverless capacity. The commitment covers the quiet weeks.
3. Rightsize with Data
AWS Compute Optimizer analyses utilisation and recommends changes for EC2, Auto Scaling groups, EBS volumes, Lambda memory, ECS on Fargate and RDS. It is free at the default level and it is a good starting list.
Treat its output as a prompt for investigation, not an instruction. Check the lookback window covers your busiest periods, look at memory as well as CPU (install the CloudWatch agent so memory metrics exist), and remember that an over-provisioned database may be protecting you from a query you haven't fixed yet.
For Lambda, tune memory rather than guessing. More memory gives more CPU, so a function can be both faster and cheaper at a higher setting. AWS Lambda Power Tuning is the tool I reach for here.
4. Storage Lifecycle
Storage is the cost that only goes up unless something deletes or tiers it.
- gp3 over gp2. gp3 EBS volumes are cheaper per GB than gp2 and give a baseline of 3,000 IOPS and 125 MB/s regardless of size. Migration is an online volume modification. There is rarely a reason to keep gp2.
- S3 Intelligent-Tiering for data with unknown or changing access patterns. Be aware of the per-object monitoring charge: it is not worth it for buckets with millions of tiny objects, and objects under 128 KB are not auto-tiered.
- Lifecycle rules for data with known patterns: expire temporary uploads, move old exports to Glacier classes, and delete incomplete multipart uploads, which quietly accumulate.
- Snapshots. Old EBS and RDS manual snapshots are a common source of forgotten spend. AWS Backup with retention rules is easier than scripts.
5. The Networking Traps
Data transfer charges are where I most often find money nobody can explain.
- NAT gateway processing. Every GB through a NAT gateway is charged on top of the hourly fee. If private workloads talk to S3 or DynamoDB through NAT, add gateway VPC endpoints for both. They are free and the change is transparent to the application.
- Interface endpoints for other heavy AWS API traffic (ECR image pulls, for example) can be cheaper than NAT, but they have an hourly charge per AZ, so do the arithmetic.
- Cross-AZ traffic is charged in both directions. Chatty services spread across AZs, or replicas reading across zones, add up.
- Public IPv4 addresses have been charged hourly since February 2024, including ones attached to running resources. Audit them.
6. CloudWatch Logs
Logging is where cost grows fastest with traffic, and it is almost always driven by ingestion rather than storage.
- Set a retention period on every log group. The default is to keep logs forever.
- Lower log levels in production and remove debug logging from hot paths. Lambda's advanced logging controls let you set the application log level without code changes.
- Use the Infrequent Access log class for logs you keep for audit but rarely query. It ingests at a lower price with reduced features.
- For high-volume logs you only need for occasional analysis, consider sending them to S3 and querying with Athena instead.
7. Tagging and Showback
You cannot manage what you cannot attribute. Agree a small set of mandatory tags (I use service, team and environment), enforce them in Terraform or CloudFormation, and activate them as cost allocation tags in the Billing console. Tags only apply to costs from the point they are activated.
Then show each team its own costs monthly. Showback, not chargeback. The aim is awareness and ownership, not internal invoicing.
A FinOps Cadence by Stage
| Stage | Cadence |
|---|---|
| Startup | Monthly look at Cost Explorer by service. Budgets and anomaly detection alerts. Fix the obvious defaults. |
| Scaleup | Mandatory tags, monthly showback per team, quarterly commitment review, Compute Optimizer reviewed each quarter. |
| Enterprise | A named FinOps owner, unit cost metrics (cost per order, per customer), commitment management as a standing process. |
At every stage, turn on AWS Cost Anomaly Detection. It is free and it catches the runaway job before the invoice does.
The Takeaway
Most AWS savings come from revisiting defaults: architecture (Graviton), commitments sized to your floor, rightsized resources, storage lifecycle, networking paths and log retention. Do them in that order, attribute costs to teams with tags, and put a monthly review in the calendar so the savings don't quietly erode.
