Multi-CDN Cost Arbitrage: Routing Traffic to the Cheapest Provider Dynamically

Back in November 2022, I watched our real-time billing dashboard tick up $14,000 in under an hour during a flash sale. We were serving about 380 Gbps across two CDNs, but 90% of that traffic was still pinned to our primary provider at $0.085 per GB. Our secondary CDN would've charged $0.02 for the exact same bytes in that region. That $12k difference wasn't a performance decision, it was just inertia. That's when our team stopped treating CDN selection as an architecture choice and started treating it as a trading problem.

Why "Cheapest CDN" Isn't a Fixed Answer

I've negotiated multi-CDN contracts for eight years now, and there's one myth I have to kill in every kickoff meeting: there is no single cheapest provider. Every vendor's pricing sheet looks simple until you map it to your actual traffic shape. One provider might offer $0.04/GB for North America on a commit, but charge $0.12/GB in South America and $0.018 per 10k requests for small objects. Another will give you dirt-cheap bandwidth in APAC but penalize you on cache purge and token auth fees. If you've only ever looked at the headline bandwidth rate, you've missed 40% of the bill.

The math gets messier once you factor in commit tiers, overage penalties, and regional egress variations. We had a client last year who was locked into a 500 TB/month commit with a premium CDN because it was "cheaper" on paper. But their traffic had shifted - 60% of their growth was coming from India and Brazil, where that provider's regional surcharge wiped out the commit discount. When we modeled their CDN Egress Cost Modeling: Understanding and Reducing Your Largest Bill Component, we found they were paying an effective $0.067/GB globally while a blended, dynamic approach would have averaged $0.031. They were essentially paying for the privilege of being locked in. You can't do effective CDN Bandwidth Commitment Pricing: When Committed Plans Save Money vs Pay-As-You-Go analysis without region-by-region traffic mapping, and that's where most teams stop.

Cost arbitrage only works if you can decide, per request or per ASN, where that byte should go based on real-time cost. That means your routing layer needs to know not just latency and error rates, but dollars per gigabyte for that specific PoP, at that specific commit utilization level, for that specific object class. We've built our system to recalculate effective cost every 60 seconds.

// Simplified cost-aware routing decision (runs at edge)
// effectiveCost = baseRate * regionMultiplier * commitUtilizationFactor + requestCost

function getEffectiveCost(provider, region, objectType) {
  const base = provider.pricing.bandwidthPerGB[region]; // e.g., 0.04
  const commitFactor = provider.commit.used / provider.commit.total > 0.9 ? 1.4 : 1.0;
  // Overage is 1.4x if you bust your commit - this is where arbitrage matters
  const requestFee = objectType === 'small' 
    ? provider.pricing.per10kRequests / 10000 
    : 0;

  // Respect cache-control semantics per https://httpwg.org/specs/
  // Don't route uncacheable or private content to cheap, low-retention CDN
  if (objectType === 'private' || objectType === 'uncacheable') {
    return Infinity; 
  }
  
  return (base * commitFactor) + requestFee;
}

const candidates = [cdnA, cdnB, cdnC]
  .map(p => ({ provider: p, cost: getEffectiveCost(p, clientRegion, objType) }))
  .sort((a, b) => a.cost - b.cost)
  .filter(c => c.cost < performanceThresholdCost);

Building Dynamic Routing That Doesn't Destroy Your Hit Ratio

The first time we tried naive cost-based switching in 2019, we saved 22% on bandwidth and increased origin load by 300%. We were shifting traffic so frequently between providers that neither CDN could build a warm cache. Our origin egress bill, which had been a rounding error, suddenly eclipsed the CDN savings. Any CDN Request Pricing Analysis: Reducing Per-Request Costs at Scale has to account for the cost of a cache miss, not just the cost of a cache hit. A $0.02/GB CDN is expensive if it has a 78% hit ratio while your $0.06/GB CDN holds 96%.

What we've learned - and what the performance guides on web.dev Articles reinforce - is that you can't route on cost alone. You have to route on *effective* cost, which includes origin shielding and cache efficiency. Our current routing engine weights three variables for every decision: real-time $/GB, measured cache hit ratio for that content class over the last 10 minutes, and p95 TTFB from that PoP. If a cheap provider's hit ratio drops below 85% for large video segments, we stop sending that class there even if it's half the price. The origin fetch + transit cost flips the equation.

We handle this with weighted DNS plus edge logic. For static, highly cacheable assets like images, JS, and VOD segments, we can be aggressive - switching providers every few minutes based on price. For APIs, personalized HTML, or anything with Cache-Control: private, no-store per the HTTP Working Group Specifications, we pin to the premium CDN with better dynamic acceleration and don't arbitrage at all. The trick is segmenting your traffic by cacheability first, then arbitraging only the cacheable 70-80%.

