- اختيار مفتاح أساسي - تستخدم المخططات الافتراضية عبارة
ORDER BYمُحسّنة لأنماط وصول محددة. ومن غير المرجح أن تتوافق أنماط وصولك مع ذلك. - استخراج البنية - قد ترغب في استخراج أعمدة جديدة من الأعمدة الموجودة، مثل العمود
Body. ويمكن تنفيذ ذلك باستخدام الأعمدة المادية (وباستخدام العروض المادية في الحالات الأكثر تعقيدًا). وهذا يتطلب تغييرات على المخطط. - تحسين الخرائط - تستخدم المخططات الافتراضية النوع Map لتخزين السمات. وتتيح هذه الأعمدة تخزين بيانات وصفية عشوائية. ورغم أن هذه قدرة أساسية، لأن البيانات الوصفية القادمة من الأحداث لا تكون معرّفة مسبقًا في كثير من الأحيان، وبالتالي لا يمكن تخزينها بغير ذلك في قاعدة بيانات ذات أنواع صارمة مثل ClickHouse، فإن الوصول إلى مفاتيح الخريطة وقيمها ليس بالكفاءة نفسها التي يوفّرها الوصول إلى عمود عادي. ونعالج ذلك بتعديل المخطط والتأكد من أن مفاتيح الخريطة الأكثر استخدامًا تكون أعمدة من المستوى الأعلى - انظر “استخراج البنية باستخدام SQL”. وهذا يتطلب تغييرًا في المخطط.
- تبسيط الوصول إلى مفاتيح الخريطة - يتطلب الوصول إلى المفاتيح داخل الخرائط صياغة أكثر تفصيلًا. ويمكنك التخفيف من ذلك باستخدام الأسماء المستعارة. راجع “استخدام الأسماء المستعارة” لتبسيط الاستعلامات.
- الفهارس الثانوية - يستخدم المخطط الافتراضي فهارس ثانوية لتسريع الوصول إلى الخرائط وتسريع الاستعلامات النصية. وهذه الفهارس لا تكون مطلوبة عادةً، كما أنها تستهلك مساحة إضافية على القرص. ويمكن استخدامها، لكن ينبغي اختبارها للتأكد من الحاجة إليها. راجع “الفهارس الثانوية / فهارس تخطي البيانات”.
- استخدام codecs - قد ترغب في تخصيص codecs للأعمدة إذا كنت تفهم طبيعة البيانات المتوقعة ولديك أدلة على أن ذلك يحسّن الضغط.
استخراج البنية باستخدام SQL
- استخراج الأعمدة من كائنات blob النصية. يكون الاستعلام عنها أسرع من استخدام عمليات السلاسل النصية وقت الاستعلام.
- استخراج المفاتيح من الخرائط. يضع المخطط الافتراضي السمات العشوائية في أعمدة من نوع Map. ويوفّر هذا النوع إمكانية العمل بلا مخطط، ما يتيح للمستخدمين عدم الحاجة إلى تعريف أعمدة السمات مسبقًا عند تعريف السجلات وtraces — وغالبًا ما يكون ذلك غير ممكن عند جمع السجلات من Kubernetes مع الحاجة إلى الاحتفاظ بتسميات الـ pod لإجراء البحث لاحقًا. كما أن الوصول إلى مفاتيح الخريطة وقيمها أبطأ من الاستعلام عن أعمدة ClickHouse العادية. لذلك، يكون من المرغوب فيه غالبًا استخراج المفاتيح من الخرائط إلى أعمدة الجدول الجذرية.
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.
ضع القواميس في الحسبانيمكن تحسين الاستعلام أعلاه للاستفادة من قواميس التعبيرات النمطية. راجع استخدام القواميس لمزيد من التفاصيل.
OTel أم ClickHouse للمعالجة؟يمكنك أيضًا إجراء المعالجة باستخدام processors وoperators الخاصة بـ OTel المجمّع كما هو موضح هنا. في معظم الحالات، ستجد أن ClickHouse أكثر كفاءةً في استخدام الموارد وأسرع بكثير من processors الخاصة بـ المجمّع. ويتمثل العيب الأساسي لتنفيذ جميع عمليات معالجة الأحداث باستخدام SQL في ربط الحل لديك بـ ClickHouse. فعلى سبيل المثال، قد ترغب في إرسال السجلات المعالَجة إلى وجهات بديلة من OTel المجمّع، مثل S3.
الأعمدة المادية
العبء الإضافيتضيف الأعمدة المادية عبئًا إضافيًا على التخزين، لأن القيم تُستخرج إلى أعمدة جديدة على القرص في وقت الإدراج.
LogAttributes:
Body من النوع String باستخدام دوال JSON هنا.
تستخرج أعمدتنا الثلاثة المادية صفحة الطلب ونوع الطلب ونطاق المُحيل. وتصل هذه الأعمدة إلى مفاتيح خريطة وتطبّق الدوال على قيمها. لذا يكون استعلامنا اللاحق أسرع بكثير:
لن تُعاد الأعمدة المادية افتراضيًا في
SELECT *. وذلك لضمان بقاء الخاصية التالية قائمة: يمكن دائمًا إعادة إدراج نتيجة SELECT * في الجدول باستخدام INSERT. ويمكن تعطيل هذا السلوك بضبط asterisk_include_materialized_columns=1، كما يمكن تفعيل ذلك في Grafana (راجع Additional Settings -> Custom Settings في إعدادات مصدر البيانات).العروض المادية
تحديثات في الوقت الفعليتُحدَّث العروض المادية في ClickHouse في الوقت الفعلي مع تدفّق البيانات إلى الجدول الذي تستند إليه، لذا فهي تعمل بصورة أقرب إلى الفهارس التي تُحدَّث باستمرار. وعلى النقيض من ذلك، تكون العروض المادية في قواعد البيانات الأخرى عادةً لقطات ثابتة لاستعلام يجب تحديثها (على نحو مشابه لـ Refreshable Materialized Views في ClickHouse).
SELECT ممكنة.
يجب أن تتذكر أن الاستعلام ليس سوى مُشغِّل يُنفَّذ على الصفوف التي تُدرَج في جدول (الجدول المصدر)، وتُرسَل نتائجه إلى جدول جديد (الجدول الهدف).
ولضمان عدم تخزين البيانات مرتين (في الجدولَين المصدر والهدف)، يمكننا تغيير محرك الجدول المصدر إلى Null table engine، مع الحفاظ على المخطط الأصلي. وستواصل مكونات OTel المجمّع لدينا إرسال البيانات إلى هذا الجدول. فعلى سبيل المثال، بالنسبة إلى السجلات، يصبح جدول otel_logs كما يلي:
/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.
otel_logs_v2 بالتنسيق المطلوب. لاحظ استخدام دوال استخراج JSON ذات الأنواع المحددة.
Body باستخدام دوال JSON:
انتبه إلى الأنواع
LogAttributes. وغالبًا ما يقوم ClickHouse بتحويل القيمة المستخرجة تلقائيًا إلى نوع الجدول الهدف، مما يقلّل من الصياغة المطلوبة. ومع ذلك، نوصي المستخدمين دائمًا باختبار عروضهم باستخدام عبارة SELECT الخاصة بالعرض مع عبارة INSERT INTO على جدول هدف يستخدم مخطط مطابقة. من المفترض أن يؤكد ذلك أن الأنواع تُعالَج بشكل صحيح. ويجب إيلاء اهتمام خاص للحالات التالية:
- إذا لم يكن المفتاح موجودًا في الخريطة، فستُعاد سلسلة نصية فارغة. وفي حالة القيم الرقمية، ستحتاج إلى تعيين هذه القيم إلى قيمة مناسبة. ويمكن تحقيق ذلك باستخدام العبارات الشرطية مثل
if(LogAttributes['status'] = ", 200, LogAttributes['status'])أو دوال التحويل إذا كانت القيم الافتراضية مقبولة، مثلtoUInt8OrDefault(LogAttributes['status'] ) - بعض الأنواع لا تُحوَّل دائمًا؛ فعلى سبيل المثال، لا تُحوَّل التمثيلات النصية للقيم الرقمية إلى قيم enum.
- تُرجع دوال استخراج JSON قيمًا افتراضية بحسب نوعها إذا لم يتم العثور على قيمة. تأكد من أن هذه القيم منطقية!
اختيار مفتاح أساسي (ترتيبي)
- اختر الأعمدة التي تتوافق مع عوامل التصفية الشائعة وأنماط الوصول لديك. فإذا كنت تبدأ عادةً تحقيقات Observability بالتصفية حسب عمود معين، مثل اسم الـ pod، فسيُستخدم هذا العمود كثيرًا في عبارات
WHERE. لذا أعطِ الأولوية لإدراج هذه الأعمدة في مفتاحك مقارنةً بالأعمدة الأقل استخدامًا. - فضّل الأعمدة التي تساعد عند التصفية على استبعاد نسبة كبيرة من إجمالي الصفوف، مما يقلل كمية البيانات التي يلزم قراءتها. وغالبًا ما تكون أسماء الخدمات ورموز الحالة مرشحة جيدة — وفي الحالة الأخيرة تحديدًا، إذا كنت تُجري التصفية حسب قيم تستبعد معظم الصفوف؛ فعلى سبيل المثال، التصفية حسب قيم 200 ستطابق في معظم الأنظمة معظم الصفوف، بخلاف أخطاء 500 التي تمثل عادةً مجموعة فرعية صغيرة.
- فضّل الأعمدة التي يُحتمل أن تكون شديدة الارتباط بأعمدة أخرى في الجدول. فهذا يساعد على ضمان تخزين هذه القيم أيضًا بشكل متجاور، مما يحسّن الضغط.
- يمكن جعل عمليات
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 كأنها عمود عادي بشكل شفاف. تأمل المثال التالي:
ALIAS باسم RemoteAddr يقرأ من الـ map LogAttributes. يمكننا الآن الاستعلام عن قيم LogAttributes['remote_addr'] عبر هذا العمود، مما يبسّط الاستعلام، أي:
ALIAS بسهولة عبر الأمر ALTER TABLE. وتصبح هذه الأعمدة متاحة فورًا، على سبيل المثال.
الاسم البديل مستبعَد افتراضيًاافتراضيًا، لا يتضمن
SELECT * أعمدة ALIAS. ويمكن إيقاف هذا السلوك بتعيين asterisk_include_alias_columns=1.تحسين الأنواع
استخدام codecs
ZSTD مناسب جدًا لمجموعات بيانات logging وtrace. وقد يؤدي رفع قيمة الضغط عن default value البالغة 1 إلى تحسين الضغط. ومع ذلك، ينبغي اختبار ذلك، لأن القيم الأعلى تفرض overhead أكبر على CPU وقت insert. وعادةً ما نرى فائدة محدودة من زيادة هذه القيمة.
علاوة على ذلك، فرغم أن timestamps تستفيد من ترميز delta من ناحية الضغط، فقد تبيّن أنها قد تؤدي إلى تراجع query performance إذا استُخدم هذا column في المفتاح الأساسي/مفتاح الترتيب. نوصي المستخدمين بتقييم الموازنة بين الضغط وquery performance في كل حالة.
استخدام القواميس
تسريع عمليات ربطيمكن للمستخدمين المهتمين بتسريع عمليات ربط باستخدام القواميس العثور على مزيد من التفاصيل هنا.
وقت الإدراج مقابل وقت الاستعلام
- وقت الإدراج - يكون هذا مناسبًا عادةً إذا كانت قيمة الإثراء لا تتغير وكانت موجودة في مصدر خارجي يمكن استخدامه لملء القاموس. في هذه الحالة، يؤدّي إثراء الصف وقت الإدراج إلى تجنّب إجراء عملية بحث في القاموس وقت الاستعلام. ويأتي ذلك على حساب أداء الإدراج، بالإضافة إلى زيادةٍ في عبء التخزين، إذ ستُخزَّن القيم المُثرية على هيئة أعمدة.
- وقت الاستعلام - إذا كانت القيم في القاموس تتغير كثيرًا، فغالبًا ما تكون عمليات البحث وقت الاستعلام أكثر ملاءمة. وهذا يجنّب الحاجة إلى تحديث الأعمدة (وإعادة كتابة البيانات) إذا تغيّرت القيم المعينة. وتأتي هذه المرونة على حساب تكلفة البحث وقت الاستعلام. وعادةً ما تكون هذه التكلفة ملحوظة إذا كانت عملية البحث مطلوبة لعدد كبير من الصفوف، مثل استخدام بحث في القاموس ضمن عبارة
filter. أما بالنسبة إلى إثراء النتائج، أي ضمنSELECT، فعادةً لا يكون هذا العبء ملحوظًا.
استخدام قواميس IP
ip_trie.
نستخدم مجموعة بيانات DB-IP على مستوى المدينة المتاحة علنًا، والمقدَّمة من DB-IP.com بموجب شروط ترخيص CC BY 4.0.
ومن ملف README، يمكننا أن نرى أن البيانات منظَّمة على النحو التالي:
URL() لإنشاء جدول في ClickHouse بأسماء الحقول الخاصة بنا، والتحقق من إجمالي عدد الصفوف:
ip_trie لدينا يتطلب تمثيل نطاقات عناوين IP بترميز CIDR، فسنحتاج إلى تحويل ip_range_start وip_range_end.
يمكن حساب قيمة CIDR لكل نطاق بإيجاز باستخدام الاستعلام التالي:
هناك الكثير مما يجري في الاستعلام أعلاه. لمن يهمه الأمر، يمكنه قراءة هذا الشرح الممتاز. وإلا، فاكتفِ باعتبار أن ما سبق يحسب قيمة CIDR لنطاق IP.
ip_trie بنية القاموس لربط بادئات الشبكة لدينا (كتل CIDR) بالإحداثيات ورموز البلدان. يعرّف الاستعلام التالي قاموسًا باستخدام هذا التخطيط، مع الجدول أعلاه كمصدر.
التحديث الدوريتُحدَّث القواميس في ClickHouse دوريًا استنادًا إلى بيانات underlying table وبند lifetime المستخدم أعلاه. ولتحديث قاموس Geo IP لدينا بحيث يعكس أحدث التغييرات في dataset DB-IP، نحتاج فقط إلى إعادة إدراج البيانات من الجدول البعيد geoip_url إلى جدول
geoip مع تطبيق التحويلات.ip_trie (الذي يحمل أيضًا الاسم ip_trie)، يمكننا استخدامه لتحديد الموقع الجغرافي لعناوين IP. ويمكن تنفيذ ذلك باستخدام الدالة dictGet() كما يلي:
RemoteAddress مُستخرجًا.
select ضمن عرض مُجسَّد:
تحديث دوريمن المرجّح أن يرغب المستخدمون في تحديث قاموس إثراء عناوين IP دوريًا استنادًا إلى البيانات الجديدة. ويمكن تحقيق ذلك باستخدام عبارة
LIFETIME في القاموس، ما يؤدي إلى إعادة تحميل القاموس دوريًا من الجدول الأساسي. لتحديث الجدول الأساسي، راجع “العروض المادية القابلة للتحديث”.استخدام قواميس Regex (تحليل وكيل المستخدم)
أنشئ جداول Memory التالية. وتحتفظ هذه الجداول بالتعابير النمطية التي نستخدمها لتحليل الأجهزة والمتصفحات وأنظمة التشغيل.
otel_logs_v2:
Tuples للبُنى المعقّدةلاحِظ استخدام Tuples في أعمدة وكيل المستخدم هذه. يُوصى باستخدام Tuples مع البُنى المعقّدة التي يكون تسلسلها الهرمي معروفًا مسبقًا. وتوفّر الأعمدة الفرعية الأداء نفسه الذي توفّره الأعمدة العادية (على عكس مفاتيح Map)، مع إتاحة استخدام أنواع غير متجانسة.
للمزيد من القراءة
تسريع الاستعلامات
استخدام Materialized views (التدريجية) للتجميعات
سيكون هذا الاستعلام أسرع بعشر مرات إذا استخدمنا الجدول
otel_logs_v2، الناتج عن العرض المادي السابق، والذي يستخرج المفتاح size من الخريطة LogAttributes. نستخدم البيانات الخام هنا لأغراض توضيحية فقط، ونوصي باستخدام العرض السابق إذا كان هذا الاستعلام شائعًا.bytes_per_hour فارغ ولم يستقبل أي بيانات بعد. ينفّذ العرض المادي لدينا عبارة SELECT المذكورة أعلاه على البيانات المُدرجة في otel_logs (وسيُنفَّذ ذلك على مستوى كتل بالحجم المُعدّ)، ثم تُرسَل النتائج إلى bytes_per_hour. تظهر البنية أدناه:
TO هنا أساسية، إذ تحدد الوجهة التي ستُرسل إليها النتائج، أي bytes_per_hour.
إذا أعدنا تشغيل OTel Collector وأعدنا إرسال السجلات، فسيُملأ جدول bytes_per_hour تدريجيًا بنتيجة الاستعلام أعلاه. وعند اكتمال ذلك، يمكننا التأكد من حجم bytes_per_hour — إذ ينبغي أن يحتوي على صف واحد لكل ساعة:
otel_logs) إلى 113 من خلال تخزين نتيجة الاستعلام. والمهم هنا أنه إذا أُدرجت سجلات جديدة في جدول otel_logs، فستُرسَل القيم الجديدة إلى bytes_per_hour بحسب الساعة المقابلة لها، حيث ستُدمَج تلقائيًا بشكل غير متزامن في الخلفية. وبالاحتفاظ بصف واحد فقط لكل ساعة، سيظل bytes_per_hour صغيرًا ومحدَّثًا دائمًا.
ونظرًا إلى أن دمج الصفوف يتم بشكل غير متزامن، فقد يكون هناك أكثر من صف واحد لكل ساعة عند تنفيذ المستخدم للاستعلام. ولضمان دمج أي صفوف متبقية وقت تنفيذ الاستعلام، لدينا خياران:
- استخدام
FINALالمُعدِّل مع اسم الجدول (وهذا ما فعلناه في استعلام العد أعلاه). - التجميع حسب مفتاح الترتيب المستخدم في جدولنا النهائي، أي Timestamp، ثم جمع المقاييس.
قد تكون هذه المكاسب أكبر بكثير مع مجموعات بيانات أكبر واستعلامات أكثر تعقيدًا. اطّلع على أمثلة هنا.
مثال أكثر تعقيدًا
UniqueUsers بالنوع AggregateFunction، مع تحديد الدالة التي تُنتج الحالات الجزئية (uniq) ونوع العمود المصدر (IPv4). وكما في SummingMergeTree، ستُدمَج الصفوف التي لها قيمة مفتاح ORDER BY نفسها (Hour في المثال أعلاه).
ويستخدم العرض المادي المرتبط الاستعلام السابق:
State بنهاية دوالنا التجميعية. يضمن ذلك إرجاع الحالة التجميعية للدالة بدلًا من النتيجة النهائية. وستتضمن هذه الحالة معلومات إضافية تتيح دمج هذه الحالة الجزئية مع حالات أخرى.
بمجرد إعادة تحميل البيانات عبر إعادة تشغيل Collector، يمكننا التأكد من توفّر 113 صفًا في جدول unique_visitors_per_hour.
GROUP BY هنا بدلًا من FINAL.
استخدام العروض المادية (التزايدية) لعمليات البحث السريع
ServiceName وSpanName وTimestamp. وفي التتبّع، يحتاج المستخدمون أيضًا إلى إمكانية إجراء عمليات بحث باستخدام TraceId محدّد واسترجاع الـ spans المرتبطة بهذا الـ trace. ومع أن ذلك موجود في مفتاح الترتيب، فإن وجوده في النهاية يعني أن التصفية لن تكون بالكفاءة نفسها، كما يُرجَّح أن يتطلّب استرجاع trace واحد فحص كميات كبيرة من البيانات.
يثبّت OTel collector أيضًا عرضًا ماديًا وجدولًا مرتبطًا بها لمعالجة هذا التحدّي. الجدول والعرض موضّحان أدناه:
otel_traces_trace_id_ts الحدين الأدنى والأقصى للطابع الزمني الخاص بالتتبّع. ويتيح هذا الجدول، المرتّب حسب TraceId، استرجاع هاتين القيمتين بكفاءة. ويمكن بعد ذلك استخدام هذه النطاقات الزمنية عند الاستعلام عن الجدول الرئيسي otel_traces. وبشكل أكثر تحديدًا، عند استرجاع تتبّع بواسطة معرّفه، يستخدم Grafana الاستعلام التالي:
timestamp لمعرّف التتبّع ae9226c78d1d360601e6383928e4d22d، ثم يستخدم ذلك لتصفية جدول otel_traces الرئيسي وإرجاع الـ spans المرتبطة به.
يمكن تطبيق هذا النهج نفسه على أنماط وصول مشابهة. ونستعرض مثالًا مشابهًا في نمذجة البيانات هنا.
استخدام الإسقاطات
ORDER BY للجدول.
في الأقسام السابقة، استعرضنا كيف يمكن استخدام العروض المادية في ClickHouse لإجراء عمليات التجميع مسبقًا، وتحويل الصفوف، وتحسين استعلامات Observability لأنماط الوصول المختلفة.
قدمنا مثالًا يرسل فيه العرض المادي الصفوف إلى جدول هدف بمفتاح ترتيب يختلف عن الجدول الأصلي الذي يستقبل عمليات الإدراج، وذلك لتحسين عمليات البحث بحسب معرّف التتبّع.
يمكن استخدام الإسقاطات لمعالجة المشكلة نفسها، إذ تتيح للمستخدم تحسين الاستعلامات على عمود لا يكون جزءًا من المفتاح الأساسي.
نظريًا، يمكن استخدام هذه الإمكانية لتوفير عدة مفاتيح ترتيب للجدول، لكن مع عيب واضح: تكرار البيانات. فعلى وجه التحديد، يجب كتابة البيانات وفق ترتيب المفتاح الأساسي الرئيسي، بالإضافة إلى الترتيب المحدد لكل إسقاط. ويؤدي ذلك إلى إبطاء عمليات الإدراج واستهلاك مساحة أكبر على القرص.
الإسقاطات مقابل العروض الماديةتوفّر الإسقاطات كثيرًا من الإمكانيات نفسها التي توفّرها العروض المادية، لكن ينبغي استخدامها بحذر، وغالبًا ما تكون العروض المادية هي الخيار المفضّل. ويجب أن تفهم عيوبها ومتى يكون استخدامها مناسبًا. فعلى سبيل المثال، رغم إمكانية استخدام الإسقاطات لإجراء عمليات التجميع مسبقًا، فإننا نوصي المستخدمين باستخدام العروض المادية لهذا الغرض.
otel_logs_v2 لدينا بحسب 500 من رموز الخطأ. ومن المرجح أن يكون هذا نمط وصول شائعًا في السجلّات، إذ يرغب المستخدمون في التصفية بحسب رموز الخطأ:
استخدم Null لقياس الأداءنحن لا نعرض النتائج هنا باستخدام
FORMAT Null. فهذا يفرض قراءة جميع النتائج من دون إرجاعها، وبذلك يمنع الإنهاء المبكر للاستعلام بسبب LIMIT. والغرض من ذلك هو فقط إظهار الوقت المستغرق في فحص جميع الصفوف العشرة ملايين.(ServiceName, Timestamp). وبينما يمكننا إضافة Status إلى نهاية مفتاح الترتيب لتحسين أداء الاستعلام أعلاه، يمكننا أيضًا إضافة إسقاط.
ALTER، فسيُنفَّذ إنشاؤه بشكل غير متزامن عند إصدار الأمر MATERIALIZE PROJECTION. ويمكنك التحقق من تقدّم هذه العملية باستخدام الاستعلام التالي، مع انتظار is_done=1.
SELECT * هنا بدلًا من ذلك، فسيتم تخزين جميع الأعمدة. ورغم أن هذا سيتيح لعدد أكبر من الاستعلامات (باستخدام أي مجموعة فرعية من الأعمدة) الاستفادة من الإسقاط، فإنه سيؤدي إلى استهلاك مساحة تخزين إضافية. لقياس مساحة القرص والضغط، راجع “قياس حجم الجدول والضغط”.
الفهارس الثانوية/فهارس تخطي البيانات
فهرس نصي للبحث النصي الكامل
tokenizer في تعريفه. واختياريًا، يمكن تحديد دالة للمعالجة المسبقة لتحويل سلسلة الإدخال قبل تجزئتها إلى رموز.
الدالتان الموصى بهما للبحث في الفهرس هما: hasAnyTokens وhasAllTokens.
كما تُحسَّن بعض دوال البحث التقليدية في السلاسل النصية تلقائيًا عند وجود فهرس نصي.
راجع التوثيق للاطلاع على التفاصيل والدوال المدعومة هنا وهنا.
في الأمثلة أدناه، نستخدم مجموعة بيانات لسجلات مهيكلة.
hasAnyTokens من دون فهرس نصي، لكن الاستعلام سينفّذ فحصًا كاملًا بطيئًا لعمود Body:
إضافة فهرس نصي
ALTER TABLE:
استخدام مُعالج مسبق
msg وid وctx وattr وغيرها).
لنفترض أننا مهتمون فقط بالبحث داخل الحقل msg.
وبدلًا من فهرسة سلسلة JSON بالكامل، يمكننا تعريف مُعالج مسبق لاستخراج قيمة msg فقط قبل التجزئة إلى رموز.
على سبيل المثال:
- تقليل كمية النص التي تُجزَّأ إلى رموز وتُفهرس،
- تقليل حجم الفهرس،
- تقليل احتمالية النتائج الإيجابية الكاذبة، و
- تحسين أداء الاستعلامات.