الوحدة 6 — التجسيد والحداثة
عرّفنا في الوحدة 3 التجسيد بأنّه ما يجعل المخزن المتّصل يعكس محتوى المخزن غير المتّصل. هذه الوحدة تدخل في تشغيله الفعليّ: بأيّ وتيرة، بأيّ محرّك، كيف نُقيّم فعّاليته، وكم يُكلّف.
دفعة وتدفّق: نمطان يتعايشان
يوجد نمطان أساسيّان للتجسيد، وينبغي الاعتراف بأنّهما مختلفان لا متبادلان.
التجسيد بالدفعات. المهمّة الأكثر شيوعًا: كلّ ساعة أو كلّ ليلة، شغّل استعلام Spark أو SQL على المخزن غير المتّصل، احسب آخر قيم الوصفة لكلّ كيان، اكت بها في المخزن المتّصل. الوصفات على 24h و7d تحتمل هذا النمط بلا فقدان معنى، لأنّ متغيّرها لا يتغيّر بسرعة أعلى.
التجسيد بالتدفّق. عمليّة Flink أو Spark Streaming تشترك في موضوع Kafka، تستقبل الأحداث لحظةً بلحظة، تُحدّث حالة التجميع في الذاكرة، وتكتب النتيجة في المخزن المتّصل. لا تنتظر جدولة، فتُحقّق حداثة تحت الدقيقة. مطلوب للعدّاد count_1h عند نقطة البيع، حيث تأخير 15 دقيقة يكفي لإفلات محتال منظّم.
في المشروع سنُشغّل الاثنين: تدفّق لعدّادات النافذة القصيرة، ودفعة ليليّة للمتوسّطات على 7 أيّام.
أمر التجسيد في Feast
في Feast الاستدعاء نفسه بسيط:
feast materialize-incremental $(date -Iseconds)
يُشغّل هذا: «لكلّ وصفة، خذ الصفوف من end_date السابق (المُخزَّن في السجلّ) إلى الآن، احسبها، اكتبها في المخزن المتّصل». الإضافة «تدريجيّة» مهمّة: لا يعيد الحساب من الصفر كلّ مرّة، بل يواصل من حيث توقّف. عمليًّا هذا الأمر يُوضَع في cron أو Airflow.
المرّة الأولى، تُشغَّل feast materialize <start_date> <end_date> مع فترة قد تمتدّ إلى ستّة أشهر. تُملأ حالة أوّليّة للمخزن المتّصل قبل الانتقال إلى الوضع التدريجيّ.
قياس الحداثة
الحداثة (freshness) لمتغيّر هي: بأيّ عمر أقصى يُقدَّم؟ تُقاس بفرق now() - materialized_at، وتُراقب لكلّ وصفة.
قواعد تُفهم عمليًّا:
- عدّاد يستعمله نموذج في مسار الاستدلال يجب أن تكون حداثته أقصر بكثير من TTL. TTL يُعرّف عمرًا مقبولًا؛ الحداثة تُخبر عن الوضع الحاليّ.
- تباعد فارق كبير بين حداثة وTTL (مثلًا حداثة متوسّط 6 دقائق مع TTL 90 دقيقة) يعني هامش أمان صحّيّ. تقارب الاثنين إنذار: أيّ تأخّر تجسيد سيُنتج قيمًا بائدة.
- قياس الحداثة على المخزن المتّصل مباشرة (لا على الجدولة)، لأنّ مهمّة تعطّلت لا يكفي أن نعرف موعدها المفترض.
المخازن الحديثة تنشر مقاييس Prometheus جاهزة على الحداثة لكلّ وصفة. الوحدة 9 تُبيّن كيف نبني تنبيهًا مفيدًا فوقها.
تكلفة التجسيد
التجسيد اليوميّ لألف وصفة على مليار صفّ ليس مجّانيًّا. البنود الأساسيّة:
- الحوسبة: Spark أو BigQuery لتشغيل التجميعات على المخزن غير المتّصل.
- الكتابة: كلّ عمليّة كتابة إلى Redis أو DynamoDB لها ثمن. مليون كتابة/ساعة تُكلّف محسوسًا.
- التخزين المتّصل: Redis في الذاكرة، الغيغابايت غالٍ. DynamoDB في القرص، أرخص لكن مع تأخّر أعلى.
قرارات تخفيض شائعة:
- حساب فارقيّ: تحديث فقط الكيانات التي تغيّرت منذ آخر تجسيد، بدل إعادة كتابة كلّ شيء.
- تجسيد كسول: بعض المتغيّرات نادرة الاستعمال تُحسَب فقط عند الطلب (
on-demand feature view). - تجميع الكتابات: كتابة عدّة وصفات لكيان واحد في عمليّة Redis واحدة (pipeline).
معالجة الفشل والاستئناف
مهمّة تجسيد يمكن أن تفشل: تعطّل Spark، تشبّع Redis، خطأ في مخطّط. البنية الصحيحة تُدير هذا:
- ذرّيّة على مستوى الكيان: كتابة كيان واحد تنجح أو تفشل، لا وضع نصفيّ.
- تسجيل الفاصل الزمنيّ الأخير المكتمل:
end_dateيُحدَّث بعد نجاح الكتابة فقط. الاستئناف يعرف من أين يبدأ. - التنبيه فوق العتبة: إذا لم تجرِ عمليّة تجسيد في 30 دقيقة، تقرير حادثة قبل أن يشتكي العملاء.
متغيّرات عند الطلب: خارج التجسيد
بعض المتغيّرات لا يستحقّ تجسيدها لأنّ حسابها يعتمد على مدخلات الطلب. مثال: المسافة الجغرافيّة بين موقع نقطة البيع الحاليّة وموقع آخر شراء للعميل.
في Feast، on_demand_feature_view يُصرّح دالّة تُنفَّذ فقط عند الاستعلام:
@on_demand_feature_view(
sources=[transactions_source, customer_last_location],
schema=[Field(name="distance_km", dtype=Float32)],
)
def compute_distance(inputs):
return haversine(
inputs["current_lat"], inputs["current_lon"],
inputs["last_lat"], inputs["last_lon"],
)
هذه الدالة تعمل داخل خادم المتغيّرات، فتضمن نفس التعريف بين التدريب (تُنفَّذ على الجدول الكامل) والاستدلال (تُنفَّذ على الطلب). لا تجسيد، ولا فجوة بين تنفيذَين مختلفَين.
انتقال متغيّر من نضارة 60 دقيقة إلى 5 دقائق يبدو تحسينًا 12×؛ في الواقع يُضاعف كلفة الحوسبة والكتابة عدّة مرّات، وقد يقلب اختيار التكنولوجيا (من Airflow إلى Flink). قبل تخفيض النضارة، اسأل: هل تُغيّر قرار النموذج فعلًا؟ إن لم تكن الإجابة نعم موثّقة، أبقِ النضارة.
الخلاصة
- التجسيد بالدفعات يكفي للمتغيّرات المستقرّة؛ التجسيد بالتدفّق لازم للعدّادات القصيرة.
- Feast يعرض
materialize-incrementalكأداة أساسيّة، ويحتفظ بمؤشّر الفاصل ليكون الاستئناف آمنًا. - الحداثة تُقاس على المخزن المتّصل مباشرة، ولا يكفي جدول التجسيد.
- المتغيّرات عند الطلب تُلغي حاجة التجسيد حين يعتمد الحساب على مدخلات الطلب، وتحفظ التطابق بين التدريب والخدمة.
الوحدة التالية: Feast عمليًّا، من apply إلى get_online_features، على قاعدة بيانات مشروعنا.