{
  "version": 1,
  "id": "finops-waste-case-study",
  "title": "Case Study: A SaaS Company Discovers 40% Cloud Waste",
  "summary": "Find, quantify and remove waste in a real-shaped bill, and report the savings.",
  "estimatedMinutes": 20,
  "prerequisites": [],
  "source": "courses-notebook CLD303_finops_cloud_cost 08_case_study",
  "lesson": "**Scenario**: You are the lead FinOps engineer at Nexlify, a B2B SaaS company with $180K/month in cloud spend. The CFO flags that cloud costs grew 60% in 6 months while revenue grew only 20%. Your task: investigate, quantify waste, and produce an optimization roadmap with projected savings.\n\n## Company Background\n\n| Attribute | Value |\n|---|---|\n| Monthly cloud spend | $180,000 |\n| Monthly revenue | $1,200,000 |\n| Monthly active users | 42,000 |\n| Cloud as % of revenue | 15% (target: < 8%) |\n| CPAU | $4.29 (target: < $2.00) |\n| Engineering team size | 45 engineers, 8 teams |\n| Deployment platform | AWS (EC2, EKS, RDS, S3, CloudFront) |\n| Tag compliance | 61% (many untagged resources) |\n| RI/SP coverage | 18% (almost all on-demand) |\n\n## Investigation Approach\n\n1. **Waste Analysis** \u2014 identify idle, over-provisioned, and unoptimized resources\n2. **Optimization Plan** \u2014 prioritize by ROI, estimate savings, assign owners\n3. **90-Day Roadmap** \u2014 phase the interventions by effort and impact\n\n```python\n# Cell 1: Waste Analysis \u2014 identify all waste categories across the Nexlify cloud account\nfrom dataclasses import dataclass, field\nfrom typing import Dict, List, Optional\nfrom collections import defaultdict\n\n\n@dataclass\nclass WasteItem:\n    category: str           # 'idle_compute', 'oversized', 'unoptimized_storage', 'no_commitment', 'egress'\n    resource_id: str\n    resource_type: str\n    team: str\n    environment: str\n    monthly_waste: float    # estimated wasteful spend in USD/month\n    monthly_total: float    # total monthly cost of this resource\n    waste_reason: str\n    effort_to_fix: str      # 'low', 'medium', 'high'\n    fix_action: str\n\n    @property\n    def waste_pct(self) -> float:\n        return (self.monthly_waste / self.monthly_total * 100) if self.monthly_total > 0 else 0\n\n\n# Full waste inventory across Nexlify's AWS account\nwaste_inventory: List[WasteItem] = [\n\n    # === IDLE COMPUTE ===\n    WasteItem('idle_compute', 'i-staging-web-01', 'EC2 m5.2xlarge', 'product', 'staging',\n              monthly_waste=280, monthly_total=280,\n              waste_reason='CPU avg 1.2%, zero traffic \u2014 never decommissioned after migration',\n              effort_to_fix='low', fix_action='Terminate instance; save snapshot'),\n\n    WasteItem('idle_compute', 'i-staging-web-02', 'EC2 m5.2xlarge', 'product', 'staging',\n              monthly_waste=280, monthly_total=280,\n              waste_reason='Duplicate of staging-web-01, both serving zero requests',\n              effort_to_fix='low', fix_action='Terminate instance'),\n\n    WasteItem('idle_compute', 'db-analytics-dev', 'RDS r5.2xlarge', 'data', 'dev',\n              monthly_waste=550, monthly_total=550,\n              waste_reason='Zero connections for 21 days \u2014 analyst moved to Redshift Serverless',\n              effort_to_fix='low', fix_action='Create final snapshot and terminate'),\n\n    WasteItem('idle_compute', 'elb-old-api', 'Application Load Balancer', 'platform', 'prod',\n              monthly_waste=22, monthly_total=22,\n              waste_reason='No targets registered, zero requests for 90 days',\n              effort_to_fix='low', fix_action='Delete ALB'),\n\n    WasteItem('idle_compute', 'cache-reports-stg', 'ElastiCache r5.large', 'product', 'staging',\n              monthly_waste=140, monthly_total=140,\n              waste_reason='Reports feature disabled in staging; cache unused',\n              effort_to_fix='low', fix_action='Delete cluster; re-provision on demand'),\n\n    WasteItem('idle_compute', 'nat-gw-dev', 'NAT Gateway', 'infra', 'dev',\n              monthly_waste=32, monthly_total=32,\n              waste_reason='Dev environment uses NAT GW but traffic is < 1 GB/month',\n              effort_to_fix='medium', fix_action='Replace with NAT instance or route through VPN'),\n\n    # === OVERSIZED INSTANCES ===\n    WasteItem('oversized', 'i-api-server-prod-01', 'EC2 m5.4xlarge', 'product', 'prod',\n              monthly_waste=280, monthly_total=560,\n              waste_reason='CPU avg 12%, memory avg 18% \u2014 4x over-provisioned vs p95 load',\n              effort_to_fix='medium', fix_action='Rightsize to m5.xlarge after load testing'),\n\n    WasteItem('oversized', 'i-api-server-prod-02', 'EC2 m5.4xlarge', 'product', 'prod',\n              monthly_waste=280, monthly_total=560,\n              waste_reason='CPU avg 14%, memory avg 20% \u2014 provisioned for \"future growth\" 18 months ago',\n              effort_to_fix='medium', fix_action='Rightsize to m5.xlarge'),\n\n    WasteItem('oversized', 'db-prod-primary', 'RDS r5.4xlarge', 'product', 'prod',\n              monthly_waste=550, monthly_total=1100,\n              waste_reason='CPU avg 8%, connections avg 12 \u2014 sized for peak never reached',\n              effort_to_fix='high', fix_action='Rightsize to r5.xlarge with maintenance window'),\n\n    WasteItem('oversized', 'eks-nodegroup-general', 'EKS m5.2xlarge nodes (8 nodes)', 'platform', 'prod',\n              monthly_waste=890, monthly_total=2240,\n              waste_reason='Node efficiency 34% CPU / 28% memory \u2014 over-provisioned node pool',\n              effort_to_fix='medium', fix_action='Enable Cluster Autoscaler; rightsize to m5.xlarge nodes'),\n\n    WasteItem('oversized', 'i-ml-training-fleet', 'EC2 p3.8xlarge (3 instances, always-on)', 'ml', 'prod',\n              monthly_waste=3200, monthly_total=4800,\n              waste_reason='Training runs only 8h/day; instances idle 16h/day with no auto-stop',\n              effort_to_fix='medium', fix_action='Migrate to Spot + SageMaker managed training jobs'),\n\n    # === UNOPTIMIZED STORAGE ===\n    WasteItem('unoptimized_storage', 's3-raw-data-lake', 'S3 Standard (48 TB)', 'data', 'prod',\n              monthly_waste=736, monthly_total=1104,\n              waste_reason='48 TB raw data in S3 Standard; data > 30 days old never accessed',\n              effort_to_fix='low', fix_action='Add lifecycle policy: IA at 30d, Glacier at 90d'),\n\n    WasteItem('unoptimized_storage', 's3-backups-prod', 'S3 Standard (120 TB)', 'infra', 'prod',\n              monthly_waste=1932, monthly_total=2760,\n              waste_reason='Database backups stored in Standard; all > 7 days old, kept for 7 years',\n              effort_to_fix='low', fix_action='Lifecycle to Glacier Deep Archive at 7d (70% savings)'),\n\n    WasteItem('unoptimized_storage', 'ebs-snapshots-dev', 'EBS Snapshots (8 TB)', 'product', 'dev',\n              monthly_waste=320, monthly_total=320,\n              waste_reason='Dev snapshots never deleted; 180 days of daily snapshots retained',\n              effort_to_fix='low', fix_action='Retention policy: keep 7 days for dev, 30 for staging'),\n\n    # === NO COMMITMENT DISCOUNTS ===\n    WasteItem('no_commitment', 'compute-on-demand-all', 'All EC2/EKS (82% on-demand)', 'platform', 'prod',\n              monthly_waste=14400, monthly_total=48000,\n              waste_reason='Only 18% RI/SP coverage; stable baseline workload is 100% on-demand',\n              effort_to_fix='low', fix_action='Purchase 1yr Compute Savings Plans for 60% baseline coverage'),\n\n    # === DATA TRANSFER / EGRESS ===\n    WasteItem('egress', 'direct-s3-api-egress', 'S3 Direct Egress (8 TB/mo)', 'product', 'prod',\n              monthly_waste=540, monthly_total=720,\n              waste_reason='Static assets served directly from S3 at $0.09/GB; no CloudFront',\n              effort_to_fix='medium', fix_action='Route through CloudFront (85% cache hit \u2192 ~75% savings)'),\n\n    WasteItem('egress', 'cross-region-replication', 'S3 Cross-Region Replication (15 TB/mo)', 'data', 'prod',\n              monthly_waste=225, monthly_total=300,\n              waste_reason='Replicating all raw data to eu-west-1; EU ops only need processed data',\n              effort_to_fix='medium', fix_action='Replicate only processed tier; reduce 75% of cross-region egress'),\n]\n\n\ndef print_waste_analysis(inventory: List[WasteItem]) -> None:\n    \"\"\"Print waste analysis by category with totals.\"\"\"\n    total_waste = sum(w.monthly_waste for w in inventory)\n    total_spend = 180_000  # Nexlify total monthly spend\n    waste_pct   = total_waste / total_spend * 100\n\n    print(f'=== Nexlify Cloud Waste Analysis ===')\n    print(f'  Total monthly cloud spend:  ${total_spend:>10,.0f}')\n    print(f'  Identified monthly waste:   ${total_waste:>10,.0f}')\n    print(f'  Waste percentage:           {waste_pct:>9.1f}%')\n    print(f'  Annual waste (if unchanged):${total_waste*12:>10,.0f}')\n    print()\n\n    # By category\n    by_cat: Dict[str, List[WasteItem]] = defaultdict(list)\n    for w in inventory:\n        by_cat[w.category].append(w)\n\n    cat_labels = {\n        'idle_compute':         'Idle Compute',\n        'oversized':            'Oversized Resources',\n        'unoptimized_storage':  'Unoptimized Storage',\n        'no_commitment':        'No Commitment Discounts',\n        'egress':               'Data Transfer / Egress',\n    }\n\n    print(f'  {\"Category\":<30s}  {\"Waste/mo\":>10s}  {\"% of Waste\":>10s}  Items')\n    print('  ' + '-' * 62)\n    for cat, items in sorted(by_cat.items(), key=lambda x: sum(i.monthly_waste for i in x[1]), reverse=True):\n        cat_waste = sum(i.monthly_waste for i in items)\n        cat_pct   = cat_waste / total_waste * 100\n        print(f'  {cat_labels.get(cat, cat):<30s}  ${cat_waste:>9,.0f}  {cat_pct:>9.1f}%  {len(items)}')\n    print()\n\n    # Top 5 individual waste items\n    print('  Top 5 Waste Items by Monthly Cost:')\n    for w in sorted(inventory, key=lambda x: x.monthly_waste, reverse=True)[:5]:\n        print(f'    ${w.monthly_waste:>6,.0f}/mo  [{w.effort_to_fix.upper():6s}]  '\n              f'{w.resource_id}  ({w.resource_type})')\n        print(f'               \u2192 {w.waste_reason[:80]}')\n\n    # By effort\n    print()\n    print('  Waste by Remediation Effort:')\n    for effort in ['low', 'medium', 'high']:\n        items = [w for w in inventory if w.effort_to_fix == effort]\n        effort_waste = sum(w.monthly_waste for w in items)\n        print(f'    {effort.upper():6s}: {len(items):2d} items  ${effort_waste:>8,.0f}/mo  '\n              f'(${effort_waste*12:>10,.0f}/yr)')\n\n\nprint_waste_analysis(waste_inventory)\n```\n\n```python\n# Cell 2: Optimization plan with 90-day roadmap and projected savings\nfrom dataclasses import dataclass\nfrom typing import List, Dict\n\n\n@dataclass\nclass OptimizationAction:\n    phase: int              # 1=immediate, 2=30-day, 3=60-90-day\n    category: str\n    action: str\n    owner: str\n    monthly_savings: float\n    one_time_cost: float    # implementation cost (engineer hours \u00d7 rate)\n    payback_months: float\n    risk: str               # 'none', 'low', 'medium'\n\n    @property\n    def annual_net_savings(self) -> float:\n        return self.monthly_savings * 12 - self.one_time_cost\n\n    @property\n    def roi_pct(self) -> float:\n        if self.one_time_cost == 0:\n            return float('inf')\n        return self.annual_net_savings / self.one_time_cost * 100\n\n\noptimization_plan: List[OptimizationAction] = [\n\n    # Phase 1: Immediate (0\u20132 weeks) \u2014 no service impact, low effort\n    OptimizationAction(1, 'Idle Cleanup', 'Terminate 5 idle compute resources (staging EC2x2, RDS, ELB, ElastiCache)',\n                       'Platform Team', monthly_savings=1074, one_time_cost=800, payback_months=0.7, risk='none'),\n\n    OptimizationAction(1, 'Storage Lifecycle', 'Add S3 lifecycle policy to raw data lake (48 TB \u2192 IA+Glacier)',\n                       'Data Team', monthly_savings=736, one_time_cost=400, payback_months=0.5, risk='none'),\n\n    OptimizationAction(1, 'Storage Lifecycle', 'Glacier Deep Archive for backup bucket (120 TB)',\n                       'Infra Team', monthly_savings=1932, one_time_cost=400, payback_months=0.2, risk='none'),\n\n    OptimizationAction(1, 'Storage Cleanup', 'Delete stale EBS snapshots (retain 7d dev, 30d staging)',\n                       'Product Team', monthly_savings=320, one_time_cost=200, payback_months=0.6, risk='none'),\n\n    OptimizationAction(1, 'Tagging', 'Tag all untagged resources via Terraform import + tag policy',\n                       'Platform Team', monthly_savings=0, one_time_cost=2400, payback_months=0, risk='none'),\n    # ^ tagging enables chargeback \u2192 indirect savings not counted here\n\n    # Phase 2: 30-Day \u2014 requires testing/validation, medium effort\n    OptimizationAction(2, 'Commitment Discounts', 'Purchase 1yr Compute Savings Plans (60% baseline coverage)',\n                       'FinOps + Finance', monthly_savings=14400, one_time_cost=1200, payback_months=0.1, risk='low'),\n    # Savings Plans require upfront planning but are financial instrument, not technical change\n\n    OptimizationAction(2, 'Rightsizing', 'Downsize 2x m5.4xlarge API servers to m5.xlarge (load test first)',\n                       'Product Team', monthly_savings=560, one_time_cost=2400, payback_months=4.3, risk='low'),\n\n    OptimizationAction(2, 'CloudFront', 'Route S3 static asset delivery through CloudFront CDN',\n                       'Product Team', monthly_savings=540, one_time_cost=1600, payback_months=3.0, risk='low'),\n\n    OptimizationAction(2, 'ML Optimization', 'Migrate ML training to Spot + SageMaker (8h/day usage pattern)',\n                       'ML Team', monthly_savings=3200, one_time_cost=4800, payback_months=1.5, risk='medium'),\n\n    OptimizationAction(2, 'Egress', 'Reduce cross-region replication to processed data only',\n                       'Data Team', monthly_savings=225, one_time_cost=800, payback_months=3.6, risk='low'),\n\n    # Phase 3: 60\u201390 Day \u2014 higher complexity, maintenance windows needed\n    OptimizationAction(3, 'Rightsizing', 'Rightsize RDS r5.4xlarge \u2192 r5.xlarge (requires maintenance window)',\n                       'Product Team', monthly_savings=550, one_time_cost=3200, payback_months=5.8, risk='medium'),\n\n    OptimizationAction(3, 'K8s Efficiency', 'Enable Cluster Autoscaler + rightsize EKS nodegroup to m5.xlarge',\n                       'Platform Team', monthly_savings=890, one_time_cost=3200, payback_months=3.6, risk='medium'),\n\n    OptimizationAction(3, 'Dev Environments', 'Scale-to-zero dev/staging environments (nights+weekends via scheduler)',\n                       'Platform Team', monthly_savings=1800, one_time_cost=4000, payback_months=2.2, risk='low'),\n    # ^ estimated 65% reduction in non-prod compute\n]\n\n\ndef print_optimization_roadmap(actions: List[OptimizationAction]) -> None:\n    total_spend = 180_000\n    phase_labels = {1: 'Phase 1 \u2014 Immediate (0\u20132 weeks)', 2: 'Phase 2 \u2014 30 Days', 3: 'Phase 3 \u2014 60\u201390 Days'}\n\n    print('=== Nexlify Optimization Roadmap ===')\n    print()\n\n    cumulative_savings = 0.0\n    total_one_time = sum(a.one_time_cost for a in actions)\n    total_monthly  = sum(a.monthly_savings for a in actions)\n\n    for phase in [1, 2, 3]:\n        phase_actions = [a for a in actions if a.phase == phase]\n        phase_monthly = sum(a.monthly_savings for a in phase_actions)\n        phase_cost    = sum(a.one_time_cost   for a in phase_actions)\n        cumulative_savings += phase_monthly\n\n        print(f'  {phase_labels[phase]}')\n        print(f'  Phase savings: ${phase_monthly:,.0f}/mo  Implementation cost: ${phase_cost:,.0f}')\n        print(f'  {\"Action\":<55s}  {\"Owner\":<15s}  {\"$/mo\":>8s}  {\"Risk\":<8s}  {\"Payback\"}')\n        print('  ' + '-' * 105)\n        for a in sorted(phase_actions, key=lambda x: x.monthly_savings, reverse=True):\n            payback_str = f'{a.payback_months:.1f}mo' if a.payback_months > 0 else 'immediate'\n            print(f'  {a.action[:55]:<55s}  {a.owner:<15s}  ${a.monthly_savings:>7,.0f}  {a.risk:<8s}  {payback_str}')\n        print()\n\n    # Summary\n    new_monthly = total_spend - total_monthly\n    new_pct_rev = new_monthly / 1_200_000 * 100  # assume revenue stays same\n\n    print('  === Projected Impact Summary ===')\n    print(f'  Current monthly spend:          ${total_spend:>10,.0f}  ({total_spend/1_200_000*100:.1f}% of revenue)')\n    print(f'  Total monthly savings:          ${total_monthly:>10,.0f}')\n    print(f'  Projected monthly spend:        ${new_monthly:>10,.0f}  ({new_pct_rev:.1f}% of revenue)')\n    print(f'  Reduction:                      {total_monthly/total_spend*100:>9.1f}%')\n    print(f'  Total implementation cost:      ${total_one_time:>10,.0f}')\n    print(f'  Annual net savings (yr 1):      ${total_monthly*12 - total_one_time:>10,.0f}')\n    print(f'  ROI:                            {(total_monthly*12 - total_one_time)/total_one_time*100:>9.0f}%')\n    print()\n    print('  === KPI Improvement ===')\n    old_cpau = total_spend / 42_000\n    new_cpau = new_monthly / 42_000\n    print(f'  CPAU: ${old_cpau:.2f} \u2192 ${new_cpau:.2f}  (target: < $2.00)')\n    print(f'  Cloud % of revenue: {total_spend/1_200_000*100:.1f}% \u2192 {new_pct_rev:.1f}%  (target: < 8%)')\n    print(f'  RI/SP Coverage: 18% \u2192 ~65%  (after Phase 2 Savings Plans purchase)')\n    print(f'  Tag Compliance: 61% \u2192 ~95%  (after Phase 1 tagging sprint)')\n\n\nprint_optimization_roadmap(optimization_plan)\n```\n\n## Case Study Lessons\n\n| Finding | Root Cause | Prevention |\n|---|---|---|\n| Idle EC2/RDS in staging | No lifecycle policy for non-prod | Auto-shutdown via scheduler; quarterly idle audit |\n| Zero RI/SP coverage | Finance not engaged in commitment decisions | Monthly FinOps review with Finance |\n| 40% over-provisioned compute | Dev sizing carried to prod | Load testing gating for prod deployments |\n| 120 TB backups in Standard | No lifecycle policy on backup bucket | Tag-based lifecycle policies in Terraform modules |\n| Static assets without CDN | Architecture review missed egress modeling | Add egress cost estimate to architecture checklist |\n| ML training instances always-on | No cost visibility for ML team | ML team showback dashboard + Spot enforcement policy |\n\n## Key Takeaways\n\n- 40% waste in a growing SaaS is common without active FinOps practice\n- Savings Plans (commitment discounts) are the single highest-impact lever: $14,400/mo savings here\n- Storage lifecycle policies are zero-risk, high-ROI: implement as Terraform defaults\n- Idle resource cleanup requires discovery tooling (AWS Cost Explorer, Trusted Advisor) run weekly\n- Implementation prioritization: sort by (monthly_savings / one_time_cost) \u2014 maximize ROI first\n- After optimization: cloud drops from 15% to ~6.3% of revenue \u2014 below the 8% target\n",
  "quiz": []
}
