Skip to main content
يوفّر هذا القسم أدلة لإعداد dbt ومهايئ ClickHouse، بالإضافة إلى مثال على استخدام dbt مع ClickHouse بالاستناد إلى مجموعة بيانات IMDB متاحة للعامة. ويغطي المثال الخطوات التالية:
  1. إنشاء مشروع dbt وإعداد مهايئ ClickHouse.
  2. تعريف نموذج.
  3. تحديث نموذج.
  4. إنشاء نموذج تزايدي.
  5. إنشاء نموذج لقطة.
  6. استخدام العروض المادية.
صُممت هذه الأدلة لتُستخدم بالاقتران مع بقية الوثائق، والميزات والإعدادات، ومرجع أساليب التجسيد.

الإعداد

اتبع التعليمات الواردة في قسم إعداد dbt ومهايئ ClickHouse لتهيئة بيئتك. مهم: تم اختبار ما يلي باستخدام بايثون 3.9.

إعداد ClickHouse

يتفوّق dbt عند نمذجة البيانات عالية الترابط. ولغرض المثال، نوفر مجموعة بيانات صغيرة من IMDB بالمخطط العلائقي التالي. وتأتي مجموعة البيانات هذه من مستودع مجموعة البيانات العلائقية. ورغم أن هذا المخطط بسيط مقارنةً بالمخططات الشائعة المستخدمة مع dbt، فإنه يمثّل عيّنةً مناسبة يسهل التعامل معها: نستخدم مجموعة فرعية من هذه الجداول كما هو موضح. أنشئ الجداول التالية:
العمود created_at في الجدول roles، وتكون قيمته الافتراضية now(). نستخدمه لاحقًا لتحديد التحديثات التزايدية في نماذجنا — راجع النماذج التزايدية.
نستخدم الدالة s3 لقراءة البيانات من المصدر عبر نقاط النهاية العامة تمهيدًا لإدراجها. شغّل الأوامر التالية لتعبئة الجداول:
قد يختلف تنفيذ هذه الخطوات بحسب سرعة الاتصال لديك، لكن يجب ألا يستغرق إكمال كل منها سوى بضع ثوانٍ. نفّذ الاستعلام التالي لاحتساب ملخّص لكل ممثل، مرتبًا تنازليًا حسب عدد مرات الظهور في الأفلام، وللتأكد من أن البيانات قد تم تحميلها بنجاح:
يجب أن تكون الاستجابة على النحو التالي:
في الأدلة اللاحقة، سنحوّل هذا الاستعلام إلى نموذج، مع تخزينه فعليًا في ClickHouse كعرض وجدول ضمن dbt.

الاتصال بـ ClickHouse

  1. أنشئ مشروع dbt. في هذه الحالة، نسمّيه تيمّنًا بمصدر imdb لدينا. وعندما يُطلب منك ذلك، اختر clickhouse كمصدر قاعدة البيانات.
  2. انتقل باستخدام cd إلى مجلد مشروعك:
  3. في هذه المرحلة، ستحتاج إلى محرر النصوص الذي تفضله. في الأمثلة أدناه، نستخدم VS Code الشهير. عند فتح دليل IMDB، ينبغي أن ترى مجموعة من ملفات yml وsql:
  4. حدّث ملف dbt_project.yml لتحديد أول نموذج لدينا، وهو actor_summary، واضبط profile على clickhouse_imdb.
  5. نحتاج بعد ذلك إلى تزويد dbt بتفاصيل الاتصال الخاصة بمثيل ClickHouse لدينا. أضف ما يلي إلى ~/.dbt/profiles.yml.
    لاحظ ضرورة تعديل اسم المستخدم وكلمة المرور. تتوفر أيضًا إعدادات إضافية موثقة هنا.
  6. من دليل IMDB، نفّذ الأمر dbt debug للتأكد من أن dbt قادر على الاتصال بـ ClickHouse.
    تأكد من أن الاستجابة تتضمن Connection test: [OK connection ok]، ما يشير إلى نجاح الاتصال.

