بنية المصفوفة والتوازي في GitHub Actions
بنية المصفوفة والتوازي في GitHub Actions
في شركات مثل Google وMeta وAmazon، تختبر خطوط CI الكود ذاته مقابل عشرات إصدارات Node.js وثلاثة أنظمة تشغيل وعلامتَي بناء مختلفتين — وذلك بالتوازي. يُتيح GitHub Actions هذا من خلال استراتيجية المصفوفة: تعريف وظيفة واحدة يتوسّع وقت التشغيل إلى مجموعة من الوظائف المتوازية. إتقان بنية المصفوفة هو الفارق بين خط أنابيب يستغرق 40 دقيقة وآخر يُنجز في 6 دقائق.
ما هي استراتيجية المصفوفة؟
المصفوفة هي خريطة متغيرات تُعرَّف تحت strategy.matrix. يحسب GitHub Actions الضرب الديكارتي لكل أبعاد المصفوفة وينشئ وظيفة واحدة لكل تركيبة. تستقبل كل وظيفة قيم صفها عبر سياق matrix — مثل ${{ matrix.os }} أو ${{ matrix.node }}.
المثال التالي يُنشئ ست وظائف متوازية (3 أنظمة تشغيل × إصداران من Node):
os مع كل عنصر من node. ثلاثة أنظمة تشغيل وإصداران = 3 × 2 = 6 وظائف متزامنة. إضافة بُعد ثالث من 4 علامات بناء ستُنتج 3 × 2 × 4 = 24 وظيفة. راعِ حدود الاستخدام (256 وظيفة كحد أقصى للمصفوفة).
الإدراج والاستثناء (includes & excludes)
نادرًا ما يكون الضرب الديكارتي هو ما تريده بالضبط. يوفر GitHub Actions خيارَين للتحكم:
include— يُضيف وظائف إضافية أو يُلحق متغيرات إضافية بتركيبات قائمة.exclude— يحذف تركيبات محددة من الناتج.
يمكن لـ include إلحاق أي مفتاح إضافي بالتركيبة — كـ experimental: true هنا — ثم تستخدمه في تعبيرات مثل continue-on-error. هذا نمط قياسي في كبرى شركات التقنية للسماح لبنايات الكاناري بالفشل دون حجب حالة الوظيفة الكلية.
fail-fast: إيقاف سريع عند الفشل
بشكل افتراضي، يُضبط fail-fast: true على كل مصفوفة. بمجرد فشل أي تركيبة، يُلغي GitHub Actions جميع الوظائف الجارية. هذا هو الإعداد الصحيح لحلقات التغذية الراجعة للمطورين — تريد أن تعرف بسرعة أن شيئًا ما معطوب بدلًا من انتظار 23 وظيفة من أصل 24.
اضبط fail-fast: false حين تحتاج الصورة الكاملة — مثلًا في خطوط أنابيب الإصدار التي تختبر كل البيئات المدعومة وتحتاج إلى تقرير كامل بالإخفاقات قبل إصدار النسخة.
fail-fast: true في CI للفروع الميزاتية (تغذية راجعة سريعة) وfail-fast: false في بنايات الإصدار والنوكتورنية (تغطية انحدار كاملة).
مجموعات التزامن (Concurrency Groups)
التوازي داخل سير العمل رائع — لكنك تحتاج أيضًا للتحكم في التوازي عبر تشغيلات سير العمل. بدون التحكم في التزامن، يُشغّل كل ضغط على PR تشغيلًا جديدًا، وتتراكم عشر تشغيلات قديمة تستهلك الدقائق بلا فائدة.
يُعرّف مفتاح concurrency مجموعة. يضمن GitHub Actions أن تشغيلًا واحدًا فقط لكل مجموعة يكون نشطًا في وقت واحد:
مع cancel-in-progress: true، بمجرد وصول ضغطة جديدة على الفرع ذاته، تُلغى كل الوظائف الجارية في المجموعة. هذه هي أكبر وفورات الوقت في CI الحقيقي وتُستخدم بشكل شامل في الشركات ذات تكرار الضغط العالي.
cancel-in-progress: false (أو استخدم مجموعة تزامن منفصلة) للوظائف التي تكتب على بنية التحتية الإنتاجية. إلغاء نشر في منتصفه قد يترك الموارد في حالة تحديث جزئي. النمط الشائع هو group: deploy-${{ github.ref }}-${{ github.sha }} مع cancel-in-progress: false لترتيب عمليات النشر في قائمة دون إلغائها.
كيف يتكامل كل شيء معًا
يُظهر الرسم التالي تشغيل CI نموذجي بمصفوفة: سير عمل واحد يُشغّل ست وظائف اختبار متوازية (3 أنظمة تشغيل × إصداران)، كلها مُقيَّدة بمجموعة تزامن تُلغي التشغيلات القديمة عند الضغطات الجديدة.
max-parallel: ضبط سقف التوازي
أحيانًا لا تريد تشغيل كل التركيبات دفعة واحدة — مثلًا إذا كانت كل وظيفة تضغط على قاعدة بيانات تجهيزية مشتركة أو API خارجي محدود المعدل. استخدم max-parallel للحدّ من التزامن:
مع سبع بيئات وmax-parallel: 4، تبدأ الأربع الأولى فورًا؛ الثلاث الباقية تُوضع في قائمة انتظار وتبدأ مع تحرر الفتحات. هذا أمر بالغ الأهمية لاختبارات التكامل التي تشترك في البنية التحتية.
أنماط إنتاجية حقيقية
- أبقِ أبعاد المصفوفة صغيرة. مصفوفة 5 × 5 × 5 = 125 وظيفة متزامنة تستنفد حصص معظم المؤسسات. اختصر إلى الحد الأدنى الذي يكتشف الأخطاء فعلًا: عادةً نظامَا تشغيل وإصداران من بيئة التشغيل.
- ثبّت إصدارات العدّائين.
ubuntu-latestيتغير بصمت. في خطوط الإنتاج، ثبّت علىubuntu-24.04لتجنب مفاجآت تحديث صورة العدّاء وسط السباق. - احذر من
outputsلوظائف المصفوفة. يبقى فقط ناتج آخر وظيفة تنتهي. اجمع النتائج عبر أثر (artifact) أو وظيفة لاحقة بـneeds. - التزامن على الفرع الرئيسي. لا تضبط
cancel-in-progress: trueعلى ضغطاتmainأوmasterأبدًا. إلغاء بناية الفرع الرئيسي في المنتصف يُفسد خطوط النشر. خصّص الإلغاء للفروع الميزاتية فقط.