الوحدة 8 — الخدمة بالدفعات والمتّصلة والتدفّقية
نموذجنا مُدرَّب، مُسجَّل، مُحزَّم. الآن السؤال العملي: كيف يصل التنبّؤ إلى مَن يحتاجه؟ الجواب ليس واحدًا. لكلّ استعمال شكل خدمة يناسبه، والاختيار يبدأ بسؤال واحد: كم من الوقت نستطيع الانتظار للحصول على التوقّع؟
ثلاثة أوضاع، ثلاثة أسئلة
نُميّز ثلاثة أوضاع رئيسية للخدمة:
- الخدمة بالدفعات (batch): نحسب التوقّعات مسبقًا لمجموعة كاملة (كلّ المشتركين، مثلًا) في تنفيذ دوريّ، ونخزّنها في قاعدة بيانات. عندما نحتاج التوقّع، نستعلم في القاعدة.
- الخدمة المتّصلة (online): API تستقبل الطلب، تحسب التوقّع فورًا، وتُرجعه. تُخصَّص لكلّ طلب حسبَ خصائصه اللحظيّة.
- الخدمة التدفّقية (streaming): نستقبل تيّار أحداث (كافكا، مثلًا)، نُحسب التوقّع لكلّ حدث فور وروده، ونُنتج تيّارًا آخر من التوقّعات.
السؤال المُوجِّه: الحدّ الأقصى المقبول لزمن الاستجابة؟
- إن كان يوم أو ساعة، الدفعات تكفي، وهي أرخص وأبسط.
- إن كان بضع مئات المللي ثانية، نحتاج خدمة متّصلة.
- إن كان أقلّ من 50 مللي ثانية مع معدّل عالٍ للأحداث، غالبًا التدفّق أو ذاكرة تخزين مؤقّت متّصلة أقرب.
الحالة الأمثل للدفعات
في مثال التسرّب لدينا، فريق التسويق يستعمل التوقّعات لتصميم حملات احتفاظ أسبوعية. لا يحتاج التوقّع في اللحظة نفسها لكلّ مشترك، بل جدولًا كاملًا صباح كلّ اثنين.
هذه الحالة المثالية للدفعات:
# scripts/batch_predict.py
import pandas as pd
import mlflow
model = mlflow.pyfunc.load_model("models:/churn-classifier@production")
subs = pd.read_sql("SELECT * FROM subscribers WHERE active = TRUE", conn)
subs["churn_score"] = model.predict(subs.drop(columns=["customer_id"]))
subs[["customer_id", "churn_score"]].to_sql("churn_predictions", conn, if_exists="replace")
يُشغَّل هذا النصّ كلّ اثنين في الساعة الثالثة صباحًا عبر Airflow أو cron مُدار. التطبيقات تقرأ من الجدول، لا تنادي أيّ API نموذجًا. فوائد الدفعات:
- رخص: نُشغّل حاسوبًا قويًّا لساعة أسبوعيًّا، لا خدمة تعمل باستمرار.
- بساطة: لا حاجة إلى موزّع أحمال ولا تحجيم آلي ولا نسخ متعدّدة.
- تشخيص أسهل: النتائج مُخزَّنة، يمكن استعراضها والتحقيق فيها لاحقًا.