Savings Plans Optimization

Production engineering guide for savings plans optimization covering patterns, implementation strategies, and operational best practices.

Savings Plans Optimization is the practice of selecting, adjusting, and managing committed computing capacity—typically in the cloud—to minimize cost while ensuring sufficient resource availability across variable workloads. It matters when cloud usage patterns are stable enough to justify multi-year commitments, but not so predictable that reserved instances alone suffice. This is the domain of engineers who operate systems with recurring, semi-stable workloads and must balance upfront payments against the risk of underutilization and overcommitment.

Planning for Commitment Duration and Scope

Optimizing Savings Plans begins with aligning commitment duration (1-year or 3-year) and scope (shared, regional, or instance-family-specific) to workload profiles. A 1-year commitment is ideal when workload patterns change every 6–12 months, while 3-year plans suit predictable, long-running services with low churn. The scope determines how broadly the commitment applies: shared scope pools capacity across all eligible instance types in a region, while instance-family-specific scope applies only to a single family (e.g., m5, c5, r5).

A shared Savings Plan in AWS, for example, applies to all EC2 instance types in us-east-1, and capacity is consumed across all running instances based on their hourly rates. When a t3.medium and a c5.large run simultaneously, their combined usage is blended into the shared pool.

To assess which scope fits best, engineers query the cloud provider’s cost and utilization data:

curl -X GET \
  -H "Authorization: Bearer $API_TOKEN" \
  "https://api.cloud.provider/v1/costs?from=2024-01-01&to=2024-03-31&group_by=instance_type,region&filter=service=EC2" \
  | jq -r '
    .results[] 
    | select(.region == "us-east-1")
    | .instance_type as $type
    | .total_hours * .hourly_rate as $total_cost
    | .utilization_percentage
    | { instance_type: $type, total_cost, utilization: .utilization_percentage }
    | .total_cost * 0.85 as $expected_savings
    | { instance_type: $type, expected_savings, utilization: .utilization_percentage }'

This query returns the expected cost per instance type and its average utilization. A high utilization (≥85%) across multiple instance families suggests shared scope is optimal. Low utilization (≤60%) across diverse families points to the need for instance-family-specific plans.

Adjusting Commitment Amounts Over Time

Once a Savings Plan is active, its commitment amount—expressed as total hourly capacity—is fixed. To optimize, engineers must adjust this amount quarterly or biannually. The adjustment process involves:

  1. Forecasting future usage.
  2. Calculating the required commitment level.
  3. Submitting a change to the existing plan.

The adjustment is not instantaneous. Changes take effect at the start of the next billing cycle and may be applied to the same plan or split into multiple plans.

To adjust a Savings Plan, engineers use a standard HTTP API:

PATCH /v1/savings-plans/abc123
Content-Type: application/json
Authorization: Bearer $API_TOKEN

{
  "commitment_hours": 15000,
  "effective_from": "2024-04-01T00:00:00Z",
  "description": "Increased commitment due to Q2 workload surge"
}

The commitment_hours field defines the total hours of compute capacity the plan covers. For a 1-year plan, 15,000 hours equates to 1,250 hours per month or roughly 41.7 hours per day—sufficient for 10 c5.large instances running continuously.

A common failure mode is commitment drift: the plan is sized for a peak workload, but actual usage varies. If the plan is committed to 15,000 hours but only 13,000 hours are used, the remaining 2,000 hours are wasted—paying for unused capacity. Conversely, if usage exceeds 15,000 hours, the plan must cover the excess via overage charges, which are typically 1.5× the plan rate.

To detect drift, engineers run:

curl -H "Authorization: Bearer $API_TOKEN" \
  "https://api.cloud.provider/v1/savings-plans/abc123/usage?from=2024-01-01&to=2024-03-31" \
  | jq -r '
    .total_commitment_hours,
    .total_actual_hours,
    .overage_hours,
    .utilization_ratio
    | { commitment: .total_commitment_hours, actual: .total_actual_hours, overage: .overage_hours, utilization: .utilization_ratio }'

A utilization ratio below 0.95 signals undercommitment; above 1.05 suggests overcommitment with potential for cost savings through reallocation.

Combining Multiple Plans Strategically

For complex workloads, a single Savings Plan is often insufficient. Engineers combine multiple plans with different scopes and durations to maximize coverage.

A typical pattern involves:

These plans are independent but share a common cost pool. The system applies usage against each plan in priority order:

  1. Use the most specific plan first (e.g., c5 instance-family plan).
  2. When that plan is exhausted, apply usage to the next most specific (e.g., us-east-1 shared plan).
  3. Finally, apply overages to the 3-year shared plan.

This prioritization is configured via a plan_priority field in the plan metadata:

{
  "id": "sp-c5-us-east-1",
  "type": "instance-family",
  "family": "c5",
  "region": "us-east-1",
  "duration": "1-year",
  "commitment_hours": 8000,
  "plan_priority": 10
}

A plan with plan_priority: 10 is applied before a plan with plan_priority: 20. If two plans are available, the one with the lower priority number is consumed first.

A silent failure occurs when plans have overlapping scopes but mismatched priorities. For example, a c5 family plan and a us-east-1 shared plan both apply to c5.large instances in us-east-1. If the c5 plan has priority 15 and the shared plan has priority 10, the system applies usage to the shared plan first, even though the c5 plan is more specific. This leads to suboptimal plan usage and missed savings.

To correct this, engineers audit the priority assignments:

curl -H "Authorization: Bearer $API_TOKEN" \
  "https://api.cloud.provider/v1/savings-plans?filter=region=us-east-1&order_by=plan_priority" \
  | jq -r '
    .[] 
    | .id,
    .type,
    .family,
    .plan_priority,
    .commitment_hours,
    .total_actual_hours,
    .utilization_ratio
    | .plan_priority as $prio
    | .utilization_ratio as $util
    | { id: .id, priority: $prio, utilization: $util, usage: .total_actual_hours, commitment: .commitment_hours }'

