Skip to content

CLOUDFLARE

Read original ↗

Dogfooding at scale: migrating cdnjs to Cloudflare's Developer Platform

Summary

Cloudflare migrated cdnjs — one of the Internet's busiest open-source JavaScript/CSS CDNs (12% of all websites, 108K req/sec, 9 billion requests/day, 98.6% cache hit rate) — from a legacy architecture split across GCP Cloud Functions + a git-sync VM + Workers KV to run entirely on Cloudflare's Developer Platform: Workers, Workflows, R2, KV, Queues, Containers, Workers Cache, and Durable Objects. The migration eliminated five architectural pain points (no shared trace, split-brain storage, object-event-based pipeline glue, 26 sharded functions, and a 1.1 TB GitHub repo GitHub couldn't serve) and consolidated both serving and publishing into a single platform with durable execution semantics.

Key takeaways

  1. R2 is the single source of truth — replaces the previous split-brain (Workers KV + GitHub repo). No size limits, so source maps and large bundles that couldn't fit in KV now live alongside everything else. The S3 API makes the entire catalog accessible to any S3 client. (Section: "How we re-built it")

  2. KV stores only metadata now — package info, version lists, SRI hashes. KV's high-read/infrequent-write profile matches metadata access perfectly; file content moved entirely to R2. (Section: "How we re-built it")

  3. Workers Cache replaces an internal caching layer — a tiered cache sits in front of the Worker, owned by the Developer Platform rather than a separate internal team. One less moving part. (Section: "How we re-built it")

  4. DigitalOcean Spaces as disaster-recovery fallback — every file published to R2 is mirrored to DigitalOcean Spaces. Serving chain is cache → R2 → DigitalOcean, so R2 having a bad day doesn't take cdnjs down. (Section: "How we re-built it")

  5. Cloudflare Workflows replaces the GCP Cloud Functions chain — a cron-triggered PackageUpdatesWorkflow every 10 minutes spawns per-version DownloadPackageWorkflow → per-file ProcessingWorkflowPublishingWorkflow. Durable execution means each step's state is preserved and resumes from the last successful step on failure. (Section: "How we re-built it")

  6. Containers handle CPU-intensive compression — text-based files are pre-compressed (Brotli + gzip), but compression is too CPU-intensive for a Worker. A Rust compression service runs in Cloudflare Containers; Workflows hands off via a Queue, hibernates, and wakes on an R2 event notification when compression completes. (Section: "How we re-built it")

  7. Durable Objects as a parent-child coordination counter — a package with thousands of files spawns thousands of child ProcessingWorkflows. A small Durable Object acts as a counter: parent increments on each spawn, children decrement on finish, parent wakes when counter hits zero. (Section: "How we re-built it")

  8. Copy-not-regenerate migration strategy — a previous attempt to re-process old packages failed because minifiers/compressors aren't deterministic across versions, producing different SRI hashes. Since users pin SRI hashes in HTML, the team migrated existing KV content to R2 as-is rather than regenerating. (Section: "Pushing the limits")

  9. Queue-sharded migration overcame subrequest limits — the 1,000 subrequest-per-invocation limit (at the time) was too low for packages with thousands of files. Work was fanned out via Queues with at-least-once delivery so no package could silently drop. (Section: "Pushing the limits")

  10. Platform limits lifted for everyone — cdnjs hit the 1,000 subrequest limit and the 1,024 Workflow step limit. Rather than just working around them, Cloudflare raised both: subrequests now go to 10 million on paid plans; Workflows default to 10,000 steps (configurable to 25,000). (Section: "Pushing the limits")

Architecture & numbers

Metric Value
Requests/sec 108,000 average
Requests/day 9 billion
Website market share 12% of all websites (48.3% JS CDN market)
Cache hit rate 98.6%
Data centers 330+ Cloudflare
Previous GitHub repo size 1.1 TB packed
Previous pipeline shape 26 GCP Cloud Functions (one per alphabet letter) + GCS object events + git-sync VM
New subrequest limit 10 million (paid plans)
New Workflow step limit 10,000 default / 25,000 configurable
Pipeline cadence Every 10 minutes (cron-triggered)

Extracted systems/concepts/patterns

Systems

Concepts

Patterns

Caveats

  • No latency or performance numbers comparing old vs new architecture disclosed
  • No failure-rate or incident-count comparison disclosed
  • Specific Workflow / Queue / Container SKU sizing not disclosed
  • Algolia search index update mechanism described only in passing
  • DigitalOcean Spaces mirroring mechanism (sync vs async, lag) not detailed
  • No per-file compression ratio or time-to-compress numbers
  • "SRI hash" integrity verification described as still in-progress for legacy data
  • Origin server still in chain during transition (pending GitHub backfill to R2)
  • No disclosure of how many Workflow instances run concurrently at peak
  • ESM module transformation explicitly future-looking ("not committing to it")

Source

Last updated · 604 distilled / 1,840 read