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

مقدمة

في هذا الدليل، سنتناول فهرسة ClickHouse بعمق. وسنوضح ونناقش بالتفصيل: يمكنك اختياريًا تنفيذ جميع عبارات ClickHouse SQL والاستعلامات الواردة في هذا الدليل بنفسك على جهازك. للاطلاع على تعليمات تثبيت ClickHouse والبدء، راجع Quick Start.
يركز هذا الدليل على الفهارس الأساسية المتناثرة في ClickHouse.للاطلاع على فهارس تخطي البيانات الثانوية في ClickHouse، راجع دليل عملي.

مجموعة البيانات

سنستخدم في هذا الدليل مجموعة بيانات نموذجية مجهولة الهوية لحركة مرور الويب.
  • سنستخدم مجموعة فرعية تضم 8.87 مليون صف (حدث) من مجموعة البيانات النموذجية.
  • يبلغ حجم البيانات غير المضغوطة 8.87 مليون حدث ونحو 700 ميغابايت. وينخفض هذا إلى 200 ميغابايت عند تخزينه في ClickHouse.
  • في مجموعتنا الفرعية، يحتوي كل صف على ثلاثة أعمدة تمثل مستخدم إنترنت (عمود UserID) نقر على عنوان URL (عمود URL) في وقت محدد (عمود EventTime).
وباستخدام هذه الأعمدة الثلاثة، يمكننا بالفعل صياغة بعض استعلامات تحليلات الويب الشائعة، مثل:
  • “ما أكثر 10 عناوين URL نقرًا من قِبل مستخدم محدد؟”
  • “من هم أكثر 10 مستخدمين نقرًا على عنوان URL محدد بشكل متكرر؟”
  • “ما أكثر الأوقات شيوعًا (مثل أيام الأسبوع) التي ينقر فيها المستخدم على عنوان URL محدد؟“

جهاز الاختبار

جميع الأرقام المتعلقة بزمن التشغيل الواردة في هذا المستند تستند إلى تشغيل ClickHouse 22.2.1 محليًا على جهاز MacBook Pro مزوّد بشريحة Apple M1 Pro وذاكرة RAM بسعة 16 جيجابايت.

مسح كامل للجدول

لكي نرى كيف يُنفَّذ استعلام على مجموعة البيانات لدينا في غياب مفتاح أساسي، ننشئ جدولًا (باستخدام محرك جدول MergeTree) بتنفيذ عبارة SQL DDL التالية:
بعد ذلك، أدرِج مجموعة فرعية من مجموعة بيانات hits في الجدول باستخدام تعليمة insert التالية بلغة SQL. يستخدم هذا دالة الجدول URL لتحميل مجموعة فرعية من مجموعة البيانات الكاملة المستضافة عن بُعد على clickhouse.com:
تكون الاستجابة كما يلي:
يُظهر ناتج ClickHouse client أن التعليمة أعلاه أدخلت 8.87 مليون صف إلى الجدول. أخيرًا، ولتسهيل المناقشات الواردة لاحقًا في هذا الدليل وجعل المخططات والنتائج قابلة لإعادة الإنتاج، نجري OPTIMIZE على الجدول باستخدام الكلمة المفتاحية FINAL:
بشكل عام، لا يكون مطلوبًا ولا مُوصىً به إجراء تحسين للجدول فورًا بعد تحميل البيانات فيه. سيتضح لاحقًا سبب الحاجة إلى ذلك في هذا المثال.
ننّفذ الآن أول استعلام لتحليلات الويب. يحسب ما يلي أكثر 10 عناوين URL نقرًا لمستخدم الإنترنت ذي المعرّف UserID 749927693:
تكون الاستجابة كما يلي:
يشير خرج نتائج ClickHouse client إلى أن ClickHouse نفّذ مسحًا كاملًا للجدول! فقد جرى تمرير كل صف على حدة من بين 8.87 مليون صف في جدولنا إلى ClickHouse. هذا لا يتوسع على نحو جيد. ولجعل ذلك أكثر كفاءة (بفارق كبير) وأسرع (بكثير)، نحتاج إلى استخدام جدول يحتوي على مفتاح أساسي مناسب. سيتيح هذا لـ ClickHouse إنشاء فهرس أساسي متناثر تلقائيًا (استنادًا إلى الأعمدة الخاصة بالمفتاح الأساسي)، ويمكن بعد ذلك استخدامه لتسريع تنفيذ استعلامنا المثال بشكل كبير.

تصميم الفهارس في ClickHouse

تصميم فهرس لنطاقات بيانات هائلة

في أنظمة إدارة قواعد البيانات العلائقية التقليدية، يحتوي الفهرس الأساسي على إدخال فهرس واحد لكل صف في الجدول. وهذا يعني أن الفهرس الأساسي سيحتوي، بالنسبة إلى مجموعة البيانات لدينا، على 8.87 مليون إدخال فهرس. ويتيح هذا النوع من الفهارس تحديد الصفوف المطلوبة بسرعة، ما يجعله عالي الكفاءة في استعلامات البحث والتحديثات النقطية. ويبلغ متوسط التعقيد الزمني للبحث عن إدخال فهرس في بنية البيانات B(+)-Tree مقدار O(log n)؛ وبصورة أدق، log_b n = log_2 n / log_2 b، حيث إن b هو عامل التفريع في B(+)-Tree وn هو عدد الصفوف المفهرسة. ونظرًا إلى أن b يكون عادةً بين عدة مئات وعدة آلاف، فإن أشجار B(+)-Tree تكون ضحلة جدًا، ولا يتطلب تحديد السجلات سوى عدد قليل من عمليات seek على القرص. ومع وجود 8.87 مليون صف وعامل تفريع مقداره 1000، يلزم في المتوسط 2.3 عملية seek على القرص. لكن هذه الإمكانية لها تكلفة: أعباء إضافية على القرص والذاكرة، وارتفاع تكلفة الإدراج عند إضافة صفوف جديدة إلى الجدول وإدخالات فهرس جديدة إلى الفهرس، وأحيانًا إعادة موازنة شجرة B-Tree. ونظرًا إلى التحديات المرتبطة بفهارس B-Tree، تعتمد محركات الجداول في ClickHouse نهجًا مختلفًا. فقد صُممت عائلة محركات MergeTree في ClickHouse وحُسّنت للتعامل مع أحجام بيانات هائلة. وقد صُممت هذه الجداول لاستقبال ملايين عمليات إدراج الصفوف في الثانية وتخزين كميات ضخمة جدًا من البيانات (مئات البيتبايتات). وتُكتب البيانات بسرعة إلى الجدول جزءًا بعد جزء، مع تطبيق قواعد لدمج الأجزاء في الخلفية. وفي ClickHouse، لكل جزء فهرسه الأساسي الخاص. وعند دمج الأجزاء، تُدمج أيضًا الفهارس الأساسية للجزء الناتج عن الدمج. وعلى النطاقات الكبيرة جدًا التي صُمم ClickHouse من أجلها، تكون الكفاءة في استخدام القرص والذاكرة أمرًا بالغ الأهمية. لذلك، وبدلًا من فهرسة كل صف، يحتوي الفهرس الأساسي للجزء على إدخال فهرس واحد (يُعرف باسم ‘mark’) لكل مجموعة من الصفوف (تُسمى ‘granule’) — وتُعرف هذه التقنية باسم الفهرس المتناثر. ويصبح استخدام الفهرسة المتناثرة ممكنًا لأن ClickHouse يخزّن صفوف الجزء على القرص مرتبةً حسب أعمدة المفتاح الأساسي. وبدلًا من تحديد مواقع الصفوف المفردة مباشرةً (كما في الفهرس المعتمد على B-Tree)، يتيح الفهرس الأساسي المتناثر له أن يحدّد بسرعة، عبر بحث ثنائي في إدخالات الفهرس، مجموعات الصفوف التي قد تطابق الاستعلام. ثم تُبث مجموعات الصفوف التي جرى تحديدها، والتي قد تحتوي على مطابقات (الحبيبات)، بالتوازي إلى ClickHouse engine من أجل العثور على المطابقات. ويجعل تصميم الفهرس هذا الفهرسَ الأساسي صغيرًا (إذ يمكنه، بل ويجب عليه، أن يتسع بالكامل في الذاكرة الرئيسية)، مع الاستمرار في تسريع أزمنة تنفيذ الاستعلامات بدرجة كبيرة، ولا سيما في استعلامات النطاق الشائعة في حالات استخدام تحليلات البيانات. يوضح ما يلي بالتفصيل كيف يبني ClickHouse فهرسه الأساسي المتناثر ويستخدمه. وفي موضع لاحق من المقالة، سنناقش بعض أفضل الممارسات لاختيار أعمدة الجدول المستخدمة في بناء الفهرس (أعمدة المفتاح الأساسي) وإزالتها وترتيبها.

جدول ذو مفتاح أساسي

أنشئ جدولًا بمفتاح أساسي مركب يتكوّن من عمودي المفتاح UserID و URL:

