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، سيقوم المهايئ بما يلي:
- إنشاء الجدول الهدف باسم النموذج
- إنشاء ClickHouse عرض مادي بالاسم
<model_name>_mv
SELECT الخاصة بـ MV. وتشترك جميع الموارد (الجدول الهدف + MVs) في تهيئة النموذج نفسها.
عدة عروض مادية
UNION في ملف النموذج، مع إحاطة SQL الخاصة بكل عرض مادي بتعليقات بالصيغة --my_mv_name:begin و --my_mv_name:end.
على سبيل المثال، سيؤدي ما يلي إلى إنشاء عرضين ماديين يكتبان البيانات إلى جدول الوجهة نفسه الخاص بالنموذج. وستكون أسماء العروض المادية بالصيغة <model_name>_mv1 و <model_name>_mv2:
كيفية تطوير مخطط الجدول الهدف
dbt run أعمدة مختلفة في SQL الخاص بـ MV.
ignore)، لكن يمكنك تغيير هذا الإعداد ليعمل وفق السلوك نفسه لإعداد on_schema_change في النماذج التزايدية.
ويمكنك أيضًا استخدام هذا الإعداد كآلية أمان. فإذا ضبطته على fail، فستفشل عملية البناء إذا كانت الأعمدة في SQL الخاص بـ MV تختلف عن أعمدة الجدول الهدف الذي أُنشئ عند أول dbt run.
استكمال البيانات
catchup=True). يمكنك تعطيل هذا السلوك بضبط إعداد catchup على False.
التجسيد مع هدف محدد صراحةً (بيتا)
- تشترك جميع الموارد (الجدول الهدف + 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علينا أيضًا تعريف بعض البيانات للجدول الهدف حتى عندما تكون الفكرة ألا نقرأ منه. والحل البديل هنا هو ببساطة ترك بيانات هذا الجدول فارغة.
- عند استدعاء
الاستخدام
events_daily.sql:
{{ 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:
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، سيؤدي ذلك إلى:
- إنشاء جدول مؤقت جديد
- تنفيذ INSERT-SELECT باستخدام SQL الخاص بكل MV
- تبديل الجدولين ذريًا
تغيير الجدول الهدف
--full-refresh. إذا حاولت تشغيل أمر dbt run عاديًا بعد تغيير المرجع materialization_target_table()، فستفشل عملية البناء وستظهر رسالة خطأ تشير إلى أن الهدف قد تغيّر.
لتغيير الهدف:
- حدِّث استدعاء
materialization_target_table() - شغِّل
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 يتعرّف على جدول كجدول هدف لعرض مادي
الجدول <table_name> يُستخدم كجدول هدف بواسطة عرض مادي يديره dbt. سيتم ضبط mv_on_schema_change تلقائيًا على “fail” لمنع فقدان البيانات.
إذا ظهرت هذه الرسالة، فهذا يعني أن dbt اكتشف أن الجدول مستهدَف بواسطة عرض مادي واحد على الأقل يديره dbt. وإذا كنت تتوقع ظهور هذه الرسالة لكنك لا تراها، فتحقق مما يلي:
- يعرّف نموذج العرض المادي
{{ materialization_target_table(ref('your_target')) }}بشكل صحيح - يتضمن نموذج العرض المادي
materialized='materialized_view'في إعداداته - تم تشغيل كلٍّ من العرض المادي والجدول الهدف مرة واحدة على الأقل
الترحيل من الهدف الضمني إلى الهدف المعرّف صراحةً
materialized='table' يعرّف المخطط نفسه للجدول الهدف الحالي الخاص بـ MV. استخدم عبارة WHERE 0 لإنشاء جدول فارغ. استخدم الاسم نفسه لنموذج العرض المادي الضمني الحالي. ستتمكن الآن من استخدام هذا النموذج لإجراء تعديلات متكررة على الجدول الهدف.
materialization_target_table() للإشارة إلى الجدول الهدف الجديد. إذا كنت تستخدم UNION ALL سابقًا، فأزل ذلك الجزء والتعليقات.
بالنسبة إلى أسماء النماذج، ستحتاج إلى اتباع اصطلاح التسمية التالي:
- إذا كان هناك MV واحد فقط مُعرَّف، فسيكون اسمه:
<old_model_name>_mv - إذا كانت هناك عدة MVs مُعرَّفة، فسيكون اسم كلٍّ منها:
<old_model_name>_mv_<name_in_comments>
my_model.sql (الهدف الضمني، نموذج واحد مع UNION ALL):
مقارنة السلوك بين نهجَي الهدف الضمني والهدف الصريح
كيفية عملها بشكل عام
السلوك أثناء الإدراج النشط
- بما أن العروض المادية في ClickHouse تعمل بوصفها مشغلات إدراج، فهي لا تلتقط البيانات إلا ما دامت موجودة. وإذا حُذف عرض مادي وأُعيد إنشاؤه (على سبيل المثال أثناء
--full-refresh)، فلن تُعالَج أي صفوف أُدرجت في جدول المصدر خلال تلك الفترة بواسطة العرض المادي. ويُشار إلى ذلك بأن العرض المادي يكون “أعمى”. - تستند جميع عمليات
catchupالمختلفة إلى عملياتINSERT INTO ... SELECTباستخدام SQL الخاص بالعروض المادية، وهي مستقلة عن آلية عمل العروض المادية. وبمجرد بدءINSERT، فإنه لا يلتقط البيانات الجديدة، لكن العرض المادي المُرفَق سيلتقطها.
عمليات الهدف الضمني
عمليات الهدف المحدَّد صراحةً
نموذج الجدول الهدف:
العروض المادية القابلة للتحديث
refreshable إلى نموذج MV لديك مع الخيارات التالية:
مثال على الوجهة الضمنية
مثال مع وجهة محددة صراحةً
القيود
- عند إنشاء عرض مادي قابل للتحديث (MV) في ClickHouse وله تبعية، لا يُصدر ClickHouse خطأ إذا كانت التبعية المحددة غير موجودة وقت الإنشاء. وبدلًا من ذلك، يظل العرض المادي القابل للتحديث في حالة غير نشطة، منتظرًا استيفاء التبعية قبل أن يبدأ في معالجة التحديثات أو إجراء التحديث. هذا السلوك مقصود من حيث التصميم، لكنه قد يؤدي إلى تأخر توفر البيانات إذا لم تُعالَج التبعية المطلوبة بسرعة. لذا ينبغي التأكد من أن جميع التبعيات معرّفة بشكل صحيح وموجودة قبل إنشاء عرض مادي قابل للتحديث.
- حتى الآن، لا يوجد “ربط dbt” فعلي بين mv وتبعياته، ولذلك لا يكون ترتيب الإنشاء مضمونًا.
- لم تُختبَر ميزة التحديث مع عدة mvs تُوجَّه إلى النموذج الهدف نفسه.