1. Break down by usage type
With CUR 2.0 in Athena:
SELECT line_item_usage_type,
product['servicecode'] AS service,
ROUND(SUM(line_item_usage_amount), 0) AS gb,
ROUND(SUM(line_item_unblended_cost), 2) AS cost
FROM cur2
WHERE billing_period = '2026-09'
AND line_item_usage_type LIKE '%Bytes%'
GROUP BY 1, 2
ORDER BY cost DESC
LIMIT 30;
How to read the usage types (prefix is the region code, e.g. EUW1-):
| Usage type contains | Meaning |
|---|---|
DataTransfer-Regional-Bytes | Cross-AZ within a region (charged on both sides) |
DataTransfer-Out-Bytes | Internet egress |
AWS-Out-Bytes / AWS-In-Bytes with two region codes | Inter-region transfer |
NatGateway-Bytes | NAT processing (see the NAT Gateway post) |
CloudFront-Out-Bytes / DataTransfer-Out-OBytes | CDN egress |
Then group the largest type by line_item_resource_id to find the resources.
2. Trace flows
VPC Flow Logs with az-id and pkt-src/dstaddr fields show which instance pairs exchange the most bytes across AZs. Map IPs to services (ENI descriptions, EKS pod IPs).
3. Cross-AZ: common causes and fixes
| Cause | Fix |
|---|---|
| App in AZ-a reads from RDS writer in AZ-b | Accept for writes; route reads to a same-AZ reader where consistency allows |
| Kubernetes service traffic spread across all AZs | Topology Aware Routing / trafficDistribution: PreferClose on Services (version-dependent) |
| Kafka/MSK consumers fetching from leaders in other AZs | Rack awareness (client.rack) for follower fetching |
| Load balancer cross-zone traffic | ALB always cross-zone (no charge for that hop); NLB cross-zone is optional and charged. Decide per NLB |
| Chatty microservices spread randomly | Co-locate high-traffic pairs or reduce chatter (batching, caching) |
| Replication (databases, caches, storage clusters) | Necessary for HA; check replication factor and compression |
Don't "fix" cross-AZ cost by putting everything in one AZ. That gives up availability. Optimise the large, avoidable flows.
4. Inter-region
- Cross-region replication (S3 CRR, DynamoDB global tables, Aurora Global Database): needed for DR tier 1/2, maybe not for everything.
- Logs and metrics shipped to a central region: aggregate or filter locally first.
- Services calling APIs in another region by accident (hard-coded endpoints). Check SDK region configs.
5. Internet egress
- Serve downloads and static content through CloudFront. Origin-to-CloudFront transfer from AWS origins isn't charged as internet egress, and CloudFront pricing often beats direct egress.
- Compress responses (gzip/brotli) and use efficient formats.
- Large data sharing with partners: consider S3 Requester Pays or data exchange options.
6. Make it visible
A monthly view: data transfer cost by type and top 10 resources, with owners. Cost allocation tags on load balancers, NAT gateways and databases help attribute it.
Tip
Start with the single largest usage type and the top 3 resources in it. Data transfer optimisation is usually a few big flows, not hundreds of small ones.
Checklist
- CUR breakdown by data transfer usage type
- Top resources and flows identified
- Cross-AZ: same-AZ reads, topology-aware routing, rack awareness, NLB cross-zone decided
- Inter-region transfers justified by DR or business need
- Internet egress via CloudFront and compression
- Monthly data transfer view with owners