إنشاء تجسيد عرض بسيط

عند استخدام تجسيد العرض، يُعاد بناء النموذج كعرض في كل تشغيل، وذلك باستخدام عبارة CREATE VIEW AS في ClickHouse. لا يتطلب ذلك أي تخزين إضافي للبيانات، لكنه يكون أبطأ عند الاستعلام مقارنةً بتجسيدات الجداول.
  1. من داخل المجلد imdb، احذف الدليل models/example:
  2. أنشئ ملفًا جديدًا في المجلد actors داخل المجلد models. هنا ننشئ ملفات يمثّل كل ملف منها نموذجًا لممثل:
  3. أنشئ ملفَي schema.yml وactor_summary.sql في المجلد models/actors.
    يُعرّف الملف schema.yml جداولنا. وستصبح هذه متاحة لاحقًا للاستخدام في وحدات الماكرو. حرّر models/actors/schema.yml ليحتوي على هذا المحتوى:
    يحدّد actors_summary.sql النموذج الفعلي لدينا. لاحظ أننا نطلب أيضًا في الدالة config أن تتم مَوضعة النموذج كـ view في ClickHouse. ويجري الرجوع إلى جداولنا من ملف schema.yml عبر الدالة source؛ فعلى سبيل المثال، يشير source('imdb', 'movies') إلى الجدول movies في قاعدة البيانات imdb. حرّر models/actors/actors_summary.sql ليحتوي على هذا المحتوى:
    لاحظ أننا نُدرج العمود updated_at في actor_summary النهائي. وسنستخدمه لاحقًا في عمليات الـ materialization التزايدية.
  4. من المجلد imdb، نفِّذ الأمر dbt run.
  5. سيمثّل dbt الـ model كـ view في ClickHouse كما هو مطلوب. ويمكننا الآن تنفيذ query على هذا الـ view مباشرةً. سيكون هذا الـ view قد أُنشئ في قاعدة البيانات imdb_dbt، ويُحدَّد ذلك من خلال parameter الخاص بـ schema في الملف ~/.dbt/profiles.yml ضمن profile ‏clickhouse_imdb.
    عند الاستعلام عن هذا العرض، يمكننا الحصول على نتائج استعلامنا السابق نفسه بصياغة أبسط:

إنشاء تجسيد لجدول

في المثال السابق، جرى تجسيد نموذجنا كعرض. ومع أن هذا قد يوفّر أداءً كافيًا لبعض الاستعلامات، فقد يكون من الأفضل تجسيد عبارات SELECT الأكثر تعقيدًا أو الاستعلامات كثيرة التنفيذ على شكل جدول. ويكون هذا التجسيد مفيدًا للنماذج التي ستستعلم عنها أدوات ذكاء الأعمال لضمان حصول المستخدمين على تجربة أسرع. وهذا يعني فعليًا تخزين نتائج الاستعلام في جدول جديد، مع ما يصاحب ذلك من أعباء تخزين إضافية — إذ يُنفَّذ عمليًا INSERT TO SELECT. لاحظ أن هذا الجدول سيُعاد إنشاؤه في كل مرة، أي إنه ليس تزايديًا. لذلك قد تؤدي مجموعات النتائج الكبيرة إلى أوقات تنفيذ طويلة — راجع قيود dbt.
  1. عدّل الملف actors_summary.sql بحيث تُضبط المعلَمة materialized على table. لاحظ كيفية تعريف ORDER BY، ولاحظ أننا نستخدم محرك الجدول MergeTree:
  2. من الدليل imdb، نفّذ الأمر dbt run. قد تستغرق هذه العملية وقتًا أطول قليلًا — نحو 10 ثوانٍ على معظم الأجهزة.
  3. أكّد إنشاء الجدول imdb_dbt.actor_summary:
    ينبغي أن يظهر الجدول بأنواع البيانات المناسبة:
  4. أكّد أن نتائج هذا الجدول متسقة مع النتائج السابقة. لاحظ التحسّن الملحوظ في وقت الاستجابة الآن بعد أن أصبح هذا النموذج جدولًا:
    لا تتردد في تنفيذ استعلامات أخرى على هذا النموذج. على سبيل المثال، أيّ الممثلين لديهم أعلى الأفلام تصنيفًا مع أكثر من 5 ظهورات؟

