SLIDE 94: Section: Cost Optimization
Running Efficiently at Scale
Why Cost Matters:
Key Insight:
At scale, small inefficiencies become massive costs. A 10ms slower query multiplied by 1 billion requests costs real money.
SLIDE 95: Cost Visibility: Understanding Your Bill
Where Money Goes
Typical Cloud Cost Breakdown:
Cost Allocation Tags:
# Tag all resources
Resources:
MyEC2Instance:
Type: AWS::EC2::Instance
Properties:
Tags:
-Key: Environment
Value: production
-Key: Team
Value: platform
-Key: Service
Value: api-gateway
-Key: CostCenter
Value: CC-1234
SLIDE 96: Cost Visibility: Understanding Your Bill (continued)
Cost Monitoring Dashboard:
def generate_cost_report():
costs = cloud_provider.get_costs(
start_date=last_month_start,
end_date=last_month_end,
group_by=["Service", "Team", "Environment"]
)
report = {
"total": sum(c.amount for c in costs),
"by_service": group_and_sum(costs, "Service"),
"by_team": group_and_sum(costs, "Team"),
"by_environment": group_and_sum(costs, "Environment"),
"trends": calculate_month_over_month(costs),
"anomalies": detect_cost_anomalies(costs),
}
if report["anomalies"]:
alert_finance_team(report["anomalies"])
return report
Cost Anomaly Detection:
def detect_cost_anomalies(current_costs, historical_costs):
anomalies = []
for service, cost in current_costs.items():
avg_cost = mean(historical_costs[service])
std_dev = stdev(historical_costs[service])
if cost > avg_cost + (2 * std_dev):
anomalies.append({
"service": service,
"current": cost,
"expected": avg_cost,
"increase": (cost - avg_cost) / avg_cost * 100
})
return anomalies
SLIDE 97: Right-Sizing: Compute Resources
Matching Resources to Actual Usage
The Problem:
Provisioned: 16 vCPU, 64GB RAM
Average usage: 2 vCPU, 8GB RAM
Utilization: 12.5%
Wasted: 87.5% of cost
Right-Sizing Analysis:
def analyze_instance_sizing(instance_id, days=14):
metrics = cloudwatch.get_metrics(
instance_id=instance_id,
metrics=["CPUUtilization", "MemoryUtilization"],
period=days
)
cpu_p95 = percentile(metrics.cpu, 95)
mem_p95 = percentile(metrics.memory, 95)
current_type = get_instance_type(instance_id)
# Find smallest instance that fits P95 + 20% headroom
recommended = find_instance_type(
cpu_required=cpu_p95 * 1.2,
memory_required=mem_p95 * 1.2
)
savings = calculate_savings(current_type, recommended)
return {
"current": current_type,
"recommended": recommended,
"monthly_savings": savings,
"cpu_p95": cpu_p95,
"memory_p95": mem_p95
}
SLIDE 98: Right-Sizing: Compute Resources (continued)
Instance Type Recommendations:
Auto-Scaling Configuration:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas:2
maxReplicas:20
metrics:
-type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization:70 # Scale at 70% CPU
SLIDE 99: Reserved Capacity: Commitments
Trading Flexibility for Savings
Pricing Models:
When to Reserve:
SLIDE 100: Reserved Capacity: Commitments (continued)
Savings Plan Calculation:
def calculate_optimal_commitment():
# Analyze 90 days of usage
hourly_costs = get_hourly_on_demand_costs(days=90)
# Find minimum consistent spend
p10_hourly = percentile(hourly_costs, 10)
# Commit to P10 (safe baseline)
recommended_commitment = p10_hourly
# Calculate savings
annual_on_demand = sum(hourly_costs) * (365/90)
reserved_cost = (
recommended_commitment * 24 * 365 * 0.6 + # 40% savings on reserved
(mean(hourly_costs) - recommended_commitment) * 24 * 365 # On-demand for rest
)
return {
"recommended_hourly_commitment": recommended_commitment,
"annual_on_demand": annual_on_demand,
"annual_with_savings_plan": reserved_cost,
"annual_savings": annual_on_demand - reserved_cost,
"savings_percentage": (annual_on_demand - reserved_cost) / annual_on_demand * 100
}
SLIDE 101: Spot Instances: Massive Savings
Using Spare Capacity
Spot Instance Characteristics:
Good for Spot:
- Batch processing jobs
- CI/CD build agents
- Data processing pipelines
- Development/testing environments
- Stateless web workers (with proper handling)
Bad for Spot:
- Single points of failure
- Stateful services without replication
- Long-running transactions
- Real-time critical systems
SLIDE 102: Spot Instances: Implementation
Handling Spot Interruptions:
import requests
import signal
import sys
def check_spot_interruption():
"""Check AWS spot interruption notice."""
try:
response = requests.get(
"http://169.254.169.254/latest/meta-data/spot/termination-time",
timeout=1
)
if response.status_code == 200:
return True
except:
pass
return False
def graceful_shutdown(signum, frame):
logger.info("Spot interruption detected, shutting down gracefully")
# Stop accepting new work
stop_accepting_requests()
# Complete in-flight requests
wait_for_requests(timeout=90)
# Checkpoint state if needed
save_checkpoint()
sys.exit(0)
# Check periodically
scheduler.every(5).seconds.do(lambda:
graceful_shutdown(None, None) if check_spot_interruption() else None
)
SLIDE 103: Spot Instances: Implementation (continued)
Mixed Instance Strategy:
# AWS Auto Scaling Group with mixed instances
AutoScalingGroup:
MixedInstancesPolicy:
InstancesDistribution:
OnDemandBaseCapacity:2 # Always have 2 on-demand
OnDemandPercentageAboveBaseCapacity:20 # 20% on-demand above base
SpotAllocationStrategy: capacity-optimized
LaunchTemplate:
Overrides:
-InstanceType: c5.xlarge
-InstanceType: c5a.xlarge
-InstanceType: c5n.xlarge
-InstanceType: c4.xlarge
Kubernetes Spot Handling:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
# Tolerate spot node taints
tolerations:
-key:"kubernetes.io/spot"
operator:"Equal"
value:"true"
effect:"NoSchedule"
# Prefer spot but allow on-demand
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
-weight:100
preference:
matchExpressions:
-key: kubernetes.io/spot
operator: In
values:["true"]
SLIDE 104: Storage Optimization: Tiering
Matching Storage to Access Patterns
Storage Tiers:
Automatic Lifecycle Policies:
{
"Rules": [
{
"ID": "ArchiveOldLogs",
"Status": "Enabled",
"Filter": { "Prefix": "logs/" },
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER"
}
],
"Expiration": {
"Days": 365
}
}
]
}
SLIDE 105: Storage Optimization: Database Costs
Reducing Database Expenses
Database Cost Factors:
Query Optimization Impact:
Before optimization:
- Query takes 500ms
- Runs 10,000 times/day
- CPU time: 5,000 seconds/day
After optimization:
- Query takes 50ms
- Same 10,000 times/day
- CPU time: 500 seconds/day
Result: 10x less database load
Potential: Downsize database instance
Savings: $500-5000/month
SLIDE 106: Data Transfer: Reducing Egress
The Hidden Cost
Data Transfer Pricing:
Reducing Egress Costs:
# Bad: Return full objects
def get_users():
users = db.query("SELECT * FROM users") # Returns all columns
return jsonify(users) # 10KB per user, 1M users = 10GB
# Good: Return only needed fields
def get_users():
users = db.query("SELECT id, name FROM users") # Only needed columns
return jsonify(users) # 100 bytes per user, 1M users = 100MB
CDN for Static Content:
Without CDN:
- 1TB static assets served from origin
- Egress cost: $90/month
With CDN:
- 1TB served from CDN edge
- CDN cost: $8.50/month
- Origin egress: ~$5/month
- Savings: $76.50/month
SLIDE 107: Architecture Optimization: Cost-Aware Design
Building for Efficiency
Serverless vs Containers:
Caching ROI:
Database query cost: $0.0001 per query
Cache cost: $100/month for Redis
Without cache:
- 10M queries/day
- Monthly cost: $30,000
With cache (95% hit rate):
- 500K database queries/day
- Monthly cost: $1,500 + $100 = $1,600
- Savings: $28,400/month
SLIDE 108: Architecture Optimization: Cost-Aware Design (continued)
Asynchronous Processing:
# Expensive: Synchronous, scales linearly
def process_order_sync(order):
validate(order) # 10ms
charge_payment(order) # 500ms (external API)
update_inventory(order) # 50ms
send_confirmation(order) # 200ms (email API)
# Total: 760ms, needs instance for full duration
# Cheaper: Async, decouple expensive operations
def process_order_async(order):
validate(order) # 10ms
queue.enqueue("process_payment", order.id)
return {"status": "processing"}
# Total: 10ms, background workers handle rest
# Can use fewer, smaller instances
Compute Efficiency:
Processing 1M images/day
Approach 1: General purpose instances
- c5.xlarge: 1000 images/hour
- 1000 hours needed = 42 instances
- Cost: $3,600/month
Approach 2: GPU instances
- p3.2xlarge: 10,000 images/hour
- 100 hours needed = 4 instances
- Cost: $1,200/month
- Savings: 67%
SLIDE 109: Cost Governance: Budgets and Alerts
Preventing Surprises
Budget Configuration:
def create_cost_budget():
budget = {
"BudgetName": "Production-Monthly",
"BudgetLimit": {
"Amount": "50000",
"Unit": "USD"
},
"TimeUnit": "MONTHLY",
"BudgetType": "COST",
"CostFilters": {
"Environment": ["production"]
},
"NotificationsWithSubscribers": [
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80 # Alert at 80%
},
"Subscribers": [
{"SubscriptionType": "EMAIL", "Address": "team@example.com"}
]
},
{
"Notification": {
"NotificationType": "FORECASTED",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 100 # Alert if forecast exceeds budget
},
"Subscribers": [
{"SubscriptionType": "SNS", "Address": "arn:aws:sns:..."}
]
}
]
}
budgets_client.create_budget(AccountId=account_id, Budget=budget)
SLIDE 110: Cost Governance: Budgets and Alerts (continued)
Automated Cost Controls:
class CostGuard:
def __init__(self, daily_limit):
self.daily_limit = daily_limit
def check_and_enforce(self):
today_cost = get_today_cost()
if today_cost > self.daily_limit * 0.8:
# Warning: approaching limit
alert_team(f"Cost at{today_cost}, approaching limit")
if today_cost > self.daily_limit:
# Critical: over limit, take action
self.emergency_cost_reduction()
def emergency_cost_reduction(self):
# Stop non-critical environments
scale_down("development")
scale_down("staging")
# Disable expensive features
feature_flags.disable("heavy_analytics")
feature_flags.disable("ml_inference")
# Alert leadership
page_engineering_leadership()
# Run every hour
scheduler.hourly(CostGuard(daily_limit=2000).check_and_enforce)
SLIDE 111: Cost: Anti-Patterns
Common Expensive Mistakes
Anti-Pattern 1: Overprovisioning
"We might need it someday"
Reality:
- 100 instances provisioned
- 20 instances actually used
- 80% waste
Anti-Pattern 2: Zombie Resources
# Resources created for testing, never deleted
zombies = [
"test-database-john-2023",
"experiment-ml-model-v17",
"temp-storage-migration",
"dev-cluster-old",
]
# Monthly cost of zombies: $5,000
# Last accessed: 6 months ago
Solution: Regular Cleanup
def find_zombie_resources():
for resource in list_all_resources():
if resource.last_accessed < days_ago(30):
if resource.tags.get("permanent") != "true":
flag_for_review(resource)
SLIDE 112: Cost: Anti-Patterns (continued)
Anti-Pattern 3: Ignoring Reserved Instance Coverage
On-demand spend: $100,000/month
Steady-state workload: 80%
Without reservations: $100,000
With reservations (80% coverage, 40% savings):
- Reserved: $80,000 × 0.6 = $48,000
- On-demand: $20,000
- Total: $68,000
- Waste: $32,000/month
Anti-Pattern 4: Cross-Region Data Transfer
Architecture:
- Application in us-east-1
- Database replica in us-west-2
- Syncing 1TB/month
Cost:
- Cross-region transfer: 1TB × $0.02 = $20/TB
- With constant sync: $600/month just for transfer
Solution:
- Co-locate when possible
- Use regional replication only where needed
SLIDE 113: Cost: Summary
Key Takeaways
Cost Visibility:
- Tag all resources (team, service, environment)
- Create cost dashboards per team
- Set up anomaly detection
- Review costs weekly
Right-Sizing:
- Analyze CPU and memory utilization
- Use P95 + 20% headroom
- Implement auto-scaling
- Review instance types quarterly
Commitment Discounts:
- Reserve steady-state baseline
- Use savings plans for flexibility
- Cover 60-80% of consistent workload
- Review annually
SLIDE 114: Cost: Summary (continued)
Spot Instances:
- Use for fault-tolerant workloads
- Handle interruptions gracefully
- Mix with on-demand for reliability
- Potential 60-90% savings
Storage Optimization:
- Implement lifecycle policies
- Use appropriate storage tiers
- Clean up unused volumes
- Archive old data
Architecture:
- Cache aggressively
- Use async where possible
- Minimize data transfer
- Choose right compute type for workload
Coming Up Next: Section: Multi-Region and Global Scale