بیشتر ادعاهای مربوط به عملکرد در فضای ابزارهای JavaScript، عمر کوتاهی مثل شیر دارند. کسی چند پکیج را در یک صبح آرامِ سه‌شنبه نصب می‌کند، خروجی ترمینال را ثبت می‌کند و یک نمودار میله‌ای پرزرق‌وبرق منتشر می‌کند. تا اسپرینت بعدی، یکی از آن ابزارها وصله‌ای منتشر کرده که کل آن ادعا را بی‌اعتبار می‌کند. اما آن پست همچنان در موتورهای جستجو ایندکس شده باقی می‌ماند و نمودار مدام به اشتراک گذاشته می‌شود؛ در حالی که اعداد، از قبل در حال دروغ گفتن به شما هستند.

این همان فسادی است که تقریباً تمام بنچمارک‌های مدیریت پکیج (package manager) را آلوده می‌کند. آن‌ها مانند عکاسی از یک رویداد هستند، در حالی که آنچه ما نیاز داریم، یک پخش زنده است.

پروژه‌ای به نام depjs/canary با این مشکل نه به عنوان یک وظیفه‌ی تقویم محتوایی، بلکه به عنوان یک مسئولیت سیستمی برخورد می‌کند. این یک بنچمارک زنده است که npm، pnpm، Yarn و dep را زیر نظر می‌گیرد و به محض اینکه هر یک از آن‌ها نسخه‌ی جدیدی منتشر کنند، کل مجموعه‌ی تست‌های خود را دوباره اجرا می‌کند. نتایج عمومی، مستمر و اجتناب‌ناپذیر هستند. وقتی چیزی خراب می‌شود، مخزن (repository) تا زمانی که مشکل حل نشود، قرمز باقی می‌ماند. اینجا خبری از گلچین کردن نتایج (cherry-picking)، پنهان شدن پشت یک پست قدیمی در وبلاگ، یا این فرض که برنده ماه گذشته هنوز هم پادشاه است، نیست.

چرا ادعاهای سرعت به تاریخ انقضا نیاز دارند

مدیریت‌کننده‌های پکیج JavaScript ثابت نمی‌مانند. فاصله بین نسخه‌های فرعی (minor versions) می‌تواند شامل بازنویسی الگوریتم‌های حل وابستگی (resolution algorithms)، تغییر در استراتژی‌های hoisting یا تغییر در نحوه کلیدگذاری کش جهانی باشد. بنچمارکی که npm 10.2.1 و pnpm 8.11.0 را ثبت می‌کند، تقریباً هیچ چیز درباره‌ی نحوه‌ی عملکرد همان ابزارها در دو نسخه‌ی بعد به شما نمی‌گوید. با این حال، وب پر است از اظهارات قطعی مانند «ابزار X سه برابر سریع‌تر است» که دقیقاً بر اساس همین نوع اسنپ‌شات‌های منجمد بنا شده‌اند.

بدتر از آن، بسیاری از تست‌ها شرایطی را که چالش‌های واقعی توسعه‌دهندگان را تعریف می‌کنند، نادیده می‌گیرند. یک مدیریت‌کننده پکیج ممکن است در یک محیط گرم و از پیش آماده شده، نصب را با سرعت برق و باد انجام دهد، اما در یک CI runner که با یک دیسک خالی شروع می‌شود، با سرعت بسیار کم حرکت کند. بدون تست کردن هر دو حالت حدی، بنچمارک به جای اینکه یک داده‌ی مهندسی قابل استفاده باشد، تبدیل به یک بیانیه مطبوعاتی می‌شود.

چگونه Canary مقایسه را خودکار می‌کند

