Featured image of post Cost Optimization: Choosing the Right Azure Compute for Your Workload

Cost Optimization: Choosing the Right Azure Compute for Your Workload

You picked serverless to save money. Now your bill looks like a horror movie.

This is a story we’ve lived twice at our transportation SaaS platform.

We started with Azure Functions on the Consumption plan because it promised “pay only for what you use.” That lasted until our delivery status sync job ran 24/7, processing 50,000 daily events. We scaled to Premium, then Dedicated. By the time we hit the bill, we realized we were paying more than if we’d just rented an App Service.

Here’s what we learned.

The ComputePlan Spectrum

PlanStartup TimeBilling ModelBest For
Consumption1-10sPer execution (100ms increments)Bursty, unpredictable workloads
Premium< 1sAlways-on + variablePredictable mid-range load
Dedicated (App Service)< 100msFixed monthlySustained, high-throughput load
Container Apps (vCPU-seconds)0.5-2sPer-vCPU per secondContainers with flexible scaling
App Service Plan< 100msFixed monthlyTraditional web apps, APIs

Real-World Case Study: Delivery Status Sync

Our delivery status sync pulls updates from carrier APIs every 5 minutes for 50,000 active shipments.

Option 1: Consumption Plan (The Dream)

1
2
3
4
Assumption:
- 50,000 executions/day
- Average execution: 200ms
- Memory: 256MB

Monthly Cost (US East 2, 2024 pricing):

  • Executions: 50,000 × 30 days = 1.5M executions

  • Free tier: 1M executions/month = 500k paid executions

  • Cost per million: $0.20

  • Executions: $0.10

  • Memory: 1.5M × 0.2s × 256MB = 77,000 GB-seconds

  • Cost per GB-second: $0.000016

  • Memory: $1.20

  • Cold starts: ~5% = 75,000 extra executions

  • Cold starts: $0.015

Total: ~$1.30/month

In theory, this is incredible.

Option 2: Consumption Plan (The Reality)

In reality:

  • Cold starts in winter (off-peak) cause 5-10 second delays
  • Customers complain: “Why isn’t my delivery tracking updating?”
  • You add parallel processing to speed things up: now 200 executions/day per shipment
  • Executions jump to 10M/month
1
2
3
4
5
New calculation:
- 10M executions: $2.00
- Memory (proportionally): $2.40
- Cold starts more frequent: $0.50
Total: ~$5/month

Still cheap! But now you’re also paying for the Premium plan tier limits you hit, so you move to Premium.

Option 3: Premium Plan

1
2
3
4
5
Premium Plan pricing:
- 1 instance (minimum): ~$0.50/hour = $360/month
- Memory is included
- No cold starts
- Billing continues even when idle

You start with 1 instance. Auto-scaling kicks in:

  • Afternoon peak: 3 instances = $1,080/month
  • But you’re only getting 60-70% CPU utilization

Reality: $900/month for underutilized capacity.

Option 4: Dedicated App Service Plan

1
2
3
4
Standard B2 Plan: ~$87/month
- 2 vCores
- 7GB RAM
- Can host 10+ functions or APIs

You consolidate:

  • Delivery status sync function
  • Order webhook handler
  • Customer notification service
  • Reporting API

Total: ~$87/month for 4 microservices.

Option 5: Container Apps

1
2
3
4
5
6
Container Apps with vCPU-second billing:
- Estimated 200,000 seconds of compute/month
- $0.000006 per vCPU-second
- 0.5 vCPU baseline: $0.60/month
- 1 vCPU burst: $1.80/month
Total: ~$2.40/month

Plus storage and log ingestion. In practice: ~$15-20/month.

This is newer infrastructure, so we haven’t fully migrated yet, but the math is compelling.

Decision Framework: Choosing Your Compute Service

Use Consumption Functions When:

  • ✓ Truly unpredictable traffic (minutes between executions)
  • ✓ Small daily execution count (< 1M)
  • ✓ Okay with cold start latency
  • ✓ Examples: Webhook handlers, scheduled cleanup jobs

Use Premium Functions When:

  • ✓ ~500k-2M executions/month
  • ✓ Need faster startup than Consumption
  • ✓ Want predictable monthly costs
  • ✓ Can’t move to App Service yet

Use Dedicated App Service When:

  • ✓ 2M+ executions/month OR sustained load
  • ✓ Latency-sensitive (no cold starts)
  • ✓ Hosting multiple services on same plan
  • ✓ This is our platform’s status sync today

