Skip to main content
يتطلب أي حل لـ Observability وسيلةً لجمع السجلات وtraces وتصديرها. ولهذا الغرض، توصي ClickHouse بمشروع OpenTelemetry (OTel). “OpenTelemetry هو إطار عمل ومجموعة أدوات لـ Observability صُمِّما لإنشاء telemetry data مثل traces وmetrics وlogs وإدارتها.” وعلى خلاف ClickHouse أو Prometheus، لا يُعد OpenTelemetry منصة backend للـ Observability، بل يركّز على إنشاء telemetry data وجمعها وإدارتها وتصديرها. وبينما كان الهدف الأولي من OpenTelemetry هو أن يسهّل عليك تزويد تطبيقاتك أو أنظمتك بقدرات telemetry باستخدام SDKs الخاصة بكل لغة، فقد توسّع ليشمل أيضًا جمع السجلات عبر OpenTelemetry Collector — وهو agent أو proxy يستقبل telemetry data ويعالجها ويصدّرها.

المكوّنات ذات الصلة بـ ClickHouse

يتكوّن OpenTelemetry من عدد من المكوّنات. وإلى جانب توفير مواصفات للبيانات وواجهة برمجة تطبيقات وبروتوكول موحّد واصطلاحات تسمية للحقول/الأعمدة، يوفّر OTel إمكانيتين أساسيتين لبناء حل للرصد باستخدام ClickHouse:
  • يُعدّ OpenTelemetry Collector وكيلًا يستقبل بيانات القياس عن بُعد ويعالجها ويصدّرها. ويستخدم الحل المعتمد على ClickHouse هذا المكوّن لكلٍّ من جمع السجلات ومعالجة الأحداث قبل تجميعها على دفعات وإدراجها.
  • SDKs الخاصة باللغات التي تنفّذ المواصفات وواجهات برمجة التطبيقات وتصدير بيانات القياس عن بُعد. وتضمن هذه SDKs عمليًا تسجيل الـ traces على نحو صحيح داخل شيفرة التطبيق، مع إنشاء الـ spans المكوِّنة لها وضمان تمرير الـ context بين الخدمات عبر البيانات الوصفية، ما يكوّن traces موزعة ويضمن إمكانية ربط الـ spans ببعضها. وتتكامل هذه SDKs مع منظومة توفّر تنفيذًا تلقائيًا للمكتبات وأطر العمل الشائعة، ما يعني أن المستخدم لا يحتاج إلى تغيير شيفرته ويحصل على instrumentation جاهزة مباشرة.
يستفيد حل الرصد المعتمد على ClickHouse من كلتا هاتين الأداتين.

التوزيعات

يحتوي OpenTelemetry Collector على عدد من التوزيعات. ولا يتوفر مستقبِل filelog مع مُصدِّر ClickHouse، وهما مطلوبان لحل يعتمد على ClickHouse، إلا في توزيعة OpenTelemetry Collector Contrib. تتضمن هذه التوزيعة العديد من المكونات وتتيح لك تجربة تهيئات متنوعة. ومع ذلك، عند التشغيل في بيئة الإنتاج، يُوصى بأن يقتصر المجمِّع على المكونات الضرورية للبيئة فقط. ومن أسباب ذلك:
  • تقليل حجم المجمِّع، مما يسرّع أوقات نشره
  • تحسين أمان المجمِّع عبر تقليل سطح الهجوم المتاح
يمكن إنشاء مجمِّع مخصص باستخدام OpenTelemetry Collector Builder.

إدخال البيانات باستخدام OTel

أدوار نشر OpenTelemetry Collector

لجمع السجلات وإدراجها في ClickHouse، نوصي باستخدام OpenTelemetry Collector. ويمكن نشر OpenTelemetry Collector في دورين رئيسيين:
  • الوكيل - تجمع مثيلات الوكيل البيانات عند الطرفية، مثلًا على الخوادم أو على عُقد Kubernetes، أو تستقبل الأحداث مباشرةً من التطبيقات المزوّدة بـ OpenTelemetry SDK. وفي الحالة الأخيرة، يعمل مثيل الوكيل مع التطبيق أو على المضيف نفسه الذي يعمل عليه التطبيق (مثل حاوية جانبية أو مجموعة شياطين). ويمكن للوكلاء إما إرسال بياناتهم مباشرةً إلى ClickHouse أو إلى مثيل بوابة. وفي الحالة الأولى، يُشار إلى ذلك باسم نمط نشر الوكيل.
  • البوابة - توفّر مثيلات البوابة خدمة مستقلة (على سبيل المثال، عملية نشر في Kubernetes)، وعادةً ما تكون لكل عنقود، أو لكل مركز بيانات، أو لكل منطقة. وتتلقى هذه المثيلات الأحداث من التطبيقات (أو من مجمّعات أخرى تعمل كوكلاء) عبر نقطة نهاية OTLP واحدة. وعادةً ما تُنشر مجموعة من مثيلات البوابة، مع استخدام موازن حمل جاهز لتوزيع الحمل بينها. وإذا كانت جميع الوكلاء والتطبيقات ترسل إشاراتها إلى نقطة النهاية الواحدة هذه، فغالبًا ما يُشار إلى ذلك باسم نمط نشر البوابة.
فيما يلي، نفترض وجود مجمّع وكيل بسيط يرسل أحداثه مباشرةً إلى ClickHouse. راجع التوسّع باستخدام البوابات لمزيد من التفاصيل حول استخدام البوابات ومتى يكون ذلك مناسبًا.

جمع السجلات

