This is a sample, not a client
Northwind Data doesn’t exist. The account, the numbers and the findings are synthetic — built to show the shape and depth of a real deliverable without publishing anyone’s infrastructure. The patterns are the ones we genuinely look for; the figures are representative of a stack this size.
Audit Nº 0001 · Sample · Prepared for Northwind Data
AWS cost audit
What we found, what to change, and the order to do it in.
Scope & method
What was reviewed.
Findings
Seven things, ranked by what they cost you.
Ordered by monthly saving, not by how interesting they are. Each one names the specific resources, the exact change, and what it costs you in effort and risk to make it.
Multi-AZ is on for staging and two internal databases
What we found · Four of the six RDS instances have Multi-AZ enabled. Two are production and should keep it. The staging replica and the two databases behind internal admin tools do not need automatic failover — none of them serve customer traffic, and none has an availability commitment.
What to do · Disable Multi-AZ on plainstack-staging-db, internal-reporting and admin-tools-db. This is an online modification with a brief failover during the change window. Keep Multi-AZ on prod-primary and prod-orders.
Evidence — RDS console → 3 instances with MultiAZ=true and zero external traffic in 30 days of CloudWatch DatabaseConnections.
The EC2 fleet is one size too large, and one generation behind
What we found · Eleven m5.2xlarge instances averaging 14% CPU and 31% memory over 30 days, including the busiest day of the month. The workload is containerised and runs on Amazon Linux, so nothing blocks a move to Graviton.
What to do · Downsize 8 instances from m5.2xlarge to m5.xlarge, leaving 3 at current size for headroom. Then migrate the whole group to m7g.xlarge (Graviton). Do the downsize first and measure for a week before the architecture change.
Evidence — CloudWatch CPUUtilization p95 = 22%, memory via the CloudWatch agent p95 = 38%, across 30 days including month-end close.
ElastiCache is provisioned about three times the working set
What we found · Two cache.r6g.xlarge nodes (26 GB each) holding a peak working set of 7.4 GB, with zero evictions in 90 days. Zero evictions across a quarter means the cache has never once been under pressure.
What to do · Move both nodes to cache.r6g.large. Keep two nodes for failover rather than consolidating to one — the saving from dropping a node isn't worth the availability change.
Evidence — BytesUsedForCache peak 7.4 GB against 26 GB provisioned; Evictions = 0 over 90 days.
Non-production runs 168 hours a week and is used for about 45
What we found · The staging and development environments — 6 EC2 instances plus 2 RDS instances — run continuously. Activity logs show use between roughly 08:00 and 19:00 on weekdays only.
What to do · Schedule a stop at 20:00 and a start at 07:30 on weekdays, off entirely at weekends. AWS Instance Scheduler, or a pair of Lambda functions on EventBridge if you'd rather not add the stack.
Evidence — Zero weekend activity in ALB request logs across 8 weeks; no overnight deploys in the CI history.
Log groups have no retention policy
What we found · Of 47 CloudWatch log groups, 41 are set to Never Expire. The oldest entries date to the account's creation. There are also 12 custom metrics with no alarm attached to them.
What to do · Set 30-day retention on application log groups and 90 days on audit and access logs. If anything needs long-term retention, export to S3 and move it to Glacier — don't pay CloudWatch prices for cold storage. Delete the 12 unused custom metrics.
Evidence — DescribeLogGroups shows retentionInDays unset on 41 of 47 groups; ~1.4 TB of ingested log data retained.
NAT Gateway is processing traffic that could take a free path
What we found · Three NAT Gateways, one per AZ. A large share of processed bytes is traffic to S3 — nightly backups and a data export job — which is routed through NAT by default. There are no gateway endpoints on the VPC.
What to do · Add gateway endpoints for S3 and DynamoDB. They are free, they are a route-table change, and they remove that traffic from the NAT meter entirely. Separately, non-production does not need three gateways; one is enough there.
Evidence — VPC Flow Logs: 62% of NAT-processed bytes have S3 prefix-list destinations. No VPC endpoints configured.
Unattached volumes and snapshots with no source
What we found · 14 EBS volumes in the available state, the oldest 19 months. 320 snapshots, of which 118 belong to volumes that no longer exist. Nine volumes are still gp2.
What to do · Snapshot then delete the 14 unattached volumes. Delete the 118 orphaned snapshots. Migrate the 9 gp2 volumes to gp3 — an online change with no downtime. Then set a Data Lifecycle Manager policy so this doesn't rebuild.
Evidence — DescribeVolumes state=available × 14 (1.6 TB); DescribeSnapshots with no matching VolumeId × 118.
Not everything is broken
What’s already in good shape.
A report that finds fault with everything isn’t an audit, it’s a sales document. These are the things we checked and found no reason to change.
- ✓Tagging is consistent across 94% of resources — better than most accounts this size, and it made this audit considerably faster.
- ✓No idle load balancers. Every ALB has healthy targets and real traffic.
- ✓S3 lifecycle rules are already in place on the two largest buckets.
- ✓Backup retention is deliberate at 7 days rather than left at a default.
- ✓No public S3 buckets, no long-lived access keys on the root account.
The plan
90 days, in the order that pays back fastest.
Everything reversible and zero-risk comes first, so most of the saving lands before you’ve scheduled a maintenance window. Commitments come last, deliberately — buying a Savings Plan before right-sizing locks in the waste for a year.
- Disable Multi-AZ on the three non-production databases (Finding 01)
- Add S3 and DynamoDB gateway endpoints (Finding 06)
- Delete unattached volumes and orphaned snapshots (Finding 07)
- Set CloudWatch retention policies (Finding 05)
- Downsize ElastiCache nodes to r6g.large (Finding 03)
- Downsize 8 EC2 instances to m5.xlarge, then measure for a week (Finding 02)
- Migrate the 9 remaining gp2 volumes to gp3 (Finding 07)
- Reduce non-production to one NAT Gateway (Finding 06)
- Schedule non-production shutdown outside working hours (Finding 04)
- Migrate the EC2 group to Graviton once the downsize has settled (Finding 02)
- Set a Data Lifecycle Manager policy so snapshots don't rebuild (Finding 07)
- Re-measure, then size a Savings Plan against the new, lower baseline
Assumptions & limits
What this report doesn’t tell you.
- Savings are estimated from current on-demand rates and 30 days of utilisation. Real figures move with usage — assume a range of roughly ±15%.
- Right-sizing recommendations assume current traffic patterns hold. If you're expecting a step change in load, say so and we'll re-cut the numbers.
- We reviewed configuration, not application behaviour. If a service is expensive because of how it's written rather than how it's provisioned, that's a code change and outside this scope.
- Graviton migration assumes your dependencies have ARM builds. We checked the base images; we did not audit every transitive dependency.
- This is a point-in-time read. Without a policy in place, accounts drift back — most of these findings will recur within a year.
That’s the deliverable
Yours would have your numbers in it.
Same structure, same depth, your account. Flat $2,500, 5–7 days, read-only access. If we don’t think there’s enough there to be worth it, we say so on the call.