Use Container Apps When:

  • ✓ Container-native workloads
  • ✓ Complex scaling requirements
  • ✓ Want consumption-based pricing at app scale
  • ✓ Multi-container orchestration

The Real Cost Analysis

For our transportation platform’s delivery status sync:

MetricConsumptionPremiumApp ServiceContainer Apps
Monthly Base$0-5$360-600$87-174$15-40
Cold Start ImpactHighNoneNoneMinimal
Scaling Efficiency90%40%70%95%
Consolidated Services114-52-3
Effective Cost (per service)$0.30-5$360-600$17-30$7-20

How We Migrated

Step 1: Identify the Problem

1
2
3
4
5
6
7
8
9
// Monitor actual costs
var metrics = await _costsApi.GetMetricsByServiceAsync(
    resourceGroup: "my-platform",
    timeframe: TimeSpan.FromMonths(1));
    
// Delivery status sync is 40% of our function costs
var expensiveService = metrics
    .OrderByDescending(m => m.EstimatedCost)
    .First();

Step 2: Right-Size with Real Data

1
2
3
Actual monthly executions: 1.2M
Actual CPU time: 50% utilized on Consumption
Peak load: 3x baseline (5 minutes × 50k shipments)

Step 3: Cost Estimate

  • Consumption: $2-5/month (but with slow cold starts)
  • Premium: $400/month (overkill)
  • App Service B2: $87/month (hosting 4 services)
  • Savings: $300/month on Premium, but also gains reliability

Step 4: Test Migration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Slot-based deployment
var productionSlot = await _appService.GetSlotAsync("production");
var stagingSlot = await _appService.GetSlotAsync("staging");

// Deploy new version to staging
await _appService.DeployAsync(stagingSlot, newVersion);

// Route 10% of traffic
var rules = new[] { 
    new TrafficRule { SlotName = "production", TrafficPercent = 90 },
    new TrafficRule { SlotName = "staging", TrafficPercent = 10 }
};
await _appService.SetTrafficRulesAsync(rules);

// Monitor metrics...
await Task.Delay(TimeSpan.FromHours(2));

// If good, route 100% to staging
await _appService.SwapAsync("production", "staging");

The Hidden Costs You Forgot

  1. Data transfer: Egress to carrier APIs costs money
  2. Storage: Logging, event history, backups
  3. Database: RUs in Cosmos, connections on SQL
  4. Monitoring: Application Insights ingestion
  5. VNet integration: Extra fees for Premium/Dedicated

For our platform, our “Functions” workload actually looked like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Functions themselves:        $20/month
Storage (logging, queues):   $15/month
Database (Cosmos):           $45/month
Application Insights:        $8/month
VNet integration (Premium):  +$10/month
────────────────────────────────
Total: $98/month (not $5!)

When we moved to App Service + consolidation:

1
2
3
4
5
6
App Service B2:              $87/month
Storage (same):              $15/month
Database (same):             $45/month
Application Insights (same): $8/month
────────────────────────────────
Total: $155/month (only $57 more!)

But with 5x throughput capacity and 20x lower latency. Worth it.

Tools for Cost Analysis

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Use Azure Cost Management API
var costs = await _costManagementClient.Query.PostAsync(
    new QueryDefinition
    {
        Type = "Summary",
        Timeframe = "MonthToDate",
        Dataset = new QueryDataset
        {
            Granularity = "Monthly",
            Filter = new QueryFilter
            {
                Dimensions = new QueryGrouping
                {
                    Name = "ResourceType",
                    Value = "Microsoft.Web/sites"
                }
            }
        }
    });

foreach (var row in costs.Rows)
{
    Console.WriteLine($"{row[0]}: ${row[1]}");
}

Or use Azure CLI:

1
2
3
4
az costmanagement query --scope /subscriptions/SUBID \
  --timeframe MonthToDate \
  --metric "PreTaxCost" \
  --grouping-dimension "ResourceType"

The Decision Today

For our platform in 2026:

  • Event handlers & webhooks: Consumption (truly bursty)
  • Status sync job: Dedicated App Service (predictable high volume)
  • Customer notifications: Container Apps (exploring)
  • Report generation: Time-triggered Premium Functions (medium load)

Total monthly: ~$250 (all compute services combined)


The lesson: Serverless doesn’t always mean cheaper. Know your traffic patterns, measure real costs, and don’t fear moving off the hype train if a different service makes financial sense.

Use the right tool. Not the trendy one.

All rights reserved
Built with Hugo
Theme Stack designed by Jimmy