بتذكرها زي كأني شايفها مبارح. الساعة كانت 3 الفجر، والتلفون برنّ بإلحاح. على الطرف الثاني كان صوت مدير المنتج، واضح عليه التوتر والقلق: “أبو عمر، وين التقرير اليومي؟ المستثمرين بستنوا الأرقام والسيستم كله واقف!”.
قلبي وقع في رجليّ. التقرير هاد كان خلاصة شغل يوم كامل من جمع بيانات من مصادر مختلفة، تنظيفها، تشغيل نماذج تعلم آلة عليها، وبعدين توليد ملخصات ورسوم بيانية. كل العملية كانت عبارة عن سلسلة من السكربتات (Scripts) اللي بتشتغل ورا بعضها باستخدام “Cron Jobs” على سيرفر لينكس. قطعة بتسلم قطعة، زي قطع الدومينو المرصوصة جنب بعض.
بعد فنجان قهوة سريع وكمية شتايم تحت النفس، بلشت رحلة التحقيق. اكتشفت بعد ساعة من البحث في سجلات (logs) متفرقة إنه أول سكربت في السلسلة، المسؤول عن سحب البيانات من أحد الـ APIs، فشل بسبب مشكلة مؤقتة في الشبكة. لكن لأنه ما في نظام تنبيه ذكي، السكربت اللي بعده اشتغل على بيانات قديمة وفارغة، واللي بعده أنتج نموذجًا مشوهًا، وهكذا… كل قطعة دومينو أسقطت اللي بعدها في سلسلة من الفشل الصامت، وما حدا عرف بالمصيبة إلا لما التقرير النهائي ما وصل. يومها، قضيت الصبحية كلها وأنا بعيد تشغيل كل خطوة يدويًا، وبتعهد لنفسي: “خلص، لازم نلاقي حل لهالمسخرة”.
هذه القصة المؤلمة، التي أعتقد أن الكثير منكم مر بنسخة مشابهة لها، هي التي دفعتنا لتبني ما يسمى بـ “منسق سير العمل” أو الـ Workflow Orchestrator. واليوم، سأشارككم هذه الرحلة بالتفصيل.
لماذا كانت طريقتنا القديمة “دومينو” على وشك الانهيار؟
لفهم حجم المشكلة، دعونا نحلل البنية القديمة التي كنا نعتمد عليها. كانت ببساطة مجموعة من السكربتات المستقلة التي يتم استدعاؤها بشكل متسلسل.
المشكلة الأولى: الاعتماد الأعمى على Cron Jobs
الـ Cron Job أداة عظيمة وبسيطة لجدولة المهام. لكنها، بالعامية، “بنت حلال بس على قدها”. هي لا تعرف شيئًا عن المهام الأخرى. هي فقط تنفذ الأمر في الوقت المحدد وتذهب في حال سبيلها. هذا يعني:
- لا يوجد إدارة للتبعيات (Dependency Management): لا توجد طريقة سهلة لقول “لا تشغل المهمة B إلا إذا نجحت المهمة A”. كنا نتحايل على هذا باستخدام `&&` في سطر الأوامر، وهو حل هش للغاية.
- لا يوجد إعادة محاولة تلقائية (Automatic Retries): إذا فشلت المهمة بسبب مشكلة مؤقتة (مثل مشكلة الشبكة في قصتي)، فإنها تفشل وانتهى. لا توجد آلية مدمجة لإعادة المحاولة بذكاء.
- مراقبة وتسجيل متفرق (Scattered Monitoring & Logging): كل سكربت يسجل مخرجاته في ملف منفصل. لمعرفة ما حدث، كان عليّ فتح 5 ملفات سجل مختلفة وتجميع القصة بنفسي. كأنك تحاول حل جريمة بجمع أدلة من خمس غرف مختلفة.
- غياب التنبيهات الذكية (Lack of Smart Alerting): لن يخبرك Cron إذا فشلت مهمتك. عليك أن تبني هذه الآلية بنفسك من الصفر لكل مهمة.
المشكلة الثانية: جحيم الفشل المتتالي (Cascading Failures)
عندما تفشل قطعة واحدة في نظام الدومينو، فإنها تسقط القطعة التي تليها. في عالم البيانات، هذا يعني أن خطأ صغيرًا في البداية يمكن أن يتضخم ليصبح كارثة في النهاية. البيانات الخاطئة التي تمر من المرحلة الأولى تؤدي إلى تحليل خاطئ في المرحلة الثانية، والذي بدوره يؤدي إلى نموذج تعلم آلة غير دقيق في المرحلة الثالثة، وينتهي بتقرير مضلل تمامًا في المرحلة الأخيرة. والأسوأ أن النظام بأكمله قد يبدو أنه “يعمل” بينما هو في الواقع ينتج “قمامة”.
“الأنظمة المبنية على تسلسل هش من السكربتات ليست مسألة ‘إذا’ ستفشل، بل ‘متى’ ستفشل، وكيف ستكتشف ذلك بعد فوات الأوان.” – من دفتر ملاحظات أبو عمر
الحل: الـ “مايسترو” أو منسق سير العمل (Workflow Orchestrator)
تخيل أن عملياتك لم تعد قطع دومينو، بل أصبحت أوركسترا موسيقية. كل سكربت أو مهمة هي عازف (كمان، بيانو، طبل…). ولكي تقدم الأوركسترا معزوفة جميلة ومتناغمة، تحتاج إلى “مايسترو”. هذا المايسترو هو تمامًا ما يفعله منسق سير العمل.
منسق سير العمل هو نظام برمجي متخصص في تعريف، جدولة، ومراقبة سير العمليات المعقدة (Workflows). هو لا يقوم بالعمل بنفسه، بل يوجه “العازفين” (السكربتات والمهام الخاصة بك) ويضمن أن كل شيء يسير وفقًا للنوتة الموسيقية (الخطة التي وضعتها).
ما هي القوة الخارقة التي يمنحك إياها المنسق؟
- إدارة التبعيات المرئية: يمكنك تحديد العلاقات المعقدة بين المهام بسهولة. “شغّل المهمة C و D معًا بعد نجاح A، ثم بعد انتهائهما، شغّل المهمة E”.
- مراقبة مركزية: واجهة مستخدم رسومية جميلة تريك حالة جميع عملياتك في مكان واحد. أي المهام تعمل الآن؟ أيها نجحت؟ أيها فشلت؟ كل شيء أمام عينيك.
- التعامل الذكي مع الفشل: يمكنك تكوين أي مهمة لإعادة المحاولة تلقائيًا 3 مرات مع فاصل زمني مدته 5 دقائق بين كل محاولة. وإذا فشلت كل المحاولات، يرسل لك تنبيهًا عبر البريد الإلكتروني أو Slack.
- سجلات مركزية (Centralized Logs): وداعًا للبحث في عشرات الملفات. يمكنك عرض سجلات أي مهمة من أي وقت بنقرة زر واحدة من الواجهة المركزية.
- إعادة التشغيل والـ Backfilling: هل فشلت مهمة في منتصف الليل؟ يمكنك بسهولة إعادة تشغيلها هي وكل المهام التي تليها. هل تريد إعادة معالجة بيانات الأسبوع الماضي؟ يمكنك ذلك بسهولة من خلال خاصية الـ Backfilling.
بطل قصتنا: Apache Airflow
هناك العديد من أدوات تنسيق سير العمل الرائعة مثل Prefect و Dagster و Mage، ولكن الأداة التي بدأنا بها رحلتنا والتي لا تزال واحدة من أكثر الأدوات شعبية في هذا المجال هي Apache Airflow. Airflow هو مشروع مفتوح المصدر بدأ في Airbnb وأصبح الآن جزءًا من مؤسسة Apache.
أجمل ما في Airflow هو أنك تحدد سير عملك كـ “كود” بايثون. هذا يمنحك قوة ومرونة هائلة.
المفاهيم الأساسية في Airflow
قبل أن نرى الكود، دعونا نفهم ثلاث مصطلحات رئيسية:
- DAG (Directed Acyclic Graph): هذا هو المصطلح الفاخر لـ “سير العمل”. إنه ببساطة تعريف لكل المهام في عمليتك وكيفية ارتباطها ببعضها البعض. هو “مخطط” يوضح تدفق العمل من البداية إلى النهاية بدون حلقات لا نهائية (Acyclic).
- Operator: هو قالب لمهمة معينة. تريد تشغيل أمر Bash؟ استخدم `BashOperator`. تريد تشغيل دالة بايثون؟ استخدم `PythonOperator`. تريد الاتصال بقاعدة بيانات؟ هناك `PostgresOperator`. الأوبريتور هو “نوع” المهمة.
- Task: هي نسخة محددة من الأوبريتور داخل الـ DAG الخاص بك. على سبيل المثال، مهمة “جلب_البيانات” هي Task تستخدم `BashOperator`.
مثال عملي: لنحول قصة “الدومينو” إلى DAG في Airflow
تذكرون سيناريو التقرير اليومي؟ (جلب البيانات، تنظيف، إنشاء تقرير، إرسال إشعار). هكذا سيبدو في Airflow باستخدام كود بايثون بسيط:
# اسم الملف: daily_report_dag.py
# يجب وضع هذا الملف في مجلد الـ dags الخاص بـ Airflow
from airflow import DAG
from airflow.operators.bash import BashOperator
from datetime import datetime, timedelta
# تعريف الإعدادات الافتراضية التي ستطبق على كل المهام
default_args = {
'owner': 'abu_omar',
'depends_on_past': False,
'email_on_failure': ['abu.omar@example.com'], # أرسل لي إيميل لو خربت الدنيا
'email_on_retry': False,
'retries': 2, # حاول مرتين قبل أن تستسلم
'retry_delay': timedelta(minutes=5), # انتظر 5 دقائق بين المحاولات
}
# تعريف الـ DAG نفسه
with DAG(
dag_id='daily_report_orchestra', # اسم الأوركسترا
default_args=default_args,
description='سير العمل اليومي لإنشاء التقارير الهامة',
schedule_interval='@daily', # شغّل هذا يوميًا
start_date=datetime(2023, 1, 1),
catchup=False, # لا تقم بتشغيل المهام الفائتة من الماضي
tags=['reporting', 'critical'],
) as dag:
# المهمة الأولى (العازف الأول): جلب البيانات
fetch_data = BashOperator(
task_id='fetch_data',
bash_command='python /path/to/my/scripts/fetch_data.py', # استدعاء السكربت الفعلي
)
# المهمة الثانية: تنظيف البيانات
clean_data = BashOperator(
task_id='clean_data',
bash_command='python /path/to/my/scripts/clean_data.py',
)
# المهمة الثالثة: إنشاء التقرير
generate_report = BashOperator(
task_id='generate_report',
bash_command='python /path/to/my/scripts/generate_report.py',
)
# المهمة الرابعة: إرسال إشعار بالنجاح
send_notification = BashOperator(
task_id='send_notification',
bash_command='echo "التقرير اليومي جاهز يا جماعة الخير!"',
)
# الآن، دور المايسترو: تحديد ترتيب العزف (التبعيات)
# هذا السطر هو قلب Airflow النابض
fetch_data >> clean_data >> generate_report >> send_notification
لاحظ جمال وبساطة السطر الأخير: fetch_data >> clean_data >> generate_report >> send_notification. بهذه العلامتين البسيطتين >>، قمنا بتعريف علاقة تبعية واضحة وصريحة. الآن Airflow يعرف بالضبط الترتيب، ولن يقوم بتشغيل `clean_data` إلا بعد النجاح المؤكد لـ `fetch_data`.
والأجمل؟ كل الإعدادات الافتراضية التي وضعناها، مثل إعادة المحاولة والتنبيه بالفشل، أصبحت الآن مطبقة على كل المهام. لقد بنينا نظامًا قويًا ومقاومًا للأخطاء في بضع عشرات من أسطر الكود.
نصائح من “أبو عمر”: كيف تروض وحش الأتمتة؟
بعد سنوات من العمل مع هذه الأدوات، جمعت بعض النصائح التي أتمنى لو أخبرني بها أحدهم في البداية.
نصيحة 1: ابدأ بسيطًا، يا خال
لا تحاول تحويل كل عملياتك إلى Airflow في يوم واحد. هذا طريق مؤكد للفشل. اختر عملية واحدة، أكثر عملية تسبب لك الألم والمشاكل (مثل قصة تقريري الليلي)، وقم بأتمتتها بشكل صحيح. النجاح في هذه العملية سيعطيك الدفعة المعنوية والخبرة للانتقال للعمليات التالية.
نصيحة 2: اجعل مهامك “ذرية” و “متكررة” (Atomic and Idempotent)
- ذرية (Atomic): كل مهمة يجب أن تفعل شيئًا واحدًا فقط وبشكل جيد. لا تضع جلب البيانات وتنظيفها في مهمة واحدة. فصلها يجعل اكتشاف الأخطاء وإعادة تشغيل جزء معين من العملية أسهل بكثير.
- متكررة (Idempotent): هذه كلمة معقدة لفكرة بسيطة. يجب أن تكون مهمتك مصممة بحيث لو قمت بتشغيلها 10 مرات بنفس المدخلات، تحصل على نفس النتيجة النهائية. هذا أمر بالغ الأهمية لخاصية إعادة المحاولة (Retries). على سبيل المثال، بدلًا من `INSERT` في قاعدة البيانات، استخدم `UPSERT` (UPDATE or INSERT).
نصيحة 3: لا تثق بأي شيء، وتحقق من كل شيء
حتى مع وجود منسق سير العمل، لا تفترض أن البيانات القادمة من نظام آخر صحيحة دائمًا. أضف مهامًا للتحقق من جودة البيانات (Data Quality Checks) كجزء من الـ DAG الخاص بك. على سبيل المثال، أضف مهمة بعد `fetch_data` تتحقق من أن عدد السجلات المستلمة ضمن النطاق المتوقع. إذا لم يكن كذلك، يمكن للمهمة أن تفشل وتوقف الـ DAG بأكمله قبل أن يسبب كارثة.
الخلاصة: من قطع الدومينو إلى الأوركسترا المتناغمة 🎼
التحول من سكربتات Cron الهشة إلى منسق سير عمل مثل Airflow لم يكن مجرد تحديث تقني، بل كان نقلة نوعية في طريقة تفكيرنا في بناء الأنظمة. لقد انتقلنا من حالة القلق الدائم والخوف من الفشل الصامت، إلى حالة من الثقة والوضوح.
اليوم، عندما تفشل مهمة في الساعة 3 فجرًا، لا يرن هاتفي. Airflow يحاول إصلاحها بنفسه، وإذا لم يستطع، يرسل لي بريدًا إلكترونيًا مفصلاً مع سجلات الخطأ لأقرأه مع قهوة الصباح. هذا هو الفرق بين العمل “في” النظام والعمل “على” النظام.
نصيحتي الأخيرة لك: إذا كنت لا تزال تدير عملياتك المهمة بسلسلة من قطع الدومينو، فقد حان الوقت لتعيين “مايسترو”. ابدأ اليوم في استكشاف عالم منسقي سير العمل. قد تكون رحلة التعلم في البداية، لكن “راحة البال” التي ستحصل عليها لا تقدر بثمن. صدقني، نومك في الليل سيشكرك على ذلك. 😉