The Ghost Inventory: Detecting Orphaned Cloud Resources in Billing Data
Every cloud account has them: resources that are still billing, but delivering nothing. A Reserved Instance purchased for a workload that was decommissioned six months ago. A Kinesis stream left provisioned after the data pipeline was replaced. An EC2 instance that a developer stopped but forgot to terminate โ still carrying its RI commitment.
The billing rows look normal. The cost is real. The value is zero.
This is the orphaned resource pattern โ infrastructure that is "configured and forgotten." It is distinct from a cost spike (the cost is flat, not elevated), distinct from a commitment loss (the resource may not have a commitment), and distinct from a scheduling miss (the resource is not being used at all, not just unused on weekends). The signal is the same billing cost maintained against near-zero or zero actual consumption.
Two Sub-Patterns, One Signal
| Pattern | SQL Condition | Real-world root cause |
|---|---|---|
| Configured and forgotten | Near-zero ConsumedQuantity for 5+ days out of 30 |
RI/SP commitment billing after workload terminated; Kinesis stream or WAF WebACL with no traffic; EC2 instance stopped but not terminated |
| Over-provisioned | avg_daily_qty < avg_daily_cost ร 0.10 |
Reserved capacity where actual consumption is a small fraction of what was committed โ the unit economics have inverted |
Both require a cost floor to suppress noise (resources below a few cents per day are not worth investigating). The "configured and forgotten" path is the higher-fidelity signal: a resource that was billing $5/day and now has five consecutive days of near-zero consumption has clearly lost its workload.
What Appears in FOCUS Billing Data
The defining characteristic: BilledCost remains at the pre-orphan level while ConsumedQuantity collapses to zero or near-zero.
| FOCUS Field | Workload running (normal) | Orphaned state |
|---|---|---|
ConsumedQuantity |
24 instance-hours/day (steady) | 0.0 โ or near-zero (trace billing for minimum charges) |
BilledCost |
$X.XX/day at committed rate | Same $X.XX/day โ commitment billing continues unchanged |
PricingCategory |
Committed / Reserved |
Same โ the pricing category does not change because the workload is gone |
ResourceId |
Stable instance ID, active | Same ID โ or "unmatched RI" rows with no associated running resource |
Tags |
Team, project, cost-center tags intact | Tags may be stripped or stale โ orphans often lose ownership metadata over time |
AWS explicitly documents this: Reserved Instances bill "for every clock-hour during the term regardless of whether an instance is running." The commitment is a purchase, not a metered service โ the billing continues until the term expires, regardless of whether there is anything consuming it.
Temporal Profile
The orphaned resource pattern is a persistent flat-zero, not a decay curve. Before the workload is decommissioned: stable, non-zero consumption. After: flat-zero consumption, unchanged billing. No gradual transition โ the workload either runs or it does not.
This distinguishes it from:
- A usage spike (QB2): quantity rises then falls
- A runaway pattern (QB3): quantity grows persistently above baseline
- A scheduling miss (QB7): quantity is present on weekdays, full on weekends when it should not be
The orphan's signature is the permanence of the near-zero state โ resources that have been at near-zero for weeks, not resources temporarily idle between batch runs.
Services with Fixed Minimum Charges
Some services charge a fixed fee regardless of usage, making their billing rows look like orphans even when the resource is correctly provisioned:
| Service | Fixed charge | When ConsumedQuantity drops to near-zero |
|---|---|---|
| Amazon Kinesis Data Streams | Shard-hours (billed regardless of throughput) | When there are no data producers writing to the stream |
| AWS WAF WebACL | $1/month per WebACL + $0.60/million requests | When the ACL is provisioned but no traffic routes through it |
| Amazon GuardDuty | Minimum account-level charge | When log volume is very low โ may appear as near-zero usage, non-zero cost |
For these services, the "configured and forgotten" signal fires on the usage charge leg (data PUT requests for Kinesis; web requests for WAF) when there is no traffic โ not on the infrastructure charge leg, which is constant by design.
Real-World Incidents
$2 million/month in orphaned resources (industry report): An e-commerce organization discovered approximately $2 million per month in cloud resources that were billing without any associated active workloads. The resources were a combination of Reserved Instances from decommissioned services, provisioned-but-idle infrastructure, and services with minimum charges that had no consumers. Detection came from a billing audit, not automated monitoring.
$127,000 surprise bill (dev.to post-mortem): A developer team's infrastructure โ provisioned for a project that had ended โ continued running for a full billing period. Nobody had set up billing alerts. Nobody had terminated the resources. The $127,000 bill appeared at month-end. The resources had been running at their normal daily rate the entire time; the bill was entirely predictable from the billing data, but nobody was watching.
$211,680/year in RI/SP waste (startup case study): A startup transitioned from on-demand to Reserved Instances at a rate that turned out to exceed their actual usage. The combination of commitment over-purchasing and workload changes left $42,000/month in committed capacity partially uncovered. A billing audit identified the gap; restructuring the RI portfolio and eliminating orphaned commitments saved $211,680 annually.
Detection Logic
-- Configured and forgotten: near-zero consumption for 5+ of 30 days
near_zero_qty_days = COUNT(days WHERE daily_consumedquantity < 0.01)
WHERE near_zero_qty_days >= 5
AND avg_daily_cost > cost_floor
-- Over-provisioned: consumption tiny relative to cost
-- (fires on reserved capacity where the unit economics are inverted)
WHERE avg_daily_qty < avg_daily_cost ร 0.10
AND avg_daily_cost > cost_floor
The cost floor excludes resources with negligible billing where the savings from termination are smaller than the investigation effort. In production environments, a threshold of $5โ$10 over 30 days is practical. For demo environments, the floor is lower to catch smaller instances.
Fix Checklist
- For orphaned RIs and Savings Plans: check the AWS RI Marketplace. Standard RIs can be listed for resale. Convertible RIs can be exchanged for a different instance type that your active workloads can absorb. Both options are preferable to waiting for the term to expire.
- For over-provisioned commitments: do not renew at the same level. Before purchasing the next RI term, verify current utilization against committed capacity. The waste pattern often develops gradually as workloads right-size over time.
- For idle services (Kinesis, WAF): delete streams and WebACLs that have no active producers or traffic. These resources have no termination penalty โ the billing stops immediately.
- For stopped EC2 instances: an EC2 instance in "stopped" state still generates EBS storage charges. An instance that has been stopped for more than a few days with no intention to restart should be terminated (EBS volumes snapshotted if needed). Set a policy: any non-production EC2 instance stopped for more than 7 days is a candidate for termination review.
- Establish a monthly orphan audit cadence: run the near-zero-qty detection query monthly on non-production accounts. Fresh billing exports combined with a simple SQL query catch orphans in minutes. Without a cadence, orphans accumulate silently until someone happens to look.
See if this pattern is in your billing data
The 5-question DropInFinOps assessment takes 2 minutes and tells you which anomaly patterns your current billing setup is positioned to catch โ and which ones are slipping through.
Take the free assessment โ