الميزة الرئيسية لاستخدام المُجمِّع هي أنه يتيح لخدماتك تفريغ البيانات بسرعة، تاركًا للمُجمِّع مهمة التعامل مع المعالجة الإضافية مثل إعادة المحاولة، والتجميع على دفعات، والتشفير، وحتى تصفية البيانات الحساسة. يستخدم المُجمِّع مصطلحات receiver وprocessor وexporter لمراحله الثلاث الرئيسية في المعالجة. تُستخدم المستقبلات لجمع البيانات، ويمكن أن تكون قائمة على السحب أو الدفع. وتوفّر المعالجات إمكانية إجراء التحويلات وإثراء الرسائل. أما المُصدِّرات فهي مسؤولة عن إرسال البيانات إلى خدمة لاحقة. ومع أن هذه الخدمة يمكن نظريًا أن تكون مُجمِّعًا آخر، فإننا نفترض في الشرح الأولي أدناه أن جميع البيانات تُرسل مباشرةً إلى ClickHouse. نوصي المستخدمين بالتعرّف على المجموعة الكاملة من المستقبلات والمعالجات والمُصدِّرات. يوفّر المُجمِّع مستقبلين رئيسيين لجمع السجلات: عبر OTLP - في هذه الحالة، تُرسل السجلات (دفعًا) مباشرةً إلى المُجمِّع من OpenTelemetry SDKs عبر بروتوكول OTLP. ويستخدم OpenTelemetry demo هذا الأسلوب، حيث تفترض مُصدِّرات OTLP في كل لغة وجود نقطة نهاية محلية للمُجمِّع. ويجب تهيئة المُجمِّع باستخدام مستقبِل OTLP في هذه الحالة — راجع العرض التوضيحي أعلاه للاطلاع على إعداد. وتتمثل ميزة هذا الأسلوب في أن بيانات السجل ستتضمن تلقائيًا معرّفات التتبّع، مما يتيح للمستخدمين لاحقًا تحديد التتبعات المرتبطة بسجل معيّن والعكس صحيح. يتطلب هذا الأسلوب من المستخدمين تضمين شيفرتهم باستخدام SDK المناسب للغتهم.
  • الكشط عبر مستقبِل filelog - يتتبع هذا المستقبِل الملفات على القرص ويحوّلها إلى رسائل سجل، ثم يرسلها إلى ClickHouse. ويتعامل هذا المستقبِل مع مهام معقدة مثل اكتشاف الرسائل متعددة الأسطر، والتعامل مع تدوير السجلات، وإنشاء نقاط تحقق لضمان المتانة عند إعادة التشغيل، واستخراج البنية. كما يستطيع هذا المستقبِل أيضًا تتبّع سجلات حاويات Docker وKubernetes، ويمكن نشره كمخطط Helm، مع استخراج بنيتها وإثرائها بتفاصيل الـ pod.
ستستخدم معظم عمليات النشر مزيجًا من المستقبلات المذكورة أعلاه. نوصي المستخدمين بقراءة توثيق المُجمِّع والتعرّف على المفاهيم الأساسية، إلى جانب بنية الإعداد وطرق التثبيت.
نصيحة: otelbin.ioيُعد otelbin.io مفيدًا للتحقق من الإعدادات وتصورها.

المهيكلة مقابل غير المهيكلة

يمكن أن تكون السجلات مهيكلة أو غير مهيكلة. يستخدم السجل المهيكل تنسيق بيانات مثل JSON، ويحدّد حقول البيانات الوصفية مثل رمز حالة HTTP وعنوان IP للمصدر.
أما السجلات غير المهيكلة، فعلى الرغم من أنها تحتوي عادةً أيضًا على بعض البنية الضمنية التي يمكن استخراجها باستخدام نمط Regex، فإنها تمثّل السجل كسلسلة نصية فقط.
نوصي المستخدمين باعتماد التسجيل المنظَّم وتسجيل السجلات بصيغة JSON (أي ndjson) كلما أمكن. سيُبسّط ذلك معالجة السجلات المطلوبة لاحقًا، سواء قبل إرسالها إلى ClickHouse باستخدام معالجات Collector أو عند وقت الإدراج باستخدام العروض المادية. وفي النهاية، توفّر السجلات المنظَّمة موارد المعالجة اللاحقة، مما يقلّل من مقدار CPU المطلوب في حل ClickHouse لديك.

مثال