هر دو ساعت یک‌بار، یک Job رجیستری npm را بررسی (poll) می‌کند. اگر نسخه‌ی جدیدی از npm، pnpm، Yarn یا dep ظاهر شود، canary بیدار می‌شود. این سیستم منتظر نمی‌ماند تا یک انسان متوجه تغییرات (changelog) شود؛ بلکه بلافاصله یک ماتریس تست کامل را اجرا می‌کند که هر چهار مدیریت‌کننده را در برابر پنج پکیج محبوب و واقعی قرار می‌دهد. این انتخاب شامل غول‌هایی مانند React، Next.js و Vite است؛ کد‌بازهایی که توسعه‌دهندگان واقعی هر روز آن‌ها را نصب می‌کنند. این‌ها میکرومشروع‌های مصنوعی نیستند که صرفاً برای تعریف کردن یک ابزار خاص طراحی شده باشند.

این رویکردِ مبتنی بر انتشار (release-triggered) اهمیت دارد، زیرا اندازه‌گیری را مستقیماً به تغییر متصل می‌کند. اگر بنچمارک فقط طبق یک برنامه‌ی شبانه اجرا می‌شد، ممکن است یک اصلاحیه فوری (hotfix) میان‌روز را از دست بدهد یا یک پسرفت (regression) را برای ساعت‌ها نادیده بگیرد. با اجرا شدن دقیقاً روی نسخه‌های جدید، canary هر بار یک سوال مستقیم می‌پرسد: آیا این انتشار، اوضاع را بهتر کرد یا بدتر؟

چهار سناریویی که بخش‌های مختلف را تحت فشار قرار می‌دهند

ماتریس تست بر اساس چهار تنظیمات متمایز ساخته شده است که مستقیماً با جریان‌های کاری (workflows) آشنای شما مطابقت دارند.

  • کش سرد، بدون فایل lock. این حالت مربوط به یک کلون تازه روی یک لپ‌تاپ جدید، یا اولین نصب پس از حذف node_modules است. هیچ‌چیز کش نشده است. هیچ‌چیز ثابت (pinned) نیست. مدیریت‌کننده پکیج باید همه چیز را از صفر حل، دریافت و نوشته باشد.
  • کش گرم، همراه با فایل lock. این مسیر ایده‌آل برای یکپارچه‌سازی مداوم (CI) در زمانی است که همه چیز درست پیش می‌رود. فایل lock به صورت محلی وجود دارد و کش هنوز فایل‌های tarball مربوط به اجرای قبلی را نگه داشته است. ابزار باید سریع عمل کند زیرا بیشتر تصمیمات قبلاً گرفته شده‌اند.
  • کش سرد، همراه با فایل lock. در اینجا فایل lock وجود دارد، اما کش پاک شده است. مدیریت‌کننده می‌تواند از مرحله‌ی حل وابستگی‌ها عبور کند، اما همچنان باید هر بایت را از طریق شبکه دانلود کند. این حالت، سرعت شبکه را از سرعت حل وابستگی‌ها جدا می‌کند.
  • کش گرم، بدون فایل lock. کش آماده است، اما فایل lock از بین رفته است. مدیریت‌کننده پکیج باید قبل از اینکه حتی بتواند استخراج فایل‌ها را شروع کند، درخت وابستگی‌ها را دوباره حل کند. این حالت کارایی solver و تجزیه‌گر متادیتا (metadata parser) را در شرایط ایده‌آل شبکه آزمایش می‌کند.

هر سناریو پنج بار اجرا می‌شود و canary نتیجه‌ی میانه (median) را نگه می‌دارد. این انتخابِ ساده، نویز زیادی را از بین می‌برد. یک اختلال گذرا در شبکه یا یک جهش ناگهانی در تأخیر (latency) رجیستری نمی‌تواند روایت اصلی را منحرف کند. داده‌های پرت (outlier) نادیده گرفته می‌شوند و تجربه‌ی معمولی ثبت می‌گردد.

Smoke Tests بر تایمرهای توخالی پیروز می‌شوند

