الانتقال إلى المحتوى الرئيسي
صُمم ClickHouse ليكون سريعًا. فهو ينفّذ الاستعلامات بدرجة عالية من التوازي، مستخدمًا جميع أنوية CPU المتاحة، وموزّعًا البيانات عبر مسارات المعالجة، وغالبًا ما يدفع العتاد إلى الاقتراب من حدوده القصوى. يوضح هذا الدليل كيفية عمل توازي الاستعلامات في ClickHouse، وكيف يمكنك ضبطه أو مراقبته لتحسين الأداء في أعباء العمل الكبيرة. نستخدم استعلامًا تجميعيًا على مجموعة البيانات uk_price_paid_simple لتوضيح المفاهيم الأساسية.

خطوة بخطوة: كيف ينفّذ ClickHouse استعلامًا تجميعيًا بالتوازي

عندما يُشغِّل ClickHouse ① استعلامًا تجميعيًا مع عامل تصفية على المفتاح الأساسي للجدول، فإنه ② يحمّل الفهرس الأساسي إلى الذاكرة من أجل ③ تحديد الحبيبات التي يجب معالجتها وتلك التي يمكن تخطيها بأمان:

توزيع العمل عبر مسارات المعالجة

ثم تُوزَّع البيانات المحددة ديناميكيًا على n من مسارات المعالجة المتوازية، حيث تُمرَّر البيانات وتُعالَج فيها كتلة بعد أخرى حتى الوصول إلى النتيجة النهائية:

يخضع عدد n من مسارات المعالجة المتوازية لإعداد max_threads، والذي يساوي افتراضيًا عدد الأنوية (الخيوط) في CPU واحد المتاح لـ ClickHouse على الخادم. في المثال أعلاه، نفترض وجود 4 أنوية. على جهاز يحتوي على 8 أنوية، ستتضاعف تقريبًا إنتاجية معالجة الاستعلام (لكن استخدام الذاكرة سيزداد أيضًا تبعًا لذلك)، لأن عددًا أكبر من المسارات سيعالج البيانات بالتوازي:

ويُعد التوزيع الفعّال للمسارات عاملًا أساسيًا لتحقيق أقصى استفادة من CPU وتقليل الزمن الإجمالي للاستعلام.

معالجة الاستعلامات على الجداول المُجزأة

عندما تكون بيانات الجدول موزعة عبر عدة خوادم على شكل shards، يعالج كل خادم الجزء الخاص به بالتوازي. وداخل كل خادم، تُعالَج البيانات المحلية باستخدام مسارات معالجة متوازية، تمامًا كما هو موضح أعلاه:

يجمع الخادم الذي يستقبل الاستعلام أولًا جميع النتائج الفرعية من الـ shards ويَدمجها في النتيجة النهائية العامة. يتيح توزيع حمل الاستعلامات عبر الـ shards التوسع الأفقي في التوازي، وخاصة في البيئات ذات الإنتاجية العالية.
يستخدم ClickHouse Cloud النُسخ المتماثلة المتوازية بدلًا من الـ shardsفي ClickHouse Cloud، يتحقق هذا المستوى نفسه من التوازي من خلال parallel replicas، التي تعمل بصورة مشابهة للـ shards في العناقيد من نوع shared-nothing. وتعالج كل replica في ClickHouse Cloud — وهي عقدة compute عديمة الحالة — جزءًا من البيانات بالتوازي وتُسهم في النتيجة النهائية، تمامًا كما يفعل shard مستقل.

مراقبة توازي الاستعلامات

استخدم هذه الأدوات للتحقق من أن استعلامك يستفيد بالكامل من موارد CPU المتاحة، ولتشخيص الحالات التي لا يحدث فيها ذلك. نُشغّل هذا على خادم اختباري يحتوي على 59 نواة CPU، مما يتيح لـ ClickHouse أن يُظهر قدراته في توازي الاستعلامات بشكل كامل. لمعرفة كيفية تنفيذ الاستعلام التجريبي، يمكننا توجيه خادم ClickHouse لإرجاع جميع إدخالات السجل بمستوى trace أثناء استعلام التجميع. في هذا العرض التوضيحي، أزلنا الشرط من الاستعلام — وإلا فلن تُعالَج سوى 3 حبيبات، وهو ما لا يوفر بيانات كافية ليستفيد ClickHouse من أكثر من عدد قليل من مسارات المعالجة المتوازية:
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
SETTINGS send_logs_level='trace';
① <Debug> ...: 3609 marks to read from 3 ranges
② <Trace> ...: Spreading mark ranges among streams
② <Debug> ...: Reading approx. 29564928 rows with 59 streams
يمكننا أن نرى أن
  • ① يحتاج ClickHouse إلى قراءة 3,609 حبيبة (يُشار إليها على أنها علامات في سجلات التتبّع) عبر 3 نطاقات بيانات.
  • ② مع 59 نواة CPU، يوزّع هذا العمل على 59 تدفق معالجة متوازيًا—واحد لكل مسار.
بدلًا من ذلك، يمكننا استخدام عبارة EXPLAIN لفحص المخطط الفعلي للمشغّلات—المعروف أيضًا باسم “مسار تنفيذ الاستعلام”—لاستعلام التجميع:
EXPLAIN PIPELINE
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple;
    ┌─explain───────────────────────────────────────────────────────────────────────────┐
 1. │ (Expression)                                                                      │
 2. │ ExpressionTransform × 59                                                          │
 3. │   (Aggregating)                                                                   │
 4. │   Resize 59 → 59                                                                  │
 5. │     AggregatingTransform × 59                                                     │
 6. │       StrictResize 59 → 59                                                        │
 7. │         (Expression)                                                              │
 8. │         ExpressionTransform × 59                                                  │
 9. │           (ReadFromMergeTree)                                                     │
10. │           MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread) × 59 0 → 1 │
    └───────────────────────────────────────────────────────────────────────────────────┘
ملاحظة: اقرأ مخطط المشغّلات أعلاه من الأسفل إلى الأعلى. يمثّل كل سطر مرحلةً في خطة التنفيذ الفيزيائية، بدءًا من قراءة البيانات من التخزين في الأسفل وانتهاءً بخطوات المعالجة النهائية في الأعلى. تُنفَّذ المشغّلات المعلَّمة بـ × 59 بالتوازي عبر مناطق بيانات غير متداخلة ضمن 59 مسار معالجة متوازيًا. يعكس ذلك قيمة max_threads ويوضح كيف تُنفَّذ كل مرحلة من مراحل الاستعلام بالتوازي عبر أنوية CPU. يمكن لـ واجهة الويب المضمّنة في ClickHouse (المتاحة عند نقطة النهاية /play) عرض الخطة الفيزيائية أعلاه في شكل تصوّر بياني. في هذا المثال، نضبط max_threads على 4 للحفاظ على التصوّر مختصرًا، بحيث يعرض 4 مسارات معالجة متوازية فقط: ملاحظة: اقرأ التصوّر من اليسار إلى اليمين. يمثّل كل صف مسار معالجة متوازيًا يمرّر كتل البيانات كتلةً تلو الأخرى، مع تطبيق تحويلات مثل التصفية والتجميع ومراحل المعالجة النهائية. في هذا المثال، يمكنك رؤية أربعة مسارات متوازية تقابل الإعداد max_threads = 4.

موازنة التحميل عبر مسارات المعالجة

لاحظ أن المشغّلات Resize في الخطة الفيزيائية أعلاه تعيد التقسيم وإعادة التوزيع لتدفقات كتل البيانات عبر مسارات المعالجة، بما يضمن استخدامها بصورة متوازنة. وتكتسب إعادة الموازنة هذه أهمية خاصة عندما تختلف نطاقات البيانات في عدد الصفوف التي تطابق شروط الاستعلام، وإلا فقد تُرهَق بعض المسارات بينما تبقى أخرى خاملة. ومن خلال إعادة توزيع العمل، تساعد المسارات الأسرع نظيراتها الأبطأ فعليًا، مما يحسّن زمن تنفيذ الاستعلام إجمالًا.

لماذا لا يُلتزم دائمًا بـ max_threads

كما ذُكر أعلاه، يُتحكَّم في عدد مسارات المعالجة المتوازية n بواسطة الإعداد max_threads، والذي يطابق افتراضيًا عدد أنوية CPU المتاحة لـ ClickHouse على الخادم:
SELECT getSetting('max_threads');
   ┌─getSetting('max_threads')─┐
1. │                        59 │
   └───────────────────────────┘
ومع ذلك، قد يتم تجاهل قيمة max_threads اعتمادًا على كمية البيانات المختارة للمعالجة:
EXPLAIN PIPELINE
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON';
...   
(ReadFromMergeTree)
MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread) × 30
كما يظهر في المقتطف أعلاه من مخطط المشغّل، على الرغم من ضبط max_threads على 59، لا يستخدم ClickHouse سوى 30 تدفقًا متزامنًا لفحص البيانات. والآن لنشغّل الاستعلام:
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON';
   ┌─max(price)─┐