لأغراض هذا المثال، نوفر مجموعة بيانات لسجلات منظَّمة (JSON) وأخرى غير منظَّمة، تحتوي كل منهما على نحو 10m صفوف، وهما متاحتان عبر الروابط التالية: نستخدم مجموعة البيانات المنظَّمة في المثال أدناه. تأكد من تنزيل هذا الملف وفك ضغطه حتى تتمكن من إعادة تنفيذ الأمثلة التالية. يمثل ما يلي إعدادًا بسيطًا لـ OTel Collector يقرأ هذه الملفات من القرص باستخدام filelog receiver، ويُخرج الرسائل الناتجة إلى stdout. نستخدم العامل json_parser لأن سجلاتنا منظَّمة. عدّل المسار إلى ملف access-structured.log.
فكّر في استخدام ClickHouse لإجراء التحليليستخرج المثال أدناه timestamp من السجل. ويتطلب ذلك استخدام العامل json_parser، الذي يحوّل سطر السجل بالكامل إلى JSON string، ثم يضع النتيجة في LogAttributes. قد يكون هذا مكلفًا من الناحية الحسابية، ويمكن تنفيذه بكفاءة أعلى في ClickHouse - استخراج البنية باستخدام SQL. ويمكن العثور على مثال غير منظَّم مكافئ يستخدم regex_parser لتحقيق ذلك هنا.
config-structured-logs.yaml
يمكنك اتباع التعليمات الرسمية لتثبيت OTel collector محليًا. والأهم من ذلك، تأكّد من تعديل التعليمات لاستخدام توزيعة contrib (التي تتضمن filelog receiver)؛ فعلى سبيل المثال، بدلًا من otelcol_0.102.1_darwin_arm64.tar.gz سينزّل المستخدمون otelcol-contrib_0.102.1_darwin_arm64.tar.gz. ويمكن العثور على الإصدارات هنا. بعد التثبيت، يمكن تشغيل OTel Collector باستخدام الأوامر التالية:
بافتراض استخدام السجلات المنظَّمة، ستظهر الرسائل بالشكل التالي في المخرجات:
يمثل ما سبق رسالة سجل واحدة كما يُنتجها OTel collector. ونُدخل هذه الرسائل نفسها إلى ClickHouse في الأقسام اللاحقة. يُحفَظ المخطط الكامل لرسائل السجل، إلى جانب أعمدة إضافية قد تظهر عند استخدام receivers أخرى، هنا. نوصي بشدة بأن يتعرّف المستخدمون إلى هذا المخطط. النقطة الأساسية هنا هي أن سطر السجل نفسه يُحفَظ كسلسلة نصية داخل الحقل Body، لكن JSON يُستخرج تلقائيًا إلى حقل Attributes بفضل json_parser. كما استُخدم operator نفسه لاستخراج الطابع الزمني إلى العمود Timestamp المناسب. للاطلاع على توصيات بشأن معالجة السجلات باستخدام OTel، راجع Processing.
المشغّلاتالمشغّلات هي أبسط وحدة في معالجة السجلات. ويؤدي كل مشغّل مهمة واحدة فقط، مثل قراءة الأسطر من ملف أو تحليل JSON من حقل. ثم تُربط المشغّلات معًا ضمن pipeline لتحقيق النتيجة المطلوبة.
لا تتضمن الرسائل أعلاه الحقلين TraceID أو SpanID. وإذا كانا موجودين، على سبيل المثال في الحالات التي يطبّق فيها المستخدمون distributed tracing، فيمكن استخراجهما من JSON باستخدام الأساليب نفسها الموضحة أعلاه. بالنسبة إلى المستخدمين الذين يحتاجون إلى جمع log files محلية أو خاصة بـ Kubernetes، نوصيهم بالتعرّف على خيارات الإعداد المتاحة لـ filelog receiver، وكيفية التعامل مع offsets، وكيفية تحليل السجلات متعددة الأسطر.

جمع سجلات Kubernetes

لجمع سجلات Kubernetes، نوصي بالرجوع إلى دليل توثيق OpenTelemetry. كما نوصي باستخدام معالج سمات Kubernetes لإثراء السجلات والمقاييس بالبيانات الوصفية للحاويات المنفردة. وقد ينتج عن ذلك بيانات وصفية ديناميكية، مثل التسميات، تُخزَّن في العمود ResourceAttributes. يستخدم ClickHouse حاليًا النوع Map(String, String) لهذا العمود. راجع Using Maps وExtracting from maps لمزيد من التفاصيل حول كيفية التعامل مع هذا النوع وتحسينه.

جمع التتبعات

بالنسبة إلى المستخدمين الذين يريدون تزويد شيفرتهم بقدرات القياس عن بُعد وجمع التتبعات، نوصي باتباع وثائق OTel الرسمية. ولإرسال الأحداث إلى ClickHouse، ستحتاج إلى Deploy ‏OTel collector لتلقّي أحداث التتبع عبر بروتوكول OTLP من خلال الـ receiver المناسب. يوفّر OpenTelemetry demo مثالًا على تزويد كل لغة مدعومة بقدرات القياس عن بُعد وإرسال الأحداث إلى collector. ويرد أدناه مثال على تهيئة collector مناسبة تُخرج الأحداث إلى stdout:

مثال

نظرًا إلى أن بيانات التتبّع يجب أن تُستقبل عبر OTLP، فإننا نستخدم أداة telemetrygen لتوليد بيانات التتبّع. اتبع التعليمات هنا للتثبيت. يستقبل الإعداد التالي أحداث التتبّع عبر مستقبِل OTLP قبل إرسالها إلى stdout. config-traces.xml
شغّل هذا الإعداد باستخدام:
أرسل أحداث التتبّع إلى المجمّع عبر telemetrygen:
سينتج عن ذلك رسائل تتبّع مشابهة للمثال أدناه، تُرسَل إلى stdout:
يمثّل ما سبق رسالة trace واحدة كما يُنتجها OTel collector. ونقوم بإدخال هذه الرسائل نفسها إلى ClickHouse في الأقسام اللاحقة. يُحفَظ المخطط الكامل لرسائل trace هنا. ونوصي بشدة بأن يتعرّف المستخدمون إلى هذا المخطط.

المعالجة - التصفية والتحويل والإثراء

كما هو موضح في المثال السابق لتعيين الطابع الزمني لحدث سجل، ستحتاج حتمًا إلى تصفية رسائل الأحداث وتحويلها وإثرائها. ويمكن تحقيق ذلك باستخدام عدد من الإمكانات في OpenTelemetry:
  • المعالِجات - تأخذ المعالِجات البيانات التي تجمعها المستقبِلات وتعدّلها أو تحوّلها قبل إرسالها إلى المصدِّرات. وتُطبَّق المعالِجات بالترتيب المحدد في قسم processors من تهيئة collector. وهي اختيارية، لكن عادةً ما يُوصى بالحد الأدنى منها. وعند استخدام OTel collector مع ClickHouse، نوصي بقصر المعالِجات على ما يلي:
  • المشغّلات - توفّر المشغّلات أبسط وحدة معالجة متاحة على مستوى receiver. كما تدعم parsing أساسيًا، بما يتيح تعيين حقول مثل Severity وTimestamp. ويُدعَم هنا أيضًا parsing لكل من JSON وregex، إلى جانب تصفية الأحداث والتحويلات الأساسية. ونوصي بإجراء تصفية الأحداث هنا.