لتبسيط المناقشات الواردة لاحقًا في هذا الدليل، وكذلك لجعل المخططات والنتائج قابلة للتكرار، فإن عبارة DDL:

  • تحدد مفتاح فرز مركبًا للجدول عبر عبارة ORDER BY.
  • تتحكم بشكل صريح في عدد إدخالات الفهرس التي سيحتوي عليها الفهرس الأساسي من خلال الإعدادات التالية:
    • index_granularity: يُضبط صراحةً على قيمته الافتراضية 8192. وهذا يعني أنه لكل مجموعة من 8192 صفًا، سيحتوي الفهرس الأساسي على إدخال فهرس واحد. على سبيل المثال، إذا كان الجدول يحتوي على 16384 صفًا، فسيحتوي الفهرس على إدخالَي فهرس.
    • index_granularity_bytes: يُضبط على 0 من أجل تعطيل حبيبية الفهرس التكيفية. وتعني حبيبية الفهرس التكيفية أن ClickHouse ينشئ تلقائيًا إدخال فهرس واحدًا لمجموعة تضم n صفًا إذا تحقق أيٌّ مما يلي:
      • إذا كانت n أقل من 8192 وكان الحجم الإجمالي لبيانات الصفوف لتلك الصفوف وعددها n أكبر من أو يساوي 10 ميغابايت (القيمة الافتراضية لـ index_granularity_bytes).
      • إذا كان الحجم الإجمالي لبيانات الصفوف لعدد n من الصفوف أقل من 10 ميغابايت، لكن n يساوي 8192.
    • compress_primary_key: يُضبط على 0 لتعطيل ضغط الفهرس الأساسي. سيسمح لنا هذا بفحص محتوياته لاحقًا عند الحاجة.

يؤدي المفتاح الأساسي في عبارة DDL أعلاه إلى إنشاء الفهرس الأساسي استنادًا إلى عمودَي المفتاح المحددين.
بعد ذلك، أدرِج البيانات:
تبدو الاستجابة كما يلي:

وحسِّن الجدول:

يمكننا استخدام الاستعلام التالي للحصول على بيانات وصفية عن جدولنا:
تكون الاستجابة كما يلي:
يُظهر ناتج عميل ClickHouse ما يلي:
  • تُخزَّن بيانات الجدول بتنسيق التنسيق الواسع في دليل محدد على القرص، ما يعني أنه سيكون هناك ملف بيانات واحد (وملف علامة واحد) لكل عمود في الجدول داخل ذلك الدليل.
  • يحتوي الجدول على 8.87 مليون صف.
  • يبلغ حجم البيانات غير المضغوطة لجميع الصفوف معًا 733.28 MB.
  • يبلغ الحجم المضغوط على القرص لجميع الصفوف معًا 206.94 MB.
  • يحتوي الجدول على فهرس أساسي يضم 1083 إدخالًا (تُسمى ‘علامات’)، ويبلغ حجم هذا الفهرس 96.93 KB.
  • إجمالًا، تشغل بيانات الجدول وملفات العلامات وملف الفهرس الأساسي معًا مساحة 207.07 MB على القرص.

تُخزَّن البيانات على القرص مرتبةً بحسب أعمدة المفتاح الأساسي

يحتوي الجدول الذي أنشأناه أعلاه على:
  • لو كنا قد حدّدنا مفتاح الفرز فقط، لكان المفتاح الأساسي قد عُرِّف ضمنيًا على أنه مطابق لمفتاح الفرز.
  • لتحقيق كفاءة في استخدام الذاكرة، حدّدنا صراحةً مفتاحًا أساسيًا لا يتضمن إلا الأعمدة التي تُصفّي الاستعلامات بناءً عليها. ويُحمَّل الفهرس الأساسي المبني على المفتاح الأساسي بالكامل إلى الذاكرة الرئيسية.
  • وللحفاظ على الاتساق في مخططات الدليل وتعظيم نسبة الضغط، عرّفنا مفتاح فرز منفصلًا يشمل جميع أعمدة الجدول (فإذا وُضعت البيانات المتشابهة في عمود ما بالقرب من بعضها، مثلًا عبر الفرز، فسيكون ضغط تلك البيانات أفضل).
  • يجب أن يكون المفتاح الأساسي بادئةً لمفتاح الفرز إذا جرى تحديدهما معًا.
تُخزَّن الصفوف المُدرجة على القرص بترتيب معجمي تصاعدي بحسب أعمدة المفتاح الأساسي (ومع العمود الإضافي EventTime من مفتاح الفرز).
يسمح ClickHouse بإدراج عدة صفوف لها قيم متطابقة في أعمدة المفتاح الأساسي. في هذه الحالة (انظر الصف 1 والصف 2 في المخطط أدناه)، يتحدد الترتيب النهائي وفقًا لمفتاح الفرز المحدد، وبالتالي وفقًا لقيمة العمود EventTime.
ClickHouse هو نظام إدارة قواعد بيانات موجَّه بالأعمدة. وكما هو موضح في المخطط أدناه:
  • في التمثيل على القرص، يوجد ملف بيانات واحد (*.bin) لكل عمود في الجدول، وتُخزَّن فيه جميع قيم ذلك العمود بتنسيق مضغوط، و
  • تُخزَّن الصفوف البالغ عددها 8.87 مليون صف على القرص بترتيب معجمي تصاعدي بحسب أعمدة المفتاح الأساسي (وأعمدة مفتاح الفرز الإضافية)، أي في هذه الحالة:
    • أولًا بحسب UserID،
    • ثم بحسب URL،
    • وأخيرًا بحسب EventTime:
تمثل UserID.bin وURL.bin وEventTime.bin ملفات البيانات الموجودة على القرص والتي تُخزَّن فيها قيم الأعمدة UserID وURL وEventTime.
  • بما أن المفتاح الأساسي يحدد الترتيب المعجمي للصفوف على القرص، فلا يمكن أن يكون للجدول إلا مفتاح أساسي واحد.
  • نرقّم الصفوف بدءًا من 0 ليتوافق ذلك مع مخطط ترقيم الصفوف الداخلي في ClickHouse، والذي يُستخدم أيضًا في رسائل التسجيل.

تُنظَّم البيانات في حبيبات لمعالجتها بالتوازي

لأغراض معالجة البيانات، تُقسَّم قيم أعمدة الجدول منطقيًا إلى حبيبات. والحبيبة هي أصغر مجموعة بيانات غير قابلة للتجزئة تُمرَّر إلى ClickHouse لمعالجة البيانات. وهذا يعني أنه بدلًا من قراءة الصفوف المفردة، يقرأ ClickHouse دائمًا مجموعة كاملة من الصفوف (حبيبة) على نحو متدفق وبالتوازي.
لا تُخزَّن قيم الأعمدة فعليًا داخل الحبيبات، فالحبيبات ليست سوى تنظيم منطقي لقيم الأعمدة لأغراض معالجة الاستعلامات.
يوضح المخطط التالي كيف تُنظَّم (قيم أعمدة) 8.87 مليون صف من جدولنا في 1083 حبيبة، نتيجة احتواء تعليمة DDL الخاصة بالجدول على الإعداد index_granularity (المضبوط على قيمته الافتراضية البالغة 8192). تنتمي منطقيًا أول 8192 صفًا (قيم أعمدتها) — استنادًا إلى الترتيب الفعلي على القرص — إلى الحبيبة 0، ثم تنتمي الـ 8192 صفًا التالية (قيم أعمدتها) إلى الحبيبة 1، وهكذا.
  • الحبيبة الأخيرة (الحبيبة 1082) “تحتوي” على أقل من 8192 صفًا.
  • ذكرنا في بداية هذا الدليل، في قسم “تفاصيل تعليمة DDL”، أننا عطّلنا درجة تحبّب الفهرس التكيفية (لتبسيط النقاشات في هذا الدليل، وكذلك لجعل المخططات والنتائج قابلة لإعادة الإنتاج). لذلك، فإن جميع الحبيبات (باستثناء الأخيرة) في جدول المثال لدينا لها الحجم نفسه.
  • بالنسبة إلى الجداول ذات درجة تحبّب الفهرس التكيفية (تكون درجة تحبّب الفهرس تكيفية بشكل افتراضي)، قد يكون حجم بعض الحبيبات أقل من 8192 صفًا بحسب أحجام بيانات الصفوف.
  • وضعنا علامة برتقالية على بعض قيم الأعمدة من أعمدة المفتاح الأساسي لدينا (UserID, URL). وتمثل قيم الأعمدة المعلَّمة بالبرتقالي هذه قيم أعمدة المفتاح الأساسي لأول صف في كل حبيبة. وكما سنرى أدناه، ستكون قيم الأعمدة المعلَّمة بالبرتقالي هذه هي الإدخالات في الفهرس الأساسي للجدول.
  • نرقّم الحبيبات بدءًا من 0 ليتوافق ذلك مع مخطط الترقيم الداخلي في ClickHouse، والذي يُستخدم أيضًا في رسائل التسجيل.

يحتوي الفهرس الأساسي على إدخال واحد لكل حبيبة

يُنشأ الفهرس الأساسي استنادًا إلى الحبيبات الموضحة في المخطط أعلاه. وهذا الفهرس هو ملف مصفوفة مسطّحة غير مضغوط (primary.idx) يحتوي على ما يُعرف بعلامات الفهرس الرقمية، بدءًا من 0. يوضح المخطط أدناه أن الفهرس يخزّن قيم أعمدة المفتاح الأساسي (القيم المميزة باللون البرتقالي في المخطط أعلاه) للصف الأول في كل حبيبة. وبعبارة أخرى: يخزّن الفهرس الأساسي قيم أعمدة المفتاح الأساسي من كل صف رقم 8192 في الجدول (استنادًا إلى الترتيب المادي للصفوف الذي تحدده أعمدة المفتاح الأساسي). على سبيل المثال
  • يخزّن إدخال الفهرس الأول (‘العلامة 0’ في المخطط أدناه) قيم أعمدة المفتاح للصف الأول من الحبيبة 0 في المخطط أعلاه،
  • ويخزّن إدخال الفهرس الثاني (‘العلامة 1’ في المخطط أدناه) قيم أعمدة المفتاح للصف الأول من الحبيبة 1 في المخطط أعلاه، وهكذا.
إجمالًا، يحتوي الفهرس على 1083 إدخالًا لجدولنا الذي يضم 8.87 مليون صف و1083 حبيبة:
  • بالنسبة إلى الجداول التي تستخدم درجة تحبّب الفهرس التكيفية، تُخزَّن أيضًا علامة إضافية “نهائية” في الفهرس الأساسي تسجّل قيم أعمدة المفتاح الأساسي لآخر صف في الجدول. ولكن لأننا عطّلنا درجة تحبّب الفهرس التكيفية (لتبسيط الشرح في هذا الدليل، وكذلك لجعل المخططات والنتائج قابلة لإعادة الإنتاج)، فإن فهرس جدول المثال لدينا لا يتضمن هذه العلامة النهائية.
  • يُحمَّل ملف الفهرس الأساسي بالكامل إلى الذاكرة الرئيسية. وإذا كان الملف أكبر من مساحة الذاكرة الحرة المتاحة، فسيُصدر ClickHouse خطأً.

في عنقود ClickHouse مُدار ذاتيًا، يمكننا استخدام دالة الجدول file لفحص محتوى الفهرس الأساسي لجدول المثال الخاص بنا.وللقيام بذلك، نحتاج أولًا إلى نسخ ملف الفهرس الأساسي إلى user_files_path على إحدى عقد العنقود قيد التشغيل:

  • الخطوة 1: احصل على مسار الجزء الذي يحتوي على ملف الفهرس الأساسي
  • SELECT path FROM system.parts WHERE table = 'hits_UserID_URL' AND active = 1
    يعيد /Users/tomschreiber/Clickhouse/store/85f/85f4ee68-6e28-4f08-98b1-7d8affa1d88c/all_1_9_4 على جهاز الاختبار.
  • الخطوة 2: احصل على user_files_path
  • إن user_files_path الافتراضي على Linux هو /var/lib/clickhouse/user_files/
    وعلى Linux، يمكنك التحقق مما إذا كان قد تغيّر: $ grep user_files_path /etc/clickhouse-server/config.xmlعلى جهاز الاختبار، كان المسار هو /Users/tomschreiber/Clickhouse/user_files/
  • الخطوة 3: انسخ ملف الفهرس الأساسي إلى user_files_path
  • cp /Users/tomschreiber/Clickhouse/store/85f/85f4ee68-6e28-4f08-98b1-7d8affa1d88c/all_1_9_4/primary.idx /Users/tomschreiber/Clickhouse/user_files/primary-hits_UserID_URL.idx

يمكننا الآن فحص محتوى الفهرس الأساسي عبر SQL:
  • احصل على عدد الإدخالات
  • SELECT count( )<br/>FROM file('primary-hits_UserID_URL.idx', 'RowBinary', 'UserID UInt32, URL String'); يعيد 1083
  • احصل على أول علامتي فهرس
  • SELECT UserID, URL<br/>FROM file('primary-hits_UserID_URL.idx', 'RowBinary', 'UserID UInt32, URL String')<br/>LIMIT 0, 2;
    يعيد240923, http://showtopics.html%3...<br/> 4073710, http://mk.ru&pos=3_0
  • احصل على آخر علامة فهرس
  • SELECT UserID, URL FROM file('primary-hits_UserID_URL.idx', 'RowBinary', 'UserID UInt32, URL String')<br/>LIMIT 1082, 1; يعيد 4292714039 │ http://sosyal-mansetleri...

وهذا يطابق تمامًا مخططنا لمحتوى الفهرس الأساسي لجدول المثال:

تُسمى إدخالات المفتاح الأساسي علامات الفهرس، لأن كل إدخال في الفهرس يشير إلى بداية نطاق بيانات معيّن. وبالتحديد في جدول المثال:
  • علامات فهرس UserID: قيم UserID المخزنة في الفهرس الأساسي مرتبة ترتيبًا تصاعديًا.
    لذلك تشير ‘العلامة 1’ في المخطط أعلاه إلى أن قيم UserID لجميع صفوف الجدول في granule 1، وفي جميع الـ granules التالية، مضمونة أن تكون أكبر من أو تساوي 4.073.710.
كما سنرى لاحقًا، يتيح هذا الترتيب العام لـ ClickHouse استخدام خوارزمية البحث الثنائي على علامات الفهرس لعمود المفتاح الأول عندما يطبّق الاستعلام عامل تصفية على العمود الأول من المفتاح الأساسي.
  • علامات فهرس URL: إن التقارب الكبير في cardinality لعمودَي المفتاح الأساسي UserID وURL يعني أن علامات الفهرس لجميع أعمدة المفتاح بعد العمود الأول، بوجه عام، لا تحدد نطاق بيانات إلا ما دام مقدار عمود المفتاح السابق يظل ثابتًا لجميع صفوف الجدول ضمن الحبيبة الحالية على الأقل.
    على سبيل المثال، لأن قيم UserID للعلامة 0 والعلامة 1 مختلفة في المخطط أعلاه، لا يمكن لـ ClickHouse أن يفترض أن جميع قيم URL لكل صفوف الجدول في الحبيبة 0 أكبر من أو تساوي 'http://showtopics.html%3...'. ومع ذلك، إذا كانت قيم UserID للعلامة 0 والعلامة 1 متطابقة في المخطط أعلاه (أي إن قيمة UserID تظل ثابتة لجميع صفوف الجدول داخل الحبيبة 0)، فسيكون بإمكان ClickHouse أن يفترض أن جميع قيم URL لكل صفوف الجدول في الحبيبة 0 أكبر من أو تساوي 'http://showtopics.html%3...'.
    سنناقش لاحقًا بمزيد من التفصيل ما يترتب على ذلك من حيث أداء تنفيذ الاستعلام.

يُستخدم الفهرس الأساسي لاختيار الحبيبات

يمكننا الآن تنفيذ استعلاماتنا بدعم من الفهرس الأساسي. يحسب ما يلي أكثر 10 عناوين URL تلقّيًا للنقرات للمستخدم UserID 749927693.
تكون الاستجابة:
يُظهر خرج عميل ClickHouse الآن أنه بدلًا من إجراء فحص كامل للجدول، لم يتم تمرير سوى 8.19 ألف صف إلى ClickHouse. إذا كان تسجيل التتبّع مُمكّنًا، فإن ملف سجل خادم ClickHouse يُظهر أن ClickHouse كان يُجري بحثًا ثنائيًا عبر 1083 من علامات فهرس UserID، لتحديد الحبيبات التي قد تحتوي على صفوف تكون فيها قيمة العمود UserID هي 749927693. ويتطلّب ذلك 19 خطوة بمتوسط تعقيد زمني قدره O(log2 n):
يمكننا أن نرى في سجل التتبّع أعلاه أن علامة واحدة فقط من بين 1083 علامة موجودة طابقت الاستعلام.

جرى تحديد العلامة 176 (إذ إن ‘علامة الحد الأيسر التي عُثر عليها’ شاملة، بينما ‘علامة الحد الأيمن التي عُثر عليها’ غير شاملة)، ولذلك تُمرَّر بعد ذلك جميع الصفوف الـ8192 من الحبيبة 176 (التي تبدأ عند الصف 1.441.792 — وسنرى ذلك لاحقًا في هذا الدليل) إلى ClickHouse من أجل العثور على الصفوف الفعلية التي تكون فيها قيمة العمود UserID هي 749927693.

يمكننا أيضًا إعادة ذلك باستخدام عبارة EXPLAIN في استعلام المثال الخاص بنا:
يكون الرد على النحو التالي:
يُظهر خرج العميل أنه تم اختيار حبيبة واحدة من أصل 1083 على أنها قد تحتوي على صفوف تكون فيها قيمة عمود UserID هي 749927693.
الخلاصةعندما يطبّق استعلام عامل تصفية على عمود يُشكّل جزءًا من مفتاح مركّب، ويكون هو عمود المفتاح الأول، فإن ClickHouse يشغّل خوارزمية البحث الثنائي على علامات الفهرس الخاصة بعمود المفتاح.

كما نوقش أعلاه، يستخدم ClickHouse فهرسه الأساسي المتناثر لاختيار الحبيبات بسرعة (عبر البحث الثنائي) التي قد تحتوي على صفوف تطابق الاستعلام. هذه هي المرحلة الأولى (اختيار الحبيبات) من تنفيذ استعلام ClickHouse. في المرحلة الثانية (قراءة البيانات)، يحدّد ClickHouse مواقع الحبيبات المختارة من أجل تمرير جميع صفوفها إلى محرك ClickHouse للعثور على الصفوف المطابقة فعليًا للاستعلام. نناقش هذه المرحلة الثانية بمزيد من التفصيل في القسم التالي.

تُستخدم ملفات العلامات لتحديد مواقع الحبيبات

يوضح المخطط التالي جزءًا من ملف الفهرس الأساسي لجدولنا. كما ناقشنا أعلاه، ومن خلال بحث ثنائي عبر علامات UserID البالغ عددها 1083 في الفهرس، جرى تحديد العلامة 176. لذلك، قد تحتوي الحبيبة 176 المقابلة على صفوف تكون قيمة عمود UserID فيها 749.927.693.

يوضح المخطط أعلاه أن العلامة 176 هي أول إدخال في الفهرس تكون فيه القيمة الدنيا لـ UserID في الحبيبة 176 المرتبطة أصغر من 749.927.693، وفي الوقت نفسه تكون القيمة الدنيا لـ UserID في الحبيبة 177 الخاصة بالعلامة التالية (العلامة 177) أكبر من هذه القيمة. لذلك، فإن الحبيبة 176 المقابلة للعلامة 176 وحدها هي التي قد تحتوي على صفوف تكون قيمة عمود UserID فيها 749.927.693.

ولتأكيد ما إذا كانت بعض الصفوف في الحبيبة 176 تحتوي على قيمة عمود UserID تساوي 749.927.693 أم لا، يجب تمرير الصفوف كلها، وعددها 8192 صفًا، التابعة لهذه الحبيبة إلى ClickHouse. ولتحقيق ذلك، يحتاج ClickHouse إلى معرفة الموقع الفعلي للحبيبة 176. في ClickHouse، تُخزَّن المواقع الفعلية لجميع الحبيبات الخاصة بجدولنا في ملفات العلامات. وعلى غرار ملفات البيانات، يوجد ملف علامات واحد لكل عمود في الجدول. يوضح المخطط التالي ملفات العلامات الثلاثة UserID.mrk وURL.mrk وEventTime.mrk التي تخزّن المواقع الفعلية للحبيبات الخاصة بأعمدة الجدول UserID وURL وEventTime. لقد ناقشنا أن الفهرس الأساسي عبارة عن ملف مصفوفة مسطّح غير مضغوط (primary.idx) يحتوي على علامات فهرس يبدأ ترقيمها من 0. وبالمثل، فإن ملف العلامات هو أيضًا ملف مصفوفة مسطّح غير مضغوط (*.mrk) يحتوي على علامات يبدأ ترقيمها من 0. وبمجرد أن يحدّد ClickHouse علامة الفهرس الخاصة بحبيبة قد تحتوي على صفوف مطابقة للاستعلام ويختارها، يمكن إجراء وصول موضعي داخل المصفوفة في ملفات العلامات للحصول على المواقع الفعلية للحبيبة. يخزّن كل إدخال في ملف العلامات لعمود معيّن موقعين على شكل إزاحتين:
  • الإزاحة الأولى (block_offset في المخطط أعلاه) تحدد موقع الكتلة في ملف بيانات العمود المضغوط التي تحتوي على النسخة المضغوطة من الحبيبة المحددة. وقد تحتوي هذه الكتلة المضغوطة على بضع حبيبات مضغوطة. وتُفكّ الكتلة المضغوطة المحددة إلى الذاكرة الرئيسية عند القراءة.
  • الإزاحة الثانية (granule_offset في المخطط أعلاه) من ملف العلامات توفّر موقع الحبيبة داخل بيانات الكتلة غير المضغوطة.
بعد ذلك، تُمرَّر الصفوف كلها، وعددها 8192 صفًا، التابعة للحبيبة غير المضغوطة المحددة إلى ClickHouse لمزيد من المعالجة.
  • بالنسبة إلى الجداول ذات التنسيق الواسع ومن دون درجة تحبّب الفهرس التكيفية، يستخدم ClickHouse ملفات العلامات .mrk كما هو موضح أعلاه، والتي تحتوي على إدخالات تضم عنوانين، طول كل منهما 8 بايت، لكل إدخال. وتمثل هذه الإدخالات المواقع الفعلية لحبيبات لها جميعًا الحجم نفسه.
تكون درجة تحبّب الفهرس التكيفية بشكل default، لكننا عطّلنا درجة تحبّب الفهرس التكيفية في جدول المثال الخاص بنا (لتبسيط المناقشات في هذا الدليل، وكذلك لجعل المخططات والنتائج قابلة لإعادة الإنتاج). ويستخدم جدولنا التنسيق الواسع لأن حجم البيانات أكبر من min_bytes_for_wide_part (والتي تبلغ 10 MB افتراضيًا في العناقيد مُدار ذاتيًا).
  • بالنسبة إلى الجداول ذات التنسيق الواسع ومع درجة تحبّب الفهرس التكيفية، يستخدم ClickHouse ملفات العلامات .mrk2، التي تحتوي على إدخالات مشابهة لملفات العلامات .mrk ولكن مع قيمة ثالثة إضافية لكل إدخال: عدد الصفوف في الحبيبة المرتبطة بالإدخال الحالي.
  • بالنسبة إلى الجداول ذات التنسيق المدمج، يستخدم ClickHouse ملفات العلامات .mrk3.
لماذا ملفات العلاماتلماذا لا يحتوي الفهرس الأساسي مباشرةً على المواقع الفعلية للحبيبات المقابلة لعلامات الفهرس؟لأنه عند هذا النطاق الهائل الذي صُمم ClickHouse للعمل عليه، من المهم تحقيق أعلى كفاءة ممكنة في استخدام القرص والذاكرة.يجب أن يكون ملف الفهرس الأساسي قابلاً للاحتواء بالكامل في الذاكرة الرئيسية.في استعلامنا المثال، استخدم ClickHouse الفهرس الأساسي وحدد حبيبة واحدة قد تحتوي على صفوف تطابق استعلامنا. ولهذه الحبيبة وحدها فقط، يحتاج ClickHouse بعد ذلك إلى المواقع الفعلية حتى يتمكن من تمرير الصفوف المقابلة لمزيد من المعالجة.علاوة على ذلك، لا تكون معلومات الإزاحة هذه مطلوبة إلا لعمودي UserID وURL.ولا تكون معلومات الإزاحة مطلوبة للأعمدة التي لا تُستخدم في الاستعلام، مثل EventTime.بالنسبة إلى استعلامنا النموذجي، يحتاج ClickHouse فقط إلى إزاحتي الموقع الفعلي للحبيبة 176 في ملف بيانات UserID ‏(UserID.bin) وإزاحتي الموقع الفعلي للحبيبة 176 في ملف بيانات URL ‏(URL.bin).ويؤدي المستوى الوسيط الذي توفره ملفات العلامات إلى تجنب تخزين إدخالات المواقع الفعلية لجميع الحبيبات البالغ عددها 1083 عبر الأعمدة الثلاثة كلها مباشرةً داخل الفهرس الأساسي، مما يتجنب وجود بيانات غير ضرورية (وقد لا تُستخدم) في الذاكرة الرئيسية.
يوضح المخطط التالي والنص أدناه كيف يحدد ClickHouse، في استعلامنا المثال، موقع الحبيبة 176 في ملف البيانات UserID.bin. ناقشنا سابقًا في هذا الدليل أن ClickHouse اختار علامة الفهرس الأساسي 176، وبالتالي الحبيبة 176، على أنها قد تحتوي على صفوف مطابقة لاستعلامنا. ويستخدم ClickHouse الآن رقم العلامة المحدد (176) من الفهرس لإجراء بحث موضعي في المصفوفة داخل ملف العلامات UserID.mrk من أجل الحصول على الإزاحتين اللازمتين لتحديد موقع الحبيبة 176. كما هو موضح، تحدد الإزاحة الأولى كتلة الملف المضغوطة داخل ملف البيانات UserID.bin، والتي تحتوي بدورها على النسخة المضغوطة من الحبيبة 176. وبمجرد فك ضغط كتلة الملف المحددة إلى الذاكرة الرئيسية، يمكن استخدام الإزاحة الثانية من ملف العلامات لتحديد موقع الحبيبة 176 داخل البيانات غير المضغوطة. ويحتاج ClickHouse إلى تحديد موقع الحبيبة 176 (وتمرير جميع القيم منها) من كلٍّ من ملف البيانات UserID.bin وملف البيانات URL.bin من أجل تنفيذ استعلامنا المثال (أكثر 10 عناوين URL نقرًا لمستخدم الإنترنت ذي UserID ‏749.927.693). يوضح المخطط أعلاه كيف يحدد ClickHouse موقع الحبيبة في ملف البيانات UserID.bin. وبالتوازي، يفعل ClickHouse الشيء نفسه للحبيبة 176 في ملف البيانات URL.bin. وتكون الحبيبتان المعنيتان مصطفّتَين، ثم تُمرَّران إلى ClickHouse engine لمزيد من المعالجة، أي تجميع قيم URL وعدّها لكل مجموعة لجميع الصفوف التي يكون فيها UserID هو 749.927.693، قبل إخراج أكبر 10 مجموعات URL أخيرًا بترتيب تنازلي حسب العدد.

استخدام فهارس أولية متعددة

قد تكون أعمدة المفتاح الثانوية غير فعّالة (أو لا)

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


نستخدم استعلامًا يحسب أفضل 10 مستخدمين نقروا على عنوان URL “http://public&#95;search” بأعلى تكرار:
الاستجابة:
تشير مخرجات العميل إلى أن ClickHouse كاد أن يُجري فحصًا كاملًا للجدول رغم أن عمود URL جزء من المفتاح الأساسي المركب! يقرأ ClickHouse‏ 8.81 مليون صف من أصل 8.87 مليون صف في الجدول. إذا كان trace_logging مُمكّنًا، فإن ملف سجل خادم ClickHouse يُظهر أن ClickHouse استخدم بحث الاستبعاد العام عبر 1083 من علامات فهرس URL لتحديد الحبيبات التي قد تحتوي على صفوف تكون فيها قيمة عمود URL مساويةً لـ “http://public&#95;search”:
يمكننا أن نرى في سجل التتبّع النموذجي أعلاه أن 1076 حبيبة بيانات (عبر علامات الفهرس) من أصل 1083 قد اختيرت على أنها قد تحتوي على صفوف ذات قيمة URL مطابقة. ويؤدي ذلك إلى تمرير 8.81 مليون صف إلى محرك ClickHouse (بالتوازي باستخدام 10 تدفقات)، من أجل تحديد الصفوف التي تحتوي فعليًا على قيمة URL “http://public&#95;search”. ومع ذلك، كما سنرى لاحقًا، فإن 39 حبيبة بيانات فقط من بين 1076 حبيبة البيانات المحددة هذه تحتوي فعليًا على صفوف مطابقة. ومع أن الفهرس الأساسي المعتمد على المفتاح الأساسي المركب (UserID, URL) كان مفيدًا جدًا في تسريع الاستعلامات التي تُصفّي الصفوف بحسب قيمة UserID محددة، فإن هذا الفهرس لا يقدّم فائدة تُذكر في تسريع الاستعلام الذي يُصفّي الصفوف بحسب قيمة URL محددة. ويرجع ذلك إلى أن عمود URL ليس عمود المفتاح الأول، ولذلك يستخدم ClickHouse خوارزمية البحث بالاستبعاد العامة (بدلًا من البحث الثنائي) على علامات فهرس عمود URL، وتعتمد فعالية هذه الخوارزمية على الفرق في الكاردينالية بين عمود URL وعمود المفتاح السابق له UserID. ولتوضيح ذلك، سنعرض بعض التفاصيل حول كيفية عمل خوارزمية البحث بالاستبعاد العامة.

خوارزمية البحث بالاستبعاد العامة

يوضح ما يلي كيفية عمل خوارزمية البحث بالاستبعاد العامة في ClickHouse عند تحديد الحبيبات عبر عمود ثانوي، حين تكون الكاردينالية لعمود المفتاح السابق منخفضة أو مرتفعة. كمثال على كلتا الحالتين، سنفترض ما يلي:
  • استعلامًا يبحث عن الصفوف التي تكون فيها قيمة URL = “W3”.
  • نسخة مجردة من جدول hits لدينا، مع قيم مبسطة لكل من UserID وURL.
  • المفتاح الأساسي المركب نفسه (UserID, URL) للفهرس. وهذا يعني أن الصفوف تُرتَّب أولًا حسب قيم UserID، ثم تُرتَّب الصفوف التي لها قيمة UserID نفسها حسب URL.
  • حجم حبيبة يساوي صفَّين، أي إن كل حبيبة تحتوي على صفَّين.
لقد ميّزنا قيم أعمدة المفتاح لأول صفوف الجدول في كل حبيبة باللون البرتقالي في المخططات أدناه. عمود المفتاح السابق ذو كاردينالية منخفضة لنفترض أن UserID منخفض الكاردينالية. في هذه الحالة، يُرجَّح أن تتوزع قيمة UserID نفسها على عدة صفوف جدول وحبيبات، وبالتالي على عدة علامات فهرسة. وبالنسبة إلى علامات الفهرسة التي لها UserID نفسه، تكون قيم URL الخاصة بها مرتبة ترتيبًا تصاعديًا (لأن صفوف الجدول مرتبة أولًا حسب UserID ثم حسب URL). وهذا يتيح ترشيحًا فعالًا كما هو موضح أدناه: هناك ثلاثة سيناريوهات مختلفة لعملية تحديد الحبيبات في بياناتنا النموذجية المجردة في المخطط أعلاه:
  1. يمكن استبعاد علامة الفهرسة 0 التي تكون فيها قيمة URL أصغر من W3 وتكون فيها أيضًا قيمة URL لعلامة الفهرسة التالية مباشرة أصغر من W3، لأن العلامتين 0 و1 لهما قيمة UserID نفسها. لاحظ أن هذا الشرط المسبق للاستبعاد يضمن أن الحبيبة 0 تتكون بالكامل من قيم UserID تساوي U1، بحيث يمكن لـ ClickHouse أن يفترض أيضًا أن القيمة العظمى لـ URL في الحبيبة 0 أصغر من W3، ومن ثم يستبعد الحبيبة.
  2. تُحدَّد علامة الفهرسة 1 التي تكون فيها قيمة URL أصغر من W3 (أو مساوية له) وتكون فيها قيمة URL لعلامة الفهرسة التالية مباشرة أكبر من W3 (أو مساوية له)، لأن ذلك يعني أن الحبيبة 1 قد تحتوي على صفوف تكون قيمة URL فيها W3.
  3. يمكن استبعاد علامتي الفهرسة 2 و3 اللتين تكون فيهما قيمة URL أكبر من W3، لأن علامات الفهرسة في الفهرس الأساسي تخزّن قيم أعمدة المفتاح لأول صف في الجدول لكل حبيبة، وبما أن صفوف الجدول مرتبة على القرص حسب قيم أعمدة المفتاح، فلا يمكن أن تحتوي الحبيبتان 2 و3 على قيمة URL تساوي W3.
عمود المفتاح السابق ذو كاردينالية مرتفعة عندما يكون UserID مرتفع الكاردينالية، فمن غير المرجح أن تتوزع قيمة UserID نفسها على عدة صفوف جدول وحبيبات. وهذا يعني أن قيم URL لعلامات الفهرسة لا تكون متزايدة بشكل رتيب: كما نرى في المخطط أعلاه، تُحدَّد جميع العلامات المعروضة التي تكون قيم URL فيها أصغر من W3 من أجل تمرير صفوف الحبيبات المرتبطة بها إلى ClickHouse engine. ويرجع ذلك إلى أنه رغم أن جميع علامات الفهرسة في المخطط تندرج ضمن السيناريو 1 الموصوف أعلاه، فإنها لا تستوفي شرط الاستبعاد المسبق المذكور، وهو أن علامة الفهرسة التالية مباشرة لها قيمة UserID نفسها التي تحملها العلامة الحالية، وبالتالي لا يمكن استبعادها. فعلى سبيل المثال، انظر إلى علامة الفهرسة 0 التي تكون فيها قيمة URL أصغر من W3 وتكون فيها أيضًا قيمة URL لعلامة الفهرسة التالية مباشرة أصغر من W3. لا يمكن استبعادها لأن علامة الفهرسة التالية مباشرة، 1، لا تحمل قيمة UserID نفسها التي تحملها العلامة الحالية 0. ويمنع هذا في النهاية ClickHouse من افتراض أي شيء بشأن القيمة العظمى لـ URL في الحبيبة 0. وبدلًا من ذلك، عليه أن يفترض أن الحبيبة 0 قد تحتوي على صفوف تكون قيمة URL فيها W3، ويُجبَر بالتالي على تحديد العلامة 0. وينطبق السيناريو نفسه على العلامات 1 و2 و3.
الخلاصةتكون خوارزمية البحث بالاستبعاد العامة التي يستخدمها ClickHouse بدلًا من خوارزمية البحث الثنائي عندما يطبّق الاستعلام عامل تصفية على عمود يشكّل جزءًا من مفتاح مركّب، لكنه ليس عمود المفتاح الأول، أكثر فعالية عندما يكون عمود المفتاح السابق أقلّ كاردينالية.
في مجموعة البيانات النموذجية لدينا، يتمتع كلا عمودي المفتاح (UserID, URL) بكاردينالية مرتفعة ومتقاربة، وكما أوضحنا، فإن خوارزمية البحث بالاستبعاد العامة لا تكون فعّالة جدًا عندما تكون كاردينالية عمود المفتاح السابق للعمود URL مرتفعة أو متقاربة معها.

ملاحظة حول فهرس تخطي البيانات

نظرًا إلى الارتفاع المتقارب في الكاردينالية لكلٍّ من UserID وURL، فإن الاستعلام الذي يصفّي حسب URL لن يستفيد كثيرًا أيضًا من إنشاء فهرس تخطي بيانات ثانوي على عمود URL في جدولنا ذي المفتاح الأساسي المركب (UserID, URL). على سبيل المثال، تُنشئ العبارتان التاليتان فهرس تخطي بيانات من نوع minmax على عمود URL في جدولنا وتملآنه بالبيانات:
أنشأ ClickHouse الآن فهرسًا إضافيًا يخزّن — لكل مجموعة من 4 حبيبات متتالية (لاحظ العبارة GRANULARITY 4 في تعليمة ALTER TABLE أعلاه) — الحد الأدنى والحد الأقصى لقيمة URL: يخزّن إدخال الفهرس الأول (mark 0 في المخطط أعلاه) الحد الأدنى والحد الأقصى لقيم URL الخاصة بالصفوف التي تنتمي إلى أول 4 حبيبات في جدولنا. ويخزّن إدخال الفهرس الثاني (mark 1) الحد الأدنى والحد الأقصى لقيم URL الخاصة بالصفوف التي تنتمي إلى الحبيبات الأربع التالية في جدولنا، وهكذا. (أنشأ ClickHouse أيضًا ملف علامات خاصًا بفهرس تخطّي البيانات من أجل تحديد مواقع مجموعات الحبيبات المرتبطة بعلامات الفهرس.) وبسبب الارتفاع المتشابه في الكاردينالية لكلٍّ من UserID وURL، لا يمكن لفهرس تخطّي البيانات الثانوي هذا أن يساعد في استبعاد الحبيبات من الاختيار عند تنفيذ الاستعلام الذي يرشّح بناءً على URL. ومن المرجّح جدًا أن تكون قيمة URL المحددة التي يبحث عنها الاستعلام (أي http://public&#95;search) واقعة بين الحد الأدنى والحد الأقصى اللذين يخزنهما الفهرس لكل مجموعة من الحبيبات، مما يضطر ClickHouse إلى اختيار مجموعة الحبيبات (لأنها قد تحتوي على صفوف تطابق الاستعلام).

الحاجة إلى استخدام عدة فهارس أساسية

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

خيارات إنشاء فهارس أساسية إضافية

إذا أردنا تسريع استعلامَي العينة بشكل كبير — الاستعلام الذي يصفّي الصفوف ذات UserID معيّن، والاستعلام الذي يصفّي الصفوف ذات URL معيّن — فعلينا استخدام عدة فهارس أساسية عبر أحد هذه الخيارات الثلاثة:
  • إنشاء جدول ثانٍ بمفتاح أساسي مختلف.
  • إنشاء عرض مادي على جدولنا الحالي.
  • إضافة إسقاط إلى جدولنا الحالي.
ستؤدي الخيارات الثلاثة جميعها عمليًا إلى تكرار بيانات العينة في جدول إضافي لإعادة تنظيم الفهرس الأساسي للجدول وترتيب فرز الصفوف. ومع ذلك، تختلف الخيارات الثلاثة في مدى شفافية هذا الجدول الإضافي للمستخدم من حيث توجيه الاستعلامات وعبارات insert. عند إنشاء جدول ثانٍ بمفتاح أساسي مختلف، يجب توجيه الاستعلامات صراحةً إلى نسخة الجدول الأنسب للاستعلام، كما يجب إدراج البيانات الجديدة صراحةً في كلا الجدولين من أجل الحفاظ على تزامنهما: أما مع عرض مادي، فيُنشأ الجدول الإضافي ضمنيًا وتظل البيانات متزامنة تلقائيًا بين الجدولين: ويُعد إسقاط الخيار الأكثر شفافية لأنه، إلى جانب الحفاظ تلقائيًا على تزامن الجدول الإضافي المُنشأ ضمنيًا (والمخفي) مع تغيّرات البيانات، يختار ClickHouse أيضًا تلقائيًا نسخة الجدول الأكثر فاعلية للاستعلامات: فيما يلي نناقش هذه الخيارات الثلاثة لإنشاء عدة فهارس أساسية واستخدامها بمزيد من التفصيل، مع أمثلة واقعية.

الخيار 1: الجداول الثانوية

سننشئ جدولًا إضافيًا جديدًا نغيّر فيه ترتيب أعمدة المفتاح ضمن المفتاح الأساسي مقارنةً بترتيبها في جدولنا الأصلي:
أدرِج جميع الصفوف البالغ عددها 8.87 مليونًا من جدولنا الأصلي في الجدول الإضافي:
تبدو الاستجابة كما يلي:
وأخيرًا، حسِّن الجدول:
لأننا غيّرنا ترتيب الأعمدة في المفتاح الأساسي، فإن الصفوف المُدخلة تُخزَّن الآن على القرص بترتيب معجمي مختلف (مقارنةً بـالجدول الأصلي)، ولذلك فإن الحبيبات الـ1083 في هذا الجدول تحتوي أيضًا على قيم مختلفة عمّا كانت عليه سابقًا: وهذا هو المفتاح الأساسي الناتج: ويمكن الآن استخدامه لتسريع تنفيذ استعلام المثال لدينا بشكل كبير، والذي يطبّق عامل تصفية على عمود URL لحساب أفضل 10 مستخدمين نقروا على URL ‏“http://public&#95;search” بأكبر عدد من المرات:
تكون الاستجابة كما يلي:
الآن، وبدلاً من إجراء فحص كامل للجدول تقريبًا، نفّذ ClickHouse ذلك الاستعلام بكفاءة أعلى بكثير. باستخدام الفهرس الأساسي من الجدول الأصلي، حيث كان UserID عمود المفتاح الأول وURL عمود المفتاح الثاني، استخدم ClickHouse خوارزمية البحث بالاستبعاد العامة عبر علامات الفهرسة لتنفيذ ذلك الاستعلام، ولم يكن ذلك فعّالًا جدًا بسبب التقارب في الارتفاع الكبير للكاردينالية لكلٍّ من UserID وURL. ومع جعل URL العمود الأول في الفهرس الأساسي، أصبح ClickHouse الآن يُجري بحثًا ثنائيًا عبر علامات الفهرسة. ويؤكد سجل التتبّع المقابل في ملف سجل خادم ClickHouse ذلك:
حدّد ClickHouse‏ 39 علامة فهرسة فقط، بدلًا من 1076 عند استخدام خوارزمية البحث بالاستبعاد العامة. لاحظ أن الجدول الإضافي مُحسَّن لتسريع تنفيذ استعلام المثال الذي يطبّق تصفية على عناوين URL. وعلى غرار الأداء السيئ لذلك الاستعلام مع جدولنا الأصلي، فإن استعلام المثال الذي يطبّق تصفية على UserIDs لن يعمل بكفاءة كبيرة مع الجدول الإضافي الجديد، لأن UserID أصبح الآن عمود المفتاح الثاني في primary index لهذا الجدول، ولذلك سيستخدم ClickHouse‏ خوارزمية البحث بالاستبعاد العامة لاختيار granules، وهو ليس فعّالًا كثيرًا مع الارتفاع المتشابه في الكاردينالية لكلٍّ من UserID وURL. افتح مربع التفاصيل للاطلاع على مزيد من المعلومات.

الاستجابة هي:
سجل الخادم:

لدينا الآن جدولان: أحدهما مُحسَّن لتسريع الاستعلامات التي تطبّق تصفية على UserIDs، والآخر لتسريع الاستعلامات التي تطبّق تصفية على عناوين URL:

الخيار 2: العروض المادية

أنشئ عرضًا ماديًا على الجدول الحالي.
تكون الاستجابة كما يلي:
  • نبدّل ترتيب أعمدة المفتاح (مقارنةً بـ الجدول الأصلي) في المفتاح الأساسي للعرض
  • يستند العرض المادي إلى جدول مُنشأ ضمنيًا يعتمد ترتيب صفوفه وفهرسه الأساسي على تعريف المفتاح الأساسي المعطى
  • يظهر الجدول المُنشأ ضمنيًا في الاستعلام SHOW TABLES ويكون اسمه بادئًا بـ .inner
  • من الممكن أيضًا إنشاء الجدول الداعم للعرض المادي صراحةً أولًا، ثم يمكن للعرض أن يستهدف ذلك الجدول عبر العبارة TO [db].[table]
  • نستخدم الكلمة المفتاحية POPULATE لملء الجدول المُنشأ ضمنيًا فورًا بجميع الصفوف البالغ عددها 8.87 مليون صف من جدول المصدر hits_UserID_URL
  • إذا أُدرجت صفوف جديدة في جدول المصدر hits_UserID_URL، فستُدرج هذه الصفوف أيضًا تلقائيًا في الجدول المُنشأ ضمنيًا
  • عمليًا، يكون للجدول المُنشأ ضمنيًا ترتيب الصفوف نفسه والفهرس الأساسي نفسه كما في الجدول الثانوي الذي أنشأناه صراحةً:
يخزّن ClickHouse ملفات بيانات الأعمدة (.bin) وملفات العلامات (.mrk2) والفهرس الأساسي (primary.idx) للجدول المُنشأ ضمنيًا في مجلد خاص داخل دليل بيانات خادم ClickHouse:
يمكن الآن استخدام الجدول المُنشأ ضمنيًا (وفهرسه الأساسي) الذي يستند إليه العرض المادي لتسريع تنفيذ استعلام المثال لدينا بدرجة كبيرة عند التصفية على عمود URL:
تكون الاستجابة:
نظرًا لأن الجدول الذي أُنشئ ضمنيًا (وفهرسه الأساسي) الذي يستند إليه العرض المادي مطابق فعليًا لـ الجدول الثانوي الذي أنشأناه صراحةً، يُنفَّذ الاستعلام بالطريقة نفسها فعليًا كما في حالة الجدول المُنشأ صراحةً. ويؤكد trace log المقابل في ملف سجل ClickHouse server أن ClickHouse يُجري البحث الثنائي على index marks:

الخيار 3: الإسقاطات

أنشئ إسقاطًا على جدولنا الحالي:
وقم بإنشاء الإسقاط فعليًا:
  • ينشئ الإسقاط جدولًا مخفيًا يعتمد ترتيب صفوفه وفهرسه الأساسي على عبارة ORDER BY المحددة له
  • لا يظهر الجدول المخفي في استعلام SHOW TABLES
  • نستخدم الكلمة المفتاحية MATERIALIZE لملء الجدول المخفي فورًا بجميع الصفوف البالغ عددها 8.87 مليون صف من جدول المصدر hits_UserID_URL
  • إذا أُدرِجت صفوف جديدة في جدول المصدر hits_UserID_URL، فستُدرَج هذه الصفوف تلقائيًا أيضًا في الجدول المخفي
  • يستهدف الاستعلام دائمًا، من ناحية الصياغة، جدول المصدر hits_UserID_URL، ولكن إذا كان ترتيب الصفوف والفهرس الأساسي للجدول المخفي يتيحان تنفيذ الاستعلام بكفاءة أكبر، فسيُستخدَم هذا الجدول المخفي بدلًا منه
  • يُرجى ملاحظة أن الإسقاطات لا تجعل الاستعلامات التي تستخدم ORDER BY أكثر كفاءة، حتى إذا كانت ORDER BY مطابقة لعبارة ORDER BY الخاصة بالإسقاط (راجع https://github.com/ClickHouse/ClickHouse/issues/47333)
  • عمليًا، يمتلك الجدول المخفي المُنشأ ضمنيًا ترتيب الصفوف نفسه والفهرس الأساسي نفسه اللذين يمتلكهما الجدول الثانوي الذي أنشأناه صراحةً:
يخزّن ClickHouse ملفات بيانات الأعمدة (.bin) وملفات العلامات (.mrk2) والفهرس الأساسي (primary.idx) الخاص بالجدول المخفي في مجلد خاص (مُشار إليه باللون البرتقالي في لقطة الشاشة أدناه)، بجوار ملفات بيانات جدول المصدر وملفات العلامات وملفات الفهرس الأساسي الخاصة به:
يمكن الآن استخدام الجدول المخفي (وفهرسه الأساسي) الذي أنشأه الإسقاط، ضمنيًا، لتسريع تنفيذ استعلامنا المثال بشكل كبير عند التصفية حسب عمود URL. لاحظ أن الاستعلام يستهدف، من ناحية الصياغة، جدول المصدر الخاص بالإسقاط.
تكون الاستجابة كما يلي:
لأن الجدول المخفي (وفهرسه الأساسي) الذي ينشئه الإسقاط مطابق عمليًا لـالجدول الثانوي الذي أنشأناه صراحةً، يُنفَّذ الاستعلام عمليًا بالطريقة نفسها كما في الجدول المُنشأ صراحةً. ويؤكد سجل التتبّع المقابل في ملف سجل ClickHouse server أن ClickHouse يُجري بحثًا ثنائيًا عبر علامات الفهرس:

الملخص

كان الفهرس الأساسي في الجدول ذي المفتاح الأساسي المركب (UserID, URL) مفيدًا جدًا في تسريع استعلام يطبّق عامل تصفية على UserID. لكن هذا الفهرس لا يساهم كثيرًا في تسريع استعلام يطبّق عامل تصفية على URL، رغم أن عمود URL جزء من المفتاح الأساسي المركب. والعكس صحيح: فقد كان الفهرس الأساسي في الجدول ذي المفتاح الأساسي المركب (URL, UserID) يسرّع استعلامًا يطبّق عامل تصفية على URL، لكنه لم يقدّم فائدة كبيرة لاستعلام يطبّق عامل تصفية على UserID. وبسبب التقارب في الارتفاع الكبير للكاردينالية بين عمودَي المفتاح الأساسي UserID وURL، فإن الاستعلام الذي يطبّق عامل تصفية على عمود المفتاح الثاني لا يستفيد كثيرًا من وجود عمود المفتاح الثاني في الفهرس. لذلك، من المنطقي إزالة عمود المفتاح الثاني من الفهرس الأساسي (مما يؤدي إلى تقليل استهلاك الفهرس للذاكرة) واستخدام فهارس أساسية متعددة بدلًا من ذلك. ومع ذلك، إذا كانت هناك فروق كبيرة في الكاردينالية بين أعمدة المفتاح في مفتاح أساسي مركب، فمن المفيد للاستعلامات ترتيب أعمدة المفتاح الأساسي حسب الكاردينالية ترتيبًا تصاعديًا. وكلما زاد الفرق في الكاردينالية بين أعمدة المفتاح، زادت أهمية ترتيب هذه الأعمدة داخل المفتاح. وسنوضّح ذلك في القسم التالي.

ترتيب أعمدة المفتاح بكفاءة

في المفتاح الأساسي المركّب، يمكن أن يؤثر ترتيب أعمدة المفتاح تأثيرًا كبيرًا في كلٍّ من:
  • كفاءة التصفية على أعمدة المفتاح الثانوية في الاستعلامات، و
  • نسبة الضغط لملفات بيانات الجدول.
ولتوضيح ذلك، سنستخدم إصدارًا من مجموعة بيانات عينة حركة مرور الويب حيث يحتوي كل صف على ثلاثة أعمدة تشير إلى ما إذا كان وصول ‘مستخدم’ على الإنترنت (العمود UserID) إلى عنوان URL (العمود URL) قد وُسِم على أنه حركة مرور بوت (العمود IsRobot) أم لا. سنستخدم مفتاحًا أساسيًا مركّبًا يضم الأعمدة الثلاثة المذكورة آنفًا، ويمكن استخدامه لتسريع استعلامات تحليلات الويب المعتادة التي تحسب:
  • مقدار حركة المرور إلى عنوان URL معيّن القادمة من البوتات (كنسبة مئوية)، أو
  • مدى ثقتنا في أن مستخدمًا معيّنًا بوت أو ليس بوتًا (أي ما النسبة المئوية من حركة المرور الصادرة عن ذلك المستخدم التي يُفترض أنها حركة مرور بوت أو ليست كذلك)
نستخدم هذا الاستعلام لحساب الكاردينالية للأعمدة الثلاثة التي نريد استخدامها كأعمدة مفتاح في مفتاح أساسي مركّب (لاحظ أننا نستخدم وظيفة الجدول URL للاستعلام عن بيانات TSV مباشرةً من دون الحاجة إلى إنشاء جدول محلي). شغّل هذا الاستعلام في clickhouse client:
تكون الاستجابة:
يمكننا أن نرى أن هناك فرقًا كبيرًا بين درجات cardinality، ولا سيما بين العمودين URL وIsRobot، ولذلك فإن ترتيب هذه الأعمدة في مفتاح أساسي مركب مهمٌّ لكلٍّ من التسريع الفعّال للاستعلامات التي تُجري تصفيةً على تلك الأعمدة، وتحقيق نسب ضغط مثالية لملفات بيانات أعمدة الجدول. ولتوضيح ذلك، سننشئ إصدارين من جدول لبيانات تحليل زيارات البوت لدينا:
  • جدولًا باسم hits_URL_UserID_IsRobot مع مفتاح أساسي مركب (URL, UserID, IsRobot)، حيث نرتب أعمدة المفتاح حسب cardinality ترتيبًا تنازليًا
  • جدولًا باسم hits_IsRobot_UserID_URL مع مفتاح أساسي مركب (IsRobot, UserID, URL)، حيث نرتب أعمدة المفتاح حسب cardinality ترتيبًا تصاعديًا
أنشئ الجدول hits_URL_UserID_IsRobot بالمفتاح الأساسي المركب (URL, UserID, IsRobot):
وقم بتعبئته بـ 8.87 مليون صف:
إليك الرد:
بعد ذلك، أنشئ الجدول hits_IsRobot_UserID_URL بمفتاح أساسي مركب (IsRobot, UserID, URL):
وقم بملئه بالـ 8.87 مليون صف نفسها التي استخدمناها لملء الجدول السابق:
تكون الاستجابة كما يلي:

التصفية بكفاءة على أعمدة المفاتيح الثانوية

عندما تتضمن query تصفيةً على عمود واحد على الأقل يكون جزءًا من مفتاح أساسي مركب، ويكون هو عمود المفتاح الأول، فإن ClickHouse يشغّل خوارزمية binary search على index marks الخاصة بعمود المفتاح. عندما تتضمن query تصفيةً (فقط) على عمود يكون جزءًا من مفتاح أساسي مركب، لكنه ليس عمود المفتاح الأول، فإن ClickHouse يستخدم generic exclusion search algorithm على index marks الخاصة بعمود المفتاح. في الحالة الثانية، يكون ترتيب أعمدة المفتاح في مفتاح أساسي مركب عاملًا مهمًا في فعالية generic exclusion search algorithm. في ما يلي query تُجري تصفيةً على عمود UserID في table رتّبنا فيه أعمدة المفتاح (URL, UserID, IsRobot) حسب كاردينالية ترتيبًا تنازليًا:
تكون الاستجابة:
هذا هو الاستعلام نفسه على الجدول الذي رتّبنا فيه أعمدة المفتاح (IsRobot, UserID, URL) حسب الكاردينالية بترتيب تصاعدي:
تكون الاستجابة:
يمكننا أن نرى أن تنفيذ الاستعلام يكون أكثر كفاءة وأسرع بكثير على الجدول الذي رتّبنا فيه أعمدة المفتاح حسب عدد القيم المميزة بترتيب تصاعدي. ويرجع ذلك إلى أن خوارزمية البحث بالاستبعاد العامة تكون أكثر فعالية عندما يتم اختيار الحبيبات عبر عمود مفتاح ثانوي يكون عمود المفتاح السابق له أقل من حيث عدد القيم المميزة. وقد أوضحنا ذلك بالتفصيل في قسم سابق من هذا الدليل.

أفضل نسبة ضغط لملفات البيانات

يقارن هذا الاستعلام نسبة ضغط العمود UserID بين الجدولين اللذين أنشأناهما أعلاه:
إليك الاستجابة:
يمكننا أن نرى أن نسبة الضغط للعمود UserID أعلى بكثير في الجدول الذي رتّبنا فيه أعمدة المفتاح (IsRobot, UserID, URL) حسب الكاردينالية بترتيب تصاعدي. مع أن البيانات نفسها تمامًا مخزَّنة في كلا الجدولين (أدرجنا 8.87 مليون صف نفسها في كلا الجدولين)، فإن ترتيب أعمدة المفتاح في المفتاح الأساسي المركّب يؤثر بدرجة كبيرة في مقدار مساحة القرص التي تتطلبها البيانات المضغوطة في ملفات بيانات الأعمدة الخاصة بالجدول:
  • في الجدول hits_URL_UserID_IsRobot ذي المفتاح الأساسي المركّب (URL, UserID, IsRobot)، حيث نرتّب أعمدة المفتاح حسب الكاردينالية بترتيب تنازلي، يشغل ملف البيانات UserID.bin مساحة 11.24 MiB على القرص
  • في الجدول hits_IsRobot_UserID_URL ذي المفتاح الأساسي المركّب (IsRobot, UserID, URL)، حيث نرتّب أعمدة المفتاح حسب الكاردينالية بترتيب تصاعدي، لا يشغل ملف البيانات UserID.bin سوى 877.47 KiB من مساحة القرص
إن الحصول على نسبة ضغط جيدة لبيانات أحد أعمدة الجدول على القرص لا يوفّر مساحة قرص فحسب، بل يجعل أيضًا الاستعلامات (وخاصة التحليلية منها) التي تتطلب قراءة البيانات من ذلك العمود أسرع، لأن عمليات i/o اللازمة لنقل بيانات العمود من القرص إلى الذاكرة الرئيسية (ذاكرة التخزين المؤقت للملفات في نظام التشغيل) تكون أقل. فيما يلي نوضّح لماذا يكون ترتيب أعمدة المفتاح الأساسي حسب الكاردينالية بترتيب تصاعدي مفيدًا لنسبة ضغط أعمدة الجدول. يوضح المخطط أدناه ترتيب الصفوف على القرص لمفتاح أساسي تكون فيه أعمدة المفتاح مرتبة حسب الكاردينالية بترتيب تصاعدي: لقد ناقشنا أن بيانات صفوف الجدول تُخزَّن على القرص مرتبة حسب أعمدة المفتاح الأساسي. في المخطط أعلاه، تُرتَّب صفوف الجدول (أي قيم أعمدتها على القرص) أولًا حسب قيمة cl، ثم تُرتَّب الصفوف التي لها قيمة cl نفسها حسب قيمة ch. وبما أن عمود المفتاح الأول cl منخفض الكاردينالية، فمن المرجّح وجود صفوف تحمل قيمة cl نفسها. ونتيجة لذلك، فمن المرجّح أيضًا أن تكون قيم ch مرتبة محليًا، أي ضمن الصفوف التي لها قيمة cl نفسها. إذا وُضعت البيانات المتشابهة في عمود ما متقاربة من بعضها بعضًا، على سبيل المثال عن طريق الفرز، فإن ضغط هذه البيانات سيكون أفضل. وبوجه عام، تستفيد خوارزمية الضغط من طول التتابعات في البيانات (فكلما زادت كمية البيانات التي تراها، كان ذلك أفضل للضغط) ومن المحلية (فكلما كانت البيانات أكثر تشابهًا، كانت نسبة الضغط أفضل). وعلى النقيض من المخطط أعلاه، يوضح المخطط أدناه ترتيب الصفوف على القرص لمفتاح أساسي تكون فيه أعمدة المفتاح مرتبة حسب الكاردينالية بترتيب تنازلي: تُرتَّب صفوف الجدول الآن أولًا حسب قيمة ch، وتُرتَّب الصفوف التي لها قيمة ch نفسها حسب قيمة cl. لكن بما أن عمود المفتاح الأول ch ذو كاردينالية عالية، فمن غير المرجّح أن توجد صفوف لها قيمة ch نفسها. ونتيجةً لذلك، فمن غير المرجّح أيضًا أن تكون قيم cl مرتبة (محليًا، أي ضمن الصفوف التي لها قيمة ch نفسها). لذلك، فعلى الأرجح أن تكون قيم cl بترتيب عشوائي، وبالتالي تكون خاصية التجاور الموضعي لديها ونسبة الضغط ضعيفتين.

الملخص

لتحسين كلٍّ من كفاءة التصفية على أعمدة المفتاح الثانوية في الاستعلامات ونسبة الضغط لملفات بيانات أعمدة الجدول، يُستحسن ترتيب أعمدة المفتاح الأساسي وفقًا للكاردينالية الخاصة بها ترتيبًا تصاعديًا.

تحديد الصفوف الفردية بكفاءة

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

مثال ملموس

أحد الأمثلة الملموسة هو خدمة لصق النصوص العادية https://pastila.nl التي طوّرها Alexey Milovidov وكتب عنها في مدونته. عند كل تغيير في منطقة النص، تُحفَظ البيانات تلقائيًا في صف ضمن جدول ClickHouse (صف واحد لكل تغيير). ومن إحدى طرق تحديد المحتوى الملصق واسترجاعه (لإصدار معيّن منه) استخدام تجزئة للمحتوى بوصفها معرّف UUID لصف الجدول الذي يحتوي على هذا المحتوى. يوضح المخطط التالي
  • ترتيب insert للصفوف عند تغيّر المحتوى (على سبيل المثال بسبب ضغطات المفاتيح أثناء كتابة النص في منطقة النص) و
  • الترتيب على القرص لبيانات الصفوف المُدرجة عند استخدام PRIMARY KEY (hash):
ونظرًا إلى أن العمود hash يُستخدم بوصفه عمود المفتاح الأساسي
  • يمكن استرجاع صفوف معيّنة بسرعة كبيرة، لكن
  • تُخزَّن صفوف الجدول (أي بيانات أعمدتها) على القرص بترتيب تصاعدي حسب قيم hash (الفريدة والعشوائية). لذلك تُخزَّن أيضًا قيم عمود المحتوى بترتيب عشوائي ومن دون تقارب في البيانات، مما يؤدي إلى نسبة ضغط دون المستوى الأمثل لملف بيانات عمود المحتوى.
ولتحسين نسبة ضغط عمود المحتوى بشكل كبير مع الحفاظ على سرعة استرجاع صفوف معيّنة، يستخدم pastila.nl قيمتي hash (ومفتاحًا أساسيًا مركبًا) لتحديد صف معيّن:
  • تجزئة للمحتوى، كما نوقش أعلاه، تختلف باختلاف البيانات، و
  • تجزئة حساسة للتقارب (بصمة) لا تتغير عند حدوث تغييرات صغيرة في البيانات.
يوضح المخطط التالي
  • ترتيب insert للصفوف عند تغيّر المحتوى (على سبيل المثال بسبب ضغطات المفاتيح أثناء كتابة النص في منطقة النص) و
  • الترتيب على القرص لبيانات الصفوف المُدرجة عند استخدام PRIMARY KEY (fingerprint, hash) المركب:
الآن تُرتَّب الصفوف على القرص أولًا حسب fingerprint، وبالنسبة إلى الصفوف التي لها قيمة fingerprint نفسها، تحدد قيمة hash الترتيب النهائي. وبما أن البيانات التي تختلف فقط باختلافات صغيرة تحصل على قيمة fingerprint نفسها، فإن البيانات المتشابهة تُخزَّن الآن متقاربة على القرص داخل عمود المحتوى. وهذا مفيد جدًا لنسبة ضغط عمود المحتوى، لأن خوارزميات الضغط تستفيد عمومًا من تقارب البيانات (فكلما كانت البيانات أكثر تشابهًا، كانت نسبة الضغط أفضل). أما المقايضة هنا فهي أن استرجاع صف معيّن يتطلب حقلين (fingerprint وhash) لتحقيق الاستفادة المثلى من المفتاح الأساسي الناتج عن PRIMARY KEY (fingerprint, hash) المركب.
آخر تعديل في ٢٩ يونيو ٢٠٢٦