Skip to main content
يجب أن يكون تجسيد materialized_view عبارة عن استعلام SELECT من table (source) موجودة. وعلى خلاف PostgreSQL، فإن عرض مادي في ClickHouse ليس “ثابتًا” (ولا توجد له عملية REFRESH مقابلة). وبدلًا من ذلك، فهو يعمل بوصفه مشغّل إدراج، إذ يُدرِج rows جديدة في target table عبر تطبيق تحويل SELECT المحدد على rows المُدرجة في source table. راجع توثيق عرض مادي في ClickHouse لمزيد من التفاصيل حول كيفية عمل العروض المادية في ClickHouse.
للاطلاع على مفاهيم التجسيد العامة وconfig المشتركة (مثل engine وorder_by وpartition_by وما إلى ذلك)، راجع صفحة التجسيدات.

كيفية إدارة الجدول الهدف

عند استخدام تجسيد ‏materialized_view، يحتاج dbt-clickhouse إلى إنشاء كلٍّ من عرض مادي وجدول هدف تُدرج فيه الصفوف المُحوَّلة. توجد طريقتان لإدارة الجدول الهدف: يؤثر النهج الذي تختاره في كيفية التعامل مع تغييرات المخطط، وعمليات التحديث الكامل، وإعدادات MV المتعددة. وتصف الأقسام التالية كل نهج بالتفصيل.

التجسيد مع الجدول الهدف الضمني

هذا هو السلوك الافتراضي. عند تعريف نموذج materialized_view، سيقوم المهايئ بما يلي:
  1. إنشاء الجدول الهدف باسم النموذج
  2. إنشاء ClickHouse عرض مادي بالاسم <model_name>_mv
يُستنتج مخطط الجدول الهدف من الأعمدة الموجودة في عبارة SELECT الخاصة بـ MV. وتشترك جميع الموارد (الجدول الهدف + MVs) في تهيئة النموذج نفسها.
راجع ملف الاختبار للاطلاع على مزيد من الأمثلة.
يمكنك أيضًا تحديد codec وttl على مستوى العمود في الجدول الهدف من خلال فرض عقد على النموذج. راجع إعدادات الأعمدة لمزيد من التفاصيل.

عدة عروض مادية

يتيح ClickHouse وجود أكثر من عرض مادي واحد يكتب السجلات إلى جدول الهدف نفسه. ولدعم ذلك في dbt-clickhouse باستخدام نهج الهدف الضمني، يمكنك إنشاء UNION في ملف النموذج، مع إحاطة SQL الخاصة بكل عرض مادي بتعليقات بالصيغة --my_mv_name:begin و --my_mv_name:end. على سبيل المثال، سيؤدي ما يلي إلى إنشاء عرضين ماديين يكتبان البيانات إلى جدول الوجهة نفسه الخاص بالنموذج. وستكون أسماء العروض المادية بالصيغة <model_name>_mv1 و <model_name>_mv2:
عند تحديث model يتضمّن عدة عروض مادية ‏(MVs)، وخصوصًا عند إعادة تسمية أحد أسماء الـ MV، لا يقوم dbt-clickhouse تلقائيًا بحذف الـ MV القديم. وبدلًا من ذلك، سيظهر لك التحذير التالي:Warning - Table <previous table name> was detected with the same pattern as model name <your model name> but was not found in this run. In case it is a renamed mv that was previously part of this model, drop it manually (!!!)

كيفية تطوير مخطط الجدول الهدف

بدءًا من الإصدار 1.9.8 من dbt-clickhouse، يمكنك التحكّم في كيفية تطوير مخطط الجدول الهدف عندما يواجه dbt run أعمدة مختلفة في SQL الخاص بـ MV.
افتراضيًا، لن يطبّق dbt أي تغييرات على الجدول الهدف (قيمة الإعداد ignore)، لكن يمكنك تغيير هذا الإعداد ليعمل وفق السلوك نفسه لإعداد on_schema_change في النماذج التزايدية. ويمكنك أيضًا استخدام هذا الإعداد كآلية أمان. فإذا ضبطته على fail، فستفشل عملية البناء إذا كانت الأعمدة في SQL الخاص بـ MV تختلف عن أعمدة الجدول الهدف الذي أُنشئ عند أول dbt run.