إنشاء تجسيد تزايدي

أنشأ المثال السابق جدولًا لتجسيد النموذج. ويُعاد إنشاء هذا الجدول مع كل تنفيذ لـ dbt. وقد يكون ذلك غير عملي وباهظ التكلفة جدًا عند التعامل مع مجموعات نتائج كبيرة أو تحويلات معقدة. ولمعالجة هذا التحدي وتقليل زمن البناء، يوفّر dbt تجسيدات تزايدية. ويتيح ذلك لـ dbt إدراج السجلات في جدول أو تحديثها منذ آخر تنفيذ، مما يجعله مناسبًا للبيانات ذات نمط الأحداث. وفي الخلفية، يُنشأ جدول مؤقت يضم جميع السجلات المحدَّثة، ثم تُدرج جميع السجلات غير المتأثرة إلى جانب السجلات المحدَّثة في جدول هدف جديد. وينتج عن ذلك قيود مشابهة لتلك الخاصة بنموذج الجدول عند التعامل مع مجموعات النتائج الكبيرة. ولتجاوز هذه القيود عند التعامل مع المجموعات الكبيرة، يدعم المهايئ وضع ‘inserts_only’، حيث تُدرج جميع التحديثات في الجدول الهدف من دون إنشاء جدول مؤقت (المزيد حول ذلك أدناه). ولتوضيح هذا المثال، سنضيف الممثل “Clicky McClickHouse”، الذي سيظهر في 910 أفلام مذهلة، بما يضمن أنه شارك في عدد أفلام يفوق حتى Mel Blanc.
  1. أولًا، نعدّل نموذجنا ليصبح تزايديًا. وتتطلّب هذه الإضافة ما يلي:
    1. unique_key - لضمان أن يتمكن المهايئ من تمييز الصفوف بشكل فريد، يجب أن نوفر unique_key - وفي هذه الحالة، يكون الحقل id من الاستعلام كافيًا. يضمن ذلك عدم وجود صفوف مكررة في جدولنا المُجسَّد. لمزيد من التفاصيل حول قيود التفرّد، راجع هنا.
    2. Incremental filter - نحتاج أيضًا إلى إخبار dbt بكيفية تحديد الصفوف التي تغيّرت عند تنفيذ تشغيل Incremental. ويتم ذلك من خلال توفير تعبير delta. وعادةً ما يتضمن هذا طابعًا زمنيًا لبيانات الأحداث؛ لذلك نستخدم حقل الطابع الزمني updated_at. يتيح هذا العمود، الذي تكون قيمته الافتراضية هي now() عند إدراج الصفوف، التعرّف على الأدوار الجديدة. بالإضافة إلى ذلك، نحتاج إلى تحديد الحالة البديلة التي تُضاف فيها عناصر actors جديدة. وباستخدام المتغير {{this}} للدلالة على الجدول المُجسَّد الحالي، نحصل على التعبير التالي: where id > (select max(id) from {{ this }}) or updated_at > (select max(updated_at) from {{this}}). نُضمّن هذا داخل الشرط {% if is_incremental() %}، بما يضمن استخدامه فقط في عمليات التشغيل Incremental وليس عند إنشاء الجدول لأول مرة. لمزيد من التفاصيل حول تصفية الصفوف في نماذج Incremental، راجع هذا النقاش في وثائق dbt.
    حدِّث الملف actor_summary.sql على النحو التالي:
    لاحظ أن نموذجنا لن يتفاعل إلا مع التحديثات والإضافات على جدولي roles وactors. وللتعامل مع جميع الجداول، يُنصح المستخدمون بتقسيم هذا النموذج إلى عدة نماذج فرعية، بحيث يكون لكل منها معايير incremental خاصة بها. ويمكن بعد ذلك الإشارة إلى هذه النماذج وربطها ببعضها. ولمزيد من التفاصيل حول الإشارة المتبادلة بين النماذج، راجع هنا.
  2. نفّذ dbt run وتحقّق من نتائج الجدول الناتج:
  3. سنضيف الآن بيانات إلى نموذجنا لتوضيح كيفية إجراء تحديث تزايدي. أضِف الممثل “Clicky McClickHouse” إلى جدول actors:
  4. لنجعل “Clicky” يؤدي دور البطولة في 910 أفلام عشوائية:
  5. تحقّق من أنه أصبح بالفعل الممثل الأكثر ظهورًا عبر الاستعلام مباشرةً عن جدول المصدر الأساسي وتجاوز أي نماذج في dbt:
  6. نفّذ dbt run وتحقّق من تحديث النموذج ومطابقته للنتائج أعلاه:

الآليات الداخلية

يمكننا تحديد تعليمات SQL التي نُفِّذت لتنفيذ التحديث التزايدي المذكور أعلاه عبر الاستعلام عن سجل الاستعلامات في ClickHouse.
عدّل الاستعلام أعلاه بما يتناسب مع فترة التنفيذ. نترك فحص النتائج للمستخدم، لكننا نُبرز الاستراتيجية العامة التي يستخدمها مهايئ لتنفيذ التحديثات التزايدية:
  1. يُنشئ مهايئ جدولًا مؤقتًا باسم actor_sumary__dbt_tmp. وتُمرَّر الصفوف التي تغيّرت إلى هذا الجدول على شكل تدفّق.
  2. يُنشأ جدول جديد باسم actor_summary_new,. ثم تُمرَّر الصفوف من الجدول القديم إلى الجديد على شكل تدفّق، مع التحقق من أن معرّفات الصفوف غير موجودة في الجدول المؤقت. وبهذا تُعالَج التحديثات والتكرارات بفعالية.
  3. تُمرَّر النتائج من الجدول المؤقت إلى جدول actor_summary الجديد على شكل تدفّق:
  4. أخيرًا، يُستبدل الجدول الجديد بالنسخة القديمة استبدالًا ذريًا عبر عبارة EXCHANGE TABLES. ثم يُحذف الجدولان القديم والمؤقت.
يوضّح الشكل التالي ذلك: قد تواجه هذه الاستراتيجية تحديات في النماذج الكبيرة جدًا. لمزيد من التفاصيل، راجع القيود.

استراتيجية الإلحاق (وضع الإدراج فقط)

للتغلّب على قيود مجموعات البيانات الكبيرة في النماذج التزايدية، يستخدم المهايئ مَعلمة إعداد dbt ‏incremental_strategy. ويمكن ضبطها على القيمة append. وعند ضبطها، تُدرَج الصفوف المحدَّثة مباشرةً في الجدول الهدف (المعروف أيضًا باسم imdb_dbt.actor_summary) ولا يُنشأ أي جدول مؤقت. ملاحظة: يتطلب وضع الإلحاق فقط أن تكون بياناتك غير قابلة للتغيير، أو أن تكون القيم المكررة مقبولة. إذا كنت تريد نموذج جدول تزايدي يدعم الصفوف المعدَّلة، فلا تستخدم هذا الوضع! لتوضيح هذا الوضع، سنضيف ممثلًا جديدًا آخر ثم نعيد تشغيل dbt run باستخدام incremental_strategy='append'.
  1. اضبط وضع الإلحاق فقط في actor_summary.sql:
  2. لنضف ممثلًا مشهورًا آخر - Danny DeBito
  3. لنجعل Danny يشارك في 920 فيلمًا عشوائيًا.
  4. نفّذ dbt run وتأكد من أن Danny قد أُضيف إلى جدول actor_summary
لاحظ مدى سرعة هذا التشغيل التزايدي مقارنةً بإدراج “Clicky”. ويكشف التحقق من جدول query_log مرة أخرى الفروق بين عمليتَي التشغيل التزايديَّتين:
في هذا التشغيل، تُضاف الصفوف الجديدة فقط مباشرةً إلى جدول imdb_dbt.actor_summary، ولا يشمل ذلك إنشاء أي جدول.