نوصي المستخدمين بتجنّب الإفراط في معالجة الأحداث باستخدام المشغّلات أو معالجات التحويل. فقد يترتب على ذلك حمل كبير على الذاكرة ووحدة المعالجة المركزية، ولا سيما parsing لـ JSON. كما يمكن تنفيذ جميع عمليات المعالجة في ClickHouse عند insert time باستخدام materialized views وcolumns، مع بعض الاستثناءات، وتحديدًا الإثراء المعتمد على السياق، مثل إضافة بيانات k8s الوصفية. لمزيد من التفاصيل، راجع استخراج البنية باستخدام SQL. إذا كانت المعالجة تُجرى باستخدام OTel collector، فنوصي بإجراء التحويلات في مثيلات gateway وتقليل أي عمل يُنفَّذ في مثيلات agent إلى الحد الأدنى. ويضمن ذلك أن تبقى الموارد المطلوبة من agents الطرفية، التي تعمل على الخوادم، عند أقل مستوى ممكن. وعادةً ما نرى المستخدمين يجرون التصفية فقط (لتقليل استخدام الشبكة غير الضروري)، وتعيين الطابع الزمني (عبر المشغّلات)، والإثراء الذي يتطلب سياقًا داخل agents. على سبيل المثال، إذا كانت مثيلات gateway موجودة في Kubernetes cluster مختلف، فسيلزم تنفيذ إثراء k8s داخل agent.

مثال

يوضح الإعداد التالي كيفية جمع بيانات ملف السجل غير المنظم. لاحظ استخدام المشغّلات لاستخراج البنية من أسطر السجل (regex_parser) وتصفية الأحداث، إلى جانب معالج لتجميع الأحداث في دفعات والحد من استخدام الذاكرة. config-unstructured-logs-with-processor.yaml

التصدير إلى ClickHouse

ترسل المصدّرات البيانات إلى نظام خلفي واحد أو أكثر أو إلى وجهات أخرى. ويمكن أن تعتمد المصدّرات على السحب أو على الدفع. ولإرسال الأحداث إلى ClickHouse، ستحتاج إلى استخدام ClickHouse exporter المعتمد على الدفع.
استخدم OpenTelemetry Collector Contribيُعد ClickHouse exporter جزءًا من OpenTelemetry Collector Contrib، وليس من التوزيعة الأساسية. يمكنك إما استخدام توزيعة contrib أو بناء collector مخصص.
يظهر أدناه ملف الإعداد الكامل. clickhouse-config.yaml
لاحظ الإعدادات المهمة التالية:
  • pipelines - يوضّح الإعداد أعلاه استخدام pipelines، وهي تتكوّن من مجموعة من receivers وprocessors وexporters، مع pipeline للسجلات وأخرى للتتبعات.
  • endpoint - تتم تهيئة الاتصال مع ClickHouse عبر المعلَمة endpoint. تؤدي connection string ‏tcp://localhost:9000?dial_timeout=10s&compress=lz4&async_insert=1 إلى إجراء الاتصال عبر TCP. إذا كنت تفضّل HTTP لأسباب تتعلق بتحويل حركة المرور، فعدّل connection string هذه كما هو موضح هنا. وتجد هنا تفاصيل الاتصال الكاملة، بما في ذلك إمكانية تحديد username وpassword داخل connection string هذه.
مهم: لاحظ أن connection string أعلاه تفعّل كلاً من الضغط (lz4) وasynchronous inserts. نوصي دائماً بتمكين الاثنين معاً. راجع Batching لمزيد من التفاصيل حول asynchronous inserts. يجب دائماً تحديد الضغط، وفي الإصدارات الأقدم من exporter لن يكون مفعّلاً تلقائياً بشكل افتراضي.
  • ttl - تحدد القيمة هنا مدة الاحتفاظ بالبيانات. مزيد من التفاصيل في “إدارة البيانات”. يجب تحديدها كوحدة زمنية بالساعات، مثل 72h. نعطّل TTL في المثال أدناه لأن بياناتنا تعود إلى عام 2019، وسيحذفها ClickHouse فوراً إذا تم إدخالها.
  • traces_table_name وlogs_table_name - يحددان اسمَي جدولي السجلات والتتبعات.
  • create_schema - يحدد ما إذا كانت الجداول ستُنشأ باستخدام schemas الافتراضية عند بدء التشغيل. تكون القيمة الافتراضية true لأغراض Getting Started. ينبغي ضبطه على false وتعريف schema خاص بك.
  • database - قاعدة البيانات الهدف.
  • retry_on_failure - إعدادات تحدد ما إذا كان ينبغي إعادة محاولة batches الفاشلة.
  • batch - يضمن batch processor إرسال الأحداث على شكل batches. نوصي بقيمة لا تقل عن 10,000 مع timeout قدره 5s (ويمكن استخدام قيم تصل إلى 100,000 إذا سمحت الذاكرة). وأيٌّ من هذين الحدّين يتم بلوغه أولاً سيؤدي إلى بدء batch ليتم flush إلى exporter. إن خفض هذه القيم يعني pipeline أقل latency مع إتاحة البيانات للاستعلام عنها بشكل أسرع، لكن على حساب المزيد من connections والمزيد من batches المرسلة إلى ClickHouse. لا نوصي بذلك إذا كنت لا تستخدم asynchronous inserts، إذ قد يسبب ذلك مشكلات too many parts في ClickHouse. وعلى العكس، إذا كنت تستخدم asynchronous inserts، فإن إتاحة هذه البيانات للاستعلام ستعتمد أيضاً على إعدادات asynchronous insert، رغم أن البيانات ستظل تُرسل من connector بسرعة أكبر. راجع Batching لمزيد من التفاصيل.
  • sending_queue - يتحكم في حجم sending queue. يحتوي كل عنصر في queue على batch. وإذا تم تجاوز سعة هذه queue، مثلاً بسبب تعذر الوصول إلى ClickHouse مع استمرار وصول الأحداث، فسيتم إسقاط batches.
