Reading the Bill
Series
Cloud Cost EngineeringPart 2 →
Tags, Tags, Tags
Most engineering teams look at the cloud bill once a month, feel vaguely alarmed, and close the tab. That alarm is data. Ignoring it is a product decision — just not a conscious one. This post treats the bill as a structured artifact you can parse, query, and act on.
What the Bill Actually Contains
AWS invoices are multi-dimensional. At the top level you see a total. One click down you have service-level rollups — EC2, RDS, S3, Data Transfer. Two clicks in you reach line items, and that is where the real information lives.
Every line item in the Cost and Usage Report (CUR) carries:
lineItem/LineItemType— OnDemand, SavingsPlanCoveredUsage, Credit, Tax, etc.lineItem/UsageType— region-prefixed usage descriptor (USE1-BoxUsage:m5.xlarge)lineItem/ResourceId— the ARN or ID of the actual resourceproduct/ProductName— human-readable service namelineItem/UnblendedCost— what you actually paid for that line item
The gap between UnblendedCost and AmortizedCost represents commitment discounts being spread across time. Always use AmortizedCost when doing trend analysis; use UnblendedCost when reconciling invoices.
Setting Up CUR in Athena
The raw CUR lives in S3. Query it with Athena.
-- Partitioned CUR table — top 20 services by amortized cost this month
SELECT
line_item_product_code AS service,
SUM(line_item_unblended_cost) AS unblended,
SUM(CASE
WHEN line_item_line_item_type IN ('SavingsPlanCoveredUsage','DiscountedUsage')
THEN reservation_effective_cost + savings_plan_effective_cost
ELSE line_item_unblended_cost
END) AS amortized
FROM cur_db.cur_table
WHERE line_item_usage_start_date >= DATE_TRUNC('month', CURRENT_DATE)
AND line_item_line_item_type NOT IN ('Credit','Refund','Tax')
GROUP BY 1
ORDER BY amortized DESC
LIMIT 20;Run this every morning. Schedule it as an Athena named query and pipe the result to a Slack channel. Cost surprises should be 24-hour surprises, not 30-day surprises.
Allocation: Where Costs Actually Land
Costs that cannot be attributed to a team or product are overhead. Overhead grows until someone is embarrassed. The allocation hierarchy should mirror your org:
Target: shared and unallocated below 5 % of total spend. Anything above that is a tagging debt you are paying interest on.
Hidden Services That Bite
These services reliably appear in bills without anyone deliberately ordering them:
| Service / Usage Type | Why It Appears |
|---|---|
AWS Support |
Enterprise/Business support tiers auto-applied |
AWSDataTransfer OUT-Bytes |
Any public-facing API or CDN miss |
AmazonCloudWatch |
Every Lambda, container, and VPC flow log |
AmazonRoute53 |
Health checks accumulate per resource |
AWS Key Management Service |
API calls, not just key storage |
AmazonEC2 NatGateway-Bytes |
Every private subnet talking to the internet |
Query for the long tail:
SELECT
line_item_product_code,
line_item_usage_type,
ROUND(SUM(line_item_unblended_cost), 2) AS cost_usd
FROM cur_db.cur_table
WHERE line_item_usage_start_date >= DATE_TRUNC('month', CURRENT_DATE)
AND line_item_unblended_cost > 0
AND line_item_line_item_type = 'Usage'
GROUP BY 1, 2
HAVING cost_usd > 10
ORDER BY cost_usd DESC;Sort ascending. The bottom of that list is where surprises hide.
Reading Resource-Level Detail
Aggregates lie. A $40k EC2 line item might be ten well-utilized instances or two hundred idle ones. Always drill to line_item_resource_id.
SELECT
line_item_resource_id,
product_instance_type,
SUM(line_item_usage_amount) AS hours,
SUM(line_item_unblended_cost) AS cost_usd
FROM cur_db.cur_table
WHERE line_item_product_code = 'AmazonEC2'
AND line_item_usage_type LIKE '%BoxUsage%'
AND line_item_usage_start_date >= DATE_TRUNC('month', CURRENT_DATE)
GROUP BY 1, 2
ORDER BY cost_usd DESC
LIMIT 50;Cross-reference the resource IDs against CloudWatch CPU utilization. Anything billing full month and averaging below 10 % CPU is a rightsizing candidate — covered in post 3.
The Billing Anatomy at a Glance
Practical Starting Checklist
- Enable CUR with resource IDs and hourly granularity — daily is too coarse for debugging.
- Enable Athena integration directly from the CUR setup wizard (creates the Glue table automatically).
- Pin the five queries above as named queries in your account.
- Set a Cost Anomaly Detection monitor on every service above $500/month.
- Add a budget alert at 80 % and 100 % of your monthly target.
None of this requires a FinOps tool subscription. The primitives are in your AWS account today.
Key Takeaways
- The CUR is the source of truth; Cost Explorer is a UI on top of a subset of it. Use both, but trust CUR for precise numbers.
AmortizedCostfor trends,UnblendedCostfor invoice reconciliation — mixing them produces wrong conclusions.- Unallocated spend above 5 % of total is a leading indicator of future budget overruns; treat it as a bug.
- Hidden services (CloudWatch, NAT Gateway, KMS API calls) routinely account for 10–15 % of a bill that nobody explicitly provisioned.
- Resource-level queries always reveal more than service-level aggregates; aggregates are a starting point, not a conclusion.
- Cost Anomaly Detection costs nothing and catches spikes in hours rather than weeks — enable it before you need it.
Series
Cloud Cost EngineeringPart 2 →
Tags, Tags, Tags