سرعت خام اگر نتیجه را تأیید نکنید، به راحتی قابل جعل است. یک مدیریت بسته می‌تواند مراحل پس از نصب را نادیده بگیرد، چند symlink را خراب کند یا نسخه‌های اشتباه را نصب کند و همچنان یک زمان‌بندی چشمگیر ثبت کند. canary از توقف در زمان‌بندی خودداری می‌کند. پس از اتمام نصب، این ابزار در واقع کد نصب‌شده را اجرا و آزمایش می‌کند.

برای مثال، یک اپلیکیشن Express را راه‌اندازی می‌کند و تأیید می‌کند که سرور شروع به گوش دادن روی پورت مورد انتظار کرده است. اگر کد اجرا نشود، بنچمارک بلافاصله با شکست مواجه می‌شود. این smoke test، مجموعه را از یک مسابقه به یک بازرسی تبدیل می‌کند. این کار به سوالی پاسخ می‌دهد که سرعت به تنهایی نمی‌تواند پاسخ دهد: آیا نصب واقعاً کار می‌کند؟

صداقت رادیکال به عنوان یک ویژگی

نویسنده canary سه قانون را در این فرآیند گنجانده است که اکثر نویسندگان بنچمارک آن‌ها را اختیاری می‌دانند.

میدان بازی یکسان. از flagها برای استانداردسازی رفتار ابزارهای مختلف استفاده می‌شود. اگر یک مدیریت بسته، نقص عملکردی را پشت یک تنظیم پیش‌فرض پنهان کند، بنچمارک آن را برملا می‌کند، به جای اینکه اجازه دهد ابزار به طور تصادفی خوب به نظر برسد.

شروع‌های سرد واقعی. قبل از هر بار تکرار، و نه فقط بار اول، npm cache و pnpm store پاک می‌شوند. کلمه «هر بار» در اینجا نقش بسیار مهمی دارد. بسیاری از بنچمارک‌ها کش را یک بار پاک می‌کنند و سپس پنج نصب را پشت سر هم اجرا می‌کنند. از اجرای دوم تا پنجم واقعاً «سرد» نیستند و اعداد به همین ترتیب بزرگ‌نمایی می‌شوند. canary هر بار از صفر شروع می‌کند.

وضعیت‌های شکست عمومی. وقتی یک نسخه جدید چیزی را خراب می‌کند، مخزن در وضعیت قرمز (شکست) باقی می‌ماند. این وضعیت در صفحه اصلی، زشت و حل‌نشده، تا زمان ارائه اصلاحیه باقی می‌ماند. هیچ سرکوب بی‌صدایی برای سبز نگه داشتن داشبورد وجود ندارد. این سیاست، شفافیت را تحمیل می‌کند. کاربری که در حال ارزیابی ابزارهاست، نه تنها می‌تواند ببیند کدام‌یک سریع‌تر است، بلکه می‌تواند ببیند کدام‌یک در طول زمان قابل اعتماد باقی مانده است.

قابلیت بازرسی غیرقابل مذاکره است

بنچمارکی که نتوانید آن را بازتولید کنید، چیزی جز شعار تبلیغاتی نیست. canary این مسئله را با یک اسکریپت bash ساده حل کرده است که به هر کسی اجازه می‌دهد هر بخشی از این مجموعه را به صورت محلی اجرا کند. نیازی نیست به شبکه یک ارائه‌دهنده ابری یا محیطی که توسط نگهدارنده به صورت دستی تنظیم شده است، اعتماد کنید. اگر شک دارید که اعداد اشتباه هستند، می‌توانید اعداد خودتان را تولید کنید.

این شفافیت همچنین پروژه را برای نگهدارندگان مفید می‌کند. وقتی یک رگرسیون رخ می‌دهد، یک توسعه‌دهنده پایین‌دست می‌تواند اسکریپت را دریافت کند، نسخه‌های ابزار را با استفاده از bisect بررسی کند و به تیم بالا‌دست یک