على افتراض أن المستخدمين قد استخرجوا structured log file ولديهم local instance of ClickHouse قيد التشغيل (مع authentication الافتراضي)، يمكنك تشغيل هذا الإعداد عبر الأمر:
لإرسال بيانات التتبّع إلى هذا المجمّع، نفّذ الأمر التالي باستخدام أداة telemetrygen:
بمجرد تشغيله، تأكد من وجود أحداث السجل باستخدام استعلام بسيط:

المخطط الجاهز للاستخدام

يوفّر ClickStack مخططًا افتراضيًا مُحسّنًايوفّر ClickStack مخططات جاهزة للسجلات والتتبعات والمقاييس تتضمن أحدث ميزات ClickHouse (فهارس نصية للبحث في النص الكامل والبحث في مفاتيح Map، وأعمدة materialized ومصفوفات ALIAS للتصفية عبر القراءة المباشرة، وعمليات lookup للصفوف باستخدام رقم block)، وقد خضعت لاختبارات أداء معيارية لتقديم أداء قوي من البداية لأحمال عمل السجلات والتتبعات. استخدمها كنقطة مرجعية لتصميمك الخاص.
بشكل افتراضي، ينشئ ClickHouse exporter جدول سجلات مستهدفًا لكلٍّ من السجلات والتتبعات. يمكن تعطيل ذلك عبر الإعداد create_schema. بالإضافة إلى ذلك، يمكن تغيير اسمي جدولَي السجلات والتتبعات من القيمتَين الافتراضيتَين otel_logs وotel_traces عبر الإعدادات المذكورة أعلاه.
في المخططات أدناه، نفترض أن TTL مفعّل بقيمة 72h.
يظهر أدناه المخطط الافتراضي للسجلات (otelcol-contrib v0.102.1):
تتوافق الأعمدة هنا مع مواصفة OTel الرسمية للسجلات، الموثقة هنا. فيما يلي بعض الملاحظات المهمة حول هذا المخطط:
  • افتراضيًا، يُقسَّم الجدول حسب التاريخ باستخدام PARTITION BY toDate(Timestamp). وهذا يجعل حذف البيانات التي انتهت صلاحيتها أكثر كفاءة.
  • يُضبط TTL عبر TTL toDateTime(Timestamp) + toIntervalDay(3)، وهو يتوافق مع القيمة المحددة في تهيئة collector. وتعني ttl_only_drop_parts=1 أنه لا تُحذف إلا الأجزاء الكاملة عندما تكون جميع الصفوف التي تحتويها قد انتهت صلاحيتها. وهذا أكثر كفاءة من حذف الصفوف داخل الأجزاء، إذ يترتب على ذلك تنفيذ عملية حذف مكلفة. نوصي دائمًا بضبط هذا الإعداد. راجع إدارة البيانات باستخدام TTL لمزيد من التفاصيل.
  • يستخدم الجدول محرك MergeTree engine التقليدي. وهذا الخيار موصى به للسجلات وآثار التتبع، ولا ينبغي أن تحتاج إلى تغييره.
  • يُرتَّب الجدول بواسطة ORDER BY (ServiceName, SeverityText, toUnixTimestamp(Timestamp), TraceId). وهذا يعني أن الاستعلامات ستكون مُحسّنة لعامل تصفية على ServiceName وSeverityText وTimestamp وTraceId، وأن الأعمدة الأسبق في القائمة ستكون التصفية عليها أسرع من الأعمدة اللاحقة. فعلى سبيل المثال، ستكون التصفية حسب ServiceName أسرع بكثير من التصفية حسب TraceId. ينبغي تعديل هذا الترتيب وفقًا لأنماط الوصول المتوقعة — راجع اختيار مفتاح أساسي.
  • يطبّق المخطط أعلاه ZSTD(1) على الأعمدة. وهذا يوفّر أفضل Compression للسجلات. يمكنك زيادة مستوى ضغط ZSTD (فوق القيمة الافتراضية 1) للحصول على ضغط أفضل، رغم أن ذلك نادرًا ما يكون مفيدًا. ستؤدي زيادة هذه القيمة إلى زيادة عبء CPU عند insert time (أثناء الضغط)، مع أن فك الضغط (وبالتالي الاستعلامات) ينبغي أن يظل بمستوى مماثل. راجع هنا لمزيد من التفاصيل. كما يُطبَّق delta encoding إضافي على Timestamp بهدف تقليل حجمه على القرص.
  • لاحظ أن ResourceAttributes وLogAttributes وScopeAttributes هي maps. ومن المهم فهم الفروق بينها. راجع “استخدام الخرائط” لمعرفة كيفية الوصول إلى هذه الخرائط وتحسين الوصول إلى المفاتيح داخلها.
  • معظم الأنواع الأخرى هنا، مثل ServiceName من النوع LowCardinality، مُحسَّنة. لاحظ أن Body، وهو JSON في سجلات الأمثلة لدينا، يُخزَّن كسلسلة String.
  • تُطبَّق bloom filters على مفاتيح الخرائط وقيمها، وكذلك على العمود Body. وتهدف هذه إلى تحسين زمن الاستعلامات التي تصل إلى هذه الأعمدة، لكنها لا تكون مطلوبة عادةً. راجع الفهارس الثانوية/فهارس تخطي البيانات.
