يجب أن يكون تجسيد materialized_view عبارة عن استعلام SELECT من table (source) موجودة. وعلى خلاف PostgreSQL، فإن عرض مادي في ClickHouse ليس "ثابتًا" (ولا توجد له عملية REFRESH مقابلة). وبدلًا من ذلك، فهو يعمل بوصفه مشغّل إدراج، إذ يُدرِج rows جديدة في الجدول الهدف عبر تطبيق تحويل SELECT المحدد على rows المُدرجة في الجدول المصدر. راجع توثيق عرض مادي في ClickHouse لمزيد من التفاصيل حول كيفية عمل العروض المادية في ClickHouse.
كيفية إدارة الجدول الهدف
عند استخدام تجسيد materialized_view، يحتاج dbt-clickhouse إلى إنشاء كلٍّ من عرض مادي وجدول هدف تُدرج فيه الصفوف المُحوَّلة. توجد طريقتان لإدارة الجدول الهدف:
| النهج | الوصف | الحالة |
|---|---|---|
| الهدف الضمني | ينشئ dbt-clickhouse الجدول الهدف ويديره تلقائيًا ضمن النموذج نفسه. ويُستنتج مخطط الجدول الهدف من SQL الخاص بـ MV. | مستقرة |
| الهدف الصريح | تُعرِّف الجدول الهدف كتجسيد منفصل من النوع table، ثم تشير إليه من نموذج MV باستخدام الماكرو materialization_target_table(). ويُنشأ MV مع عبارة TO تشير إلى ذلك الجدول. هذه الوظيفة متاحة بدءًا من dbt-clickhouse version 1.10. تحذير: هذه الميزة في مرحلة بيتا، وقد تتغير واجهة برمجة تطبيقات بناءً على ملاحظات المجتمع. |
بيتا |
يؤثر النهج الذي تختاره في كيفية التعامل مع تغييرات المخطط، وعمليات التحديث الكامل، وإعدادات MV المتعددة. وتصف الأقسام التالية كل نهج بالتفصيل.
التجسيد مع الجدول الهدف الضمني
هذا هو السلوك الافتراضي. عند تعريف نموذج materialized_view، سيقوم المهايئ بما يلي:
- إنشاء الجدول الهدف باسم النموذج
- إنشاء ClickHouse عرض مادي بالاسم
<model_name>_mv
يُستنتج مخطط الجدول الهدف من الأعمدة الموجودة في عبارة SELECT الخاصة بـ MV. وتشترك جميع الموارد (الجدول الهدف + MVs) في تهيئة النموذج نفسها.
-- models/events_mv.sql
{{
config(
materialized='materialized_view',
engine='SummingMergeTree()',
order_by='(event_date, event_type)'
)
}}
SELECT
toStartOfDay(event_time) AS event_date,
event_type,
count() AS total
FROM {{ source('raw', 'events') }}
GROUP BY event_date, event_typeراجع ملف الاختبار للاطلاع على مزيد من الأمثلة.
عدة عروض مادية
يتيح ClickHouse وجود أكثر من عرض مادي واحد يكتب السجلات إلى جدول الهدف نفسه. ولدعم ذلك في dbt-clickhouse باستخدام نهج الهدف الضمني، يمكنك إنشاء UNION في ملف النموذج، مع إحاطة SQL الخاصة بكل عرض مادي بتعليقات بالصيغة --my_mv_name:begin و --my_mv_name:end.
على سبيل المثال، سيؤدي ما يلي إلى إنشاء عرضين ماديين يكتبان البيانات إلى جدول الوجهة نفسه الخاص بالنموذج. وستكون أسماء العروض المادية بالصيغة <model_name>_mv1 و <model_name>_mv2:
--mv1:begin
select a,b,c from {{ source('raw', 'table_1') }}
--mv1:end
union all
--mv2:begin
select a,b,c from {{ source('raw', 'table_2') }}
--mv2:endكيفية تطوير مخطط الجدول الهدف
بدءًا من الإصدار 1.9.8 من dbt-clickhouse، يمكنك التحكّم في كيفية تطوير مخطط الجدول الهدف عندما يواجه dbt run أعمدة مختلفة في SQL الخاص بـ MV.
{{config(
materialized='materialized_view',
engine='MergeTree()',
order_by='(id)',
on_schema_change='fail' # this setting
)}}افتراضيًا، لن يطبّق dbt أي تغييرات على الجدول الهدف (قيمة الإعداد ignore)، لكن يمكنك تغيير هذا الإعداد ليعمل وفق السلوك نفسه لإعداد on_schema_change في النماذج التزايدية.
ويمكنك أيضًا استخدام هذا الإعداد كآلية أمان. فإذا ضبطته على fail، فستفشل عملية البناء إذا كانت الأعمدة في SQL الخاص بـ MV تختلف عن أعمدة الجدول الهدف الذي أُنشئ عند أول dbt run.
استكمال البيانات
افتراضيًا، عند إنشاء عرض مادي (MV) أو إعادة إنشائه، يُملأ الجدول الهدف أولًا بالبيانات التاريخية قبل إنشاء الـ MV نفسه (catchup=True). يمكنك تعطيل هذا السلوك بضبط إعداد catchup على False.
{{config(
materialized='materialized_view',
engine='MergeTree()',
order_by='(id)',
catchup=False # this setting
)}}| العملية | catchup: True (الافتراضي) |
catchup: False |
|---|---|---|
النشر المبدئي (dbt run) |
تُملأ الجدول الهدف بالبيانات التاريخية | يُنشأ الجدول الهدف فارغًا |
التحديث الكامل (dbt run --full-refresh) |
يُعاد إنشاء الجدول الهدف وتعبئته بالبيانات التاريخية | يُعاد إنشاء الجدول الهدف فارغًا، مع فقدان البيانات الحالية |
| التشغيل العادي | يلتقط العرض المادي عمليات الإدراج الجديدة | يلتقط العرض المادي عمليات الإدراج الجديدة |
التجسيد مع هدف محدد صراحةً (بيتا)
ينشئ 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:
{{
config(
materialized='table',
engine='SummingMergeTree()',
order_by='(event_date, event_type)',
partition_by='toYYYYMM(event_date)'
)
}}
SELECT
toDate(now()) AS event_date,
'' AS event_type,
toUInt64(0) AS total
WHERE 0 -- Creates empty table with correct schemaهذا هو الحل الالتفافي الذي نذكره في قسم القيود. قد تفقد هنا بعض عمليات التحقق الخاصة بـ dbt، لكن سيظل المخطط خاضعًا للتحقق على مستوى ClickHouse.
الخطوة 2: تعريف العروض المُجسَّدة التي تشير إلى جدول الهدف
على سبيل المثال، يمكنك تعريف عدة MVs مختلفة في نماذج مختلفة كما يلي، ويمكن أن تشير حتى إلى جدول الهدف نفسه. لاحظ استدعاء الماكرو الجديد {{ materialization_target_table(ref('events_daily')) }}، الذي يضبط جدول الهدف لـ MV.
النموذج page_events_aggregator.sql:
{{ config(materialized='materialized_view') }}
{{ materialization_target_table(ref('events_daily')) }}
SELECT
toStartOfDay(event_time) AS event_date,
event_type,
count() AS total
FROM {{ source('raw', 'page_events') }}
GROUP BY event_date, event_typeالنموذج mobile_events_aggregator.sql:
{{ config(materialized='materialized_view') }}
{{ materialization_target_table(ref('events_daily')) }}
SELECT
toStartOfDay(event_time) AS event_date,
event_type,
count() AS total
FROM {{ source('raw', 'mobile_events') }}
GROUP BY event_date, event_typeخيارات الإعداد
عند استخدام جداول هدف صريحة، فبالإضافة إلى إعدادات التجسيد العامة والإعدادات الخاصة بالجدول، تنطبق الإعدادات التالية:
على الجدول الهدف (materialized='table'):
| الخيار | الوصف | القيمة الافتراضية |
|---|---|---|
mv_on_schema_change |
كيفية التعامل مع تغييرات المخطط عند استخدام الجدول بواسطة MVs مُدارة عبر dbt. ويتبع السلوك نفسه لإعداد on_schema_change في النماذج incremental. |
تحذير: سيتصرف نموذج materialized='table' كالمعتاد إذا لم تكن هناك MVs تشير إليه، لذا حتى إذا تم تعريف هذا الإعداد فسيتم تجاهله. وإذا كان الجدول هو الهدف لـ MVs، فستكون القيمة الافتراضية لهذا الإعداد هي mv_on_schema_change='fail' لحماية البيانات داخل هذه الجداول. |
repopulate_from_mvs_on_full_refresh |
عند استخدام --full-refresh، بدلًا من تشغيل SQL الخاص بالجدول، أعد إنشاء الجدول بتنفيذ عمليات INSERT-SELECT باستخدام SQL من جميع MVs التي تشير إليه. |
False |
على العرض المادي (materialized='materialized_view'):
| الخيار | الوصف | القيمة الافتراضية |
|---|---|---|
catchup |
ما إذا كان سيتم تحميل البيانات التاريخية الفائتة عند إنشاء MV. | True |
العمليات الشائعة
التحديث الكامل باستخدام الجداول الهدف المحددة صراحةً
عند استخدام --full-refresh، ستُعاد إنشاء الجداول الهدف المحددة صراحةً (لذا قد تفقد بعض البيانات إذا كان إدخال البيانات جارياً أثناء هذه العملية). وسيختلف ذلك بحسب إعدادات التكوين لديك:
الخيار 1: السلوك الافتراضي لـ --full-refresh. ستُعاد إنشاء كل شيء، ولكن أثناء إعادة إنشاء MVs، سيكون الجدول الهدف فارغًا أو محمّلًا جزئيًا.
سيُحذف كل شيء ثم يُعاد إنشاؤه. إذا كنت تريد إعادة إدراج البيانات باستخدام SQL الخاص بـ MVs، فأبقِ الإعداد catchup=True:
-- models/page_events_aggregator.sql
{{ config(
materialized='materialized_view',
catchup=True -- this is the default value so you don't need to actually set it.
) }}
{{ materialization_target_table(ref('events_daily')) }}
...الخيار 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، سيؤدي ذلك إلى:
- إنشاء جدول مؤقت جديد
- تنفيذ INSERT-SELECT باستخدام SQL الخاص بكل MV
- تبديل الجدولين ذريًا
وبذلك لن تظهر بيانات فارغة في جدولك أثناء إعادة إنشاء MVs.
-- models/events_daily.sql
{{
config(
materialized='table',
engine='SummingMergeTree()',
order_by='(event_date, event_type)',
repopulate_from_mvs_on_full_refresh=True
)
}}
...تغيير الجدول الهدف
لا يمكنك تغيير الجدول الهدف الخاص بـ 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 لمعرفة الجهة التي يكتب فيها العرض المادي:
SELECT
name as mv_name,
replaceRegexpOne(
create_table_query,
'.*TO\\s+`?([^`\\s(]+)`?\\.`?([^`\\s(]+)`?.*',
'\\1.\\2'
) AS target_table
FROM system.tables
WHERE database = 'your_schema'
AND engine = 'MaterializedView'تحقّق مما إذا كان 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 لإنشاء جدول فارغ. استخدم الاسم نفسه لنموذج العرض المادي الضمني الحالي. ستتمكن الآن من استخدام هذا النموذج لإجراء تعديلات متكررة على الجدول الهدف.
-- models/events_daily.sql
{{
config(
materialized='table',
engine='MergeTree()',
order_by='(event_date, event_type)'
)
}}
SELECT
toDate(now()) AS event_date,
'' AS event_type,
toUInt64(0) AS total
WHERE 02. حدِّث نماذج 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):
--mv1:begin
select a, b, c from {{ source('raw', 'table_1') }}
--mv1:end
union all
--mv2:begin
select a, b, c from {{ source('raw', 'table_2') }}
--mv2:endبعد (الهدف المُصرَّح به، ملفات نماذج منفصلة):
-- models/my_model_mv_mv1.sql
{{ config(materialized='materialized_view') }}
{{ materialization_target_table(ref('events_daily')) }}
select a, b, c from {{ source('raw', 'table_1') }}-- models/my_model_mv_mv2.sql
{{ config(materialized='materialized_view') }}
{{ materialization_target_table(ref('events_daily')) }}
select a, b, c from {{ source('raw', 'table_2') }}3. كرّر هذه العملية حسب الحاجة باتباع الإرشادات الواردة في قسم الهدف المحدَّد صراحةً.
مقارنة السلوك بين نهجَي الهدف الضمني والهدف الصريح
كيفية عملها بشكل عام
| العملية | الهدف الضمني | الهدف الصريح |
|---|---|---|
| أول تشغيل لـ dbt | تُنشأ جميع الموارد | تُنشأ جميع الموارد |
| تشغيل dbt التالي | لا يمكن إدارة الموارد بشكل منفصل، إذ تتم جميع التغييرات معًا: الجدول الهدف: تُدار التغييرات باستخدام الإعداد on_schema_change. وتكون قيمته الافتراضية ignore، لذا لا تُعالَج الأعمدة الجديدة.العروض المادية: تُحدَّث جميعها بعمليات alter table modify query |
يمكن تطبيق التغييرات بشكل منفصل: الجدول الهدف: يُكتشَف تلقائيًا ما إذا كانت هذه الجداول جداولَ هدف لعروض مادية معرّفة في dbt. وإذا كانت كذلك، فتُدار تغييرات الأعمدة افتراضيًا باستخدام الإعداد mv_on_schema_change بالقيمة fail، لذلك ستفشل العملية عند حدوث تغييرات في الأعمدة. وقد أضفنا هذه القيمة الافتراضية كطبقة حمايةالعروض المادية: يُحدَّث SQL الخاص بها بعمليات alter table modify query. |
| dbt run –full-refresh | لا يمكن إدارة الموارد بشكل منفصل، إذ تتم جميع التغييرات معًا: الجدول الهدف: يُعاد إنشاء الجدول الهدف فارغًا. ويتوفر catchup لتهيئة backfill باستخدام SQL الخاص بجميع العروض المادية معًا. وتكون قيمة catchup هي True افتراضيًاالعروض المادية: يُعاد إنشاء جميعها. |
ستُطبَّق التغييرات بشكل منفصل: الجدول الهدف: سيُعاد إنشاؤه كالمعتاد. العروض المادية: حذف وإعادة إنشاء. يتوفر catchup لإجراء backfill أولي. وتكون قيمة catchup هي True افتراضيًا. ملاحظة: أثناء العملية، سيكون الجدول الهدف فارغًا أو محمّلًا جزئيًا إلى أن يُعاد إنشاء العروض المادية. لتجنّب ذلك، راجع القسم التالي لمعرفة كيفية التكرار على الجدول الهدف. |
السلوك أثناء الإدراج النشط
عند تكرار نماذجك، يجب أن تكون على دراية بكيفية تفاعل العمليات المختلفة مع البيانات التي يجري إدراجها:
- بما أن العروض المادية في ClickHouse تعمل بوصفها مشغلات إدراج، فهي لا تلتقط البيانات إلا ما دامت موجودة. وإذا حُذف عرض مادي وأُعيد إنشاؤه (على سبيل المثال أثناء
--full-refresh)، فلن تُعالَج أي صفوف أُدرجت في جدول المصدر خلال تلك الفترة بواسطة العرض المادي. ويُشار إلى ذلك بأن العرض المادي يكون "أعمى". - تستند جميع عمليات
catchupالمختلفة إلى عملياتINSERT INTO ... SELECTباستخدام SQL الخاص بالعروض المادية، وهي مستقلة عن آلية عمل العروض المادية. وبمجرد بدءINSERT، فإنه لا يلتقط البيانات الجديدة، لكن العرض المادي المُرفَق سيلتقطها.
يلخّص الجدول التالي مدى أمان كل عملية عندما تحدث عمليات الإدراج بشكل نشط على جدول المصدر.
عمليات الهدف الضمني
| العملية | العملية الداخلية | السلامة أثناء تنفيذ عمليات الإدراج |
|---|---|---|
أول dbt run |
1. إنشاء الجدول الهدف 2. إدراج البيانات (إذا كانت catchup=True)3. إنشاء العروض المادية |
⚠️ يكون العرض المادي غير ملتقط للبيانات بين الخطوتين 1 و3. أي صفوف تُدرج في المصدر خلال هذه الفترة لن يتم التقاطها. |
عمليات dbt run اللاحقة |
ALTER TABLE ... MODIFY QUERY |
✅ آمن. يتم تحديث العرض المادي بشكل ذري. |
dbt run --full-refresh |
1. إنشاء جدول احتياطي 2. إدراج البيانات (إذا كانت catchup=True)3. حذف العروض المادية 4. EXCHANGE TABLES 5. إعادة إنشاء العروض المادية |
⚠️ يكون العرض المادي غير ملتقط للبيانات أثناء إعادة الإنشاء. البيانات التي تُدرج في المصدر بين الخطوتين 3 و5 لن تظهر في الجدول الهدف الجديد. |
عمليات الهدف المحدَّد صراحةً
نماذج العرض المادي:
| العملية | العملية الداخلية | الأمان أثناء حدوث عمليات الإدراج |
|---|---|---|
أول dbt run |
1. إنشاء MV (باستخدام العبارة TO)2. تشغيل الاستدراك (إذا كان catchup=True) |
✅ يتم إنشاء MV أولًا، لذا تُلتقط عمليات الإدراج الجديدة فورًا. ⚠️ قد يؤدي الاستدراك إلى تكرار البيانات — فقد يتداخل استعلام التحميل التاريخي مع الصفوف التي تعالجها MV بالفعل. ويكون ذلك آمنًا عند استخدام محرك يزيل التكرار (مثل ReplacingMergeTree). |
عمليات dbt run اللاحقة |
ALTER TABLE ... MODIFY QUERY |
✅ آمن. يتم تحديث MV بشكل ذري. |
dbt run --full-refresh على MVs |
1. حذف MV وإعادة إنشائها 2. تشغيل الاستدراك (إذا كان catchup=True) |
⚠️ تكون MV غير قادرة على الالتقاط أثناء إعادة الإنشاء (بين الحذف والإنشاء). ⚠️ قد يؤدي الاستدراك إلى تكرار البيانات إذا كانت عمليات الإدراج تحدث بالتزامن. |
نموذج الجدول الهدف:
| العملية | العملية الداخلية | الأمان أثناء حدوث عمليات الإدراج |
|---|---|---|
dbt run |
تُطبَّق تغييرات المخطط وفقًا للإعداد mv_on_schema_change |
✅ آمن. لا توجد حركة بيانات. |
dbt run --full-refresh (الافتراضي) |
إعادة إنشاء الجدول (مع تركه فارغًا) | ⚠️ يكون الجدول الهدف فارغًا إلى أن تعيد MVs تعبئته بالبيانات التاريخية. وتواصل MVs الإدراج في الجدول الجديد بمجرد إنشائه. |
dbt run --full-refresh مع repopulate_from_mvs_on_full_refresh=True |
1. إنشاء جدول احتياطي 2. إدراج البيانات باستخدام SQL الخاص بكل MV 3. تبديل الجداول بشكل ذري |
⚠️ تكون MV غير قادرة على الالتقاط أثناء إعادة الإنشاء. البيانات التي تُدرج بين الخطوتين 1 و3 لن تظهر في الجدول الجديد. قد يتغير هذا في الإصدارات القادمة. |
العروض المادية القابلة للتحديث
تُعد العروض المادية القابلة للتحديث نوعًا خاصًا من العروض المادية في ClickHouse، إذ تعيد تنفيذ الاستعلام دوريًا وتخزّن النتيجة، على غرار طريقة عمل العروض المادية في قواعد البيانات الأخرى. ويكون ذلك مفيدًا في الحالات التي تحتاج فيها إلى لقطات أو تجميعات دورية بدلًا من مشغّلات الإدراج الآنية.
لاستخدام عرض مادي قابل للتحديث، أضف كائن إعداد refreshable إلى نموذج MV لديك مع الخيارات التالية:
| Option | Description | Required | Default Value |
|---|---|---|---|
| interval | عبارة interval (مطلوبة)، مثل EVERY 5 MINUTE |
نعم | |
| randomize | عبارة التوزيع العشوائي، وتظهر بعد RANDOMIZE FOR |
||
| append | إذا ضُبطت على True، فسيُدرِج كل تحديث صفوفًا في الجدول من دون حذف الصفوف الموجودة. ولا تكون عملية الإدراج ذرية، تمامًا كما في INSERT SELECT العادي. |
False | |
| depends_on | قائمة تبعيات للعرض المادي القابل للتحديث. يُرجى تقديم التبعيات بالتنسيق التالي {schema}.{view_name} |
||
| depends_on_validation | ما إذا كان يجب التحقق من وجود التبعيات المحددة في depends_on. وإذا كانت إحدى التبعيات لا تتضمن schema، فسيُجرى التحقق على schema default |
False |
مثال على الوجهة الضمنية
{{
config(
materialized='materialized_view',
engine='MergeTree()',
order_by='(event_date)',
refreshable={
"interval": "EVERY 5 MINUTE",
"randomize": "1 MINUTE",
"append": True,
"depends_on": ['schema.depend_on_model'],
"depends_on_validation": True
}
)
}}
SELECT
toStartOfDay(event_time) AS event_date,
count() AS total
FROM {{ source('raw', 'events') }}
GROUP BY event_dateمثال مع وجهة محددة صراحةً
{{
config(
materialized='materialized_view',
refreshable={
"interval": "EVERY 1 HOUR",
"append": False
}
)
}}
{{ materialization_target_table(ref('events_daily')) }}
SELECT
toStartOfDay(event_time) AS event_date,
event_type,
count() AS total
FROM {{ source('raw', 'events') }}
GROUP BY event_date, event_typeتحديث معاملات التحديث
اعتبارًا من الإصدار 1.10.2 من dbt-clickhouse، تُطبَّق التغييرات على معاملات التحديث (interval وrandomize وdepends_on) الخاصة بعرض مادي قابل للتحديث موجود مباشرةً عند تشغيل dbt run عادي، باستخدام ALTER TABLE ... MODIFY REFRESH. لا يُحذف العرض المادي ولا يُعاد إنشاؤه، لذا لا تُفقد أي بيانات ولا توجد «نافذة أعمى» أثناء تطبيق التغيير.
لا يمكن تطبيق بعض التحويلات مباشرةً. وبدلًا من تجاهلها بصمت، سيفشل dbt التشغيل مع ظهور خطأ يطلب منك استخدام dbt run --full-refresh:
- تحويل عرض مادي عادي إلى عرض قابل للتحديث (بإضافة إعداد
refreshable)، أو العكس (بإزالته). - تغيير إعداد
append، إذ لا يسمح ClickHouse بإضافةAPPENDأو إزالته عبرMODIFY REFRESH.
تذكّر أن --full-refresh يحذف العرض المادي ويعيد إنشاؤه، مع الآثار الموضحة في السلوك أثناء استيعاب البيانات النشط.
القيود
- عند إنشاء عرض مادي قابل للتحديث (MV) في ClickHouse وله تبعية، لا يُصدر ClickHouse خطأ إذا كانت التبعية المحددة غير موجودة وقت الإنشاء. وبدلًا من ذلك، يظل العرض المادي القابل للتحديث في حالة غير نشطة، منتظرًا استيفاء التبعية قبل أن يبدأ في معالجة التحديثات أو إجراء التحديث. هذا السلوك مقصود من حيث التصميم، لكنه قد يؤدي إلى تأخر توفر البيانات إذا لم تُعالَج التبعية المطلوبة بسرعة. لذا ينبغي التأكد من أن جميع التبعيات معرّفة بشكل صحيح وموجودة قبل إنشاء عرض مادي قابل للتحديث.
- حتى الآن، لا يوجد "ربط dbt" فعلي بين mv وتبعياته، ولذلك لا يكون ترتيب الإنشاء مضمونًا.
- لم تُختبَر ميزة التحديث مع عدة mvs تُوجَّه إلى النموذج الهدف نفسه.