Skip to main content
Cloud Cost Engineering

Reading the Bill

Ravinder··5 min read
Cloud CostFinOpsAWSCost AllocationBilling
Share:
Part 1 of 10
Reading the Bill

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 resource
  • product/ProductName — human-readable service name
  • lineItem/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:

flowchart TD A[Total AWS Bill] --> B[Shared / Unallocated] A --> C[Platform Team] A --> D[Product Teams] D --> E[Team Alpha] D --> F[Team Beta] D --> G[Team Gamma] C --> H[Networking] C --> I[Observability] C --> J[Security Tools] B --> K[Needs Tag Remediation] K --> E K --> F K --> G

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

sequenceDiagram participant AWS as AWS Services participant CUR as Cost & Usage Report (S3) participant Athena as Athena participant Dashboard as Cost Dashboard participant Team as Engineering Team AWS->>CUR: Hourly line items (gzipped parquet) CUR->>Athena: Partitioned table refresh Athena->>Dashboard: Scheduled queries Dashboard->>Team: Daily Slack digest Team->>Athena: Ad-hoc investigation

Practical Starting Checklist

  1. Enable CUR with resource IDs and hourly granularity — daily is too coarse for debugging.
  2. Enable Athena integration directly from the CUR setup wizard (creates the Glue table automatically).
  3. Pin the five queries above as named queries in your account.
  4. Set a Cost Anomaly Detection monitor on every service above $500/month.
  5. 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.
  • AmortizedCost for trends, UnblendedCost for 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.
Part 1 of 10
Share: