Skip to main content
نوصي المستخدمين دائمًا بإنشاء مخططهم الخاص للسجلات والتتبعات للأسباب التالية:
  • اختيار مفتاح أساسي - تستخدم المخططات الافتراضية عبارة ORDER BY مُحسّنة لأنماط وصول محددة. ومن غير المرجح أن تتوافق أنماط وصولك مع ذلك.
  • استخراج البنية - قد ترغب في استخراج أعمدة جديدة من الأعمدة الموجودة، مثل العمود Body. ويمكن تنفيذ ذلك باستخدام الأعمدة المادية (وباستخدام العروض المادية في الحالات الأكثر تعقيدًا). وهذا يتطلب تغييرات على المخطط.
  • تحسين الخرائط - تستخدم المخططات الافتراضية النوع Map لتخزين السمات. وتتيح هذه الأعمدة تخزين بيانات وصفية عشوائية. ورغم أن هذه قدرة أساسية، لأن البيانات الوصفية القادمة من الأحداث لا تكون معرّفة مسبقًا في كثير من الأحيان، وبالتالي لا يمكن تخزينها بغير ذلك في قاعدة بيانات ذات أنواع صارمة مثل ClickHouse، فإن الوصول إلى مفاتيح الخريطة وقيمها ليس بالكفاءة نفسها التي يوفّرها الوصول إلى عمود عادي. ونعالج ذلك بتعديل المخطط والتأكد من أن مفاتيح الخريطة الأكثر استخدامًا تكون أعمدة من المستوى الأعلى - انظر “استخراج البنية باستخدام SQL”. وهذا يتطلب تغييرًا في المخطط.
  • تبسيط الوصول إلى مفاتيح الخريطة - يتطلب الوصول إلى المفاتيح داخل الخرائط صياغة أكثر تفصيلًا. ويمكنك التخفيف من ذلك باستخدام الأسماء المستعارة. راجع “استخدام الأسماء المستعارة” لتبسيط الاستعلامات.
  • الفهارس الثانوية - يستخدم المخطط الافتراضي فهارس ثانوية لتسريع الوصول إلى الخرائط وتسريع الاستعلامات النصية. وهذه الفهارس لا تكون مطلوبة عادةً، كما أنها تستهلك مساحة إضافية على القرص. ويمكن استخدامها، لكن ينبغي اختبارها للتأكد من الحاجة إليها. راجع “الفهارس الثانوية / فهارس تخطي البيانات”.
  • استخدام codecs - قد ترغب في تخصيص codecs للأعمدة إذا كنت تفهم طبيعة البيانات المتوقعة ولديك أدلة على أن ذلك يحسّن الضغط.
نشرح كل حالة من حالات الاستخدام المذكورة أعلاه بالتفصيل أدناه. مهم: مع أنه يُشجَّع المستخدمون على توسيع مخططاتهم وتعديلها لتحقيق أفضل ضغط وأفضل أداء للاستعلامات، ينبغي لهم الالتزام بتسمية مخطط OTel للأعمدة الأساسية حيثما أمكن. يفترض ملحق ClickHouse Grafana وجود بعض أعمدة OTel الأساسية للمساعدة في بناء الاستعلامات، مثل Timestamp وSeverityText. والأعمدة المطلوبة للسجلات والتتبعات موثقة هنا [1][2] وهنا على التوالي. ويمكنك اختيار تغيير أسماء هذه الأعمدة، مع تجاوز القيم الافتراضية في إعدادات الملحق.
يأتي ClickStack مع مخطط افتراضي مُحسَّنيوفّر ClickStack مخططات جاهزة للاستخدام للسجلات والتتبعات والمقاييس تتضمن أحدث ميزات ClickHouse (فهارس نصية للبحث النصي الكامل والبحث في مفاتيح الخريطة، وأعمدة مادية ومصفوفات ALIAS للتصفية بالقراءة المباشرة، وعمليات بحث عن الصفوف برقم الكتلة)، وقد خضعت لاختبارات معيارية لتقديم أداء قوي جاهزًا للاستخدام لأحمال عمل السجلات والتتبعات. استخدمها كنقطة مرجعية لتصميمك الخاص.

استخراج البنية باستخدام SQL

سواء كنت تُدخل سجلات مهيكلة أو غير مهيكلة، فغالبًا ما تحتاج إلى القدرة على:
  • استخراج الأعمدة من كائنات blob النصية. يكون الاستعلام عنها أسرع من استخدام عمليات السلاسل النصية وقت الاستعلام.
  • استخراج المفاتيح من الخرائط. يضع المخطط الافتراضي السمات العشوائية في أعمدة من نوع Map. ويوفّر هذا النوع إمكانية العمل بلا مخطط، ما يتيح للمستخدمين عدم الحاجة إلى تعريف أعمدة السمات مسبقًا عند تعريف السجلات وtraces — وغالبًا ما يكون ذلك غير ممكن عند جمع السجلات من Kubernetes مع الحاجة إلى الاحتفاظ بتسميات الـ pod لإجراء البحث لاحقًا. كما أن الوصول إلى مفاتيح الخريطة وقيمها أبطأ من الاستعلام عن أعمدة ClickHouse العادية. لذلك، يكون من المرغوب فيه غالبًا استخراج المفاتيح من الخرائط إلى أعمدة الجدول الجذرية.