وضع الحذف والإدراج (تجريبي)

تاريخيًا، لم يكن ClickHouse يوفّر سوى دعم محدود لعمليات التحديث والحذف، وذلك من خلال عمليات التعديل غير المتزامنة. وقد تكون هذه العمليات كثيفة جدًا من حيث استهلاك موارد IO، لذا يُنصح عمومًا بتجنبها. قدّم ClickHouse 22.8 عمليات الحذف خفيفة الوزن، وقدّم ClickHouse 25.7 عمليات التحديث خفيفة الوزن. ومع إتاحة هذه الميزات، تصبح التعديلات الناتجة عن استعلامات التحديث المفردة فورية من منظور المستخدم، حتى عند تطبيقها ماديًا بشكل غير متزامن. يمكن تهيئة هذا الوضع لنموذج عبر المعلَمة incremental_strategy، أي:
تعمل هذه الاستراتيجية مباشرةً على جدول النموذج الهدف، لذا إذا حدثت مشكلة أثناء التنفيذ، فمن المرجّح أن تصبح بيانات النموذج التزايدي في حالة غير صالحة، إذ لا يوجد تحديث ذري. باختصار، يعتمد هذا النهج على ما يلي:
  1. ينشئ المهايئ جدولًا مؤقتًا باسم actor_sumary__dbt_tmp. وتُمرَّر الصفوف التي تغيّرت إلى هذا الجدول.
  2. يُنفَّذ أمر DELETE على جدول actor_summary الحالي. وتُحذف الصفوف حسب المعرّف بالاعتماد على actor_sumary__dbt_tmp
  3. تُدرَج الصفوف من actor_sumary__dbt_tmp في actor_summary باستخدام INSERT INTO actor_summary SELECT * FROM actor_sumary__dbt_tmp.
تظهر هذه العملية أدناه:

وضع insert_overwrite (تجريبي)

ينفّذ الخطوات التالية:
  1. إنشاء جدول مرحلي (مؤقت) له البنية نفسها لعلاقة النموذج التزايدي: CREATE TABLE {staging} AS {target}.
  2. إدراج السجلات الجديدة فقط (الناتجة عن SELECT) في الجدول المرحلي.
  3. استبدال الأقسام الجديدة فقط (الموجودة في الجدول المرحلي) في الجدول الهدف.

يوفّر هذا النهج المزايا التالية:
  • هو أسرع من الاستراتيجية الافتراضية لأنه لا ينسخ الجدول بالكامل.
  • وهو أكثر أمانًا من الاستراتيجيات الأخرى لأنه لا يعدّل الجدول الأصلي حتى تكتمل عملية INSERT بنجاح. وفي حال حدوث فشل أثناء التنفيذ، يبقى الجدول الأصلي من دون تعديل.
  • ويطبّق أفضل ممارسات هندسة البيانات المتعلقة بـ “ثبات الأقسام”، مما يبسّط معالجة البيانات التزايدية والمتوازية وعمليات التراجع وغير ذلك.

إنشاء لقطة snapshot

تتيح لقطات dbt الاحتفاظ بسجلٍّ للتغييرات التي تطرأ على نموذج قابل للتغيير بمرور الوقت. ويتيح ذلك بدوره إجراء استعلامات عند نقطة زمنية محددة على النماذج، بحيث يمكن للمحللين “العودة بالزمن” إلى الحالة السابقة للنموذج. ويتحقق ذلك باستخدام الأبعاد بطيئة التغيّر من النوع 2، حيث تسجّل أعمدة تاريخَي البداية والنهاية متى كان الصف صالحًا. ويدعم مهايئ ClickHouse هذه الوظيفة، كما هو موضّح أدناه. يفترض هذا المثال أنك أكملت إنشاء نموذج جدول تزايدي. تأكّد من أن ملف actor_summary.sql لا يضبط inserts_only=True. يجب أن يبدو ملف models/actor_summary.sql كما يلي:
  1. أنشئ ملفًا باسم actor_summary في دليل snapshots.
  2. حدّث محتوى ملف actor_summary.sql بالمحتوى التالي:
بعض الملاحظات حول هذا المحتوى:
  • يحدّد استعلام select النتائج التي تريد أخذ snapshot لها بمرور الوقت. وتُستخدم الدالة ref للإشارة إلى نموذج actor_summary الذي أنشأناه سابقًا.
  • نحتاج إلى عمود timestamp للإشارة إلى تغيّرات السجلات. يمكن استخدام عمود updated_at لدينا هنا (راجع إنشاء نموذج جدول تزايدي). وتشير parameter المسماة strategy إلى استخدام timestamp للدلالة على التحديثات، بينما تحدد parameter المسماة updated_at العمود الذي سيُستخدم. وإذا لم يكن هذا موجودًا في النموذج لديك، يمكنك بدلًا من ذلك استخدام استراتيجية check. وهذا أقل كفاءةً بدرجة كبيرة، كما يتطلب من المستخدم تحديد قائمة بالأعمدة المطلوب مقارنتها. ويقارن dbt بين القيم الحالية والتاريخية لهذه الأعمدة، ويسجّل أي تغييرات (أو لا يفعل شيئًا إذا كانت متطابقة).
  1. نفّذ الأمر dbt snapshot.
لاحظ أنه تم إنشاء جدول actor_summary_snapshot في قاعدة بيانات snapshots (كما تحدده المعلمة target_schema).
  1. عند معاينة عيّنة من هذه البيانات، سترى كيف أضاف dbt العمودين dbt_valid_from و dbt_valid_to. أما الأخير فقيَمه مضبوطة على NULL. وستُحدَّث هذه القيم في عمليات التشغيل اللاحقة.
  2. اجعل ممثلنا المفضل Clicky McClickHouse يظهر في عشرة أفلام إضافية.
  3. أعِد تنفيذ الأمر dbt run من الدليل imdb. سيؤدي ذلك إلى تحديث النموذج التزايدي. وبعد اكتمال ذلك، شغّل dbt snapshot لالتقاط التغييرات.
  4. إذا أجرينا الآن استعلامًا على اللقطة، فسنلاحظ وجود صفّين لـ Clicky McClickHouse. وأصبحت للسجل السابق الآن قيمة dbt_valid_to. وسُجِّلت القيمة الجديدة بالقيمة نفسها في العمود dbt_valid_from، مع قيمة dbt_valid_to تساوي null. وإذا كانت لدينا صفوف جديدة، فستُضاف هذه أيضًا إلى اللقطة.
لمزيد من التفاصيل حول لقطات dbt، اطّلع على هنا.

استخدام ملفات seed

يوفّر dbt إمكانية تحميل البيانات من ملفات CSV. وهذه الإمكانية لا تناسب تحميل صادرات كبيرة من قاعدة بيانات، بل صُممت أكثر للملفات الصغيرة التي تُستخدم عادةً لجداول الأكواد والقواميس، مثل ربط رموز البلدان بأسماء البلدان. وفي مثال بسيط، سننشئ ثم نرفع قائمة برموز الأنواع باستخدام وظيفة seed.
  1. ننشئ قائمة برموز الأنواع من مجموعة البيانات الحالية لدينا. من دليل dbt، استخدم clickhouse-client لإنشاء الملف seeds/genre_codes.csv:
  2. نفّذ الأمر dbt seed. سيؤدي ذلك إلى إنشاء table جديدة باسم genre_codes في database لدينا imdb_dbt (كما هو محدد في إعدادات schema) مع rows من ملف CSV الخاص بنا.
  3. أكّد أنه تم تحميلها:

معلومات إضافية

لا تقدّم الأدلة السابقة سوى لمحة موجزة عن إمكانات dbt. ويُنصح المستخدمون بالرجوع إلى وثائق dbt الممتازة.
آخر تعديل في ٢٤ يوليو ٢٠٢٦