استكمال البيانات

افتراضيًا، عند إنشاء عرض مادي (MV) أو إعادة إنشائه، يُملأ الجدول الهدف أولًا بالبيانات التاريخية قبل إنشاء الـ MV نفسه (catchup=True). يمكنك تعطيل هذا السلوك بضبط إعداد catchup على False.
مخاطر فقدان البيانات مع التحديث الكاملسيؤدي استخدام catchup: False مع dbt run --full-refresh إلى حذف جميع البيانات الحالية في الجدول الهدف. سيُعاد إنشاء الجدول فارغًا ولن يلتقط بعد ذلك سوى البيانات الجديدة. تأكد من توفّر نسخ احتياطية إذا كانت البيانات التاريخية قد تكون مطلوبة لاحقًا.

التجسيد مع هدف محدد صراحةً (بيتا)

بيتاهذه الميزة في مرحلة بيتا، وهي متاحة بدءًا من الإصدار 1.10 من dbt-clickhouse. قد تتغير واجهة برمجة التطبيقات بناءً على ملاحظات المجتمع.
ينشئ dbt-clickhouse افتراضيًا ويدير كلًا من الجدول الهدف والعروض المادية ضمن نموذج واحد (وهو نهج الهدف الضمني الموضح أعلاه). لكن لهذا النهج بعض القيود:
  • تشترك جميع الموارد (الجدول الهدف + MVs) في الإعدادات نفسها. وإذا كانت عدة MVs تشير إلى الجدول الهدف نفسه، فيجب تعريفها معًا باستخدام صياغة UNION ALL.
  • لا يمكن التعامل مع أي من هذه الموارد بشكل منفصل، إذ يجب إدارة جميعها باستخدام ملف النموذج نفسه.
  • لا يمكنك بسهولة التحكم في اسم كل MV.
  • تتشارك جميع الإعدادات بين الجدول الهدف وMVs، مما يصعّب تهيئة كل مورد على حدة ويجعل من الصعب تحديد التهيئة التي تخص كل مورد.
تتيح لك ميزة الهدف المحدد صراحةً تعريف الجدول الهدف بشكل منفصل كتجسيد table عادي، ثم الرجوع إليه من نماذج العرض المادي لديك.

الفوائد

  • موارد منفصلة بالكامل: أصبح الآن بالإمكان تعريف كل مورد على حدة، مما يحسّن سهولة القراءة.
  • تطابق 1:1 بين موارد dbt وCH: يمكنك الآن استخدام أدوات dbt لإدارتها وتكرارها كلٌّ على حدة.
  • إتاحة تهيئات مختلفة: أصبح الآن بالإمكان تطبيق تهيئة مختلفة على كل مورد.
  • لم تعد هناك حاجة للالتزام باتفاقيات التسمية: تُنشأ الآن جميع الموارد باستخدام الاسم الذي تحدده لها، وليس الاسم المخصّص المضاف إليه اللاحقة _mv في MVs.