مرة أخرى، سيتوافق ذلك مع الأعمدة المطابقة للمواصفة الرسمية لـ OTel الخاصة بالتتبعات والموثقة هنا. ويستخدم المخطط هنا العديد من الإعدادات نفسها المستخدمة في مخطط السجلات أعلاه، مع أعمدة Link إضافية خاصة بـ spans. نوصي المستخدمين بتعطيل الإنشاء التلقائي للمخطط وإنشاء جداولهم يدويًا. يتيح ذلك تعديل المفاتيح الأساسية والثانوية، كما يتيح إضافة أعمدة إضافية لتحسين أداء الاستعلام. ولمزيد من التفاصيل، راجع تصميم المخطط.

تحسين عمليات الإدراج

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

التجميع على دفعات

افتراضيًا، تؤدي كل عملية insert تُرسَل إلى ClickHouse إلى أن ينشئ ClickHouse فورًا جزءًا تخزينيًا يحتوي على بيانات تلك العملية، إلى جانب بيانات وصفية أخرى يجب تخزينها. لذلك، فإن إرسال عدد أقل من عمليات insert بحيث تحتوي كل واحدة منها على قدر أكبر من البيانات، بدلًا من إرسال عدد أكبر من عمليات insert يحتوي كلٌّ منها على قدر أقل من البيانات، يقلل عدد عمليات الكتابة المطلوبة. نوصي بإدراج البيانات في دفعات كبيرة نسبيًا لا تقل عن 1,000 صف في المرة الواحدة. لمزيد من التفاصيل، راجع هنا. افتراضيًا، تكون عمليات insert إلى ClickHouse متزامنة، كما أنها لا تُحدث أثرًا إضافيًا عند تكرارها إذا كانت متطابقة. وبالنسبة إلى الجداول التي تستخدم محركات من عائلة MergeTree، يعمل ClickHouse افتراضيًا على إزالة تكرار عمليات insert تلقائيًا. وهذا يعني أن عمليات insert تتحمّل حالات مثل ما يلي:
  • (1) إذا كانت هناك مشكلة في العقدة التي تستقبل البيانات، فستنتهي مهلة استعلام insert (أو سيظهر خطأ أكثر تحديدًا) ولن يتلقى المرسِل تأكيدًا.
  • (2) إذا كانت العقدة قد كتبت البيانات، لكن تعذّر إعادة التأكيد إلى مُرسِل الاستعلام بسبب انقطاع في الشبكة، فسيتلقى المرسِل إما انتهاء مهلة أو خطأ في الشبكة.
من منظور الـ collector، قد يصعب التمييز بين الحالتين (1) و(2). ومع ذلك، في كلتا الحالتين، يمكن إعادة محاولة عملية insert غير المؤكدة فورًا. وما دام استعلام insert المُعاد يحتوي على البيانات نفسها وبالترتيب نفسه، فسيتجاهل ClickHouse تلقائيًا عملية insert المُعادة إذا كانت عملية insert الأصلية (غير المؤكدة) قد نجحت. نوصي بأن يستخدم المستخدمون معالج الدفعات الموضّح في الإعدادات السابقة لتحقيق ما سبق. فهذا يضمن إرسال عمليات insert على شكل دفعات متسقة من الصفوف تستوفي المتطلبات المذكورة أعلاه. وإذا كان من المتوقع أن يحقق الـ collector معدل throughput مرتفعًا (أحداثًا في الثانية)، وكان بالإمكان إرسال ما لا يقل عن 10,000 حدث في كل عملية insert، فعادةً ما يكون هذا هو التجميع الوحيد المطلوب في مسار المعالجة. ويمكن استخدام قيم تصل إلى 100,000 إذا كانت الذاكرة تسمح بذلك. وفي هذه الحالة، سيقوم الـ collector بتفريغ الدفعات قبل بلوغ timeout الخاص بمعالج الدفعات، مما يضمن بقاء زمن الانتقال من طرف إلى طرف في مسار المعالجة منخفضًا، وأن تكون الدفعات ذات حجم متسق.

استخدم asynchronous inserts