1. │  594300000 │ -- 594.30 million
   └────────────┘
   
1 row in set. Elapsed: 0.013 sec. Processed 2.31 million rows, 13.66 MB (173.12 million rows/s., 1.02 GB/s.)
Peak memory usage: 27.24 MiB.   
كما هو موضح في المخرجات أعلاه، عالج الاستعلام 2.31 مليون صف وقرأ 13.66MB من البيانات. ويعود ذلك إلى أنه خلال مرحلة تحليل الفهرس، اختار ClickHouse 282 حبيبة للمعالجة، يحتوي كل منها على 8,192 صفًا، ليصل الإجمالي إلى نحو 2.31 مليون صف:
EXPLAIN indexes = 1
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON';
    ┌─explain───────────────────────────────────────────────┐
 1. │ Expression ((Project names + Projection))             │
 2. │   Aggregating                                         │
 3. │     Expression (Before GROUP BY)                      │
 4. │       Expression                                      │
 5. │         ReadFromMergeTree (uk.uk_price_paid_simple)   │
 6. │         Indexes:                                      │
 7. │           PrimaryKey                                  │
 8. │             Keys:                                     │
 9. │               town                                    │
10. │             Condition: (town in ['LONDON', 'LONDON']) │
11. │             Parts: 3/3                                │
12. │             Granules: 282/3609                        │
    └───────────────────────────────────────────────────────┘  
بغضّ النظر عن قيمة max_threads المُعدّة، لا يخصّص ClickHouse مسارات معالجة متوازية إضافية إلا عندما تكون هناك بيانات كافية تبرّر ذلك. وتشير كلمة “max” في max_threads إلى حدٍّ أقصى، لا إلى عددٍ مضمون من خيوط التنفيذ المستخدمة. والمقصود بـ “البيانات الكافية” يتحدّد أساسًا من خلال إعدادين يحددان الحد الأدنى لعدد الصفوف (163,840 افتراضيًا) والحد الأدنى لعدد البايتات (2,097,152 افتراضيًا) التي ينبغي أن يعالجها كل مسار معالجة: بالنسبة إلى عناقيد shared-nothing: بالنسبة إلى العناقيد ذات التخزين المشترك (مثل ClickHouse Cloud): بالإضافة إلى ذلك، هناك حدٌّ أدنى صارم لحجم مهمة القراءة، ويُتحكَّم فيه بواسطة:
لا تعدّل هذه الإعداداتلا نوصي بتعديل هذه الإعدادات في بيئات الإنتاج. وهي معروضة هنا فقط لتوضيح سبب عدم كون max_threads محدِّدًا دائمًا للمستوى الفعلي من التوازي.
لأغراض العرض التوضيحي، لنفحص الخطة الفعلية بعد تجاوز هذه الإعدادات لفرض أقصى درجة من التزامن:
EXPLAIN PIPELINE
SELECT
   max(price)
FROM
   uk.uk_price_paid_simple
WHERE town = 'LONDON'
SETTINGS
  max_threads = 59,
  merge_tree_min_read_task_size = 0,
  merge_tree_min_rows_for_concurrent_read_for_remote_filesystem = 0, 
  merge_tree_min_bytes_for_concurrent_read_for_remote_filesystem = 0;
...   
(ReadFromMergeTree)
MergeTreeSelect(pool: PrefetchedReadPool, algorithm: Thread) × 59
يستخدم ClickHouse الآن 59 تدفقًا متزامنًا لفحص البيانات، مع الالتزام الكامل بالقيمة المضبوطة لـ max_threads. يوضح هذا أنه عند تنفيذ استعلامات على مجموعات بيانات صغيرة، يحد ClickHouse التوازي عمدًا. استخدم تجاوزات الإعدادات للاختبار فقط—not in production—إذ قد تؤدي إلى تنفيذ غير فعّال أو إلى تنازع على الموارد.

أهم النقاط

  • يعالج ClickHouse الاستعلامات بالتوازي باستخدام مسارات معالجة مرتبطة بـ max_threads.
  • يعتمد العدد الفعلي للمسارات على حجم البيانات المختارة للمعالجة.
  • استخدم EXPLAIN PIPELINE وسجلات التتبّع لتحليل استخدام المسارات.

أين يمكن العثور على مزيد من المعلومات

إذا كنت ترغب في التعمق أكثر في كيفية تنفيذ ClickHouse للاستعلامات بالتوازي وكيف يحقق أداءً عاليًا على نطاق واسع، فاطّلع على الموارد التالية:
آخر تعديل في ٢٥ يونيو ٢٠٢٦