الانتقال إلى المحتوى الرئيسي

المقدمة

يعالج ClickHouse الاستعلامات بسرعة فائقة، لكن كيف تُوزَّع هذه الاستعلامات ويُنفَّذ تنفيذها بالتوازي عبر عدة خوادم؟
في هذا الدليل، سنناقش أولًا كيف يوزّع ClickHouse الاستعلام عبر عدة شظايا من خلال الجداول الموزعة، ثم كيف يمكن للاستعلام أن يستفيد من عدة نُسخ متماثلة أثناء تنفيذه.

البنية المُجزّأة

في بنية shared-nothing، تُقسَّم العناقيد عادةً إلى عدة شظايا، بحيث تحتوي كل شظية على مجموعة فرعية من إجمالي البيانات. ويعلو هذه الشظايا جدول موزّع، يوفّر عرضًا موحّدًا لكامل البيانات. يمكن إرسال عمليات القراءة إلى الجدول المحلي. وفي هذه الحالة، لن يُنفَّذ الاستعلام إلا على الشظية المحددة. أو يمكن إرساله إلى الجدول الموزّع، وعندها ستنفّذ كل شظية الاستعلامات المطلوبة. وسيقوم الخادم الذي تم الاستعلام من خلاله عن الجدول الموزّع بتجميع البيانات وإرجاع الرد إلى العميل: يوضح الشكل أعلاه ما يحدث عندما يرسل عميل استعلامًا إلى جدول موزّع:
  1. يُرسل استعلام SELECT إلى جدول موزّع على إحدى العقد بشكل عشوائي (باستخدام استراتيجية التناوب أو بعد توجيهه إلى خادم محدد بواسطة موازن تحميل). وستعمل هذه العقدة الآن كمنسّق.
  2. ستحدّد العقدة كل شظية تحتاج إلى تنفيذ الاستعلام استنادًا إلى المعلومات التي يحددها الجدول الموزّع، ثم يُرسل الاستعلام إلى كل شظية.
  3. تقرأ كل شظية البيانات وتُرشّحها وتُجمّعها محليًا، ثم ترسل حالة قابلة للدمج إلى المنسّق.
  4. تدمج العقدة المنسِّقة البيانات ثم ترسل الاستجابة إلى العميل.
عند إضافة النسخ المتماثلة إلى هذا المزيج، تبقى العملية متشابهة إلى حد كبير، ويكمن الفرق الوحيد في أن نسخة متماثلة واحدة فقط من كل شظية ستنفّذ الاستعلام. وهذا يعني أنه يمكن بعد ذلك معالجة المزيد من الاستعلامات بالتوازي.

بنية غير مُشظّاة

تتمتع ClickHouse Cloud ببنية تختلف كثيرًا عن تلك المعروضة أعلاه. (راجع “بنية ClickHouse Cloud” لمزيد من التفاصيل). ومع فصل المعالجة عن التخزين، ومع توفر مساحة تخزين تكاد تكون غير محدودة، تصبح الحاجة إلى الشظايا أقل أهمية. يوضح الشكل أدناه بنية ClickHouse Cloud: تتيح لنا هذه البنية إضافة النسخ المتماثلة وإزالتها بشكل شبه فوري، مما يضمن قابلية توسع عالية جدًا للعنقود. ويضمن عنقود ClickHouse Keeper (الموضح على اليمين) وجود مرجع وحيد موثوق للبيانات الوصفية. ويمكن للنسخ المتماثلة جلب البيانات الوصفية من عنقود ClickHouse Keeper مع احتفاظها جميعًا بالبيانات نفسها. أما البيانات نفسها فتُخزَّن في التخزين الكائني، وتتيح لنا ذاكرة التخزين المؤقت على SSD تسريع الاستعلامات. لكن كيف يمكننا الآن توزيع تنفيذ الاستعلامات عبر عدة خوادم؟ في بنية مُشظّاة، كان ذلك واضحًا إلى حد ما لأن كل شظية كان يستطيع بالفعل تنفيذ استعلام على مجموعة فرعية من البيانات. فكيف يعمل ذلك عندما لا توجد شظايا؟

تقديم النسخ المتماثلة المتوازية

لموازاة تنفيذ الاستعلام عبر عدة خوادم، نحتاج أولًا إلى القدرة على تعيين أحد خوادمنا بصفته منسّقًا. والمنسّق هو العقدة التي تنشئ قائمة المهام التي يجب تنفيذها، وتضمن تنفيذها جميعًا، وتجميع نتائجها وإرجاعها إلى العميل. وكما هو الحال في معظم الأنظمة الموزعة، يكون هذا دور العقدة التي تستقبل الاستعلام الأولي. ونحتاج أيضًا إلى تحديد وحدة العمل. في بنية تعتمد على الشظايا، تكون وحدة العمل هي الـشظية، أي مجموعة فرعية من البيانات. ومع النسخ المتماثلة المتوازية سنستخدم جزءًا صغيرًا من الجدول، يُسمى الحبيبات، بوصفه وحدة العمل. والآن، لنرَ كيف يعمل ذلك عمليًا بمساعدة الشكل أدناه: مع النسخ المتماثلة المتوازية:
  1. يُرسَل الاستعلام من العميل إلى عقدة واحدة بعد مروره عبر موازن تحميل. وتصبح هذه العقدة المنسّق لهذا الاستعلام.
  2. تحلّل العقدة فهرس كل جزء، وتحدّد الأجزاء والحبيبات المناسبة للمعالجة.
  3. يقسّم المنسّق عبء العمل إلى مجموعة من الحبيبات التي يمكن إسنادها إلى نسخ متماثلة مختلفة.
  4. تُعالَج كل مجموعة من الحبيبات بواسطة النسخ المتماثلة المقابلة لها، وتُرسَل حالة قابلة للدمج إلى المنسّق عند الانتهاء.
  5. أخيرًا، يدمج المنسّق جميع نتائج النسخ المتماثلة ثم يعيد النتيجة إلى العميل.
توضّح الخطوات أعلاه كيفية عمل النسخ المتماثلة المتوازية من الناحية النظرية. ومع ذلك، عمليًا، هناك كثير من العوامل التي قد تمنع هذه الآلية من العمل على نحو مثالي:
  1. قد تكون بعض النسخ المتماثلة غير متاحة.
  2. تكون آلية النسخ المتماثل في ClickHouse غير متزامنة، وقد لا تحتوي بعض النسخ المتماثلة على الأجزاء نفسها في لحظة معينة.
  3. يجب التعامل مع كمون الذيل بين النسخ المتماثلة.
  4. تختلف ذاكرة التخزين المؤقت لنظام الملفات من نسخة متماثلة إلى أخرى بحسب النشاط على كل نسخة متماثلة، ما يعني أن إسناد المهام عشوائيًا قد يؤدي إلى أداء أقل كفاءة نظرًا إلى محلية ذاكرة التخزين المؤقت.
نستعرض في الأقسام التالية كيفية تجاوز هذه العوامل.

الإعلانات

لمعالجة (1) و(2) من القائمة أعلاه، قدّمنا مفهوم الإعلان. لنتصوّر كيف يعمل ذلك باستخدام الشكل أدناه:
  1. يُرسَل الاستعلام من العميل إلى إحدى العُقد بعد مروره عبر موازن التحميل. وتصبح هذه العقدة المنسّق لهذا الاستعلام.
  2. ترسل العقدة المنسِّقة طلبًا للحصول على إعلانات من جميع النسخ المتماثلة في العنقود. وقد تكون لدى النسخ المتماثلة تصوّرات مختلفة قليلًا عن المجموعة الحالية من الأجزاء في جدولٍ ما. لذلك نحتاج إلى جمع هذه المعلومات لتجنّب اتخاذ قرارات جدولة غير صحيحة.
  3. تستخدم العقدة المنسِّقة بعد ذلك الإعلانات لتحديد مجموعة من الحبيبات التي يمكن إسنادها إلى النسخ المتماثلة المختلفة. هنا، على سبيل المثال، نرى أنه لم تُسنَد أي حبيبات من الجزء 3 إلى النسخة المتماثلة 2 لأن هذه النسخة المتماثلة لم تُدرِج هذا الجزء في إعلانها. ولاحظ أيضًا أنه لم تُسنَد أي مهام إلى النسخة المتماثلة 3 لأن هذه النسخة لم تقدّم إعلانًا.
  4. بعد أن تعالج كل نسخة متماثلة الاستعلام على مجموعتها الفرعية من الحبيبات وتُرسَل الحالة القابلة للدمج مرة أخرى إلى المنسّق، يدمج المنسّق النتائج ثم يُرسَل الرد إلى العميل.

التنسيق الديناميكي

لمعالجة مشكلة كمون الذيل، أضفنا التنسيق الديناميكي. ويعني ذلك أن الحبيبات لا تُرسَل كلها إلى نسخة متماثلة ضمن طلب واحد، بل تتمكن كل نسخة متماثلة من طلب مهمة جديدة (أي مجموعة من الحبيبات المطلوب معالجتها) من المنسّق. ويزوّد المنسّق النسخة المتماثلة بمجموعة الحبيبات بناءً على الإعلان الذي تلقّاه. لنفترض أننا وصلنا إلى مرحلة من العملية تكون فيها جميع النسخ المتماثلة قد أرسلت إعلانًا يتضمن جميع الأجزاء. يوضّح الشكل أدناه كيفية عمل التنسيق الديناميكي:
  1. تُبلغ النسخ المتماثلة عقدة المنسّق بأنها قادرة على معالجة المهام، ويمكنها أيضًا تحديد مقدار العمل الذي تستطيع معالجته.
  2. يوزّع المنسّق المهام على النسخ المتماثلة.
  1. تتمكن النسختان المتماثلتان 1 و2 من إنهاء مهمتهما بسرعة كبيرة، لذا تطلبان مهمة أخرى من عقدة المنسّق.
  2. يوزّع المنسّق مهامًا جديدة على النسختين المتماثلتين 1 و2.
  1. تكون جميع النسخ المتماثلة قد انتهت الآن من معالجة مهامها. وتطلب المزيد من المهام.
  2. يتحقق المنسّق، باستخدام الإعلانات، من المهام التي لا تزال بحاجة إلى معالجة، لكنه لا يجد أي مهام متبقية.
  3. يُبلغ المنسّق النسخ المتماثلة بأن كل شيء قد تمت معالجته. وسيقوم الآن بدمج جميع الحالات القابلة للدمج والرد على الاستعلام.

إدارة محلية ذاكرة التخزين المؤقت

آخر مشكلة محتملة متبقية هي كيفية التعامل مع محلية ذاكرة التخزين المؤقت. إذا نُفِّذ الاستعلام عدة مرات، فكيف نضمن توجيه المهمة نفسها إلى النسخة المتماثلة نفسها؟ في المثال السابق، كانت المهام موزعة على النحو التالي:
النسخة المتماثلة 1النسخة المتماثلة 2النسخة المتماثلة 3
الجزء 1g1, g6, g7g2, g4, g5g3
الجزء 2g1g2, g4, g5g3
الجزء 3g1, g6g2, g4, g5g3
ولضمان إسناد المهام نفسها إلى النسخ المتماثلة نفسها بحيث تستفيد من ذاكرة التخزين المؤقت، يحدث أمران. أولًا، تُحتسب قيمة hash للجزء + مجموعة الحبيبات (أي المهمة). ثانيًا، يُطبَّق باقي قسمة عدد النسخ المتماثلة لتحديد إسناد المهمة. نظريًا يبدو هذا جيدًا، لكن عمليًا قد يتسبب حمل مفاجئ على إحدى النسخ المتماثلة، أو تدهور في الشبكة، في زيادة زمن الاستجابة عند الذيل إذا استُخدمت النسخة المتماثلة نفسها باستمرار لتنفيذ مهام معينة. إذا كانت قيمة max_parallel_replicas أقل من عدد النسخ المتماثلة، فستُختار نسخ متماثلة عشوائية لتنفيذ الاستعلام.

استيلاء المهام

إذا كانت إحدى النسخ المتماثلة تعالج المهام أبطأ من غيرها، فستحاول النسخ الأخرى ‘الاستيلاء’ على المهام التي تكون مخصّصةً مبدئيًا لتلك النسخة المتماثلة وفقًا لـ hash، وذلك لتقليل كمون الذيل.

القيود

لهذه الميزة قيود معروفة، وقد وُثِّقت أهمها في هذا القسم.
إذا صادفت مشكلة لا تندرج ضمن القيود المذكورة أدناه، وتشتبه في أن النسخة المتماثلة المتوازية هي السبب، يُرجى فتح issue على GitHub باستخدام الوسم comp-parallel-replicas.
القيدالوصف
الاستعلامات المعقدةتعمل النسخ المتماثلة المتوازية حاليًا بشكل جيد إلى حدٍّ ما مع الاستعلامات البسيطة. ويمكن أن تؤثر عناصر التعقيد مثل CTEs وsubqueries وJOINs والاستعلامات غير المسطّحة وغيرها سلبًا في أداء الاستعلام.
الاستعلامات الصغيرةإذا كنت تنفّذ استعلامًا لا يعالج عددًا كبيرًا من الصفوف، فقد لا يؤدي تشغيله على عدة نُسخ متماثلة إلى تحسين زمن التنفيذ، لأن زمن الشبكة اللازم للتنسيق بين النُسخ المتماثلة قد يضيف دورات إضافية إلى تنفيذ الاستعلام. يمكنك الحد من هذه المشكلات باستخدام الإعداد: parallel_replicas_min_number_of_rows_per_replica.
تُعطَّل النسخ المتماثلة المتوازية مع FINAL
لا تُستخدم الإسقاطات مع النسخ المتماثلة المتوازية معًا
البيانات عالية التفرّد والتجميع المعقّدقد يؤدي التجميع عالي التفرّد الذي يتطلب إرسال كمية كبيرة من البيانات إلى إبطاء استعلاماتك بشكل ملحوظ.
التوافق مع المحلّل الجديدقد يؤدي المحلّل الجديد إلى إبطاء تنفيذ الاستعلام أو تسريعه بشكل ملحوظ في سيناريوهات محددة.
SettingDescription
enable_parallel_replicas0: معطّل
1: مفعّل
2: فرض استخدام النسخة المتماثلة المتوازية، وسيُطلق استثناء إذا لم تُستخدم.
cluster_for_parallel_replicasاسم العنقود المُستخدَم للنسخ المتماثل المتوازي؛ وإذا كنت تستخدم ClickHouse Cloud، فاستخدم default.
max_parallel_replicasالحد الأقصى لعدد النسخ المتماثلة المستخدمة في تنفيذ الاستعلام عبر عدة نسخ متماثلة. وإذا تم تحديد عدد أقل من عدد النسخ المتماثلة في العنقود، فسيتم اختيار العُقد عشوائيًا. ويمكن أيضًا الإفراط في تخصيص هذه القيمة لمراعاة التوسّع الأفقي.
parallel_replicas_min_number_of_rows_per_replicaيساعد في تقييد عدد النسخ المتماثلة المستخدمة بناءً على عدد الصفوف المطلوب معالجتها. ويُحدَّد عدد النسخ المتماثلة المستخدمة وفقًا لما يلي:
estimated rows to read / min_number_of_rows_per_replica.
enable_analyzerلا يُدعَم تنفيذ الاستعلام باستخدام النسخ المتماثلة المتوازية إلا عند تفعيل المُحلِّل

استقصاء المشكلات المتعلقة بالنسخ المتماثلة المتوازية

يمكنك التحقق من الإعدادات المستخدمة لكل query في جدول system.query_log. ويمكنك أيضًا الاطلاع على جدول system.events لرؤية جميع الأحداث التي حدثت على الخادم، كما يمكنك استخدام table function clusterAllReplicas لرؤية الجداول على جميع النسخ المتماثلة (إذا كنت من مستخدمي Cloud، فاستخدم default).
Query
SELECT
   hostname(),
   *
FROM clusterAllReplicas('default', system.events)
WHERE event ILIKE '%ParallelReplicas%'
Response
┌─hostname()───────────────────────┬─event──────────────────────────────────────────┬─value─┬─description──────────────────────────────────────────────────────────────────────────────────────────┐
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasHandleRequestMicroseconds      │   438 │ Time spent processing requests for marks from replicas                                               │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasHandleAnnouncementMicroseconds │   558 │ Time spent processing replicas announcements                                                         │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasReadUnassignedMarks            │   240 │ Sum across all replicas of how many unassigned marks were scheduled                                  │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasReadAssignedForStealingMarks   │     4 │ Sum across all replicas of how many of scheduled marks were assigned for stealing by consistent hash │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasStealingByHashMicroseconds     │     5 │ Time spent collecting segments meant for stealing by hash                                            │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasProcessingPartsMicroseconds    │     5 │ Time spent processing data parts                                                                     │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasStealingLeftoversMicroseconds  │     3 │ Time spent collecting orphaned segments                                                              │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasUsedCount                      │     2 │ Number of replicas used to execute a query with task-based parallel replicas                         │
│ c-crimson-vd-86-server-rdhnsx3-0 │ ParallelReplicasAvailableCount                 │     6 │ Number of replicas available to execute a query with task-based parallel replicas                    │
└──────────────────────────────────┴────────────────────────────────────────────────┴───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─hostname()───────────────────────┬─event──────────────────────────────────────────┬─value─┬─description──────────────────────────────────────────────────────────────────────────────────────────┐
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasHandleRequestMicroseconds      │   698 │ Time spent processing requests for marks from replicas                                               │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasHandleAnnouncementMicroseconds │   644 │ Time spent processing replicas announcements                                                         │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasReadUnassignedMarks            │   190 │ Sum across all replicas of how many unassigned marks were scheduled                                  │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasReadAssignedForStealingMarks   │    54 │ Sum across all replicas of how many of scheduled marks were assigned for stealing by consistent hash │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasStealingByHashMicroseconds     │     8 │ Time spent collecting segments meant for stealing by hash                                            │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasProcessingPartsMicroseconds    │     4 │ Time spent processing data parts                                                                     │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasStealingLeftoversMicroseconds  │     2 │ Time spent collecting orphaned segments                                                              │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasUsedCount                      │     2 │ Number of replicas used to execute a query with task-based parallel replicas                         │
│ c-crimson-vd-86-server-e9kp5f0-0 │ ParallelReplicasAvailableCount                 │     6 │ Number of replicas available to execute a query with task-based parallel replicas                    │
└──────────────────────────────────┴────────────────────────────────────────────────┴───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─hostname()───────────────────────┬─event──────────────────────────────────────────┬─value─┬─description──────────────────────────────────────────────────────────────────────────────────────────┐
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasHandleRequestMicroseconds      │   620 │ Time spent processing requests for marks from replicas                                               │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasHandleAnnouncementMicroseconds │   656 │ Time spent processing replicas announcements                                                         │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasReadUnassignedMarks            │     1 │ Sum across all replicas of how many unassigned marks were scheduled                                  │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasReadAssignedForStealingMarks   │     1 │ Sum across all replicas of how many of scheduled marks were assigned for stealing by consistent hash │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasStealingByHashMicroseconds     │     4 │ Time spent collecting segments meant for stealing by hash                                            │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasProcessingPartsMicroseconds    │     3 │ Time spent processing data parts                                                                     │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasStealingLeftoversMicroseconds  │     1 │ Time spent collecting orphaned segments                                                              │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasUsedCount                      │     2 │ Number of replicas used to execute a query with task-based parallel replicas                         │
│ c-crimson-vd-86-server-ybtm18n-0 │ ParallelReplicasAvailableCount                 │    12 │ Number of replicas available to execute a query with task-based parallel replicas                    │
└──────────────────────────────────┴────────────────────────────────────────────────┴───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘
┌─hostname()───────────────────────┬─event──────────────────────────────────────────┬─value─┬─description──────────────────────────────────────────────────────────────────────────────────────────┐
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasHandleRequestMicroseconds      │   696 │ Time spent processing requests for marks from replicas                                               │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasHandleAnnouncementMicroseconds │   717 │ Time spent processing replicas announcements                                                         │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasReadUnassignedMarks            │     2 │ Sum across all replicas of how many unassigned marks were scheduled                                  │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasReadAssignedForStealingMarks   │     2 │ Sum across all replicas of how many of scheduled marks were assigned for stealing by consistent hash │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasStealingByHashMicroseconds     │    10 │ Time spent collecting segments meant for stealing by hash                                            │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasProcessingPartsMicroseconds    │     6 │ Time spent processing data parts                                                                     │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasStealingLeftoversMicroseconds  │     2 │ Time spent collecting orphaned segments                                                              │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasUsedCount                      │     2 │ Number of replicas used to execute a query with task-based parallel replicas                         │
│ c-crimson-vd-86-server-16j1ncj-0 │ ParallelReplicasAvailableCount                 │    12 │ Number of replicas available to execute a query with task-based parallel replicas                    │
└──────────────────────────────────┴────────────────────────────────────────────────┴───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────┘
يتضمن جدول system.text_log أيضًا معلومات عن تنفيذ الاستعلامات باستخدام النسخ المتماثلة المتوازية:
Query
SELECT message
FROM clusterAllReplicas('default', system.text_log)
WHERE query_id = 'ad40c712-d25d-45c4-b1a1-a28ba8d4019c'
ORDER BY event_time_microseconds ASC
Response
┌─message────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ (from 54.218.178.249:59198) SELECT * FROM session_events WHERE type='type2' LIMIT 10 SETTINGS allow_experimental_parallel_reading_from_replicas=2; (stage: Complete)                                                                                       │
│ Query SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') SETTINGS allow_experimental_parallel_reading_from_replicas = 2 to stage Complete │
│ Access granted: SELECT(clientId, sessionId, pageId, timestamp, type) ON default.session_events                                                                                                                                                             │
│ Query SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') to stage WithMergeableState only analyze │
│ Access granted: SELECT(clientId, sessionId, pageId, timestamp, type) ON default.session_events                                                                                                                                                             │
│ Query SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') from stage FetchColumns to stage WithMergeableState only analyze │
│ Query SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') SETTINGS allow_experimental_parallel_reading_from_replicas = 2 to stage WithMergeableState only analyze │
│ Access granted: SELECT(clientId, sessionId, pageId, timestamp, type) ON default.session_events                                                                                                                                                             │
│ Query SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') SETTINGS allow_experimental_parallel_reading_from_replicas = 2 from stage FetchColumns to stage WithMergeableState only analyze │
│ Query SELECT __table1.clientId AS clientId, __table1.sessionId AS sessionId, __table1.pageId AS pageId, __table1.timestamp AS timestamp, __table1.type AS type FROM default.session_events AS __table1 WHERE __table1.type = 'type2' LIMIT _CAST(10, 'UInt64') SETTINGS allow_experimental_parallel_reading_from_replicas = 2 from stage WithMergeableState to stage Complete │
│ The number of replicas requested (100) is bigger than the real number available in the cluster (6). Will use the latter number to execute the query.                                                                                                       │
│ Initial request from replica 4: 2 parts: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
Received from 4 replica

│ Reading state is fully initialized: part all_0_2_1 with ranges [(0, 182)] in replicas [4]; part all_3_3_0 with ranges [(0, 62)] in replicas [4]                                                                                                            │
│ Sent initial requests: 1 Replicas count: 6                                                                                                                                                                                                                 │
│ Initial request from replica 2: 2 parts: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
Received from 2 replica

│ Sent initial requests: 2 Replicas count: 6                                                                                                                                                                                                                 │
│ Handling request from replica 4, minimal marks size is 240                                                                                                                                                                                                 │
│ Going to respond to replica 4 with 1 parts: [part all_0_2_1 with ranges [(128, 182)]]. Finish: false; mine_marks=0, stolen_by_hash=54, stolen_rest=0                                                                                                       │
│ Initial request from replica 1: 2 parts: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
Received from 1 replica

│ Sent initial requests: 3 Replicas count: 6                                                                                                                                                                                                                 │
│ Handling request from replica 4, minimal marks size is 240                                                                                                                                                                                                 │
│ Going to respond to replica 4 with 2 parts: [part all_0_2_1 with ranges [(0, 128)], part all_3_3_0 with ranges [(0, 62)]]. Finish: false; mine_marks=0, stolen_by_hash=0, stolen_rest=190                                                                  │
│ Initial request from replica 0: 2 parts: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
Received from 0 replica

│ Sent initial requests: 4 Replicas count: 6                                                                                                                                                                                                                 │
│ Initial request from replica 5: 2 parts: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
Received from 5 replica

│ Sent initial requests: 5 Replicas count: 6                                                                                                                                                                                                                 │
│ Handling request from replica 2, minimal marks size is 240                                                                                                                                                                                                 │
│ Going to respond to replica 2 with 0 parts: []. Finish: true; mine_marks=0, stolen_by_hash=0, stolen_rest=0                                                                                                                                                │
│ Initial request from replica 3: 2 parts: [part all_0_2_1 with ranges [(0, 182)], part all_3_3_0 with ranges [(0, 62)]]----------
Received from 3 replica

│ Sent initial requests: 6 Replicas count: 6                                                                                                                                                                                                                 │
│ Total rows to read: 2000000                                                                                                                                                                                                                                │
│ Handling request from replica 5, minimal marks size is 240                                                                                                                                                                                                 │
│ Going to respond to replica 5 with 0 parts: []. Finish: true; mine_marks=0, stolen_by_hash=0, stolen_rest=0                                                                                                                                                │
│ Handling request from replica 0, minimal marks size is 240                                                                                                                                                                                                 │
│ Going to respond to replica 0 with 0 parts: []. Finish: true; mine_marks=0, stolen_by_hash=0, stolen_rest=0                                                                                                                                                │
│ Handling request from replica 1, minimal marks size is 240                                                                                                                                                                                                 │
│ Going to respond to replica 1 with 0 parts: []. Finish: true; mine_marks=0, stolen_by_hash=0, stolen_rest=0                                                                                                                                                │
│ Handling request from replica 3, minimal marks size is 240                                                                                                                                                                                                 │
│ Going to respond to replica 3 with 0 parts: []. Finish: true; mine_marks=0, stolen_by_hash=0, stolen_rest=0                                                                                                                                                │
│ (c-crimson-vd-86-server-rdhnsx3-0.c-crimson-vd-86-server-headless.ns-crimson-vd-86.svc.cluster.local:9000) Cancelling query because enough data has been read                                                                                              │
│ Read 81920 rows, 5.16 MiB in 0.013166 sec., 6222087.194288318 rows/sec., 391.63 MiB/sec.                                                                                                                                                                   │
│ Coordination done: Statistics: replica 0 - {requests: 2 marks: 0 assigned_to_me: 0 stolen_by_hash: 0 stolen_unassigned: 0}; replica 1 - {requests: 2 marks: 0 assigned_to_me: 0 stolen_by_hash: 0 stolen_unassigned: 0}; replica 2 - {requests: 2 marks: 0 assigned_to_me: 0 stolen_by_hash: 0 stolen_unassigned: 0}; replica 3 - {requests: 2 marks: 0 assigned_to_me: 0 stolen_by_hash: 0 stolen_unassigned: 0}; replica 4 - {requests: 3 marks: 244 assigned_to_me: 0 stolen_by_hash: 54 stolen_unassigned: 190}; replica 5 - {requests: 2 marks: 0 assigned_to_me: 0 stolen_by_hash: 0 stolen_unassigned: 0} │
│ Peak memory usage (for query): 1.81 MiB.                                                                                                                                                                                                                   │
│ Processed in 0.024095586 sec.                                                                                                                                                                                                                              │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
أخيرًا، يمكنك أيضًا استخدام EXPLAIN PIPELINE. يوضّح هذا كيف سيُنفِّذ ClickHouse الاستعلام والموارد التي سيستخدمها في تنفيذه. لنأخذ الاستعلام التالي مثالًا:
SELECT count(), uniq(pageId) , min(timestamp), max(timestamp) 
FROM session_events 
WHERE type='type3' 
GROUP BY toYear(timestamp) LIMIT 10
لنلقِ نظرة على مسار تنفيذ الاستعلام بدون استخدام parallel replica:
EXPLAIN PIPELINE (without parallel replica)
EXPLAIN PIPELINE graph = 1, compact = 0 
SELECT count(), uniq(pageId) , min(timestamp), max(timestamp) 
FROM session_events 
WHERE type='type3' 
GROUP BY toYear(timestamp) 
LIMIT 10 
SETTINGS allow_experimental_parallel_reading_from_replicas=0 
FORMAT TSV;
والآن مع النسخ المتماثلة المتوازية:
EXPLAIN PIPELINE (with parallel replica)
EXPLAIN PIPELINE graph = 1, compact = 0 
SELECT count(), uniq(pageId) , min(timestamp), max(timestamp) 
FROM session_events 
WHERE type='type3' 
GROUP BY toYear(timestamp) 
LIMIT 10 
SETTINGS allow_experimental_parallel_reading_from_replicas=2 
FORMAT TSV;
آخر تعديل في ٢٩ يونيو ٢٠٢٦