القيود

  • تعريف الجدول الهدف ليس نهجًا طبيعيًا في dbt: فهو ليس استعلام SQL يقرأ من جدول مصدر، لذا تفقد هنا عمليات التحقق الخاصة بـ dbt. سيظل SQL الخاص بـ MV خاضعًا للتحقق باستخدام أدوات dbt المساعدة، كما سيُتحقق من توافقه مع أعمدة الجدول الهدف على مستوى CH.
  • وجدنا بعض المشكلات المرتبطة بقيود الدالة ref(): نحتاج إلى استخدامها للإشارة إلى النماذج فيما بينها، لكن لا يمكن استخدامها إلا للإشارة إلى النماذج upstream، وليس downstream. وهذا يسبب بعض المشكلات في هذا التنفيذ. لقد أنشأنا issue في مستودع dbt-core، ونجري حاليًا نقاشًا معهم للبحث عن حلول ممكنة (dbt-labs/dbt-core#12319):
    • عند استدعاء ref() من داخل كتلة config، فإنها تُرجع النموذج الحالي، لا النموذج المُشار إليه. وهذا يمنعنا من تعريفه في قسم config()، ويجبرنا على استخدام comment لإضافة هذه التبعية. ونحن نتبع النمط نفسه الموضح في وثائق dbt باستخدام أسلوب “—depends_on:”.
    • تعمل ref() بالنسبة لنا لأنها تفرض إنشاء الجدول الهدف أولًا، ولكن في مخطط التبعيات في الوثائق المُولَّدة، سيُرسَم الجدول الهدف على أنه تبعية upstream أخرى، وليس downstream، مما يجعل فهمه أكثر صعوبة قليلًا.
    • يفرض unit-test علينا أيضًا تعريف بعض البيانات للجدول الهدف حتى عندما تكون الفكرة ألا نقرأ منه. والحل البديل هنا هو ببساطة ترك بيانات هذا الجدول فارغة.

الاستخدام

الخطوة 1: عرِّف جدول هدف كنموذج جدول عادي النموذج events_daily.sql:
هذا هو الحل الالتفافي الذي نذكره في قسم القيود. قد تفقد هنا بعض عمليات التحقق الخاصة بـ dbt، لكن سيظل المخطط خاضعًا للتحقق على مستوى ClickHouse. الخطوة 2: تعريف العروض المُجسَّدة التي تشير إلى جدول الهدف على سبيل المثال، يمكنك تعريف عدة MVs مختلفة في نماذج مختلفة كما يلي، ويمكن أن تشير حتى إلى جدول الهدف نفسه. لاحظ استدعاء الماكرو الجديد {{ materialization_target_table(ref('events_daily')) }}، الذي يضبط جدول الهدف لـ MV. النموذج page_events_aggregator.sql:
النموذج mobile_events_aggregator.sql:

خيارات الإعداد

عند استخدام جداول هدف صريحة، فبالإضافة إلى إعدادات التجسيد العامة والإعدادات الخاصة بالجدول، تنطبق الإعدادات التالية: على الجدول الهدف (materialized='table'): على العرض المادي (materialized='materialized_view'):
عادةً ما تحتاج فقط إلى تعيين catchup على True في MVs، أو تعيين repopulate_from_mvs_on_full_refresh على True في جداولها الهدف. وإذا عيّنت كليهما على True، فقد يؤدي ذلك إلى تكرار البيانات.

العمليات الشائعة

التحديث الكامل باستخدام الجداول الهدف المحددة صراحةً

عند استخدام --full-refresh، ستُعاد إنشاء الجداول الهدف المحددة صراحةً (لذا قد تفقد بعض البيانات إذا كان إدخال البيانات جارياً أثناء هذه العملية). وسيختلف ذلك بحسب إعدادات التكوين لديك: الخيار 1: السلوك الافتراضي لـ --full-refresh. ستُعاد إنشاء كل شيء، ولكن أثناء إعادة إنشاء MVs، سيكون الجدول الهدف فارغًا أو محمّلًا جزئيًا. سيُحذف كل شيء ثم يُعاد إنشاؤه. إذا كنت تريد إعادة إدراج البيانات باستخدام SQL الخاص بـ MVs، فأبقِ الإعداد catchup=True:
الخيار 2: أريد إعادة إنشاء الجدول الهدف ولا أريد أن أقرأ بيانات فارغة أثناء إعادة إنشاء MVs. إذا كنت بحاجة أولًا إلى تحديث SQL الخاص بـ MVs، فيمكنك ضبط catchup=False فيها ثم تنفيذ dbt run أو dbt run --full-refresh على MVs. تأكد من إنشاء MVs قبل تشغيل --full-refresh على نموذج الجدول الهدف، لأنه يستخدم تعريفات MV من ClickHouse. عيّن repopulate_from_mvs_on_full_refresh=True في نموذج الجدول الهدف. عند تنفيذ dbt run --full-refresh، سيؤدي ذلك إلى:
  1. إنشاء جدول مؤقت جديد
  2. تنفيذ INSERT-SELECT باستخدام SQL الخاص بكل MV
  3. تبديل الجدولين ذريًا
وبذلك لن تظهر بيانات فارغة في جدولك أثناء إعادة إنشاء MVs.

تغيير الجدول الهدف

لا يمكنك تغيير الجدول الهدف الخاص بـ MV بدون --full-refresh. إذا حاولت تشغيل أمر dbt run عاديًا بعد تغيير المرجع materialization_target_table()، فستفشل عملية البناء وستظهر رسالة خطأ تشير إلى أن الهدف قد تغيّر. لتغيير الهدف:
  1. حدِّث استدعاء materialization_target_table()
  2. شغِّل dbt run --full-refresh -s your_mv_model

استكشاف المشكلات الشائعة وإصلاحها

يكون الجدول الهدف فارغًا أثناء/بعد تنفيذ run

هناك عدة أسباب قد تؤدي إلى ذلك:
  • قد تكون العروض المادية مهيأة باستخدام catchup=False، أو قد يكون الجدول الهدف مهيأ باستخدام repopulate_from_mvs_on_full_refresh=False، ولذلك لا يتم تنفيذ تحميل البيانات التاريخية عند إنشاء العروض المادية أو عند إعادة إنشاء الجدول الهدف. هذا هو السلوك المتوقع، لذا إذا كنت تريد إعادة إدراج البيانات باستخدام SQL الخاص بالعروض المادية، فتأكد من ضبط catchup=True في العرض المادي (وهذه هي القيمة الافتراضية) أو repopulate_from_mvs_on_full_refresh=True في الجدول الهدف. وتأكد من عدم تفعيل الخيارين معًا في الوقت نفسه لتجنب التكرار. راجع قسم التهيئة لمزيد من التفاصيل.
  • أثناء تنفيذ dbt run --full-refresh، إذا كانت العروض المادية تستخدم القيمة الافتراضية catchup=True، فسيُعاد إنشاء الهدف، وستُعيد العروض المادية إدراج البيانات تباعًا. لتجنب هذا الوضع، راجع التحديث الكامل مع الأهداف الصريحة.

يستخدم dbt run --full-refresh على جدول الهدف مع repopulate_from_mvs_on_full_refresh=True المنطقَ المستمد من إصدارات العرض المادي القديمة، وليس من SQL الموجود حاليًا في المشروع

يستخدم repopulate_from_mvs_on_full_refresh=True SQL الخاص بـ MV الموجود والمُعرّف مسبقًا في ClickHouse. وللتأكد من استخدام تعريف العرض المادي الجديد، نفّذ dbt run لكل عرض مادي قبل تنفيذ dbt run --full-refresh على جدول الهدف.

توجد بيانات مكررة بعد تنفيذ عملية تشغيل

الأسباب المحتملة:
  • قد يكون كلٌّ من catchup=True على العروض المادية وrepopulate_from_mvs_on_full_refresh=True على الجدول الهدف مفعّلًا: أبقِ واحدًا منهما فقط بحسب العمليات التي تريد تشغيلها. راجع قسم الإعدادات لمزيد من التفاصيل.
  • لم يتم تعريف الجدول الهدف باستخدام WHERE 0: يجب إنشاء الجدول الهدف فارغًا، لكن قد يُدرج الاستعلام الداخلي بيانات إذا لم يتم تضمين WHERE 0. تأكد من تضمين العبارة.

فقدان البيانات أثناء الاستيعاب النشط بعد تنفيذ dbt run --full-refresh

تكون بعض الصفوف من الجدول المصدر مفقودة في الجدول الهدف بعد تنفيذ dbt run --full-refresh. تعمل العروض المادية في ClickHouse كمشغّلات insert — فهي لا تلتقط البيانات إلا أثناء وجودها. أثناء التحديث الكامل، توجد نافذة قصيرة يُزال فيها الـ MV ثم يُعاد إنشاؤه («النافذة العمياء»). وأي صفوف تُدرج في الجدول المصدر خلال هذه النافذة لن يتم التقاطها. راجع قسم السلوك أثناء الاستيعاب النشط لمزيد من التفاصيل.

تقنيات تصحيح الأخطاء

تحقّق من الوجهة الحالية لـ MV في ClickHouse

استعلم عن system.tables لمعرفة الجهة التي يكتب فيها العرض المادي:

تحقّق مما إذا كان dbt يتعرّف على جدول كجدول هدف لعرض مادي

أثناء تشغيل dbt، ابحث عن رسالة السجل التالية:
الجدول <table_name> يُستخدم كجدول هدف بواسطة عرض مادي يديره dbt. سيتم ضبط mv_on_schema_change تلقائيًا على “fail” لمنع فقدان البيانات.
إذا ظهرت هذه الرسالة، فهذا يعني أن dbt اكتشف أن الجدول مستهدَف بواسطة عرض مادي واحد على الأقل يديره dbt. وإذا كنت تتوقع ظهور هذه الرسالة لكنك لا تراها، فتحقق مما يلي:
  • يعرّف نموذج العرض المادي {{ materialization_target_table(ref('your_target')) }} بشكل صحيح
  • يتضمن نموذج العرض المادي materialized='materialized_view' في إعداداته
  • تم تشغيل كلٍّ من العرض المادي والجدول الهدف مرة واحدة على الأقل

الترحيل من الهدف الضمني إلى الهدف المعرّف صراحةً

إذا كانت لديك نماذج عرض مادي حالية تستخدم نهج الهدف الضمني وتريد الترحيل إلى نهج الهدف المعرّف صراحةً، فاتبع الخطوات التالية: 1. أنشئ نموذج الجدول الهدف أنشئ ملف نموذج جديدًا باستخدام materialized='table' يعرّف المخطط نفسه للجدول الهدف الحالي الخاص بـ MV. استخدم عبارة WHERE 0 لإنشاء جدول فارغ. استخدم الاسم نفسه لنموذج العرض المادي الضمني الحالي. ستتمكن الآن من استخدام هذا النموذج لإجراء تعديلات متكررة على الجدول الهدف.
2. حدِّث نماذج MV لديك أنشئ نماذج جديدة يتضمّن كلٌّ منها SQL الخاص بـ MV واستدعاء الماكرو materialization_target_table() للإشارة إلى الجدول الهدف الجديد. إذا كنت تستخدم UNION ALL سابقًا، فأزل ذلك الجزء والتعليقات. بالنسبة إلى أسماء النماذج، ستحتاج إلى اتباع اصطلاح التسمية التالي:
  • إذا كان هناك MV واحد فقط مُعرَّف، فسيكون اسمه: <old_model_name>_mv
  • إذا كانت هناك عدة MVs مُعرَّفة، فسيكون اسم كلٍّ منها: <old_model_name>_mv_<name_in_comments>
سابقًا في my_model.sql (الهدف الضمني، نموذج واحد مع UNION ALL):
بعد (الهدف المُصرَّح به، ملفات نماذج منفصلة):
3. كرّر هذه العملية حسب الحاجة باتباع الإرشادات الواردة في قسم الهدف المحدَّد صراحةً.

مقارنة السلوك بين نهجَي الهدف الضمني والهدف الصريح

كيفية عملها بشكل عام

السلوك أثناء الإدراج النشط

عند تكرار نماذجك، يجب أن تكون على دراية بكيفية تفاعل العمليات المختلفة مع البيانات التي يجري إدراجها:
  • بما أن العروض المادية في ClickHouse تعمل بوصفها مشغلات إدراج، فهي لا تلتقط البيانات إلا ما دامت موجودة. وإذا حُذف عرض مادي وأُعيد إنشاؤه (على سبيل المثال أثناء --full-refresh)، فلن تُعالَج أي صفوف أُدرجت في جدول المصدر خلال تلك الفترة بواسطة العرض المادي. ويُشار إلى ذلك بأن العرض المادي يكون “أعمى”.
  • تستند جميع عمليات catchup المختلفة إلى عمليات INSERT INTO ... SELECT باستخدام SQL الخاص بالعروض المادية، وهي مستقلة عن آلية عمل العروض المادية. وبمجرد بدء INSERT، فإنه لا يلتقط البيانات الجديدة، لكن العرض المادي المُرفَق سيلتقطها.
يلخّص الجدول التالي مدى أمان كل عملية عندما تحدث عمليات الإدراج بشكل نشط على جدول المصدر.

عمليات الهدف الضمني

عمليات الهدف المحدَّد صراحةً

نماذج العرض المادي: نموذج الجدول الهدف:
توصيات لبيئات الإنتاج ذات استيعاب البيانات النشط
  • أوقف استيعاب البيانات أثناء عمليات dbt إن أمكن: فهذا يجعل جميع العمليات آمنة ويمنع فقدان أي بيانات.
  • استخدم محركًا يزيل التكرار إن أمكن (مثل ReplacingMergeTree) في الجدول الهدف للتعامل مع التكرارات المحتملة الناتجة عن تداخلات الاستدراك.
  • فضّل ALTER TABLE ... MODIFY QUERY (أي dbt run العادي بدون --full-refresh) متى أمكن — فهذا آمن دائمًا.
  • انتبه إلى الفترات الحرجة أثناء عمليات dbt.

العروض المادية القابلة للتحديث

تُعد العروض المادية القابلة للتحديث نوعًا خاصًا من العروض المادية في ClickHouse، إذ تعيد تنفيذ الاستعلام دوريًا وتخزّن النتيجة، على غرار طريقة عمل العروض المادية في قواعد البيانات الأخرى. ويكون ذلك مفيدًا في الحالات التي تحتاج فيها إلى لقطات أو تجميعات دورية بدلًا من مشغّلات الإدراج الآنية.
يمكن استخدام العروض المادية القابلة للتحديث مع كلٍّ من نهج الهدف الضمني ونهج الهدف الصريح. ويكون إعداد refreshable مستقلًا عن طريقة إدارة الجدول الهدف.
لاستخدام عرض مادي قابل للتحديث، أضف كائن إعداد refreshable إلى نموذج MV لديك مع الخيارات التالية:

مثال على الوجهة الضمنية

مثال مع وجهة محددة صراحةً

القيود

  • عند إنشاء عرض مادي قابل للتحديث (MV) في ClickHouse وله تبعية، لا يُصدر ClickHouse خطأ إذا كانت التبعية المحددة غير موجودة وقت الإنشاء. وبدلًا من ذلك، يظل العرض المادي القابل للتحديث في حالة غير نشطة، منتظرًا استيفاء التبعية قبل أن يبدأ في معالجة التحديثات أو إجراء التحديث. هذا السلوك مقصود من حيث التصميم، لكنه قد يؤدي إلى تأخر توفر البيانات إذا لم تُعالَج التبعية المطلوبة بسرعة. لذا ينبغي التأكد من أن جميع التبعيات معرّفة بشكل صحيح وموجودة قبل إنشاء عرض مادي قابل للتحديث.
  • حتى الآن، لا يوجد “ربط dbt” فعلي بين mv وتبعياته، ولذلك لا يكون ترتيب الإنشاء مضمونًا.
  • لم تُختبَر ميزة التحديث مع عدة mvs تُوجَّه إلى النموذج الهدف نفسه.
آخر تعديل في ٣ يوليو ٢٠٢٦