المقدمة
مفاهيم ClickHouse
تُنسب الجداول إلى قواعد بيانات في ClickHouse. وبشكل افتراضي، تُستخدم قاعدة البيانات
default — ويمكن تعديل ذلك في OpenTelemetry collector.
كحد أدنى، ينبغي أن تفهم أساسيات ClickHouse التالية:
تُعد هذه المفاهيم محورية لأداء ClickHouse. فهي تحدد كيفية كتابة البيانات، وكيفية تنظيمها على القرص، ومدى كفاءة ClickHouse في تخطّي قراءة البيانات وقت الاستعلام. وكل تحسين في هذا الدليل، سواء كان أعمدة materialized، أو skip indexes، أو المفاتيح الأساسية، أو projections، أو materialized views، يعتمد على هذه الآليات الأساسية.
يُنصح بمراجعة وثائق ClickHouse التالية قبل الشروع في أي عملية ضبط:
- إنشاء الجداول في ClickHouse - مقدمة بسيطة إلى الجداول.
- الأجزاء
- الأقسام
- عمليات الدمج
- المفاتيح الأساسية/indexes
- كيف يخزّن ClickHouse البيانات: الأجزاء والحبيبات - دليل أكثر تقدمًا حول كيفية تنظيم البيانات والاستعلام عنها في ClickHouse، مع تغطية مفصلة للحبيبات والمفاتيح الأساسية.
- MergeTree- دليل مرجعي متقدم لـ MergeTree مفيد للأوامر والتفاصيل الداخلية.
التحسين 1. تحويل السمات التي يُستعلَم عنها كثيرًا إلى أعمدة مُجسَّدة
LogAttributes وScopeAttributes وResourceAttributes، ثم تحويلها إلى أعمدة من المستوى الأعلى باستخدام الأعمدة المُجسَّدة.
وغالبًا ما يكون هذا التحسين وحده كافيًا لتوسيع نطاق عمليات نشر ClickStack إلى عشرات التيرابايت يوميًا، وينبغي تطبيقه قبل النظر في تقنيات ضبط أكثر تقدمًا.
لماذا نُجسِّد السمات
Map(String, String). ورغم أن هذا يوفّر مرونة، فإن الاستعلام عن المفاتيح الفرعية داخل map له تأثير مهم على الأداء.
عند الاستعلام عن مفتاح واحد من عمود Map، يجب على ClickHouse قراءة عمود map بالكامل من disk. وإذا كانت map تحتوي على عدد كبير من المفاتيح، فإن ذلك يؤدي إلى IO غير ضروري واستعلامات أبطأ مقارنة بقراءة عمود مخصّص.
يساعد تجسيد السمات التي يُستعلم عنها كثيرًا على تجنّب هذا العبء الإضافي، وذلك عبر استخراج القيمة عند insert time وتخزينها كعمود مستقل.
الأعمدة المُجسَّدة:
- تُحتسب تلقائيًا أثناء عمليات الإدراج
- لا يمكن تعيينها صراحةً في عبارات INSERT
- تدعم أي expression في ClickHouse
- تتيح تحويل النوع من String إلى أنواع رقمية أو من نوع Date أكثر كفاءة
- تُمكّن من استخدام skip indexes والمفتاح الأساسي
- تقلّل reads من disk عبر تجنّب الوصول الكامل إلى map
يكتشف ClickStack تلقائيًا الأعمدة المُجسَّدة المستخرجة من map ويستخدمها بشفافية أثناء تنفيذ الاستعلامات، حتى عندما يواصل المستخدمون الاستعلام عن مسار السمة الأصلي.
مثال
ResourceAttributes:
ResourceAttributes.k8s.pod.name:"checkout-675775c4cc-f2p9c":
ويؤدي ذلك إلى مسند SQL مشابه لما يلي:
Map، يتعيّن على ClickHouse قراءة العمود ResourceAttributes كاملًا لكل صف مطابق — وقد يكون ذلك ضخمًا جدًا إذا كانت Map تحتوي على مفاتيح كثيرة.
إذا كان الاستعلام عن هذه السمة يتكرر كثيرًا، فينبغي تجسيدها كعمود من المستوى الأعلى.
لاستخراج اسم الكبسولة وقت الإدراج، أضِف عمودًا مُجسَّدًا:
PodName.
يمكن للمستخدمين الآن الاستعلام عن أسماء الكبسولات بكفاءة باستخدام بناء جملة Lucene، مثل PodName:"checkout-675775c4cc-f2p9c"
بالنسبة إلى البيانات المُدرجة حديثًا، يُلغي هذا الحاجة إلى الوصول إلى map بالكامل ويقلّل بشكل كبير من عمليات الإدخال/الإخراج.
ومع ذلك، حتى إذا واصل المستخدمون الاستعلام باستخدام مسار السمة الأصلي مثل ResourceAttributes.k8s.pod.name:"checkout-675775c4cc-f2p9c"، فإن ClickStack سيُعيد كتابة الاستعلام تلقائيًا داخليًا لاستخدام العمود المُجسَّد PodName، أي باستخدام الشرط:
افتراضيًا، تُستبعَد الأعمدة المُجسَّدة من
SELECT * queries. وهذا يحافظ على القاعدة الثابتة التي تقضي بإمكانية إعادة إدراج نتائج الاستعلام دائمًا في الجدول.تخزين البيانات التاريخية بشكل مادي
system.mutations، على سبيل المثال.
is_done = 1 لعملية الـ mutation المقابلة.
التحسين 2. إضافة فهارس التخطي
- تصفية السلاسل النصية عالية الكاردينالية، مثل TraceId، ومعرّفات الجلسات، ومفاتيح السمات أو قيمها
- تصفية المفاتيح الفرعية في Map، المسرَّعة بواسطة فهارس نصية على أعمدة
*AttributeItems - التصفية على النطاقات الرقمية، مثل مدة span
text(tokenizer = 'array') في كامل الجدول بدلًا من مرشحات Bloom، ويضيف فهرس text(tokenizer = 'splitByNonAlpha') على lower(Body) لأغراض البحث النصي الكامل. راجع “الجداول والمخططات التي يستخدمها ClickStack” للاطلاع على DDL الكامل.
مرشحات Bloom
materialize لفهرس التخطي - راجع “إجراء materialize لفهرس التخطي.”
بمجرد إنشائه وتنفيذ materialize له، يمكن لـ ClickHouse تخطي حبيبات كاملة من المؤكد أنها لا تحتوي على اسم الكبسولة المطلوب، مما قد يقلل كمية البيانات المقروءة أثناء استعلامات مثل PodName:"checkout-675775c4cc-f2p9c".
تكون bloom filters أكثر فعالية عندما يكون توزيع القيم بحيث تظهر قيمة معيّنة في عدد صغير نسبيًا من الأجزاء. ويحدث هذا غالبًا بشكل طبيعي في أعباء عمل observability، حيث ترتبط البيانات الوصفية مثل أسماء الكبسولات أو معرّفات التتبّع أو معرّفات الجلسات بالوقت، وبالتالي تتجمع وفق مفتاح الترتيب الخاص بالجدول.
وكما هو الحال مع جميع فهارس التخطي، ينبغي إضافة bloom filters بشكل انتقائي والتحقق منها في مقابل أنماط الاستعلام الفعلية لضمان أنها توفّر فائدة قابلة للقياس - راجع “تقييم فعالية فهرس التخطي.”
الفهارس النصية
WHERE. الفهارس النصية هي فهارس معكوسة تربط الرموز بإزاحات دقيقة داخل الجزء. ونظراً لأنها تقيّم الإزاحات بدلاً من الحبيبات ولا تنتج أي إيجابيات كاذبة، فيمكنها عادةً تلبية شرط WHERE من دون تحميل العمود الأساسي. ويُعرف هذا التحسين باسم القراءة المباشرة. وبما أن تحميل البيانات يكون غالباً أكبر عامل في زمن الاستعلام، فإن القراءة المباشرة يمكن أن تقلّل كمون الاستعلام بشكل ملحوظ.
إضافةً إلى ذلك، يمكن أيضاً الاستعلام عن الفهارس النصية نفسها، ما يوفّر الإكمال التلقائي وأدوات الفحص الداخلي الأخرى في ClickStack.
يغطي مُقسِّما الرموز التاليان معظم أنماط ClickStack:
مُقسِّم الرموز Array لأعمدة Map والأعمدة من النوع Array
mapKeys ومصفوفات العناصر المُجسَّدة باستخدام مُقسِّم الرموز array:
splitByNonAlpha لمحتوى السجل
Body من فهرس نصي splitByNonAlpha.
ويعرّف ClickStack هذا الفهرس على lower(Body) بحيث تتمكّن عمليات بحث Lucene
المتجاهلة لحالة الأحرف من استخدامه:
text(tokenizer = 'splitByNonAlpha') على
lower(Body)، فإنه يعيد كتابة استعلامات Lucene ذات العمود الضمني مثل error أو
"connection refused" إلى hasAllTokens(lower(Body), lower(...))، ما يتيح
للفهرس تنفيذها من دون قراءة عمود Body بالكامل. بالنسبة إلى معظم أحمال عمل سجلات
observability، فهذا أكبر تسريع منفرد متاح لعمليات التصفية.
فهارس النص مقابل
tokenbf_v1نوع الفهرس الأقدم tokenbf_v1 (الذي لا يزال مستخدمًا في مخطط تتبعات الافتراضي لـ
lower(SpanName)) متشابه وظيفيًا، لكنه مُهمَل في ClickHouse 26.2
والإصدارات الأحدث. ينبغي أن تستخدم فهارس البحث النصي الجديدة text(tokenizer = ...).الفهارس النصية في مخطط السجلات الافتراضي
otel_logs الافتراضي، المُزامَن من المصدر الأصلي، متضمّنًا كل فهرس نصي نوقش أعلاه: text(tokenizer = 'array') على TraceId، وعلى كل من mapKeys(...) ومصفوفات *AttributeItems، وtext(tokenizer = 'splitByNonAlpha') على lower(Body) لأغراض البحث النصي الكامل. للاطلاع على DDL القياسي، راجع “الجداول والمخططات المستخدمة بواسطة ClickStack”؛ كما يَرِد أدناه المخطط نفسه.
فهارس MinMax
SpanAttributes:
تنفيذ فهرس التخطي ماديًا
تجسيد فهارس التخطييكون تجسيد فهرس التخطي عادةً خفيفًا وآمنًا، خاصةً مع فهارس MinMax. أما فهارس مرشح Bloom على مجموعات البيانات الكبيرة، فقد يفضّل المستخدمون تجسيدها على مستوى كل partition على حدة للتحكم في استهلاك الموارد بشكل أفضل، على سبيل المثال:
is_done = 1 لعملية الـ mutation المقابلة.
بعد اكتمال ذلك، تأكد من إنشاء بيانات الفهرس:
0.01 إلى 0.05 إلى إنتاج فهرس أصغر يُقيَّم بسرعة أكبر، لكن على حساب تقليم أقل فعالية. ورغم أنه قد يتم تخطي عدد أقل من granules، فقد يتحسن زمن استجابة الاستعلام الإجمالي بسبب سرعة تقييم الفهرس.
لذلك، فإن ضبط مُعامِلات مرشح Bloom يُعد تحسينًا يعتمد على workload، وينبغي التحقق منه باستخدام query patterns فعلية وأحجام بيانات مماثلة لبيئة production.
لمزيد من التفاصيل حول فهارس التخطي، راجع الدليل “فهم فهارس ClickHouse لتخطي البيانات.”
تقييم فعالية فهارس التخطي
EXPLAIN indexes = 1، إذ يوضّح عدد الأجزاء والحبيبات التي جرى استبعادها في كل مرحلة من مراحل تخطيط الاستعلام. في معظم الحالات، يُفترض أن ترى انخفاضًا كبيرًا في عدد الحبيبات عند مرحلة Skip، ويفضَّل أن يحدث ذلك بعد أن يكون المفتاح الأساسي قد قلّص نطاق البحث بالفعل. تُقيَّم فهارس التخطي بعد تقليص الأقسام وتقليص المفتاح الأساسي، لذا يُقاس أثرها بأفضل شكل مقارنةً بالأجزاء والحبيبات المتبقية.
يؤكد EXPLAIN ما إذا كان هذا التقليص يحدث، لكنه لا يضمن بالضرورة تحسنًا فعليًا في السرعة. فتقييم فهارس التخطي له كلفة، لا سيما إذا كان الفهرس كبيرًا. احرص دائمًا على إجراء benchmark للاستعلامات قبل إضافة الفهرس وتجسيده ماديًا وبعد ذلك، للتأكد من وجود تحسينات فعلية في الأداء.
على سبيل المثال، تأمّل فهرس التخطي الافتراضي من نوع مرشح Bloom للحقل TraceId والمضمَّن في مخطط تتبعات الافتراضي:
EXPLAIN indexes = 1 لمعرفة مدى فعاليته في استعلام انتقائي:
FORMAT Null لتجنّب الحمل الإضافي الناتج عن تسلسل النتائج، وعطّل ذاكرة التخزين المؤقت لشروط الاستعلام للحفاظ على قابلية تكرار التشغيل:
use_query_condition_cache عدم تأثر النتائج بقرارات التصفية المخزنة مؤقتًا، كما أن ضبط use_skip_indexes = 0 يوفّر خط أساس واضحًا للمقارنة. وإذا كان التقليم فعّالًا وكانت كلفة تقييم الفهرس منخفضة، فمن المفترض أن يكون الاستعلام المفهرس أسرع بشكل ملحوظ، كما في المثال أعلاه.
متى تُضاف فهارس التخطي
IN التي تُسرّعها Bloom filters، كما تتيح أيضًا الشروط المعتمدة على الرموز (hasToken, hasAllTokens, has) المستخدمة في البحث النصي الكامل وفي تحسين القراءة المباشرة لـ Map. وفي العناقيد الأقدم التي لا تدعم فهارس النص بعد، تظل Bloom filters خيارًا قويًا.
تكون Bloom filters أكثر فعالية مع الأعمدة النصية عالية الكاردينالية، حيث يكون تكرار كل قيمة منخفضًا نسبيًا، ما يعني أن معظم الأجزاء والحبيبات لا تحتوي على القيمة المطلوب البحث عنها. وكقاعدة عامة، تكون Bloom filters أكثر جدوى عندما يحتوي العمود على ما لا يقل عن 10,000 قيمة مميزة، وغالبًا ما تحقق أفضل أداء مع 100,000+ قيمة مميزة. كما تكون أكثر فعالية عندما تكون القيم المطابقة متجمعة في عدد صغير من الأجزاء المتسلسلة، وهو ما يحدث عادةً عندما يكون العمود مترابطًا مع مفتاح الترتيب. ومرة أخرى، قد تختلف النتائج من حالة إلى أخرى — فلا شيء يغني عن الاختبار العملي في بيئة واقعية.
التحسين 3. القراءة المباشرة لـ Map
LogAttributes['k8s.pod.name'] = 'checkout'، يجب على ClickHouse قراءة عمود LogAttributes بالكامل من نوع Map من
القرص وفكّ بيانات كل صف لتقييم الشرط. إن تجسيد السمات كثيرة الاستعلام
يحل هذه المشكلة للمفاتيح التي تعرفها مسبقًا، لكنه لا يتوسّع ليتعامل مع
السمات غير المحددة مسبقًا التي يصفّي المستخدمون عليها عند الحاجة.
حتى إذا كان المخطط يحتوي على فهارس على mapKeys وmapValues، فإن هذه الفهارس تستطيع إخبارك ما إذا كان الصف يحتوي على مفتاح معيّن، وما إذا كان يحتوي على قيمة معيّنة، لكنها لا تستطيع تحديد ما إذا كان المفتاح والقيمة ينتميان إلى الإدخال نفسه. بعبارة أخرى، يجيب mapKeys عن mapContainsKey(ResourceAttributes, 'foo') ويجيب mapValues عن mapContainsValue(ResourceAttributes, 'bar')، لكن لا يجيب أيٌّ منهما عن ResourceAttributes['foo'] = 'bar'.
من خلال دمج المفاتيح والقيم في عمود واحد من نوع Array(String)، يتيح
تحسين القراءة المباشرة لـ Map الإجابة عن ResourceAttributes['foo'] = 'bar'
من دون تحميل الـ Map الفعلي. وغالبًا ما تكون قيم Map كبيرة، ويزداد
حجمها مع زيادة حجم البيانات. وعند دمج ذلك مع إعادة كتابة للاستعلام على مستوى التطبيق،
تصبح مرشحات المساواة على أي مفتاح فرعي في Map استدعاءً واحدًا لـ has(...)
مدعومًا بذلك الفهرس، من دون أي إلغاء تسلسل لـ Map في وقت الاستعلام. بالإضافة إلى ذلك،
فإن تكلفة التخزين الوحيدة هي تكلفة فهرس النص، لأن العمود الفعلي
هو عمود ALIAS ولا يتم تخزينه.
هذا التحسين تلقائي. يوفّر ClickStack الأعمدة والفهارس اللازمة
في جدولَي السجلات والتتبعات الافتراضيين، ويعيد كتابة مرشحات
الفهرسة الفرعية لـ Map في وقت التشغيل عندما يدعم ClickHouse server المتصل الميزة
الأساسية. وإذا كان مخططك لا يحتوي على هذه الأعمدة، أو كانت لديك
أعمدة Map إضافية تريد تسريعها إلى جانب الإعدادات الافتراضية، فتابع القراءة
لتمكينها.
المخطط
Map تريد تسريع الوصول إليه، يعرّف ClickStack عمود Array(String) ALIAS
يربط كل مفتاح بقيمته باستخدام =:
text(tokenizer = 'array') على
العمود ALIAS رمزًا واحدًا لكل زوج key=value، ويستخدم ClickHouse ذلك
لاستبعاد الحبيبات من دون الرجوع إلى Map المصدر:
إعادة كتابة الاستعلام
LogAttributeItems، وتستبعد
صفوفًا كاملة لا تحتوي على الرمز key=value، ولا تفك تسلسل
الـ Map المصدر LogAttributes مطلقًا للصفوف غير المطابقة. بالنسبة إلى أعباء عمل
الرصد عالية الكاردينالية، يحقق هذا عادةً خفضًا بمقدار مرتبة كاملة
في I/O مقارنةً بالوصول عبر الفهرسة الفرعية لـ Map.
تتم إعادة الكتابة تلقائيًا — فالاستعلامات المحفوظة ولوحات المعلومات والتنبيهات التي
تشير إلى LogAttributes['key'] تستفيد من هذه الزيادة في السرعة من دون أي تغيير.
متطلبات إصدار ClickHouse
SELECT version(), ويُخزَّن مؤقتًا لكل اتصال) ولا
يُصدر الصيغة المُعاد كتابتها إلا إذا كان إصدار الخادم عند الحد المطلوب أو أعلى منه. أما
الخوادم الأقدم فتعود تلقائيًا إلى صيغة الفهرسة الأصلية لـ Map.
لماذا ALIAS وليس MATERIALIZEDتمثل مصفوفة items عرضًا للبيانات الموجودة أصلًا في العمود Map.
فتخزينها مرتين — مرة في Map ومرة في المصفوفة — يضاعف عمليات I/O الخاصة بالكتابة
من دون إتاحة أنماط استعلام جديدة. كما أن الفهرس النصي على العمود
ALIAS
يُبنى وقت الإدراج من بيانات المصدر نفسها، لذا لا تضيف عملية التحسين إلى القرص إلا
المساحة التي يشغلها الفهرس.التحسين 4. تعديل المفتاح الأساسي
ملاحظة حول المصطلحاتفي هذا المستند، يُستخدم مصطلح “مفتاح الترتيب” بالتبادل مع “المفتاح الأساسي”. ومن الناحية الدقيقة، يختلفان في ClickHouse، لكنهما في ClickStack يشيران عادةً إلى الأعمدة نفسها المحددة في عبارة
ORDER BY للجدول. ولمزيد من التفاصيل، راجع وثائق ClickHouse حول اختيار مفتاح أساسي يختلف عن مفتاح الفرز.- السجلات (
otel_logs) -(toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp) - التتبعات (
otel_traces) -(ServiceName, SpanName, toDateTime(Timestamp))
اختيار مفتاح أساسي
تعديل المفتاح الأساسي الافتراضيتكون المفاتيح الأساسية الافتراضية كافية في معظم الحالات. يجب إجراء التغييرات بحذر، وفقط مع فهم واضح لأنماط الاستعلامات. قد يؤدي تعديل المفتاح الأساسي إلى تراجع الأداء في مسارات عمل أخرى، لذا يُعد الاختبار أمرًا ضروريًا.
- اختر الأعمدة التي تتوافق مع عوامل التصفية الشائعة وأنماط الوصول لديك. إذا كنت تبدأ عادةً تحقيقات Observability بالتصفية حسب عمود معيّن، مثل اسم الـ pod، فسيُستخدم هذا العمود كثيرًا في عبارات
WHERE. أعطِ الأولوية لإدراج هذه الأعمدة في مفتاحك على حساب الأعمدة الأقل استخدامًا. - فضّل الأعمدة التي تساعد، عند التصفية، على استبعاد نسبة كبيرة من إجمالي الصفوف، مما يقلّل كمية البيانات التي يلزم قراءتها. غالبًا ما تكون أسماء الخدمات ورموز الحالة مرشّحات جيدة — وفي الحالة الثانية، فقط إذا كنت تُصفّي بحسب قيم تستبعد معظم الصفوف. فعلى سبيل المثال، التصفية حسب الرموز 200 ستطابق، في معظم الأنظمة، معظم الصفوف، بخلاف أخطاء 500 التي ستنطبق على مجموعة فرعية صغيرة.
- فضّل الأعمدة التي يُرجَّح أن تكون شديدة الارتباط بأعمدة أخرى في الجدول. سيساعد ذلك على ضمان تخزين هذه القيم أيضًا بشكل متجاور، مما يحسّن الضغط.
- يمكن جعل عمليات
GROUP BY(التجميعات الخاصة بالمخططات) وORDER BY(الفرز) للأعمدة الموجودة في مفتاح الترتيب أكثر كفاءة من حيث استهلاك الذاكرة.
تغيير المفتاح الأساسي
SeverityText قبل ServiceName.
1
إنشاء جدول جديد
مفتاح الترتيب مقابل المفتاح الأساسيلاحظ أنه في المثال أعلاه، يجب تحديد
PRIMARY KEY وORDER BY.
في ClickStack، يكونان متطابقين تقريبًا في معظم الحالات.
يتحكم ORDER BY في التخطيط الفعلي للبيانات، بينما يحدد PRIMARY KEY الفهرس المتناثر.
وفي حالات نادرة جدًا ومع أحمال العمل الكبيرة للغاية، قد يختلفان، لكن ينبغي لمعظم المستخدمين إبقاؤهما متوافقين.نادرًا ما تكون إعادة تحميل البيانات التاريخية الموجودة إلى جدول جديد مجدية على نطاق واسع. فعادةً ما تكون تكلفة المعالجة وعمليات IO مرتفعة، ولا تبرر مكاسب الأداء. وبدلًا من ذلك، اترك البيانات الأقدم تنتهي صلاحيتها عبر TTL، بينما تستفيد البيانات الأحدث من المفتاح المُحسَّن.
SeverityText العمود الأول في المفتاح الأساسي. وفي هذه الحالة، يُنشأ جدول للبيانات الجديدة مع الاحتفاظ بالجدول القديم لأغراض التحليل التاريخي.
1
إنشاء جدول جديد
أنشئ الجدول الجديد بالمفتاح الأساسي المطلوب. لاحظ اللاحقة_23_01_2025 — عدّلها لتكون التاريخ الحالي. على سبيل المثال:2
إنشاء جدول Merge
لا يخزّن محرك Merge (ويجب عدم الخلط بينه وبين MergeTree) البيانات بنفسه، لكنه يتيح القراءة من أي عدد من الجداول الأخرى في الوقت نفسه.تفترض
currentDatabase() أن الأمر يُشغَّل في قاعدة البيانات الصحيحة. وإلا، فحدّد اسم قاعدة البيانات صراحةً.otel_logs.3
تحديث ClickStack UI للقراءة من جدول Merge
اضبط ClickStack UI لاستخدامotel_logs_merge بصفته الجدول الخاص بمصدر بيانات السجلات.في هذه المرحلة، تستمر عمليات الكتابة إلى otel_logs باستخدام المفتاح الأساسي الأصلي، بينما تستخدم عمليات القراءة جدول Merge. لا يوجد أي تغيير مرئي للمستخدمين أو أي تأثير على استيعاب البيانات.4
تبديل الجداول
تُستخدم الآن عبارةEXCHANGE لتبديل اسمي الجدولين otel_logs وotel_logs_23_01_2025 تبديلًا ذريًا.otel_logs الجديد بالمفتاح الأساسي المحدّث. وتبقى البيانات الحالية في otel_logs_23_01_2025، ولا تزال متاحة عبر جدول Merge. وتشير اللاحقة إلى تاريخ تطبيق التغيير، وتمثّل أحدث timestamp موجود في ذلك الجدول.تتيح هذه العملية تغيير المفتاح الأساسي من دون انقطاع في استيعاب البيانات ومن دون أي تأثير مرئي للمستخدمين.SeverityNumber يجب أن يكون جزءًا من المفتاح الأساسي بدلًا من SeverityText. ويمكن تكرار العملية التالية كلما دعت الحاجة إلى تغيير المفتاح الأساسي.
1
إنشاء جدول جديد
أنشئ الجدول الجديد بالمفتاح الأساسي المطلوب. في المثال أدناه، استُخدم30_01_2025 بوصفه لاحقة للدلالة على تاريخ الجدول. على سبيل المثال:2
تبديل الجداول
تُستخدم الآن عبارةEXCHANGE لتبديل اسمي الجدولين otel_logs وotel_logs_30_01_2025 تبديلًا ذريًا.otel_logs الجديد بالمفتاح الأساسي المحدّث. وتبقى البيانات القديمة في otel_logs_30_01_2025، ويمكن الوصول إليها عبر جدول Merge.الجداول الزائدةإذا كانت سياسات TTL مطبّقة، وهو ما يُنصح به، فإن الجداول ذات المفاتيح الأساسية الأقدم التي لم تعد تستقبل عمليات كتابة ستفرغ تدريجيًا مع انتهاء صلاحية البيانات. ويجب مراقبتها وتنظيفها دوريًا بمجرد أن تصبح خالية من البيانات. وحتى الآن، تتم عملية التنظيف هذه يدويًا.
تسريع البحث عن الصفوف باستخدام أعمدة الكتل
(_block_number, _block_offset) يحدده بشكل فريد داخل
جزء. عند النقر على صف سجل في ClickStack UI لفتح لوحة التفاصيل، يُجري ClickStack
استعلامًا لاحقًا لجلب ذلك الصف وحده. من دون أعمدة الكتل، يجب أن
تتضمن عبارة WHERE الخاصة بالصف عددًا كافيًا من الأعمدة — عادةً المفتاح الأساسي
بالإضافة إلى Body وSeverityText — لتمييز الصف. ومع أعمدة الكتل،
يكفي المفتاح الأساسي مع _block_number و_block_offset. أما
الأعمدة الكبيرة مثل Body فلا تُقرأ مطلقًا أثناء عملية البحث، مما يسرّع الاستعلام فعليًا.
يكتشف ClickStack هذا الإعداد من تعليمة CREATE الخاصة بالجدول، ويُنشئ
عبارة WHERE الأخف تلقائيًا عند تمكين كلا العمودين. ولا
يلزم إجراء أي تغيير في إعدادات التطبيق.
للاستفادة من هذا التحسين في جدول سجل أو تتبّع موجود:
ALTER. وتستمر الأجزاء الموجودة
في استخدام آلية البحث القديمة لكل صف إلى أن تُعاد كتابتها عبر عملية دمج.
التحسين 5. الاستفادة من العروض المُجسَّدة
التحسين 6. الاستفادة من الإسقاطات
ORDER BY الخاص بالجدول الأساسي، مما يتيح لـ ClickHouse استبعاد البيانات بفاعلية أكبر لأنماط الوصول التي لا تتوافق مع الترتيب الأصلي.
يمكن للعروض المُجسَّدة تحقيق أثر مشابه من خلال كتابة الصفوف صراحةً إلى جدول هدف منفصل بمفتاح ترتيب مختلف. ويتمثل الفرق الأساسي في أن الإسقاطات تُدار تلقائيًا وبشفافية بواسطة ClickHouse، بينما العروض المُجسَّدة هي جداول صريحة يجب أن يسجلها ClickStack ويختارها عمدًا.
عندما يستهدف الاستعلام الجدول الأساسي، يقيّم ClickHouse التخطيط الأساسي وأي إسقاطات متاحة، ويأخذ عينات من فهارسها الأساسية، ثم يختار التخطيط القادر على إنتاج النتيجة الصحيحة مع قراءة أقل عدد من الحبيبات. ويُتخذ هذا القرار تلقائيًا بواسطة محلل الاستعلامات.
لذلك، تُعد الإسقاطات في ClickStack الأنسب لـ إعادة ترتيب البيانات فقط، في الحالات التالية:
- تكون أنماط الوصول مختلفة جوهريًا عن المفتاح الأساسي الافتراضي
- يكون من غير العملي تغطية جميع مسارات العمل بمفتاح ترتيب واحد
- تريد أن يختار ClickHouse التخطيط المادي الأمثل بشفافية
مثال: الإسقاطات
استخدم رموز البدلفي مثال الإسقاط أعلاه، استُخدم رمز بدل (
SELECT *). ومع أن اختيار مجموعة فرعية من الأعمدة قد يقلّل من الكلفة الإضافية للكتابة، فإنه يحدّ أيضًا من الحالات التي يمكن فيها استخدام الإسقاط، لأن الاستعلامات المؤهلة هي فقط تلك التي يمكن تلبيتها بالكامل من خلال هذه الأعمدة. في ClickStack، يقيّد هذا غالبًا استخدام الإسقاط بحالات محدودة جدًا. لذلك، يُنصح عمومًا باستخدام رمز بدل لتوسيع نطاق الاستخدام إلى أقصى حد.قد يستغرق تجسيد الإسقاط وقتًا طويلًا ويستهلك قدرًا كبيرًا من الموارد. ولأن بيانات observability تنتهي صلاحيتها عادةً عبر TTL، فلا ينبغي اللجوء إلى ذلك إلا عند الضرورة القصوى. وفي معظم الحالات، يكفي أن يقتصر تطبيق الإسقاط على البيانات المُدخلة حديثًا، مما يتيح له تحسين النطاقات الزمنية الأكثر ورودًا في الاستعلامات، مثل آخر 24 ساعة.
SELECT *)، وعندما تتوافق فلاتر الاستعلام بدرجة كبيرة مع ORDER BY الخاص بالإسقاط.
ستستفيد الاستعلامات التي تُجري تصفية حسب TraceId (وخاصةً باستخدام المساواة) وتتضمن نطاقًا زمنيًا من الإسقاط المذكور أعلاه. على سبيل المثال:
TraceId، أو التي تعتمد أساسًا على التصفية وفق أبعاد أخرى لا تقع في مقدمة مفتاح ترتيب الإسقاط، لن تستفيد عادةً من ذلك (وقد تُقرأ عبر البنية الأساسية بدلًا من ذلك).
يمكن للإسقاطات أيضًا تخزين عمليات التجميع (على غرار العروض المُجسَّدة). في ClickStack، لا يُنصح عمومًا بعمليات التجميع المعتمدة على الإسقاطات، لأن اختيارها يعتمد على محلّل ClickHouse، كما أن استخدامَها قد يكون أصعب من حيث التحكم فيه وفهمه. وبدلًا من ذلك، يُفضَّل استخدام العروض المُجسَّدة الصريحة التي يمكن لـ ClickStack تسجيلها واختيارها عمدًا على مستوى التطبيق.
التكاليف والإرشادات
- عبء
insertالإضافي: إن إسقاطSELECT *ذي مفتاح ترتيب مختلف يعني فعليًا كتابة البيانات مرتين، ما يزيد من I/O للكتابة وقد يتطلب موارد CPU إضافية ومعدل نقل أعلى للقرص للحفاظ على معدل الإدخال. - استخدمها باعتدال: من الأفضل حجز الإسقاطات لأنماط الوصول المتباينة فعلًا، حيث يتيح ترتيب مادي ثانٍ تقليصًا فعّالًا للبيانات لعدد كبير من الاستعلامات، مثل حالتي فريقين يستعلمان عن مجموعة البيانات نفسها بطرائق مختلفة جذريًا.
- تحقق باستخدام اختبارات قياس الأداء: كما هو الحال مع جميع عمليات الضبط، قارن بين زمن استجابة الاستعلامات الفعلي واستخدام الموارد قبل إضافة إسقاط وتجسيده وبعده.
الإسقاطات خفيفة الوزن باستخدام _part_offset
الإسقاطات خفيفة الوزن في ClickStack ما تزال Betaلا يُوصى باستخدام الإسقاطات خفيفة الوزن المستندة إلى
_part_offset مع أعباء عمل ClickStack. فعلى الرغم من أنها تقلّل التخزين وعمليات I/O الخاصة بالكتابة، فقد تؤدي إلى زيادة الوصول العشوائي وقت الاستعلام، كما أن سلوكها في بيئات production وعلى نطاق observability ما يزال قيد التقييم. وقد تتغير هذه التوصية مع نضج هذه الميزة وتوفّر مزيد من البيانات التشغيلية._part_offset إلى base table، بدلًا من تكرار الصفوف كاملة. ويمكن أن يخفّض ذلك بشكل كبير overhead التخزين، كما تتيح التحسينات الأخيرة إجراء pruning على مستوى granule، مما يجعلها أقرب في سلوكها إلى الفهارس الثانوية الحقيقية. انظر:
البدائل
ORDER BY متعددة، فليست الإسقاطات الخيار الوحيد. وبحسب القيود التشغيلية والطريقة التي تريد أن يوجّه بها ClickStack الاستعلامات، ضع في اعتبارك ما يلي:
- تهيئة مجمّع OpenTelemetry لديك للكتابة إلى جدولين بمفاتيح
ORDER BYمختلفة، وإنشاء مصادر ClickStack منفصلة لكل جدول. - إنشاء عرض مُجسَّد كمسار نسخ؛ أي إرفاق عرض مُجسَّد بالجدول الرئيسي بحيث يحدّد الصفوف الخام ويكتبها إلى جدول ثانوي بمفتاح
ORDER BYمختلف (كنمط لإزالة التطبيع أو التوجيه). أنشئ مصدرًا لهذا الجدول الهدف. يمكن العثور على أمثلة هنا.