# ECS Service CPU Underutilized

> Fargate bills the CPU units a task definition reserves, not the cycles it burns, so a service averaging under 10% CPU is paying for roughly 10x its need. ZopNight names the next rung down the Fargate CPU ladder and prices the saving from the real per-task rate delta, memory term included.

Source: https://zop.dev/integrations/aws/recommendations/ecs-service-cpu-underutilized
Updated: 2026-08-19

---

## Reservation billing makes low utilization expensive

On Fargate, the task definition's CPU setting [is the bill](https://aws.amazon.com/ecs/pricing/): 1024 units cost the same fully
idle as fully busy. Its trigger, read from [the utilization series ECS reports per service](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cloudwatch-metrics.html), is a 30-day CPU average under 10% (a service
provisioned an order of magnitude above its duty cycle), with memory under 50% confirming
the task is not secretly memory-bound, and services that are stopped, draining, inactive,
or scaled to zero excluded because there is nothing running to resize.

## The saving is a rate delta, not a ratio

An obvious-looking shortcut overstates this finding: halving CPU does not halve a Fargate
task's price, because the per-task rate is a CPU term *plus* a memory term and the memory
usually stays put. An earlier version applied the CPU ratio to the whole cost and ran
1.2–1.4× hot. The current math steps the task one rung down the Fargate CPU ladder, clamps
memory into the smaller tier's valid band, and prices the saving as the difference between
the two real per-task rates. Abstention follows when the task's CPU or memory values are
unknown, off-ladder, already at the floor, or the delta comes out non-positive.

## Bursts veto the downsize

A service that idles all day and slams its full reservation at peak (payment burst
patterns are the canonical case) is already throttling at the worst moment, and shrinking
it would sharpen exactly that. The rule reads the trusted peak over the full metric window
and suppresses the recommendation when it approaches saturation, however low the average.

## Pull the same series

```bash
aws cloudwatch get-metric-statistics \
  --namespace AWS/ECS --metric-name CPUUtilization \
  --dimensions Name=ClusterName,Value=my-cluster Name=ServiceName,Value=my-service \
  --start-time "$(date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)" \
  --end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --period 86400 --statistics Average Maximum
```

## Applying it

The change is a new task definition revision with the smaller CPU (and clamped memory) and
a service update. ZopNight can drive this as an automated resize, and the same
ladder-step-plus-clamp logic generates the target so the displayed and applied values never
diverge. Watch latency and CPU for a week after; a service that lands at 40–60% on the
smaller size is done, and one that climbs past the peak guard's comfort was the veto case
all along.
