
ستون حاوية على خادم واحد
صندوق معادن عارية واحد يشغل عشرات إلى مئات حاويات Hoody. يجعل KSM و BTRFS dedup التكلفة الإضافية قريبة من الصفر.
أوّل مهمّة في خطّ الأنابيب تضع node_modules في tar وتُمرِّرها إلى رابط Hoody. عشرون مهمّة لاحقة تطلب نفس الرابط وتفكّ tar. مع ?n=20 المُنتِج ينتظر اتّصال كل العشرين عاملاً، ثم يبثّ مرّة واحدة — مُتفرِّعاً إلى كلّهم. لا دلو S3، لا cache action، لا فاتورة خروج.
لا مكتبة عميل، لا خَدَم، لا SDK. المُنتِج يبثّ ملف tar إلى PUT /pipe/cache?n=20. كل عامل يبثّه عائداً من GET /pipe/cache?n=20. الأنبوب يحتفظ بالبايتات فقط أثناء طيرانها — لا على القرص أبداً.
tar يحزم node_modules؛ zstd يضغط أثناء الطيران؛ curl يُرسل البايتات بـ PUT مباشرةً إلى مسار الأنبوب. لا ملف مؤقّت، لا خطوة upload-artifact، لا بيانات اعتماد دلو.
كل عامل اختبار يطلب نفس الرابط بـ GET، يفكّ الضغط، ويفكّ tar إلى دليل عمله. العمّال البطيئون يُطبّقون ضغطاً عكسياً على المُنتِج لكنّهم لا يحجبون الأسرع.
?n=20مُعامل استعلام واحد. المُنتِج والمستهلِك يتّفقان على نفس n. الأنبوب يحتفظ بالرفع حتى يتّصل ذلك العدد بالضبط، ثم يفتح البوّابات.
ذاكرة CI كانت ضريبة: تخزين ستكتب فوقه غداً، خروج كل مرّة يسحب فيها عامل، وقت هندسي على غرابات cache action. الأنبوب يحذف البنود الثلاثة دفعةً واحدة.
لا دلو S3 لأنّه لا تخزين. الأنبوب ينسى البايتات لحظة انتهاء النقل، فلا شيء يُتقاضى عنه لكل جيجابايت-شهر أو لكل جيجابايت-خروج. الذاكرة تتوقّف عن كونها بنداً.
لا مفاتيح بصيغة yaml، لا تقسيم save-cache / restore-cache، لا تنقيح لماذا لم تحدث ضربة ذاكرة على المُشغِّل الصحيح. فقط curl. السطران ذاتهما يعملان على GitHub Actions وBuildKite وJenkins وحاسوبك المحمول وحاوية cron.
الفروع تستخدم مسارات مختلفة فحسب. /pipe/cache/main، /pipe/cache/feat-x، /pipe/cache/PR-742. لا شيء يجب إبطاله. لا شيء يجب طرده. حين يموت الفرع، يتوقّف مساره عن كونه مطلوباً وذاك هو كامل دورة الحياة.
على عبء عمل حقيقي — node_modules بحجم 800 ميجابايت تقريباً، عشرون عامل اختبار متوازياً، مئة تشغيل CI يومياً — معظم الفاتورة خروج، لا تخزين.
خروج 20×
كلٌّ من العشرين عاملاً يسحب الذاكرة من S3. عشرون تنزيلاً لملف tar بحجم 800 ميجابايت يساوي 16 جيجابايت خروج لكل تشغيل CI. الدلو نفسه هو الجزء السهل — الخروج هو ما يتراكم.
نقل 1×
المُنتِج يبثّ 800 ميجابايت مرّةً واحدة. الأنبوب يفرّع البايتات إلى كل العشرين مستقبِلاً في الطيران. نقل واحد عبر السلك، لا مضاعف لكل مستقبِل، لا فاتورة تخزين.
الأرقام توضيحيّة لذاكرة monorepo Node نموذجيّة. الوفر الفعلي يعتمد على حجم tar وتفرّع العمّال وسعر الخروج الذي يتقاضاه مزوّدك من منطقة الذاكرة. الشكل — خطّي مقابل ثابت بعدد العمّال — لا يتغيّر.
طبقة الذاكرة هي HTTP. كانت كذلك دائماً. لم نلاحظ فقط.
الذواكر لم تكن قطّ عن التخزين. كانت عن إيصال نفس البايتات إلى N عاملاً دون إعادة بناء. HTTP يفعل ذلك أصلاً — حالما تسمح لرابط بأن يتفرّع إلى عدد معروف من المستقبِلين. الدلو كان حلاً التفافياً للتفرّع الذي لم يكن لدينا.
PUT واحد، n GETs، بايتات متطابقة. الضغط العكسي لكل مستقبِل فلا يُبطئ العامل البطيء السريعين.
PUT /pipe/cache?n=20الأنبوب يحتفظ بالبايتات للنقل وينساها عند انتهائه. لا شيء يجب طرده، لا دورة حياة، لا نسخ احتياطيّة.
TTL ≤ 5 دقائق · ثم يُطردكل فرع يختار مساره. لا فضاء مفاتيح مشترك، لا تصادمات. المسار هو مفتاح الذاكرة والرابط في آن.
/pipe/cache/[branch]معظم ذواكر CI تحلّ المشكلة نفسها: إيصال نفس tar إلى N عاملاً. تفعل ذلك عبر التخزين والخروج. الأنبوب يفعله عبر السلك.
أمرا curl. رابط واحد. عشرون عاملاً مُتغذِّياً.