عادةً ما يضطر المستخدمون إلى إرسال دفعات أصغر عندما يكون معدل النقل في collector منخفضًا، ومع ذلك يظلون يتوقعون أن تصل البيانات إلى ClickHouse بأقل زمن انتقال ممكن من طرف إلى طرف. في هذه الحالة، تُرسل الدفعات الصغيرة عند انتهاء مهلة timeout الخاصة بـ معالج الدفعات. وقد يؤدي ذلك إلى مشكلات، وهنا تبرز الحاجة إلى asynchronous inserts. تظهر هذه الحالة عادةً عندما تُضبط collectors التي تعمل بدور agent للإرسال مباشرةً إلى ClickHouse. ويمكن لـ gateways، من خلال عملها كمجمِّعات مركزية، أن تخفف هذه المشكلة — راجع التوسع باستخدام gateways. إذا لم يكن من الممكن ضمان دفعات كبيرة، فيمكنك إسناد التجميع إلى ClickHouse باستخدام asynchronous inserts. مع asynchronous inserts، تُدرج البيانات أولًا في buffer، ثم تُكتب لاحقًا إلى مساحة تخزين قاعدة البيانات، أي بشكل غير متزامن. عند تمكين asynchronous inserts، عندما يستقبل ClickHouse ① insert query، تُكتب بيانات الاستعلام ② فورًا إلى buffer داخل الذاكرة. وعندما ③ تحدث عملية flush التالية للـ buffer، تُرتَّب بيانات الـ buffer ترتيبًا ثم تُكتب كجزء إلى مساحة تخزين قاعدة البيانات. لاحظ أن البيانات لا تكون قابلة للبحث عبر queries قبل flush إلى مساحة تخزين قاعدة البيانات؛ كما أن عملية flush للـ buffer قابلة للضبط. لتمكين asynchronous inserts للمجمّع، أضف async_insert=1 إلى connection string. نوصي باستخدام wait_for_async_insert=1 (وهو الإعداد الافتراضي) للحصول على ضمانات التسليم — راجع هنا لمزيد من التفاصيل. تُدرج بيانات async insert بمجرد تنفيذ flush لـ buffer في ClickHouse. ويحدث ذلك إما بعد تجاوز async_insert_max_data_size أو بعد مرور async_insert_busy_timeout_ms مللي ثانية منذ أول INSERT query. وإذا ضُبط async_insert_stale_timeout_ms على قيمة غير صفرية، فستُدرج البيانات بعد مرور async_insert_stale_timeout_ms milliseconds منذ آخر query. يمكنك ضبط هذه settings للتحكم في زمن الانتقال من طرف إلى طرف في pipeline لديك. وترد هنا settings إضافية يمكن استخدامها لضبط flush للـ buffer. وبوجه عام، تكون القيم الافتراضية مناسبة.
فكّر في asynchronous inserts التكيفيةفي الحالات التي يُستخدم فيها عدد قليل من agents، مع معدل نقل منخفض ومتطلبات صارمة لزمن الانتقال من طرف إلى طرف، فقد تكون asynchronous inserts التكيفية مفيدة. لكن هذه الميزة لا تكون مناسبة عادةً لحالات استخدام Observability ذات معدل النقل المرتفع، كما هو الحال مع ClickHouse.
أخيرًا، لا يكون سلوك deduplication السابق المرتبط بالإدراجات المتزامنة في ClickHouse مُمكّنًا افتراضيًا عند استخدام asynchronous inserts. وإذا لزم الأمر، فراجع الإعداد async_insert_deduplicate. يمكن العثور على التفاصيل الكاملة حول ضبط هذه feature هنا، مع شرح معمّق هنا.

معماريات النشر

يمكن اعتماد عدة معماريات للنشر عند استخدام OTel collector مع ClickHouse. نوضح أدناه كل خيار ومتى يكون مناسبًا على الأرجح.

agents فقط

في معمارية agents فقط، ينشر المستخدمون OTel collector كـ agents على الأطراف. تستقبل هذه agents traces من التطبيقات المحلية (مثلًا كحاوية sidecar) وتجمع logs من الخوادم وعُقد Kubernetes. في هذا الوضع، ترسل agents بياناتها مباشرةً إلى ClickHouse. تُعد هذه المعمارية مناسبة لعمليات النشر الصغيرة والمتوسطة. وتتمثل ميزتها الرئيسية في أنها لا تتطلب عتادًا إضافيًا، وتُبقي البصمة الإجمالية لموارد حل observability من ClickHouse عند الحد الأدنى، مع اقتران بسيط بين التطبيقات وcollectors. ينبغي التفكير في الانتقال إلى معمارية قائمة على البوابة بمجرد أن يتجاوز عدد agents عدة مئات. فهذه المعمارية تنطوي على عدة عيوب تجعل توسيع نطاقها صعبًا:
  • توسيع نطاق الاتصالات - سينشئ كل agent اتصالًا مع ClickHouse. ومع أن ClickHouse قادر على الحفاظ على مئات، إن لم يكن آلاف، اتصالات insert المتزامنة، فإن ذلك سيصبح في نهاية المطاف عاملًا مقيِّدًا وسيجعل inserts أقل كفاءة — أي إن ClickHouse سيستهلك موارد أكثر للإبقاء على هذه الاتصالات. ويؤدي استخدام gateways إلى تقليل عدد الاتصالات وجعل inserts أكثر كفاءة.
  • المعالجة عند الأطراف - يجب تنفيذ أي تحويلات أو معالجة للأحداث عند الأطراف أو داخل ClickHouse في هذه المعمارية. وإلى جانب كون ذلك مقيِّدًا، فقد يعني هذا إما إنشاء materialized views معقدة في ClickHouse أو نقل قدر كبير من العمليات الحسابية إلى الأطراف — حيث قد تتأثر الخدمات الحرجة وتكون الموارد شحيحة.
  • الدفعات الصغيرة وزمن الاستجابة - قد تجمع collectors الخاصة بـ agents عددًا قليلًا جدًا من الأحداث، كلٌّ على حدة. وهذا يعني عادةً أنها تحتاج إلى تهيئتها لتنفيذ flush على فاصل زمني محدد للوفاء بـ اتفاقيات مستوى الخدمة الخاصة بالتسليم. وقد يؤدي ذلك إلى أن يرسل collector دفعات صغيرة إلى ClickHouse. وعلى الرغم من أن هذا يُعد عيبًا، فإنه يمكن التخفيف منه باستخدام Asynchronous inserts — انظر تحسين inserts.