// Edge Worker (Cloudflare / Fastly Compute@Edge) - traffic segmentation
// Route only cacheable traffic via cost arbitrage

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const cacheability = getCacheability(request); // checks Cache-Control, Cookie, Auth

    if (cacheability === 'DYNAMIC') {
      // Don't arbitrage - send to premium CDN with Argo/Tiered Cache
      return fetch(request, { backend: "premium_origin_shield" });
    }

    // For cacheable content, pick cheapest healthy CDN
    const region = request.cf.coloRegion || "US-EAST";
    const cheapest = await getCheapestHealthyCDN(region, url.pathname);
    
    // Sticky routing per /24 to preserve hit ratio - don't flap per-request
    const clientSubnet = request.headers.get("x-client-subnet") || "default";
    const stickyChoice = await getStickyMapping(clientSubnet, cheapest);

    // Add header for reconciliation and billing verification
    request.headers.set("x-cdn-cost-route", `${stickyChoice.id}:${stickyChoice.costPerGB}`);
    
    return fetch(request, { backend: stickyChoice.backend });
  }
}

Where the Real Arbitrage Margins Hide

Not all regions are created equal, and that's exactly why multi-CDN cost arbitrage works. In North America and Western Europe, competition has driven pay-as-you-go rates into a tight band - maybe $0.03 to $0.08 per GB. You won't get rich arbitraging there unless you're at massive scale. The money is in the edges: South America, India, Southeast Asia, and Australia, where the spread between a legacy premium provider and a newer volume-tier CDN can be 4x to 6x. I regularly see $0.11/GB vs $0.019/GB for the same PoP city.

Request pricing makes those gaps even wider for specific workloads. If you're serving millions of tiny thumbnails, tile maps, or API JSON blobs under 10KB, your bill is dominated by per-request fees, not bandwidth. One provider we use charges $0.0075 per 10k HTTPS requests, another charges $0.12. For a customer doing 800M thumbnail requests a day, that's the difference between $18k/month and $288k/month before a single gigabyte is counted. That's why you have to model both dimensions together.

Region / Traffic Class Premium CDN A (Commit) Volume CDN B (PayG) Budget CDN C (PayG) Arbitrage Savings Observed
North America - Large Video (GB) $0.042 / GB $0.035 / GB $0.018 / GB 18-57% depending on commit fill
South America - Large Video (GB) $0.110 / GB $0.045 / GB $0.025 / GB 59-77% by shifting SA to CDN C
APAC (India/Singapore) - Small Images $0.060 / GB + $0.12 / 10k req $0.030 / GB + $0.01 / 10k req $0.022 / GB + $0.0075 / 10k req 62% total bill reduction (req fees dominate)
Europe - API / Dynamic (per 10k req) $0.09 / 10k req $0.15 / 10k req $0.18 / 10k req -40% (negative - keep on premium)
Australia - Mixed Static $0.095 / GB $0.055 / GB $0.028 / GB 71% with CDN C, but hit ratio 84% vs 94%

Building the Cost-Aware Routing Layer: From Cost Tables to Live Traffic Shifts

Most teams start with a spreadsheet and end with a surprise bill. A static "cheapest CDN" table doesn't survive contact with real traffic because pricing isn't static. Bunny might be $0.01/GB in North America and Europe but $0.03/GB in Singapore and $0.045/GB in South America. CloudFront is $0.085/GB on-demand, but if you've got a CDN Reserved Capacity Pricing: Locking In Rates for Predictable Workloads commitment at $0.02/GB, it's suddenly the cheapest option until you burn through that commit. We rebuild the cost table every 15 minutes, not daily, pulling live usage from each provider's billing API and weighting it by region, POP, and asset type.

You don't want to do this with plain DNS round-robin either. That'll wreck your hit ratio before you save a dime. Our production stack is usually weighted DNS (Route 53, NS1, or Constellix) for coarse 70/30 or 60/20/20 splits, plus an edge steering layer for granular decisions - a Cloudflare Worker, Fastly Compute@Edge, or even an NGINX map in front of your origin shield. The key is normalizing cache keys across all providers and forcing a shared origin shield. Without a single shield region, we’ve seen hit ratios collapse from 96% to 78% across three CDNs, and that extra 18% origin fetch at $0.08-$0.09/GB egress wipes out your entire arbitrage margin. If you're not already tiering your origin storage and compressing at the edge, fix that first - see CDN Storage Cost Optimization: Tiering, Compression, and Lifecycle Policies - because you can't arbitrage efficiently if your origin is bleeding.

The other non-negotiable is commit burn-down tracking. Arbitrage isn't "always route to the cheapest." It's "route to the cheapest *after* your commits are satisfied." If you've pre-bought 500TB at Akamai for $0.02/GB, you *must* send that 500TB to Akamai first, even if Bunny is $0.01/GB spot. Only your overflow traffic is truly arb-able. We track this with a simple burn-down counter in Redis or Durable Objects and flip weights once commitments hit 95%.

// Cost-aware routing worker (Cloudflare / Fastly Compute@Edge)
const COST_TABLE = {
  "na-east": { bunny: 0.010, cloudfront: 0.020, fastly: 0.12 }, // cloudfront = reserved rate
  "eu-west": { bunny: 0.010, cloudfront: 0.085, fastly: 0.12 },
  "apac":    { bunny: 0.030, cloudfront: 0.085, fastly: 0.12 },
  "sa":      { bunny: 0.045, cloudfront: 0.110, fastly: 0.15 }
};