This reveals gaps: a plan with high utilization but low priority is likely underused, while a plan with low utilization but high priority is consuming resources that could be better allocated.

Automating Plan Reconciliation and Reporting

To maintain savings, engineers automate reconciliation between actual usage and plan coverage. The reconciliation process computes:

A reconciliation script runs monthly:

#!/bin/bash


set -euo pipefail

API_TOKEN=$(cat /secrets/cloud-api-token)
OUTPUT_DIR="/reports/savings-plans-reconciliation"

mkdir -p "$OUTPUT_DIR"

for plan_id in $(curl -s \
    -H "Authorization: Bearer $API_TOKEN" \
    "https://api.cloud.provider/v1/savings-plans?filter=active=true" \
    | jq -r '.[].id'); do

  usage_data=$(curl -s \
    -H "Authorization: Bearer $API_TOKEN" \
    "https://api.cloud.provider/v1/savings-plans/$plan_id/usage?from=2024-04-01&to=2024-04-30")

  total_hours=$(echo "$usage_data" | jq -r '.total_actual_hours')
  commitment_hours=$(echo "$usage_data" | jq -r '.total_commitment_hours')
  overage_hours=$(echo "$usage_data" | jq -r '.overage_hours')

  effective_rate=$(echo "$usage_data" | jq -r '.effective_hourly_rate')

  cat > "$OUTPUT_DIR/$plan_id.json" << EOF
{
  "plan_id": "$plan_id",
  "total_commitment_hours": $commitment_hours,
  "total_actual_hours": $total_hours,
  "overage_hours": $overage_hours,
  "utilization_ratio": $(echo "$total_hours / $commitment_hours" | bc -l),
  "effective_hourly_rate": $effective_rate,
  "coverage_percentage": $(echo "scale=2; $total_hours / $commitment_hours * 100" | bc -l)
}
EOF

done

# Generate summary report
jq -s '
  [.[].plan_id as $id
   | .[].total_commitment_hours as $commit
   | .[].total_actual_hours as $actual
   | .[].overage_hours as $overage
   | .[].coverage_percentage as $cover
   | {
       plan_id: $id,
       commitment_hours: $commit,
       actual_hours: $actual,
       overage_hours: $overage,
       coverage_percentage: $cover
     }
   ]
   | {
       total_commitment_hours: add(.[] | .commitment_hours),
       total_actual_hours: add(.[] | .actual_hours),
       total_overage_hours: add(.[] | .overage_hours),
       average_coverage_percentage: add(.[] | .coverage_percentage) / length,
       total_savings: (add(.[] | .commitment_hours) * 0.08) - (add(.[] | .actual_hours) * 0.05)
     }
' "$OUTPUT_DIR"/*.json > "$OUTPUT_DIR/summary.json"

This script outputs a JSON report with detailed plan coverage and a summary of overall performance. The coverage_percentage metric—total actual hours divided by total commitment hours—reveals how well the plans are utilized.

A common oversight is assuming that a plan is fully utilized when the total_actual_hours equals total_commitment_hours, but the plan is not fully covered if the actual hours are spread across multiple smaller workloads. A plan with 10,000 committed hours and 10,000 actual hours may have 80% of its capacity used in 10,000 hours, but the remaining 2,000 hours are underutilized because they are fragmented.

Detecting and Resolving Plan Underutilization

Underutilization is the most insidious failure in Savings Plans. It occurs when the actual usage is less than the committed hours, but the system does not flag it automatically.

To detect underutilization, engineers define a rule:

> If utilization_ratio < 0.85 and overage_hours < 200 for a plan over a 3-month period, then the plan is underutilized.

The rule is implemented in a monitoring pipeline:

SELECT
  plan_id,
  total_commitment_hours,
  total_actual_hours,
  (total_actual_hours / total_commitment_hours) AS utilization_ratio,
  (total_commitment_hours - total_actual_hours) AS underutilized_hours
FROM savings_plans_usage
WHERE period = 'Q2-2024'
  AND total_commitment_hours > 10000
  AND (total_actual_hours / total_commitment_hours) < 0.85
  AND total_overage_hours < 200
ORDER BY underutilized_hours DESC;

Results are surfaced via a dashboard or alerting system. When a plan is underutilized, engineers investigate:

A frequent cause of underutilization is instance type drift: a service that initially ran on m5.large now runs on m5.xlarge, but the Savings Plan is still configured for m5.large. The new instances are not eligible for the plan, leading to missed savings.

To resolve this, engineers update the plan's eligible instance types:

PATCH /v1/savings-plans/abc123
Content-Type: application/json
Authorization: Bearer $API_TOKEN

{
  "eligible_instance_types": [
    "m5.large",
    "m5.xlarge",
    "m5.2xlarge"
  ],
  "description": "Added support for m5.xlarge and m5.2xlarge instances"
}

This change ensures that all m5 instances, regardless of size, contribute to the plan’s capacity.

Final Considerations

The most subtle failure is plan fragmentation: a single large workload spread across multiple plans, each covering only a subset of the instances. This happens when engineers create new plans for new services without consolidating existing ones.

To avoid fragmentation, engineers conduct quarterly plan audits:

The audit is a manual process, but can be automated via a script that identifies plans with more than 80% overlap in instance types and region.

Savings Plans Optimization is not a one-time task. It is a continuous practice of aligning commitment to actual usage, adjusting plans dynamically, and refining the portfolio over time. The key to success lies not in the tools, but in the consistency of the process.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.