التوسّع باستخدام gateways

يمكن نشر OTel collector كمثيلات gateway لمعالجة القيود المذكورة أعلاه. فهي توفّر خدمة مستقلة، عادةً لكل مركز بيانات أو لكل منطقة. وتتلقى هذه المثيلات الأحداث من التطبيقات (أو من collectors أخرى تؤدي دور agent) عبر OTLP endpoint واحد. وعادةً ما تُنشر مجموعة من مثيلات gateway، مع استخدام load balancer جاهز لتوزيع الحمل فيما بينها. الهدف من هذه المعمارية هو نقل المعالجة كثيفة الاستهلاك حسابيًا بعيدًا عن agents، مما يقلّل استخدامهم للموارد إلى الحد الأدنى. ويمكن لهذه الـ gateways تنفيذ مهام التحويل التي كان سيتعيّن على agents تنفيذها لولا ذلك. وعلاوة على ذلك، فمن خلال تجميع الأحداث من عدد كبير من agents، يمكن للـ gateways ضمان إرسال batches كبيرة إلى ClickHouse، مما يتيح insertion بكفاءة. كما يمكن توسيع gateway collectors هذه بسهولة مع إضافة المزيد من agents وازدياد throughput للأحداث. ويظهر أدناه مثال على config لـ gateway، مع config agent مرتبط يستهلك ملف السجل المنظَّم المستخدم في المثال. لاحظ استخدام OTLP للتواصل بين agent وgateway. clickhouse-agent-config.yaml
clickhouse-gateway-config.yaml
يمكن تشغيل هذه التهيئات باستخدام الأوامر التالية.
العيب الرئيسي لهذه المعمارية هو التكلفة والعبء الإضافي المرتبطان بإدارة مجموعة من وحدات collector. للاطلاع على مثال لإدارة معماريات أكبر قائمة على الـ gateway وما يرتبط بها من دروس مستفادة، نوصي بهذه التدوينة.

إضافة Kafka

قد يلاحظ القرّاء أن المعماريات المذكورة أعلاه لا تستخدم Kafka كصف انتظار للرسائل. يُعد استخدام Kafka كمخزن مؤقت للرسائل نمط تصميم شائعًا في معماريات السجلات، وقد شاع مع حزمة ELK. ويوفر ذلك عدة فوائد؛ أهمها أنه يتيح ضمانات أقوى لتسليم الرسائل ويساعد على التعامل مع الضغط العكسي. تُرسل الرسائل من وكلاء التجميع إلى Kafka وتُكتب على القرص. ومن الناحية النظرية، ينبغي أن يوفر مثيل Kafka عنقودي مخزنًا مؤقتًا للرسائل عالي الإنتاجية، لأن كتابة البيانات خطيًا إلى القرص تتطلب كلفة حوسبية أقل من تحليل الرسالة ومعالجتها — ففي Elastic، على سبيل المثال، تفرض عمليتا tokenization وindexing كلفة إضافية كبيرة. كما أن نقل البيانات بعيدًا عن الوكلاء يقلل من خطر فقدان الرسائل نتيجة تدوير السجلات عند المصدر. وأخيرًا، يوفّر بعض إمكانات إعادة تشغيل الرسائل والنسخ المتماثل عبر المناطق، وهو ما قد يكون مناسبًا لبعض حالات الاستخدام. ومع ذلك، يمكن لـ ClickHouse التعامل مع إدراج البيانات بسرعة كبيرة جدًا — ملايين الصفوف في الثانية على عتاد متوسط. ويُعد الضغط العكسي من ClickHouse نادرًا. وغالبًا ما يعني الاعتماد على صف Kafka مزيدًا من التعقيد المعماري والتكلفة. وإذا كان بإمكانك تبنّي مبدأ أن السجلات لا تحتاج إلى ضمانات التسليم نفسها المطلوبة للمعاملات المصرفية وغيرها من البيانات بالغة الأهمية، فنحن نوصي بتجنّب تعقيد Kafka. ومع ذلك، إذا كنت تحتاج إلى ضمانات تسليم عالية أو إلى القدرة على إعادة تشغيل البيانات (وربما إلى عدة وجهات)، فقد تكون Kafka إضافة معمارية مفيدة. في هذه الحالة، يمكن تهيئة وكلاء OTel لإرسال البيانات إلى Kafka عبر Kafka exporter. وتقوم مثيلات البوابة، بدورها، باستهلاك الرسائل باستخدام Kafka receiver. نوصي بالرجوع إلى وثائق Confluent وOTel لمزيد من التفاصيل.

تقدير الموارد

تعتمد متطلبات الموارد لـ OTel collector على معدل تدفق الأحداث، وحجم الرسائل، ومقدار المعالجة المنفَّذة. ويوفّر مشروع OpenTelemetry اختبارات أداء يمكن للمستخدمين الاستفادة منها لتقدير متطلبات الموارد. وفقًا لخبرتنا، يمكن لمثيل gateway مزوّد بـ 3 أنوية و12GB من RAM التعامل مع نحو 60 ألف حدث في الثانية. ويفترض ذلك pipeline معالجة بسيطة تقتصر على إعادة تسمية الحقول من دون regular expression. بالنسبة إلى مثيلات agent المسؤولة عن إرسال الأحداث إلى gateway، والتي يقتصر دورها على تعيين timestamp للحدث فقط، نوصي المستخدمين بتحديد الحجم بناءً على العدد المتوقع من السجلات في الثانية. وتمثل القيم التالية أرقامًا تقريبية يمكنك استخدامها كنقطة بداية:
آخر تعديل في ٢٣ يوليو ٢٠٢٦