{
  "version": 1,
  "id": "finops-compute-optimization",
  "title": "Compute Cost Optimization",
  "summary": "Rightsizing, spot, savings plans and commitment coverage, in the order that avoids waste.",
  "estimatedMinutes": 15,
  "prerequisites": [],
  "source": "courses-notebook CLD303_finops_cloud_cost 03_compute_optimization",
  "quiz": [
    {
      "prompt": "Which pricing model offers the deepest discount (70-90%) but can be reclaimed with ~2 minutes notice?",
      "options": [
        "On-Demand",
        "1-year Standard RI",
        "Compute Savings Plan",
        "Spot Instances"
      ],
      "answer": 3
    },
    {
      "prompt": "The recommended baseline for commitment coverage is approximately:",
      "options": [
        "100% of peak usage",
        "p10 of hourly usage (the trough)",
        "Average daily usage",
        "Whatever fits the budget"
      ],
      "answer": 1
    },
    {
      "prompt": "An RI/SP utilization of 90% means:",
      "options": [
        "90% of your bill is committed",
        "90% of the purchased committed capacity is actually consumed",
        "You should buy 10% more RIs",
        "Spot Instances are 90% available"
      ],
      "answer": 1
    },
    {
      "prompt": "A FinOps team finds CPU < 5% on average for 14 days on an EC2 instance. The recommended action is:",
      "options": [
        "Buy a 3-year RI for it",
        "Migrate it to a larger instance",
        "Terminate the instance - it is idle",
        "Add more replicas"
      ],
      "answer": 2
    },
    {
      "prompt": "The correct sequence of cost optimization before commitment is:",
      "options": [
        "Buy RIs first, then rightsize",
        "Rightsize first, then commit to RIs/SPs",
        "Buy Spot first, then commit",
        "Commit and rightsize simultaneously without measurement"
      ],
      "answer": 1
    }
  ],
  "lesson": "Master the three commitment discount models (Reserved Instances, Savings Plans, Spot), understand rightsizing methodology, detect idle resources \u2014 then build a break-even optimizer that recommends the optimal commitment strategy for a given workload.\n\n## Commitment Discount Models\n\n| Model | Discount vs On-Demand | Flexibility | Commitment | Best For |\n|---|---|---|---|---|\n| **On-Demand** | 0% | Maximum | None | Unpredictable, short-lived |\n| **Reserved Instance (RI)** | 30\u201372% | Locked to instance type/region | 1 or 3 years | Steady-state, predictable |\n| **Savings Plans (SP)** | 20\u201366% | Flexible (instance family or compute) | 1 or 3 years | Variable mix, new instance types |\n| **Spot Instances** | 70\u201390% | Can be interrupted with 2-min notice | None | Fault-tolerant batch, ML training |\n\n### Reserved Instance Types\n\n| RI Type | Scope | Flexibility |\n|---|---|---|\n| Standard RI | Specific instance type + region | No change after purchase |\n| Convertible RI | Instance family + region | Can exchange for different type |\n| Zonal RI | Specific AZ | Capacity reservation guaranteed |\n\n### Savings Plans Types\n\n| SP Type | Covers | Discount |\n|---|---|---|\n| Compute SP | EC2 + Lambda + Fargate (any region/OS/size) | Up to 66% |\n| EC2 Instance SP | Specific EC2 instance family in one region | Up to 72% |\n| SageMaker SP | SageMaker training + inference | Up to 64% |\n\n## Rightsizing\n\nRightsizing matches instance size to actual workload requirements:\n\n| Signal | Interpretation | Action |\n|---|---|---|\n| CPU avg < 10%, max < 30% | Over-provisioned for compute | Downsize or use Graviton |\n| Memory avg < 20%, max < 40% | Over-provisioned for memory | Downsize instance family |\n| Network throughput < 10% of capacity | Over-provisioned for network | Smaller instance |\n| CPU avg > 80% sustained | Under-provisioned (performance risk) | Upsize |\n\n### Idle Resource Detection\n\n| Resource | Idle Signal |\n|---|---|\n| EC2 instance | CPU < 5% for 14 consecutive days |\n| RDS instance | Connection count = 0 for 7 days |\n| EBS volume | Not attached to any instance |\n| Elastic IP | Not associated with running instance |\n| Load balancer | Zero requests for 7 days |\n\n```python\n# Cell 1: RI / Savings Plan break-even calculator\nfrom dataclasses import dataclass\nfrom typing import Dict, List, Tuple\nimport math\n\n\n@dataclass\nclass CommitmentOption:\n    name: str\n    hourly_rate: float       # effective hourly cost with commitment\n    upfront_cost: float      # total upfront payment (0 for No Upfront)\n    term_months: int\n    commitment_type: str     # 'ri_standard', 'ri_convertible', 'savings_plan', 'on_demand', 'spot'\n    flexibility: str         # 'none', 'low', 'high', 'maximum'\n    interruption_risk: bool  # True for Spot\n\n    @property\n    def total_cost(self) -> float:\n        \"\"\"Total cost over full term including upfront.\"\"\"\n        return self.upfront_cost + (self.hourly_rate * 730 * self.term_months)\n\n\n# AWS pricing for m5.xlarge (4 vCPU, 16 GB) \u2014 approximate 2024 values\nM5_XLARGE_OPTIONS: List[CommitmentOption] = [\n    CommitmentOption('On-Demand',                  0.192, 0,      1,  'on_demand',      'maximum', False),\n    CommitmentOption('Spot',                        0.040, 0,      1,  'spot',           'maximum', True),\n    CommitmentOption('1yr No Upfront RI',           0.122, 0,      12, 'ri_standard',    'none',    False),\n    CommitmentOption('1yr Partial Upfront RI',      0.109, 480,    12, 'ri_standard',    'none',    False),\n    CommitmentOption('1yr All Upfront RI',          0.101, 889,    12, 'ri_standard',    'none',    False),\n    CommitmentOption('3yr No Upfront RI',           0.075, 0,      36, 'ri_standard',    'none',    False),\n    CommitmentOption('3yr All Upfront RI',          0.067, 1771,   36, 'ri_standard',    'none',    False),\n    CommitmentOption('1yr Compute Savings Plan',    0.128, 0,      12, 'savings_plan',   'high',    False),\n    CommitmentOption('3yr Compute Savings Plan',    0.082, 0,      36, 'savings_plan',   'high',    False),\n    CommitmentOption('1yr EC2 Instance SP',         0.115, 0,      12, 'savings_plan',   'low',     False),\n    CommitmentOption('3yr EC2 Instance SP',         0.071, 0,      36, 'savings_plan',   'low',     False),\n]\n\n\ndef break_even_analysis(\n    options: List[CommitmentOption],\n    baseline: CommitmentOption,\n    usage_hours_per_month: float = 730,  # 730h = 100% utilization\n) -> None:\n    \"\"\"Compare all commitment options against baseline (on-demand) and compute savings.\"\"\"\n    baseline_monthly = baseline.hourly_rate * usage_hours_per_month\n\n    print(f'=== Commitment Discount Analysis: m5.xlarge ===')\n    print(f'Baseline (On-Demand): ${baseline_monthly:.2f}/month  (${baseline.hourly_rate:.3f}/hr)')\n    print(f'Usage: {usage_hours_per_month:.0f} hours/month')\n    print()\n    print(f'  {\"Option\":<32s}  {\"$/mo\":>8s}  {\"Savings\":>8s}  {\"Term\":>6s}  {\"Break-even\":>12s}  {\"Flexibility\":<12s}')\n    print('  ' + '-' * 90)\n\n    for opt in sorted(options, key=lambda o: o.hourly_rate * usage_hours_per_month + o.upfront_cost / max(o.term_months, 1)):\n        if opt.name == baseline.name:\n            continue\n\n        monthly_cost  = opt.hourly_rate * usage_hours_per_month\n        monthly_savings = baseline_monthly - monthly_cost\n        savings_pct = monthly_savings / baseline_monthly * 100\n\n        if opt.upfront_cost > 0 and monthly_savings > 0:\n            break_even_months = opt.upfront_cost / monthly_savings\n            be_str = f'{break_even_months:.1f} months'\n        elif opt.upfront_cost == 0:\n            be_str = 'immediate'\n        else:\n            be_str = 'never'\n\n        interrupt = ' \u26a0 interrupt' if opt.interruption_risk else ''\n        print(f'  {opt.name:<32s}  ${monthly_cost:>7.2f}  '\n              f'{savings_pct:>7.1f}%  {opt.term_months:>4}mo  '\n              f'{be_str:>12s}  {opt.flexibility:<12s}{interrupt}')\n\n\non_demand = next(o for o in M5_XLARGE_OPTIONS if o.commitment_type == 'on_demand')\nbreak_even_analysis(M5_XLARGE_OPTIONS, on_demand, usage_hours_per_month=730)\n```\n\n```python\n# Cell 2: Rightsizing recommendation engine + idle resource detector\nfrom dataclasses import dataclass, field\nfrom typing import Dict, List, Optional\nimport statistics\n\n\n@dataclass\nclass InstanceMetrics:\n    instance_id: str\n    instance_type: str\n    vcpus: int\n    memory_gb: float\n    monthly_cost: float\n    cpu_avg_pct: float       # 14-day average CPU utilization\n    cpu_max_pct: float       # 14-day peak CPU utilization\n    memory_avg_pct: float\n    memory_max_pct: float\n    network_avg_mbps: float\n    connection_count: int    # For RDS: daily average connections\n    service: str             # 'EC2', 'RDS'\n\n\n@dataclass\nclass RightsizingRecommendation:\n    instance: InstanceMetrics\n    action: str              # 'terminate', 'downsize', 'keep', 'upsize'\n    reason: str\n    estimated_monthly_savings: float\n    recommended_type: Optional[str] = None\n\n\n# Simplified instance family sizing (each step is ~50% of resources)\nEC2_FAMILIES: Dict[str, List[str]] = {\n    'm5': ['m5.large', 'm5.xlarge', 'm5.2xlarge', 'm5.4xlarge', 'm5.8xlarge'],\n    'r5': ['r5.large', 'r5.xlarge', 'r5.2xlarge', 'r5.4xlarge'],\n    'c5': ['c5.large', 'c5.xlarge', 'c5.2xlarge', 'c5.4xlarge'],\n}\nHOURLY_COSTS: Dict[str, float] = {\n    'm5.large': 0.096, 'm5.xlarge': 0.192, 'm5.2xlarge': 0.384, 'm5.4xlarge': 0.768,\n    'r5.large': 0.126, 'r5.xlarge': 0.252, 'r5.2xlarge': 0.504,\n    'c5.large': 0.085, 'c5.xlarge': 0.170, 'c5.2xlarge': 0.340,\n}\n\n\ndef recommend_rightsizing(metrics: InstanceMetrics) -> RightsizingRecommendation:\n    \"\"\"Produce a rightsizing or termination recommendation based on utilization metrics.\"\"\"\n\n    # Idle detection\n    if metrics.service == 'EC2' and metrics.cpu_avg_pct < 5.0 and metrics.cpu_max_pct < 15.0:\n        return RightsizingRecommendation(\n            instance=metrics, action='terminate',\n            reason=f'CPU avg={metrics.cpu_avg_pct:.1f}%, max={metrics.cpu_max_pct:.1f}% \u2014 idle for 14 days',\n            estimated_monthly_savings=metrics.monthly_cost,\n        )\n\n    if metrics.service == 'RDS' and metrics.connection_count == 0:\n        return RightsizingRecommendation(\n            instance=metrics, action='terminate',\n            reason='Zero database connections for 7 days \u2014 likely orphaned',\n            estimated_monthly_savings=metrics.monthly_cost,\n        )\n\n    # Over-provisioned: recommend downsize\n    if metrics.cpu_avg_pct < 20.0 and metrics.memory_avg_pct < 30.0:\n        # Find next smaller instance in the same family\n        family = metrics.instance_type.rsplit('.', 1)[0]\n        sizes = EC2_FAMILIES.get(family, [])\n        current_idx = next((i for i, s in enumerate(sizes) if s == metrics.instance_type), -1)\n\n        if current_idx > 0:\n            smaller = sizes[current_idx - 1]\n            new_cost = HOURLY_COSTS.get(smaller, 0) * 730\n            savings = metrics.monthly_cost - new_cost\n            return RightsizingRecommendation(\n                instance=metrics, action='downsize',\n                reason=f'CPU avg={metrics.cpu_avg_pct:.1f}%, mem avg={metrics.memory_avg_pct:.1f}% \u2014 over-provisioned',\n                estimated_monthly_savings=round(savings, 2),\n                recommended_type=smaller,\n            )\n\n    # Under-provisioned: warn\n    if metrics.cpu_avg_pct > 80.0 or metrics.memory_avg_pct > 85.0:\n        return RightsizingRecommendation(\n            instance=metrics, action='upsize',\n            reason=f'CPU avg={metrics.cpu_avg_pct:.1f}%, mem avg={metrics.memory_avg_pct:.1f}% \u2014 performance risk',\n            estimated_monthly_savings=-metrics.monthly_cost * 0.5,  # negative = additional cost\n        )\n\n    return RightsizingRecommendation(\n        instance=metrics, action='keep',\n        reason='Utilization within healthy range',\n        estimated_monthly_savings=0.0,\n    )\n\n\n# Sample fleet\nfleet = [\n    InstanceMetrics('i-aaa111', 'm5.2xlarge', 8,  32, 280, cpu_avg_pct=3.2,  cpu_max_pct=9.8,  memory_avg_pct=8.0,  memory_max_pct=12.0, network_avg_mbps=10,  connection_count=0,  service='EC2'),\n    InstanceMetrics('i-bbb222', 'm5.xlarge',  4,  16, 140, cpu_avg_pct=18.0, cpu_max_pct=45.0, memory_avg_pct=22.0, memory_max_pct=55.0, network_avg_mbps=50,  connection_count=0,  service='EC2'),\n    InstanceMetrics('i-ccc333', 'r5.2xlarge', 8,  64, 368, cpu_avg_pct=72.0, cpu_max_pct=95.0, memory_avg_pct=88.0, memory_max_pct=97.0, network_avg_mbps=200, connection_count=0,  service='EC2'),\n    InstanceMetrics('db-prod1', 'r5.xlarge',  4,  32, 184, cpu_avg_pct=35.0, cpu_max_pct=60.0, memory_avg_pct=45.0, memory_max_pct=70.0, network_avg_mbps=80,  connection_count=120, service='RDS'),\n    InstanceMetrics('db-stg01', 'r5.xlarge',  4,  32, 184, cpu_avg_pct=2.0,  cpu_max_pct=5.0,  memory_avg_pct=10.0, memory_max_pct=15.0, network_avg_mbps=5,   connection_count=0,   service='RDS'),\n    InstanceMetrics('i-ddd444', 'm5.4xlarge', 16, 64, 560, cpu_avg_pct=12.0, cpu_max_pct=28.0, memory_avg_pct=15.0, memory_max_pct=35.0, network_avg_mbps=40,  connection_count=0,  service='EC2'),\n]\n\nrecommendations = [recommend_rightsizing(m) for m in fleet]\n\nprint('=== Rightsizing Recommendations ===')\nprint(f'  {\"Instance\":<12s}  {\"Type\":<14s}  {\"Action\":<10s}  {\"$/mo Savings\":>12s}  Reason')\nprint('  ' + '-' * 80)\ntotal_savings = 0.0\nfor rec in recommendations:\n    m = rec.instance\n    new_type = f' \u2192 {rec.recommended_type}' if rec.recommended_type else ''\n    savings_str = f'${rec.estimated_monthly_savings:.2f}' if rec.estimated_monthly_savings != 0 else '$0'\n    print(f'  {m.instance_id:<12s}  {m.instance_type + new_type:<22s}  '\n          f'{rec.action.upper():<10s}  {savings_str:>10s}  {rec.reason}')\n    if rec.estimated_monthly_savings > 0:\n        total_savings += rec.estimated_monthly_savings\n\nprint(f'\\n  Total monthly savings opportunity: ${total_savings:.2f}/mo  (${total_savings*12:.0f}/yr)')\n```\n\n## Commitment Strategy Decision Tree\n\n```\nWorkload type?\n\u251c\u2500\u2500 Fault-tolerant batch / ML training  \u2192  Spot (70\u201390% off)\n\u251c\u2500\u2500 Steady-state, same instance type    \u2192  Standard RI (up to 72% off)\n\u251c\u2500\u2500 Steady-state, may change type       \u2192  Savings Plan (up to 66% off)\n\u2514\u2500\u2500 Unpredictable, bursty               \u2192  On-Demand (no commitment)\n\nCoverage target:\n  - RI/SP cover baseline (p10 of hourly usage)\n  - On-Demand covers burst above baseline\n  - Spot covers opportunistic batch above baseline\n```\n\n## Key Takeaways\n\n- Spot instances offer the largest discount but require interruption handling (SQS, checkpointing)\n- Savings Plans are generally preferred over RIs due to flexibility across instance types\n- Cover only your baseline usage with commitments \u2014 not peak \u2014 to avoid wasted reservations\n- Idle EC2 (CPU < 5% avg) and zero-connection RDS are prime termination candidates\n- Rightsizing before purchasing RIs avoids locking in discount on over-provisioned resources\n"
}
