معماری جدید cdnjs روی Cloudflare؛ درس های مهاجرت یک CDN با 9 میلیارد درخواست روزانه
cdnjs از 23 ژوئن 2026 به طور کامل روی Cloudflare Developer Platform اجرا می شود و Cloudflare جزئیات این مهاجرت را در 30 ژوئیه منتشر کرد. این پرونده فنی فقط درباره یک CDN نیست؛ نمونه ای واقعی از تبدیل pipeline چندابری، storage دوگانه و orchestration مبتنی بر event به معماری دارای source of truth، workflow بادوام، queue و fallback چندلایه است.
cdnjs چیست و چرا این مقیاس اهمیت دارد؟
cdnjs یک CDN رایگان و متن باز برای libraryهای JavaScript و CSS است. کاربر به جای bundle کردن یا self-host کردن libraryهایی مانند jQuery، Bootstrap یا Lodash می تواند URL نسخه مشخص را در script یا link قرار دهد. مدل استفاده بدون signup، API key و rate limit، همراه URLهای immutable، باعث شده cdnjs در tutorialها، demoها و پاسخ های فنی فراوان حضور داشته باشد. Cloudflare همچنین نقش داده های آموزشی و خروجی مدل های زبانی را در تداوم این الگو برجسته کرده است.
طبق گزارش Cloudflare، این سرویس میانگین 108 هزار درخواست در ثانیه، 9 میلیارد درخواست در روز، بیش از 330 data center و hit rate حدود 98.6 درصد دارد. این اعداد دلیل اهمیت case study هستند: تصمیمی که در یک SaaS کوچک فقط چند ثانیه خطا ایجاد می کند، در cdnjs می تواند میلیون ها صفحه را تحت تأثیر بگذارد. مهاجرت باید بدون تغییر byte فایل، URL، SRI hash یا قرارداد cache انجام می شد.
cdnjs برای هر فایل SRI hash ارائه می کند، اما Cloudflare اشاره کرده هنوز در حال رفع ناسازگاری برخی hashهای ذخیره شده قدیمی است. نکته معماری این است که محتوای immutable و hash-pinned، امکان «بازسازی منطقی همان فایل» را محدود می کند. اگر minifier یا compressor نسخه دیگری byte متفاوت تولید کند، حتی خروجی functionally معادل هم contract مصرف کننده را می شکند. بنابراین preservation بایت از صحت معنایی مهم تر می شود.
پنج درد اصلی معماری قبلی
در 2020 بخش serving به Workers و KV منتقل شده بود و bare-metal origin نقش fallback داشت، اما publishing pipeline روی GCP باقی مانده بود. این pipeline نسخه های تازه npm و GitHub را پیدا می کرد، archive را دانلود، extract، minify و compress می کرد و نتیجه را در چند storage می نوشت. معماری در زمان ساخت منطقی بود، زیرا Workflows، Queues، R2، Containers و Durable Objects هنوز وجود نداشتند؛ شش سال بعد، همین راه حل به مانع توسعه تبدیل شد.
1. نبود trace مشترک
یک package update از Cloud Functions، eventهای GCS، Pub/Sub، VM مربوط به git-sync و Workers KV عبور می کرد. correlation ID مشترکی وجود نداشت؛ بخشی از داستان در GCP Logging و بخشی در Cloudflare Logpush بود. خطر اصلی fail کامل نبود، بلکه partial success بود: package در KV قابل سرو می شد اما به GitHub repository نمی رسید و divergence ممکن بود هفته ها پنهان بماند. هیچ component دید end-to-end برای alert کردن نداشت.
2. split-brain storage
فایل ها هم در KV و origin edge و هم در GitHub repository نگهداری می شدند. pipeline در پایان به هر دو می نوشت، اما authority روشن نبود. وقتی دو نسخه drift می کردند، reconcile قابل اعتماد دشوار بود. این نمونه کلاسیک مسئله dual write است: موفقیت در مقصد اول و شکست مقصد دوم، transaction سراسری ندارد. بدون outbox، idempotency و state machine، consistency به مجموعه ای از assumptionها تبدیل می شود.
3. استفاده از storage event به عنوان message queue
زنجیره Cloud Functionها با ایجاد object در bucket به مرحله بعد trigger می شد. storage هم محل داده و هم سیستم پیام بود، اما dead-letter queue، visibility backlog و replay تمیز وجود نداشت. event-driven بودن به خودی خود مشکل نیست؛ مشکل زمانی است که event transport semantics روشن ندارد. برای pipeline طولانی باید بدانید message حداقل یک بار، حداکثر یک بار یا دقیقاً یک بار چگونه پردازش می شود و failure چه stateی باقی می گذارد.
4. 26 Function برای حروف الفبا
فقط بررسی updateهای npm به 26 Cloud Function تقسیم شده بود، هرکدام برای یک حرف. این sharding دستی deployment و log مستقل می ساخت و health fleet باید در 26 نقطه بررسی می شد. partitioning در مقیاس لازم است، اما partition ثابت براساس الفبا معمولاً load متوازن یا elasticity خوب نمی دهد. queue و worker pool می تواند unit کار را براساس backlog توزیع کند و scale را از نام shard جدا سازد.
5. repository با بیش از 1.1 ترابایت packed storage
VM مربوط به git-sync همه فایل ها را در repository cdnjs mirror می کرد. سال ها release حجم packed storage را از 1.1 ترابایت عبور داد؛ archive service GitHub دیگر tarball یا zip تولید نمی کرد، fork عملی نبود و clone کند شده بود. Git برای source و history عالی است، اما object store عمومی برای میلیون ها artifact تولیدشده نیست. نشانه هشدار زمانی است که repository به storage backend و deployment database هم زمان تبدیل شود.
| درد قدیمی | علت معماری | اثر |
|---|---|---|
| نبود trace مشترک | چند cloud و چند log بدون correlation | debug دستی و partial failure پنهان |
| split-brain | dual write به KV و GitHub | drift و reconcile دشوار |
| storage event به جای queue | نبود DLQ و replay روشن | failure recovery ضعیف |
| 26 shard دستی | partition براساس حرف | deployment و health پیچیده |
| Git repository عظیم | استفاده از Git برای artifact storage | clone، fork و archive غیرعملی |
معماری جدید: هر سرویس برای یک نوع داده
در معماری تازه، R2 source of truth محتوای فایل است. فایل های بزرگ تر مانند source map، bundle و font pack نیز کنار بقیه قرار می گیرند و S3 API امکان دسترسی ابزارهای سازگار را می دهد. انتخاب object storage برای artifact immutable طبیعی است: scale حجمی، lifecycle و API آن برای blob طراحی شده، نه برای history Git یا read pattern metadata کوچک.
KV فقط metadata نگه می دارد: اطلاعات package، فهرست versionها و SRI hashها. این data shape خواندن بسیار زیاد و نوشتن کم دارد و با ماهیت KV هماهنگ است. جداسازی content و metadata اجازه می دهد هرکدام storage مناسب خود را داشته باشند. اشتباه رایج این است که یک database را برای همه dataها انتخاب کنیم و بعد با partition، index و cache پیچیده کمبود fit را جبران کنیم.
Workers Cache جلوی Worker قرار دارد و tiered caching را فراهم می کند. Cloudflare می گوید این لایه جای cache داخلی جداگانه قبلی را گرفته و moving part را کاهش داده است. مسیر serving پس از miss به R2 می رسد و اگر R2 فایل را برنگرداند، Worker به DigitalOcean Spaces fallback می کند. هر فایل publish شده به R2 در DigitalOcean mirror می شود؛ بنابراین chain عملی cache → R2 → DigitalOcean است.
نکته کلیدی معماری جدید «استفاده از سرویس های زیاد» نیست؛ تعیین authority روشن، data shape مناسب و مسیر failure قابل توضیح است.
Publishing pipeline با Workflows، Queue و Container
هر ده دقیقه یک cron، PackageUpdatesWorkflow را trigger می کند تا npm و GitHub را برای نسخه تازه بررسی کند. برای هر version، DownloadPackageWorkflow archive را وارد R2 می کند؛ سپس ProcessingWorkflow برای هر فایل extraction، minification و compression را هماهنگ می کند. در پایان PublishingWorkflow نتیجه را در R2 و KV می نویسد و index جست وجوی Algolia را به روزرسانی می کند. این مراحل یک state machine نام دار می سازند، نه زنجیره ای ضمنی از object eventها.
Workflows execution بادوام دارد؛ state هر step حفظ می شود و در network timeout یا compression error از آخرین مرحله موفق ادامه می دهد. این ویژگی برای job چندساعته مهم است، زیرا retry از ابتدا هم هزینه را بالا می برد و هم side effect را تکرار می کند. برای استفاده درست، هر step باید idempotent باشد یا idempotency key داشته باشد. durable بودن orchestration، function غیرایمن را خودکار دقیقاً یک بار نمی کند.
Compression برای Worker بیش ازحد CPU-intensive بود، پس به service نوشته شده با Rust در Cloudflare Containers واگذار شد. ProcessingWorkflow فایل خام را در R2 می نویسد، job را به Queue می فرستد و hibernate می شود. Container فایل را compress و نتیجه را در bucket دیگر می نویسد؛ R2 event دوباره workflow را بیدار می کند. این الگو نشان می دهد serverless و container رقیب مطلق نیستند: orchestration سبک با workflow و compute سنگین با container ترکیب می شود.
نوع دوم انتظار در سطح package است. parent باید تمام childهای فایل را قبل از publish کامل منتظر بماند. برای package دارای هزاران فایل، هزاران child موازی ایجاد می شود. یک Durable Object کوچک counter را نگه می دارد: parent به ازای spawn افزایش و child در پایان کاهش می دهد؛ وقتی counter صفر شد parent بیدار می شود. این barrier synchronization ساده، state coordination را از polling و scan مکرر جدا می کند.
| جزء | مسئولیت در cdnjs | اصل قابل تعمیم |
|---|---|---|
| Workers | مسیر serving و هماهنگی edge | compute نزدیک کاربر و routing |
| Workers Cache | cache tiered جلوی Worker | کاهش origin read و moving part |
| R2 | source of truth فایل ها | object store برای artifact immutable |
| KV | metadata و SRI | data store مطابق read-heavy metadata |
| Workflows | orchestration بادوام | state و resume برای pipeline چندمرحله ای |
| Queues | fan-out و تحویل at-least-once | backpressure و توزیع کار |
| Containers | compression CPU-intensive | compute سنگین خارج از Worker |
| Durable Objects | counter هماهنگی parent/child | stateful coordination کوچک |
چگونه بدون شکستن SRI مهاجرت کردند؟
تلاش قبلی برای re-process کردن packageهای قدیمی rollback شد، چون خروجی minifier و compressor جدید byte-match با محتوای KV نبود. فایل ها از نظر منطقی صحیح بودند، اما SRI hash تغییر می کرد و browser script دارای integrity قدیمی را رد می کرد. این یک درس مهم است: در سیستم content-addressed یا hash-pinned، compatibility در سطح byte تعریف می شود. rebuild از source لزوماً migration امن نیست.
راه حل نهایی، کپی محتوای موجود از KV به R2 بدون regeneration بود. مسئله از «پردازش دوباره میلیون ها فایل» به «کپی کامل و قابل اثبات میلیون ها object» تغییر کرد. packageها براساس نام shard و از طریق Queue بین invocationها fan-out شدند. ضمانت at-least-once Queue به این معنی بود که package بی صدا حذف نمی شود، اما consumer باید duplicate delivery را تحمل کند. checksum، inventory count و idempotent write برای اطمینان از completeness ضروری اند.
در مسیر migration دو limit پلتفرم آشکار شد: 1000 subrequest در هر Worker invocation و 1024 step در Workflow. تیم به جای workaround محلی، limitهای پلتفرم را افزایش داد؛ طبق گزارش، subrequest در paid plan تا 10 میلیون و Workflow به صورت پیش فرض 10 هزار step و قابل تنظیم تا 25 هزار رسید. این بخش را نباید به عنوان توصیه استفاده بی حد فهمید؛ limit بزرگ تر همچنان نیازمند budget، observability و حفاظت از runaway job است.
الگوی اعتبارسنجی مهاجرت immutable content
- فهرست کامل objectها، versionها و hashهای منبع را freeze کنید.
- محتوا را byte-for-byte کپی کنید؛ regeneration را مسیر جدا بدانید.
- write مقصد را idempotent و قابل retry بسازید.
- count، size و hash را در source و destination مقایسه کنید.
- مهاجرت را shard کنید اما coverage shardها را با manifest مرکزی بسنجید.
- read path جدید را ابتدا shadow یا درصدی فعال کنید.
- fallback به storage قبلی را تا پایان backfill و verification نگه دارید.
- پس از cutover، drift detector و rollback window تعریف کنید.
قابلیت اطمینان، fallback و کاهش سطح امنیت
DigitalOcean Spaces فقط backup سرد نیست؛ fallback زنده read path است. اگر cache miss رخ دهد و R2 فایل را برنگرداند، Worker mirror را می خواند. این طراحی availability را بالا می برد، اما consistency mirror و هزینه egress باید monitor شود. fallback که هرگز exercise نشود ممکن است هنگام incident کار نکند؛ synthetic check و game day باید مسیر ثانویه را واقعاً آزمایش کنند.
کاهش moving part نیز کنترل امنیتی است. pipeline قدیمی Cloud Functionها، VM، container image، GCS bucket و service-account keyهای متعدد داشت و هرکدام patch، audit و permission جدا می خواست. ادغام روی platform واحد الزاماً همه ریسک را حذف نمی کند و concentration risk می سازد، اما inventory و identity boundary را ساده تر می کند. طراحی خوب باید blast radius platform را با mirror مستقل و export قابل حمل جبران کند.
SRI و URL immutable برای مصرف کننده مزیت supply-chain هستند، ولی فقط در صورت صحت hash و اعتماد به منبع اولیه package. Cloudflare اشاره کرده برخی hashهای قدیمی هنوز نیاز به تطبیق دارند. تیم مصرف کننده باید version را pin و integrity/crossorigin را استفاده کند؛ latest URL یا حذف SRI به امید اینکه CDN امن است، contract را ضعیف می کند. CDN delivery بخشی از chain است، نه تضمین کامل سلامت dependency.
هشت درس معماری برای تیم های کوچک تر
- یک source of truth تعیین کنید: dual write بدون authority روشن دیر یا زود drift می سازد.
- data store را براساس شکل داده انتخاب کنید: blob در object storage، metadata read-heavy در KV.
- orchestration را صریح کنید: زنجیره event storage جای state machine، retry و observability را نمی گیرد.
- CPU-heavy را از edge function جدا کنید: container و serverless می توانند مکمل باشند.
- at-least-once را جدی بگیرید: consumer باید duplicate را تحمل و idempotency را enforce کند.
- immutable migration را byte-level ببینید: rebuild صحیح می تواند hash و contract را بشکند.
- fallback مستقل بسازید: mirror در provider دیگر concentration risk را کاهش می دهد.
- limit را در طراحی آشکار کنید: subrequest، step، queue backlog و object count بخشی از architecture هستند.
این درس ها به معنی کپی کردن stack Cloudflare نیستند. یک سرویس کوچک ممکن است با object storage، queue و یک worker ساده تر بهتر کار کند. ارزش case study در مرزبندی مسئولیت هاست. اگر team شما برای debug یک job باید log پنج سرویس را دستی join کند، اگر دو storage هم زمان authority هستند یا اگر bucket event نقش queue را بازی می کند، همان smellها در مقیاس کوچک نیز وجود دارند.
جدول تصمیم برای بازطراحی pipeline محتوا
| نشانه | ریشه محتمل | اقدام |
|---|---|---|
| خروجی در دو storage متفاوت است | dual write بدون transaction | authority واحد و replication یک طرفه |
| job از مرحله اول retry می شود | نبود durable state | workflow/state machine با checkpoint |
| storage event گم شده قابل replay نیست | message semantics مبهم | queue با DLQ و visibility |
| فایل های تولیدی repository را متورم کرده اند | Git به عنوان artifact store | object storage و manifest |
| کار CPU-heavy timeout می شود | اجرای نامناسب در function کوتاه | container یا batch compute |
| migration hashها را عوض می کند | regeneration غیرقطعی | کپی byte-for-byte و verify |
| provider outage کل serving را قطع می کند | نبود mirror واقعی | fallback مستقل و test دوره ای |
برای شروع، لازم نیست big-bang migration انجام دهید. ابتدا observability و manifest end-to-end اضافه کنید، سپس authority را مشخص سازید. read path را از write path جدا کنید و replication را یک طرفه کنید. پس از آن pipeline جدید را برای package یا tenant محدود اجرا کنید. وقتی byte/hash و latency برابر شد، traffic را مرحله ای منتقل کنید. ترتیب اصلاحات به کاهش uncertainty کمک می کند و rollback را ممکن نگه می دارد.
پرسش های متداول
cdnjs چه زمانی کاملاً به Cloudflare Developer Platform منتقل شد؟
Cutover کامل در 23 ژوئن 2026 انجام شد و گزارش فنی در 30 ژوئیه منتشر شد.
R2 در معماری جدید چه نقشی دارد؟
source of truth محتوای فایل است؛ KV فقط metadata مانند package، version و SRI را نگه می دارد.
مسیر fallback serving چیست؟
Workers Cache، سپس R2 و در صورت نبود فایل DigitalOcean Spaces.
چرا محتوای قدیمی را دوباره build نکردند؟
نسخه های جدید minifier و compressor byte متفاوت و SRI hash جدید می ساختند، پس فایل ها as-is کپی شدند.
Workflows و Containers چگونه با هم کار می کنند؟
Workflow state و انتظار را مدیریت می کند؛ compression CPU-heavy در container انجام و با Queue و R2 event هماهنگ می شود.
مهم ترین درس برای تیم کوچک چیست؟
source of truth واحد، retry idempotent، queue واقعی و observability end-to-end را قبل از بزرگ شدن سیستم ایجاد کنید.
جمع بندی
مهاجرت cdnjs نشان می دهد معماری مدرن فقط serverless کردن همه چیز نیست. فایل ها در R2، metadata در KV، cache در Workers Cache، orchestration در Workflows، fan-out در Queues، compute سنگین در Containers و coordination کوچک در Durable Objects قرار گرفته اند. مهم تر از نام سرویس ها، authority روشن و failure path قابل توضیح است.
جزئیات cutover، مقیاس ترافیک، معماری قدیم و جدید، شکست migration اولیه و افزایش limitهای پلتفرم در گزارش رسمی Cloudflare درباره مهاجرت cdnjs منتشر شده است. برای تیم شما، بهترین استفاده از این گزارش ساخت checklist smellهای معماری و اصلاح مرحله ای pipeline موجود است، نه بازسازی بی دلیل همان stack.