const COMMIT = { provider: "cloudfront", committedTB: 500, usedTB: 412 };
const SHIELD_HOST = "shield-origin.example.com";

function pickProvider(region, bytes) {
  // Burn committed traffic first - don't arbitrage against yourself
  if (COMMIT.usedTB < COMMIT.committedTB) {
    COMMIT.usedTB += bytes / (1024**4);
    return COMMIT.provider;
  }
  // Overflow: pick cheapest by region, with health check
  const costs = COST_TABLE[region];
  return Object.entries(costs)
    .filter(([p]) => isHealthy(p)) // 5s health check via /__cdn-health
    .sort((a,b) => a[1] - b[1])[0][0];
}

export default {
  async fetch(req) {
    const region = req.cf?.continent?.toLowerCase() || "na-east";
    const provider = pickProvider(region, 0);
    const url = new URL(req.url);
    url.hostname = `${provider}.${SHIELD_HOST}`;
    // Keep cache-key consistent across providers
    req.headers.set("X-Cache-Key", `${url.pathname}?v=1`);
    return fetch(url, req);
  }
}

Advanced Patterns and Why Your Arbitrage Margin Quietly Disappears

Once the basic router's live, the next margin killers are subtle. First is volume-tier erosion. CloudFront, Akamai, and Fastly all have cliffs where your per-GB rate drops sharply after 10PB, 50PB, etc. We had a client saving $18k/month by shifting 40% of US video traffic to Bunny, only to bump their remaining CloudFront traffic back up from $0.02 to $0.055/GB because they fell below their tier. Net loss: $7k/month. You have to model tiers holistically. This is where a shield architecture saves you twice - you consolidate cache MISS traffic to one shield POP per CDN, keeping your origin hit ratio at 94-96% even with three CDNs in rotation. Without shielding, every provider cold-fetches the same long-tail objects and you pay for it three times.

Second is the compute vs. bandwidth trade-off. The cheapest bandwidth provider is often the most expensive for edge compute. Routing a personalized image-optimization request to Bunny at $0.01/GB looks great until you realize you just moved that $0.60 per million requests image transform off CloudFront Functions ($0.10 per million) or Cloudflare Workers. We've seen arbitrage logic that saved $0.04/GB on delivery but added $0.11/GB in compute. If you're running logic at the edge, you must factor function pricing into the routing decision. Our rule: static assets (video, images, large JS bundles) are arbitrage-eligible; dynamic, compute-heavy paths are pinned. For a deeper breakdown, read CDN Edge Compute Cost Optimization: Function Pricing Models and Efficiency Tricks before you let your router touch those routes.

When debugging, don't trust any single dashboard. Reconcile three numbers hourly: bytes billed by the CDN, bytes served from your origin logs, and bytes reported by your RUM/analytics. A healthy pipeline shows a 3-5% delta between CDN billed and origin served. If that delta jumps above 8-10%, you're likely double-caching, serving stale-while-revalidate incorrectly, or 302-looping between CDNs. Also watch for cache poisoning from inconsistent Vary headers - one provider normalizing `accept-encoding` differently can fragment your cache into 4x the keys. Alert on hit ratio *per provider* and on origin egress cost per 1M requests, not just total bandwidth.

Frequently Asked Questions

Won't constantly switching CDNs hurt our cache hit ratio and TTFB?

It will if you do naive DNS flipping. With a shared origin shield, normalized cache keys, and request collapsing enabled on all providers, we've kept global hit ratios at 94-96% even with traffic split across three CDNs and re-balanced every 15 minutes. TTFB variance stays under 15-25ms if you pin latency-sensitive regions. The hit ratio killer isn't the switch - it's having three independent origin fetches for the same object. Shield it once.

How often should we recalculate the cheapest provider?

Don't do it per-request - you'll flap. Don't do it daily either - you'll miss commits burning down. Every 10-15 minutes is the sweet spot. Pull provider billing/latency/error-rate metrics, update your weighted DNS or edge config, and shift no more than 10-15% of traffic per interval. That dampens cache churn while still reacting fast enough to catch a provider outage or a commit threshold.

Can we arbitrage between regions on the *same* CDN?

Absolutely, and it's often safer than switching providers. Most CDNs price APAC and South America 2-4x higher than NA/EU. If you have regional flexibility, you can use Argo-style tiered routing or route APAC long-tail video via a cheaper EU shield POP with slightly higher latency but $0.03/GB savings. For large objects >5MB, the bandwidth win usually dwarfs the 40-60ms latency penalty.

What's a realistic saving to expect from cost arbitrage?

For a 1-5 PB/month workload with at least one reserved commit, 25-40% on delivery costs is typical - that's $25k-$80k/month at scale. Below ~200TB/month, the operational overhead usually isn't worth it; you're better off just committing to one budget provider like Bunny or Cloudflare R2 + CDN. Above 10PB, your savings come more from tier management than per-GB switching - that's where reserved capacity strategy matters most.

Related Articles

References & Standards: HTTP Working Group Specifications · web.dev Articles · IETF Datatracker