تأمل الاستعلامات التالية: لنفترض أننا نريد احتساب مسارات URL التي تستقبل أكبر عدد من طلبات POST باستخدام السجلات المهيكلة. تُخزَّن كتلة JSON داخل العمود Body بوصفها String. بالإضافة إلى ذلك، قد تُخزَّن أيضًا في العمود LogAttributes بوصفها Map(String, String) إذا كان المستخدم قد فعّل json_parser في الـ المجمّع.
بافتراض أن LogAttributes متاح، فهذا هو الاستعلام لحساب مسارات URL في الموقع التي تستقبل أكبر عدد من طلبات POST:
لاحظ هنا استخدام صيغة map، مثل LogAttributes['request_path']، واستخدام الدالة path لإزالة معلمات الاستعلام من عنوان URL. إذا لم يكن المستخدم قد فعَّل تحليل JSON في المجمّع، فستكون LogAttributes فارغة، مما يضطرنا إلى استخدام دوال JSON لاستخراج الأعمدة من Body ذي النوع String.
يُفضَّل استخدام ClickHouse للتحليلنوصي عمومًا بأن يُجري المستخدمون تحليل JSON في ClickHouse للسجلات المنظَّمة. ونحن على ثقة بأن ClickHouse يوفّر أسرع تنفيذ لتحليل JSON. ومع ذلك، ندرك أنك قد ترغب في إرسال السجلات إلى مصادر أخرى، وألا يكون هذا المنطق موجودًا في SQL.
والآن لننظر في الأمر نفسه بالنسبة إلى السجلات غير المهيكلة:
يتطلّب استعلام مماثل للسجلات غير المهيكلة استخدام التعبيرات النمطية عبر الدالة extractAllGroupsVertical.
إن ازدياد تعقيد الاستعلامات وتكلفتها عند تحليل السجلات غير المهيكلة (لاحظ فرق الأداء) هو السبب في أننا نوصي المستخدمين دائمًا باستخدام السجلات المهيكلة متى أمكن.
ضع القواميس في الحسبانيمكن تحسين الاستعلام أعلاه للاستفادة من قواميس التعبيرات النمطية. راجع استخدام القواميس لمزيد من التفاصيل.
يمكن تلبية حالتي الاستخدام هاتين في ClickHouse عبر نقل منطق الاستعلام أعلاه إلى وقت الإدراج. نستعرض أدناه عدة أساليب، مع توضيح الحالات المناسبة لكل منها.
OTel أم ClickHouse للمعالجة؟يمكنك أيضًا إجراء المعالجة باستخدام processors وoperators الخاصة بـ OTel المجمّع كما هو موضح هنا. في معظم الحالات، ستجد أن ClickHouse أكثر كفاءةً في استخدام الموارد وأسرع بكثير من processors الخاصة بـ المجمّع. ويتمثل العيب الأساسي لتنفيذ جميع عمليات معالجة الأحداث باستخدام SQL في ربط الحل لديك بـ ClickHouse. فعلى سبيل المثال، قد ترغب في إرسال السجلات المعالَجة إلى وجهات بديلة من OTel المجمّع، مثل S3.

الأعمدة المادية

توفّر الأعمدة المادية أبسط حل لاستخراج البنية من الأعمدة الأخرى. وتُحتسب قيم هذه الأعمدة دائمًا في وقت الإدراج، ولا يمكن تحديدها في استعلامات INSERT.
العبء الإضافيتضيف الأعمدة المادية عبئًا إضافيًا على التخزين، لأن القيم تُستخرج إلى أعمدة جديدة على القرص في وقت الإدراج.
تدعم الأعمدة المادية أي تعبير في ClickHouse، ويمكنها الاستفادة من أي من الدوال التحليلية الخاصة بـ معالجة السلاسل النصية (بما في ذلك التعابير النمطية والبحث) وURL، وإجراء تحويلات الأنواع، واستخراج القيم من JSON، أو العمليات الرياضية. نوصي باستخدام الأعمدة المادية للمعالجة الأساسية. وهي مفيدة بشكل خاص لاستخراج القيم من نوع خريطة، ورفعها إلى أعمدة على المستوى الأعلى، وإجراء تحويلات الأنواع. وغالبًا ما تكون أكثر فائدة عند استخدامها في مخططات بسيطة جدًا أو بالاقتران مع العروض المادية. انظر إلى المخطط التالي للسجلات، حيث استخرج المجمّع بيانات JSON إلى العمود LogAttributes:
يمكن العثور على المخطّط المكافئ لاستخراج البيانات من Body من النوع String باستخدام دوال JSON هنا. تستخرج أعمدتنا الثلاثة المادية صفحة الطلب ونوع الطلب ونطاق المُحيل. وتصل هذه الأعمدة إلى مفاتيح خريطة وتطبّق الدوال على قيمها. لذا يكون استعلامنا اللاحق أسرع بكثير:
لن تُعاد الأعمدة المادية افتراضيًا في SELECT *. وذلك لضمان بقاء الخاصية التالية قائمة: يمكن دائمًا إعادة إدراج نتيجة SELECT * في الجدول باستخدام INSERT. ويمكن تعطيل هذا السلوك بضبط asterisk_include_materialized_columns=1، كما يمكن تفعيل ذلك في Grafana (راجع Additional Settings -> Custom Settings في إعدادات مصدر البيانات).

العروض المادية

توفّر العروض المادية طريقة أقوى لتطبيق التصفية والتحويلات باستخدام SQL على السجلات والتتبعات. تتيح لك العروض المادية نقل تكلفة المعالجة من وقت الاستعلام إلى وقت الإدراج. والعرض المادي في ClickHouse ليس سوى مُشغِّل ينفّذ استعلامًا على كتل البيانات أثناء إدراجها في جدول. وتُدرَج نتائج هذا الاستعلام في جدول “هدف” ثانٍ.
تحديثات في الوقت الفعليتُحدَّث العروض المادية في ClickHouse في الوقت الفعلي مع تدفّق البيانات إلى الجدول الذي تستند إليه، لذا فهي تعمل بصورة أقرب إلى الفهارس التي تُحدَّث باستمرار. وعلى النقيض من ذلك، تكون العروض المادية في قواعد البيانات الأخرى عادةً لقطات ثابتة لاستعلام يجب تحديثها (على نحو مشابه لـ Refreshable Materialized Views في ClickHouse).
يمكن نظريًا أن يكون الاستعلام المرتبط بالعرض المادي أي استعلام، بما في ذلك التجميع، رغم وجود قيود على عمليات JOIN. وبالنسبة إلى أعباء عمل التحويلات والتصفية المطلوبة للسجلات والتتبعات، يمكنك اعتبار أي عبارة SELECT ممكنة. يجب أن تتذكر أن الاستعلام ليس سوى مُشغِّل يُنفَّذ على الصفوف التي تُدرَج في جدول (الجدول المصدر)، وتُرسَل نتائجه إلى جدول جديد (الجدول الهدف). ولضمان عدم تخزين البيانات مرتين (في الجدولَين المصدر والهدف)، يمكننا تغيير محرك الجدول المصدر إلى Null table engine، مع الحفاظ على المخطط الأصلي. وستواصل مكونات OTel المجمّع لدينا إرسال البيانات إلى هذا الجدول. فعلى سبيل المثال، بالنسبة إلى السجلات، يصبح جدول otel_logs كما يلي:
يُعد Null table engine أسلوب تحسين قويًا — فكّر فيه على أنه /dev/null. هذا الجدول لن يخزّن أي بيانات، لكن أي عروض مادية مرفقة ستظل تُنفَّذ على الصفوف المُدرجة قبل التخلّص منها. تأمّل الاستعلام التالي. فهو يحوّل صفوفنا إلى format نرغب في الاحتفاظ به، مع استخراج جميع الأعمدة من LogAttributes (ونفترض أن المجمّع قد ضبط ذلك باستخدام العامل json_parser)، وتعيين SeverityText وSeverityNumber (استنادًا إلى بعض الشروط البسيطة وتعريف هذه الأعمدة). وفي هذه الحالة، نحدّد أيضًا فقط الأعمدة التي نعرف أنها ستُملأ — مع تجاهل أعمدة مثل TraceId وSpanId وTraceFlags.
نستخرج أيضاً عمود Body أعلاه - احتياطاً في حال إضافة سمات إضافية لاحقاً لا يستخرجها استعلام SQL الخاص بنا. ينبغي أن يتضغط هذا العمود بكفاءة في ClickHouse وسيُستخدم نادراً، مما لا يؤثر على أداء الاستعلام. وأخيراً، نُقلّص Timestamp إلى DateTime (لتوفير المساحة - انظر “Optimizing Types”) باستخدام cast.
التعبيرات الشرطيةلاحظ استخدام التعبيرات الشرطية أعلاه لاستخراج SeverityText وSeverityNumber. وهي مفيدة جدًا لصياغة الشروط المعقدة والتحقق مما إذا كانت القيم معيّنة داخل maps — إذ نفترض بسذاجة أن جميع المفاتيح موجودة في LogAttributes. ننصح المستخدمين بالتعرّف عليها جيدًا، فهي خير معين لك في تحليل السجلات، إلى جانب الدوال المخصّصة للتعامل مع قيم NULL!
نحتاج إلى جدول لاستقبال هذه النتائج. يتوافق الجدول الهدف أدناه مع الاستعلام أعلاه:
الأنواع المختارة هنا مبنيةٌ على التحسينات التي تمت مناقشتها في “تحسين الأنواع”.
لاحظ كيف غيّرنا المخطط بشكل جذري. عمليًا، من المرجح أيضًا أن تكون لديك أعمدة Trace سترغب في الحفاظ عليها، بالإضافة إلى العمود ResourceAttributes (يحتوي هذا عادةً على بيانات Kubernetes الوصفية). يمكن لـ Grafana الاستفادة من أعمدة التتبّع لتوفير وظيفة الربط بين السجلات والتتبّعات - راجع “استخدام Grafana”.
فيما يلي، ننشئ عرضًا ماديًا otel_logs_mv، ينفّذ عبارة SELECT المذكورة أعلاه على جدول otel_logs ويرسل النتائج إلى otel_logs_v2.
يوضح الشكل أدناه ما سبق: إذا أعدنا الآن تشغيل تهيئة المجمّع المستخدمة في “التصدير إلى ClickHouse”، فستظهر البيانات في otel_logs_v2 بالتنسيق المطلوب. لاحظ استخدام دوال استخراج JSON ذات الأنواع المحددة.
يُعرض أدناه عرض مادي مكافئ يعتمد على استخراج الأعمدة من العمود Body باستخدام دوال JSON:

انتبه إلى الأنواع

تعتمد العروض المادية المذكورة أعلاه على التحويل الضمني للأنواع، وخاصةً عند استخدام خريطة LogAttributes. وغالبًا ما يقوم ClickHouse بتحويل القيمة المستخرجة تلقائيًا إلى نوع الجدول الهدف، مما يقلّل من الصياغة المطلوبة. ومع ذلك، نوصي المستخدمين دائمًا باختبار عروضهم باستخدام عبارة SELECT الخاصة بالعرض مع عبارة INSERT INTO على جدول هدف يستخدم مخطط مطابقة. من المفترض أن يؤكد ذلك أن الأنواع تُعالَج بشكل صحيح. ويجب إيلاء اهتمام خاص للحالات التالية:
  • إذا لم يكن المفتاح موجودًا في الخريطة، فستُعاد سلسلة نصية فارغة. وفي حالة القيم الرقمية، ستحتاج إلى تعيين هذه القيم إلى قيمة مناسبة. ويمكن تحقيق ذلك باستخدام العبارات الشرطية مثل if(LogAttributes['status'] = ", 200, LogAttributes['status']) أو دوال التحويل إذا كانت القيم الافتراضية مقبولة، مثل toUInt8OrDefault(LogAttributes['status'] )
  • بعض الأنواع لا تُحوَّل دائمًا؛ فعلى سبيل المثال، لا تُحوَّل التمثيلات النصية للقيم الرقمية إلى قيم enum.
  • تُرجع دوال استخراج JSON قيمًا افتراضية بحسب نوعها إذا لم يتم العثور على قيمة. تأكد من أن هذه القيم منطقية!
تجنب Nullableتجنب استخدام Nullable في ClickHouse لبيانات Observability. فنادرًا ما تكون هناك حاجة في logs وtraces إلى التمييز بين القيمة الفارغة وNULL. وتضيف هذه الميزة عبئًا إضافيًا على التخزين، كما تؤثر سلبًا في query performance. راجع هنا لمزيد من التفاصيل.

اختيار مفتاح أساسي (ترتيبي)

بمجرد استخراج الأعمدة المطلوبة، يمكنك البدء في تحسين مفتاح الترتيب/المفتاح الأساسي. يمكن تطبيق بعض القواعد البسيطة للمساعدة في اختيار مفتاح الترتيب. قد تتعارض الإرشادات التالية أحيانًا، لذا يُستحسن النظر فيها بهذا الترتيب. ويمكن أن تسفر هذه العملية عن عدة مفاتيح مرشحة، وعادةً ما تكون 4-5 كافية:
  1. اختر الأعمدة التي تتوافق مع عوامل التصفية الشائعة وأنماط الوصول لديك. فإذا كنت تبدأ عادةً تحقيقات Observability بالتصفية حسب عمود معين، مثل اسم الـ pod، فسيُستخدم هذا العمود كثيرًا في عبارات WHERE. لذا أعطِ الأولوية لإدراج هذه الأعمدة في مفتاحك مقارنةً بالأعمدة الأقل استخدامًا.
  2. فضّل الأعمدة التي تساعد عند التصفية على استبعاد نسبة كبيرة من إجمالي الصفوف، مما يقلل كمية البيانات التي يلزم قراءتها. وغالبًا ما تكون أسماء الخدمات ورموز الحالة مرشحة جيدة — وفي الحالة الأخيرة تحديدًا، إذا كنت تُجري التصفية حسب قيم تستبعد معظم الصفوف؛ فعلى سبيل المثال، التصفية حسب قيم 200 ستطابق في معظم الأنظمة معظم الصفوف، بخلاف أخطاء 500 التي تمثل عادةً مجموعة فرعية صغيرة.
  3. فضّل الأعمدة التي يُحتمل أن تكون شديدة الارتباط بأعمدة أخرى في الجدول. فهذا يساعد على ضمان تخزين هذه القيم أيضًا بشكل متجاور، مما يحسّن الضغط.
  4. يمكن جعل عمليات GROUP BY وORDER BY للأعمدة الموجودة في مفتاح الترتيب أكثر كفاءة من حيث الذاكرة.

بعد تحديد المجموعة الفرعية من الأعمدة الخاصة بمفتاح الترتيب، يجب تعريفها بترتيب محدد. ويمكن أن يؤثر هذا الترتيب بدرجة كبيرة في كلٍّ من كفاءة التصفية على أعمدة المفاتيح الثانوية في الاستعلامات ونسبة الضغط لملفات بيانات الجدول. وبوجه عام، من الأفضل ترتيب المفاتيح تصاعديًا حسب الكاردينالية. لكن ينبغي موازنة ذلك مع حقيقة أن التصفية على الأعمدة التي تظهر لاحقًا في مفتاح الترتيب ستكون أقل كفاءة من التصفية على الأعمدة التي تظهر مبكرًا في الـ tuple. وازن بين هذين الاعتبارين وراعِ أنماط الوصول لديك. والأهم من ذلك، اختبر البدائل المختلفة. ولمزيد من الفهم لمفاتيح الترتيب وكيفية تحسينها، نوصي بـهذه المقالة.
البنية أولًانوصي بتحديد مفاتيح الترتيب بعد الانتهاء من هيكلة السجلات. لا تستخدم مفاتيح داخل خرائط attribute كمفتاح ترتيب، ولا تعبيرات استخراج JSON. وتأكد من أن مفاتيح الترتيب لديك موجودة كأعمدة جذرية في جدولك.

استخدام Map

تُظهر الأمثلة السابقة استخدام صيغة map['key'] للوصول إلى القيم في الأعمدة من النوع Map(String, String). وإلى جانب استخدام ترميز map للوصول إلى المفاتيح المتداخلة، تتوفر أيضًا دوال map المتخصصة في ClickHouse لتصفية هذه الأعمدة أو تحديدها. على سبيل المثال، يحدِّد الاستعلام التالي جميع المفاتيح الفريدة المتاحة في العمود LogAttributes باستخدام الدالة mapKeys، متبوعةً بـ الدالة groupArrayDistinctArray (وهي combinator).
تجنب النقاطلا نوصي باستخدام النقاط في أسماء أعمدة Map، وقد نلغي دعمها مستقبلًا. استخدم _.

استخدام الأسماء المستعارة

الاستعلام عن أنواع Map أبطأ من الاستعلام عن الأعمدة العادية - راجع “تسريع الاستعلامات”. بالإضافة إلى ذلك، فهو أكثر تعقيدًا من حيث الصياغة وقد يكون من المرهق كتابته. ولمعالجة هذه المشكلة، نوصي باستخدام أعمدة Alias. تُحتسب أعمدة ALIAS وقت الاستعلام ولا تُخزَّن في الجدول. لذلك، لا يمكن إجراء INSERT لقيمة في عمود من هذا النوع. وباستخدام الأسماء المستعارة، يمكننا الإشارة إلى مفاتيح Map وتبسيط الصياغة، مع إظهار إدخالات Map كأنها عمود عادي بشكل شفاف. تأمل المثال التالي:
لدينا عدة أعمدة materialized وعمود ALIAS باسم RemoteAddr يقرأ من الـ map LogAttributes. يمكننا الآن الاستعلام عن قيم LogAttributes['remote_addr'] عبر هذا العمود، مما يبسّط الاستعلام، أي:
علاوة على ذلك، يمكن إضافة ALIAS بسهولة عبر الأمر ALTER TABLE. وتصبح هذه الأعمدة متاحة فورًا، على سبيل المثال.
الاسم البديل مستبعَد افتراضيًاافتراضيًا، لا يتضمن SELECT * أعمدة ALIAS. ويمكن إيقاف هذا السلوك بتعيين asterisk_include_alias_columns=1.

تحسين الأنواع

تنطبق أفضل ممارسات ClickHouse العامة لتحسين الأنواع على حالة استخدام ClickHouse.

استخدام codecs

بالإضافة إلى تحسينات الأنواع، يمكنك اتباع أفضل الممارسات العامة لـ codecs عند محاولة تحسين الضغط لمخططات ClickHouse Observability. بوجه عام، ستجد أن codec ‏ZSTD مناسب جدًا لمجموعات بيانات logging وtrace. وقد يؤدي رفع قيمة الضغط عن default value البالغة 1 إلى تحسين الضغط. ومع ذلك، ينبغي اختبار ذلك، لأن القيم الأعلى تفرض overhead أكبر على CPU وقت insert. وعادةً ما نرى فائدة محدودة من زيادة هذه القيمة. علاوة على ذلك، فرغم أن timestamps تستفيد من ترميز delta من ناحية الضغط، فقد تبيّن أنها قد تؤدي إلى تراجع query performance إذا استُخدم هذا column في المفتاح الأساسي/مفتاح الترتيب. نوصي المستخدمين بتقييم الموازنة بين الضغط وquery performance في كل حالة.

استخدام القواميس

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

وقت الإدراج مقابل وقت الاستعلام

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

استخدام قواميس IP

يُعد الإثراء الجغرافي للسجلات والتتبعات بقيم خط العرض وخط الطول باستخدام عناوين IP من المتطلبات الشائعة في مجال Observability. ويمكن تحقيق ذلك باستخدام القاموس المنظَّم ip_trie. نستخدم مجموعة بيانات DB-IP على مستوى المدينة المتاحة علنًا، والمقدَّمة من DB-IP.com بموجب شروط ترخيص CC BY 4.0. ومن ملف README، يمكننا أن نرى أن البيانات منظَّمة على النحو التالي:
استنادًا إلى هذه البنية، لنبدأ بإلقاء نظرة سريعة على البيانات باستخدام دالة الجدول url():
لتسهيل الأمر، لنستخدم محرك الجدول URL() لإنشاء جدول في ClickHouse بأسماء الحقول الخاصة بنا، والتحقق من إجمالي عدد الصفوف:
نظرًا لأن قاموس ip_trie لدينا يتطلب تمثيل نطاقات عناوين IP بترميز CIDR، فسنحتاج إلى تحويل ip_range_start وip_range_end. يمكن حساب قيمة CIDR لكل نطاق بإيجاز باستخدام الاستعلام التالي:
هناك الكثير مما يجري في الاستعلام أعلاه. لمن يهمه الأمر، يمكنه قراءة هذا الشرح الممتاز. وإلا، فاكتفِ باعتبار أن ما سبق يحسب قيمة CIDR لنطاق IP.
بالنسبة إلى غرضنا، لن نحتاج إلا إلى نطاق IP ورمز البلد والإحداثيات، لذا لنُنشئ جدولًا جديدًا ونُدرج فيه بيانات Geo IP الخاصة بنا:
لإجراء عمليات بحث عن عناوين IP بزمن استجابة منخفض في ClickHouse، سنستفيد من القواميس لتخزين تعيينات المفتاح -> السمات لبيانات Geo IP الخاصة بنا في الذاكرة. يوفّر ClickHouse بنية القاموس ip_trie بنية القاموس لربط بادئات الشبكة لدينا (كتل CIDR) بالإحداثيات ورموز البلدان. يعرّف الاستعلام التالي قاموسًا باستخدام هذا التخطيط، مع الجدول أعلاه كمصدر.
يمكننا اختيار صفوف من القاموس والتأكد من أن مجموعة البيانات هذه متاحة لعمليات البحث:
التحديث الدوريتُحدَّث القواميس في ClickHouse دوريًا استنادًا إلى بيانات underlying table وبند lifetime المستخدم أعلاه. ولتحديث قاموس Geo IP لدينا بحيث يعكس أحدث التغييرات في dataset ‏DB-IP، نحتاج فقط إلى إعادة إدراج البيانات من الجدول البعيد geoip_url إلى جدول geoip مع تطبيق التحويلات.
الآن بعد أن حمّلنا بيانات Geo IP إلى قاموس ip_trie (الذي يحمل أيضًا الاسم ip_trie)، يمكننا استخدامه لتحديد الموقع الجغرافي لعناوين IP. ويمكن تنفيذ ذلك باستخدام الدالة dictGet() كما يلي:
لاحظ سرعة جلب البيانات هنا. يتيح لنا ذلك إثراء السجلات. في هذه الحالة، نختار إجراء الإثراء في وقت الاستعلام. وبالعودة إلى مجموعة بيانات السجلات الأصلية، يمكننا استخدام ما سبق لتجميع سجلاتنا بحسب البلد. ويفترض ما يلي أننا نستخدم المخطط الناتج عن العرض المادي السابق، والذي يتضمن عمود RemoteAddress مُستخرجًا.
نظرًا لأن تعيين عنوان IP إلى موقع جغرافي قد يتغير، فمن المرجح أن يرغب المستخدمون في معرفة الموقع الذي صدر منه الطلب وقت إرساله، لا الموقع الجغرافي الحالي للعنوان نفسه. ولهذا السبب، يُفضَّل هنا على الأرجح إجراء الإثراء عند الفهرسة. ويمكن تنفيذ ذلك باستخدام الأعمدة المُجسَّدة كما هو موضح أدناه، أو في عبارة select ضمن عرض مُجسَّد:
تحديث دوريمن المرجّح أن يرغب المستخدمون في تحديث قاموس إثراء عناوين IP دوريًا استنادًا إلى البيانات الجديدة. ويمكن تحقيق ذلك باستخدام عبارة LIFETIME في القاموس، ما يؤدي إلى إعادة تحميل القاموس دوريًا من الجدول الأساسي. لتحديث الجدول الأساسي، راجع “العروض المادية القابلة للتحديث”.
توفّر البلدان والإحداثيات المذكورة أعلاه إمكانات تصور تتجاوز مجرد التجميع والتصفية حسب البلد. وللاستلهام، راجع “تصور البيانات الجغرافية”.

استخدام قواميس Regex (تحليل وكيل المستخدم)

يُعدّ تحليل سلاسل وكيل المستخدم من المشكلات الكلاسيكية في التعابير النمطية، وهو مطلب شائع في مجموعات البيانات المعتمدة على السجلات وآثار التتبع. ويوفّر ClickHouse تحليلًا فعّالًا لـ وكيل المستخدم باستخدام قواميس شجرة التعابير النمطية. تُعرَّف قواميس شجرة التعابير النمطية في ClickHouse مفتوح المصدر باستخدام نوع مصدر القاموس YAMLRegExpTree، الذي يوفّر المسار إلى ملف YAML يحتوي على شجرة التعابير النمطية. وإذا رغبت في توفير قاموس تعابير نمطية خاص بك، فيمكن العثور على تفاصيل البنية المطلوبة هنا. نركّز أدناه على تحليل وكيل المستخدم باستخدام uap-core، ونحمّل قاموسنا لاستخدامه مع تنسيق CSV المدعوم. هذا النهج متوافق مع OSS وClickHouse Cloud.
في الأمثلة أدناه، نستخدم لقطات snapshots من أحدث تعابير uap-core النمطية لتحليل وكيل المستخدم من يونيو 2024. ويمكن العثور على أحدث ملف، الذي يُحدَّث من حين لآخر، هنا. يمكنك اتباع الخطوات هنا للتحميل إلى ملف CSV المستخدم أدناه.
أنشئ جداول Memory التالية. وتحتفظ هذه الجداول بالتعابير النمطية التي نستخدمها لتحليل الأجهزة والمتصفحات وأنظمة التشغيل.
يمكن تعبئة هذه الجداول من ملفات CSV المستضافة علنًا التالية، باستخدام دالة الجدول url:
بعد تعبئة جداول الذاكرة، يمكننا تحميل قواميس التعبيرات النمطية. لاحظ أننا بحاجة إلى تحديد قيم المفاتيح كأعمدة، إذ ستكون هذه هي السمات التي يمكننا استخراجها من وكيل المستخدم.
بعد تحميل هذه القواميس، يمكننا إدخال وكيل المستخدم نموذجي واختبار إمكانات الاستخراج الجديدة باستخدام القواميس:
نظرًا لأن القواعد المرتبطة بـ وكيل المستخدم نادرًا ما تتغير، ولا يحتاج القاموس إلى التحديث إلا عند ظهور متصفحات وأنظمة تشغيل وأجهزة جديدة، فمن المنطقي إجراء هذا الاستخراج عند الإدراج. يمكننا إما تنفيذ ذلك باستخدام عمود مُجسَّد أو باستخدام عرض مادي. في ما يلي نعدّل العرض المادي الذي استُخدم سابقًا:
يتطلب ذلك تعديل مخطط الجدول الهدف otel_logs_v2:
بعد إعادة تشغيل المجمّع وإدخال السجلات المنظَّمة، وبالاستناد إلى الخطوات الموثَّقة سابقًا، يمكننا الاستعلام عن أعمدة Device وBrowser وOs التي استخرجناها حديثًا.
Tuples للبُنى المعقّدةلاحِظ استخدام Tuples في أعمدة وكيل المستخدم هذه. يُوصى باستخدام Tuples مع البُنى المعقّدة التي يكون تسلسلها الهرمي معروفًا مسبقًا. وتوفّر الأعمدة الفرعية الأداء نفسه الذي توفّره الأعمدة العادية (على عكس مفاتيح Map)، مع إتاحة استخدام أنواع غير متجانسة.

للمزيد من القراءة

للمزيد من الأمثلة والتفاصيل حول القواميس، نوصي بالمقالات التالية:

تسريع الاستعلامات

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

استخدام Materialized views (التدريجية) للتجميعات

في الأقسام السابقة، استعرضنا استخدام Materialized views لتحويل البيانات وتصفيتها. ومع ذلك، يمكن أيضًا استخدام Materialized views لاحتساب التجميعات مسبقًا عند الإدراج وتخزين النتيجة. ويمكن بعد ذلك تحديث هذه النتيجة بنتائج عمليات الإدراج اللاحقة، مما يتيح عمليًا احتساب التجميعات مسبقًا وقت الإدراج. وتتمثل الفكرة الأساسية هنا في أن النتائج ستكون غالبًا تمثيلًا أصغر للبيانات الأصلية (على شكل ملخص جزئي في حالة التجميعات). وعند إقران ذلك باستعلام أبسط لقراءة النتائج من الجدول الهدف، ستكون أزمنة الاستعلام أسرع مما لو أُجري الحساب نفسه على البيانات الأصلية. انظر إلى الاستعلام التالي، حيث نحسب إجمالي حركة المرور لكل ساعة باستخدام السجلات المنظَّمة:
يمكننا تخيّل أن هذا قد يكون مخططًا خطيًا شائعًا ينشئه المستخدمون في Grafana. ولا شك أن هذا الاستعلام سريع جدًا — فمجموعة البيانات لا تضم سوى 10 ملايين صف، وClickHouse سريع! لكن إذا وسّعنا ذلك إلى مليارات وتريليونات من الصفوف، فسنرغب بالطبع في الحفاظ على مستوى أداء هذا الاستعلام.
سيكون هذا الاستعلام أسرع بعشر مرات إذا استخدمنا الجدول otel_logs_v2، الناتج عن العرض المادي السابق، والذي يستخرج المفتاح size من الخريطة LogAttributes. نستخدم البيانات الخام هنا لأغراض توضيحية فقط، ونوصي باستخدام العرض السابق إذا كان هذا الاستعلام شائعًا.
نحتاج إلى جدول لتلقّي النتائج إذا أردنا تنفيذ هذا الحساب عند الإدراج باستخدام عرض مادي. يجب ألا يحتفظ هذا الجدول إلا بصف واحد لكل ساعة. وإذا وصل تحديث لساعة موجودة بالفعل، فيجب دمج الأعمدة الأخرى في صف تلك الساعة الحالي. ولكي يحدث هذا الدمج للحالات التزايدية، يجب تخزين حالات جزئية للأعمدة الأخرى. يتطلب ذلك نوع محرك خاصًا في ClickHouse: ‏SummingMergeTree. إذ يستبدل جميع الصفوف التي لها مفتاح الترتيب نفسه بصف واحد يحتوي على قيم مجمّعة للأعمدة الرقمية. وسيقوم الجدول التالي بدمج أي صفوف لها التاريخ نفسه، مع جمع أي أعمدة رقمية.
لتوضيح طريقة عمل العرض المادي، افترض أن جدول bytes_per_hour فارغ ولم يستقبل أي بيانات بعد. ينفّذ العرض المادي لدينا عبارة SELECT المذكورة أعلاه على البيانات المُدرجة في otel_logs (وسيُنفَّذ ذلك على مستوى كتل بالحجم المُعدّ)، ثم تُرسَل النتائج إلى bytes_per_hour. تظهر البنية أدناه:
تُعد عبارة TO هنا أساسية، إذ تحدد الوجهة التي ستُرسل إليها النتائج، أي bytes_per_hour. إذا أعدنا تشغيل OTel Collector وأعدنا إرسال السجلات، فسيُملأ جدول bytes_per_hour تدريجيًا بنتيجة الاستعلام أعلاه. وعند اكتمال ذلك، يمكننا التأكد من حجم bytes_per_hour — إذ ينبغي أن يحتوي على صف واحد لكل ساعة:
لقد خفّضنا فعليًا عدد الصفوف هنا من 10m (في otel_logs) إلى 113 من خلال تخزين نتيجة الاستعلام. والمهم هنا أنه إذا أُدرجت سجلات جديدة في جدول otel_logs، فستُرسَل القيم الجديدة إلى bytes_per_hour بحسب الساعة المقابلة لها، حيث ستُدمَج تلقائيًا بشكل غير متزامن في الخلفية. وبالاحتفاظ بصف واحد فقط لكل ساعة، سيظل bytes_per_hour صغيرًا ومحدَّثًا دائمًا. ونظرًا إلى أن دمج الصفوف يتم بشكل غير متزامن، فقد يكون هناك أكثر من صف واحد لكل ساعة عند تنفيذ المستخدم للاستعلام. ولضمان دمج أي صفوف متبقية وقت تنفيذ الاستعلام، لدينا خياران:
  • استخدام FINAL المُعدِّل مع اسم الجدول (وهذا ما فعلناه في استعلام العد أعلاه).
  • التجميع حسب مفتاح الترتيب المستخدم في جدولنا النهائي، أي Timestamp، ثم جمع المقاييس.
وعادةً ما يكون الخيار الثاني أكثر كفاءة ومرونة (إذ يمكن استخدام الجدول لأغراض أخرى)، لكن الخيار الأول قد يكون أبسط في بعض الاستعلامات. نعرض كلا الخيارين أدناه:
لقد سرّع هذا استعلامنا من 0.6s إلى 0.008s — أي بأكثر من 75 مرة!
قد تكون هذه المكاسب أكبر بكثير مع مجموعات بيانات أكبر واستعلامات أكثر تعقيدًا. اطّلع على أمثلة هنا.

مثال أكثر تعقيدًا

يُجمِّع المثال أعلاه عددًا بسيطًا لكل ساعة باستخدام SummingMergeTree. أما الإحصاءات التي تتجاوز المجاميع البسيطة فتتطلب محرك جدول هدف مختلفًا: AggregatingMergeTree. لنفترض أننا نريد حساب عدد عناوين IP الفريدة (أو المستخدمين الفريدين) لكل يوم. الاستعلام الخاص بذلك:
للاحتفاظ بعدد العناصر المميّزة لأغراض التحديث التزايدي، يلزم استخدام AggregatingMergeTree.
لضمان أن يعرف ClickHouse أن حالات التجميع ستُخزَّن، نُعرِّف العمود UniqueUsers بالنوع AggregateFunction، مع تحديد الدالة التي تُنتج الحالات الجزئية (uniq) ونوع العمود المصدر (IPv4). وكما في SummingMergeTree، ستُدمَج الصفوف التي لها قيمة مفتاح ORDER BY نفسها (Hour في المثال أعلاه). ويستخدم العرض المادي المرتبط الاستعلام السابق:
لاحظ كيف نُلحق اللاحقة State بنهاية دوالنا التجميعية. يضمن ذلك إرجاع الحالة التجميعية للدالة بدلًا من النتيجة النهائية. وستتضمن هذه الحالة معلومات إضافية تتيح دمج هذه الحالة الجزئية مع حالات أخرى. بمجرد إعادة تحميل البيانات عبر إعادة تشغيل Collector، يمكننا التأكد من توفّر 113 صفًا في جدول unique_visitors_per_hour.
يجب أن يستخدم استعلامنا النهائي اللاحقة Merge في دوالنا (لأن الأعمدة تخزّن حالات التجميع الجزئية):
لاحظ أننا نستخدم GROUP BY هنا بدلًا من FINAL.

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

ينبغي مراعاة أنماط الوصول عند اختيار مفتاح الترتيب في ClickHouse، إلى جانب الأعمدة التي تُستخدم كثيرًا في عبارات التصفية والتجميع. وقد يكون ذلك مقيِّدًا في حالات استخدام Observability، حيث تكون لدى المستخدمين أنماط وصول أكثر تنوعًا لا يمكن حصرها ضمن مجموعة واحدة من الأعمدة. ويتضح ذلك بأفضل شكل في مثال مضمَّن في مخططات OTel الافتراضية. تأمّل المخطط الافتراضي للتتبعات:
هذا المخطط مُحسَّن للتصفية حسب ServiceName وSpanName وTimestamp. وفي التتبّع، يحتاج المستخدمون أيضًا إلى إمكانية إجراء عمليات بحث باستخدام TraceId محدّد واسترجاع الـ spans المرتبطة بهذا الـ trace. ومع أن ذلك موجود في مفتاح الترتيب، فإن وجوده في النهاية يعني أن التصفية لن تكون بالكفاءة نفسها، كما يُرجَّح أن يتطلّب استرجاع trace واحد فحص كميات كبيرة من البيانات. يثبّت OTel collector أيضًا عرضًا ماديًا وجدولًا مرتبطًا بها لمعالجة هذا التحدّي. الجدول والعرض موضّحان أدناه:
يضمن هذا العرض عمليًا أن يتضمن الجدول otel_traces_trace_id_ts الحدين الأدنى والأقصى للطابع الزمني الخاص بالتتبّع. ويتيح هذا الجدول، المرتّب حسب TraceId، استرجاع هاتين القيمتين بكفاءة. ويمكن بعد ذلك استخدام هذه النطاقات الزمنية عند الاستعلام عن الجدول الرئيسي otel_traces. وبشكل أكثر تحديدًا، عند استرجاع تتبّع بواسطة معرّفه، يستخدم Grafana الاستعلام التالي:
يحدّد تعبير الجدول الشائع هنا الحدين الأدنى والأقصى لـ timestamp لمعرّف التتبّع ae9226c78d1d360601e6383928e4d22d، ثم يستخدم ذلك لتصفية جدول otel_traces الرئيسي وإرجاع الـ spans المرتبطة به. يمكن تطبيق هذا النهج نفسه على أنماط وصول مشابهة. ونستعرض مثالًا مشابهًا في نمذجة البيانات هنا.

استخدام الإسقاطات

تتيح لك إسقاطات ClickHouse تحديد عدة عبارات ORDER BY للجدول. في الأقسام السابقة، استعرضنا كيف يمكن استخدام العروض المادية في ClickHouse لإجراء عمليات التجميع مسبقًا، وتحويل الصفوف، وتحسين استعلامات Observability لأنماط الوصول المختلفة. قدمنا مثالًا يرسل فيه العرض المادي الصفوف إلى جدول هدف بمفتاح ترتيب يختلف عن الجدول الأصلي الذي يستقبل عمليات الإدراج، وذلك لتحسين عمليات البحث بحسب معرّف التتبّع. يمكن استخدام الإسقاطات لمعالجة المشكلة نفسها، إذ تتيح للمستخدم تحسين الاستعلامات على عمود لا يكون جزءًا من المفتاح الأساسي. نظريًا، يمكن استخدام هذه الإمكانية لتوفير عدة مفاتيح ترتيب للجدول، لكن مع عيب واضح: تكرار البيانات. فعلى وجه التحديد، يجب كتابة البيانات وفق ترتيب المفتاح الأساسي الرئيسي، بالإضافة إلى الترتيب المحدد لكل إسقاط. ويؤدي ذلك إلى إبطاء عمليات الإدراج واستهلاك مساحة أكبر على القرص.
الإسقاطات مقابل العروض الماديةتوفّر الإسقاطات كثيرًا من الإمكانيات نفسها التي توفّرها العروض المادية، لكن ينبغي استخدامها بحذر، وغالبًا ما تكون العروض المادية هي الخيار المفضّل. ويجب أن تفهم عيوبها ومتى يكون استخدامها مناسبًا. فعلى سبيل المثال، رغم إمكانية استخدام الإسقاطات لإجراء عمليات التجميع مسبقًا، فإننا نوصي المستخدمين باستخدام العروض المادية لهذا الغرض.
انظر إلى الاستعلام التالي، الذي يرشّح جدول otel_logs_v2 لدينا بحسب 500 من رموز الخطأ. ومن المرجح أن يكون هذا نمط وصول شائعًا في السجلّات، إذ يرغب المستخدمون في التصفية بحسب رموز الخطأ:
استخدم Null لقياس الأداءنحن لا نعرض النتائج هنا باستخدام FORMAT Null. فهذا يفرض قراءة جميع النتائج من دون إرجاعها، وبذلك يمنع الإنهاء المبكر للاستعلام بسبب LIMIT. والغرض من ذلك هو فقط إظهار الوقت المستغرق في فحص جميع الصفوف العشرة ملايين.
يتطلب الاستعلام أعلاه فحصًا خطيًا باستخدام مفتاح الترتيب الذي اخترناه (ServiceName, Timestamp). وبينما يمكننا إضافة Status إلى نهاية مفتاح الترتيب لتحسين أداء الاستعلام أعلاه، يمكننا أيضًا إضافة إسقاط.
لاحظ أنه يجب علينا أولًا إنشاء الإسقاط ثم تمثيله ماديًا. ويؤدي هذا الأمر الأخير إلى تخزين البيانات مرتين على القرص، بترتيبين مختلفين. كما يمكن أيضًا تعريف الإسقاط عند إنشاء البيانات، كما هو موضح أدناه، وستتم صيانته تلقائيًا عند إدراج البيانات.
ومن المهم ملاحظة أنه إذا أُنشئ الإسقاط عبر ALTER، فسيُنفَّذ إنشاؤه بشكل غير متزامن عند إصدار الأمر MATERIALIZE PROJECTION. ويمكنك التحقق من تقدّم هذه العملية باستخدام الاستعلام التالي، مع انتظار is_done=1.
إذا كررنا الاستعلام أعلاه، فسنرى أن الأداء قد تحسّن بشكل ملحوظ، ولكن على حساب مساحة تخزين إضافية (راجع “قياس حجم الجدول والضغط” لمعرفة كيفية قياس ذلك).
في المثال أعلاه، نحدّد الأعمدة المستخدمة في الاستعلام السابق ضمن الإسقاط. وهذا يعني أنه لن تُخزَّن على القرص، كجزء من الإسقاط، سوى هذه الأعمدة المحددة، مرتبةً حسب Status. وإذا استخدمنا SELECT * هنا بدلًا من ذلك، فسيتم تخزين جميع الأعمدة. ورغم أن هذا سيتيح لعدد أكبر من الاستعلامات (باستخدام أي مجموعة فرعية من الأعمدة) الاستفادة من الإسقاط، فإنه سيؤدي إلى استهلاك مساحة تخزين إضافية. لقياس مساحة القرص والضغط، راجع “قياس حجم الجدول والضغط”.

الفهارس الثانوية/فهارس تخطي البيانات

مهما كانت درجة إحكام ضبط المفتاح الأساسي في ClickHouse، فستتطلب بعض الاستعلامات حتمًا إجراء فحص كامل للجدول. ويمكن الحد من ذلك باستخدام العروض المادية (وكذلك الإسقاطات لبعض الاستعلامات)، لكن هذا يفرض أعمال صيانة إضافية ويتطلب أيضًا أن يكون المستخدمون على علم بتوفرها حتى يضمنوا الاستفادة منها. وفي حين تعالج قواعد البيانات العلائقية التقليدية هذه المشكلة باستخدام الفهارس الثانوية، فإن هذه الفهارس لا تكون فعّالة في قواعد البيانات العمودية مثل ClickHouse. وبدلًا من ذلك، يستخدم ClickHouse فهارس “Skip”، التي يمكنها تحسين أداء الاستعلام بشكل كبير من خلال تمكين قاعدة البيانات من تجاوز أجزاء كبيرة من البيانات التي لا تحتوي على أي قيم مطابقة. تستخدم مخططات OTel الافتراضية فهارس ثانوية في محاولة لتسريع الوصول إلى بيانات Map. ومع أننا نجدها غير فعّالة عمومًا، ولا نوصي بنسخها إلى مخططك المخصص، فإن فهارس التخطي قد تظل مفيدة. ينبغي لك قراءة دليل الفهارس الثانوية وفهمه قبل محاولة تطبيقها. وعمومًا، تكون فعّالة عندما توجد علاقة ارتباط قوية بين المفتاح الأساسي والعمود/التعبير المستهدف غير الأساسي، وعندما يبحث المستخدمون عن قيم نادرة، أي القيم التي لا تظهر في عدد كبير من الحبيبات. يوفّر ClickHouse فهرسًا نصيًا مخصّصًا للبحث النصي الكامل. ويُنشئ هذا الفهرس فهرسًا معكوسًا للبيانات النصية المُجزأة إلى رموز، مما يتيح استعلامات بحث سريعة قائمة على الرموز. تتوفر الفهارس النصية بدءًا من الإصدار 26.2 من ClickHouse. يمكن تعريفها على أنواع الأعمدة التالية في جداول MergeTree: String، وFixedString، وArray(String)، وArray(FixedString)، وأعمدة Map (باستخدام دالتي الخرائط mapKeys وmapValues). يتطلب الفهرس النصي وسيط tokenizer في تعريفه. واختياريًا، يمكن تحديد دالة للمعالجة المسبقة لتحويل سلسلة الإدخال قبل تجزئتها إلى رموز. الدالتان الموصى بهما للبحث في الفهرس هما: hasAnyTokens وhasAllTokens. كما تُحسَّن بعض دوال البحث التقليدية في السلاسل النصية تلقائيًا عند وجود فهرس نصي. راجع التوثيق للاطلاع على التفاصيل والدوال المدعومة هنا وهنا. في الأمثلة أدناه، نستخدم مجموعة بيانات لسجلات مهيكلة.
يمكننا أيضًا استخدام hasAnyTokens من دون فهرس نصي، لكن الاستعلام سينفّذ فحصًا كاملًا بطيئًا لعمود Body:

إضافة فهرس نصي

يمكن إضافة فهرس نصي إلى العمود Body عند إنشاء الجدول:
أو يمكن إضافته لاحقًا باستخدام ALTER TABLE:
إذا شغّلنا استعلام SELECT نفسه مرة أخرى، فسيُجري عملية بحث في الفهرس النصي. ستنخفض كمية البيانات التي يجري الوصول إليها من غيغابايتات إلى ميغابايتات، وسيتحسن الأداء بنحو 45 ضعفًا.

استخدام مُعالج مسبق

في مجموعة البيانات هذه، يحتوي العمود Body على سلسلة بتنسيق JSON تضم عدة أزواج مفتاح-قيمة (مثل msg وid وctx وattr وغيرها). لنفترض أننا مهتمون فقط بالبحث داخل الحقل msg. وبدلًا من فهرسة سلسلة JSON بالكامل، يمكننا تعريف مُعالج مسبق لاستخراج قيمة msg فقط قبل التجزئة إلى رموز. على سبيل المثال:
في هذا المثال، يقوم المعالج المسبق بما يلي:
  • تقليل كمية النص التي تُجزَّأ إلى رموز وتُفهرس،
  • تقليل حجم الفهرس،
  • تقليل احتمالية النتائج الإيجابية الكاذبة، و
  • تحسين أداء الاستعلامات.
مقارنةً بالفهرس غير المُعالَج مسبقًا، يتحسن الأداء إلى نحو الضعف تقريبًا. ويؤدي استخدام مُعالج مسبق أيضًا إلى تقليص حجم الفهرس من غيغابايتات إلى بضع مئات من الكيلوبايتات، أي ما يقارب 0.01% من حجمه الأصلي
**فهارس أخرى للبحث النصي يمكن الاطلاع على مزيد من التفاصيل حول فهارس التخطي الثانوية هنا.

الاستخراج من قيم Map

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

قياس حجم الجدول والضغط

أحد الأسباب الرئيسية لاستخدام ClickHouse في Observability هو الضغط. فإلى جانب خفض تكاليف التخزين بشكل كبير، فإن تقليل حجم البيانات على القرص يعني عمليات I/O أقل واستعلامات وعمليات insert أسرع. وعادةً ما يفوق الانخفاض في IO أي overhead تفرضه خوارزمية الضغط من ناحية CPU. لذلك ينبغي أن يكون تحسين ضغط البيانات هو نقطة التركيز الأولى عند العمل على ضمان سرعة استعلامات ClickHouse. يمكن العثور على تفاصيل قياس الضغط هنا.
آخر تعديل في ٢٣ يوليو ٢٠٢٦