الإدراجات التلقائية مع التحديث للصفوف المُدرجة
ALTER أو عبارة DELETE غير الفعّالة، وذلك من خلال إتاحة إدراج نسخ متعددة من الصف نفسه وتحديد إحداها على أنها الإصدار الأحدث. وفي المقابل، تتولى عملية خلفية إزالة الإصدارات الأقدم من الصف نفسه بشكل غير متزامن، بما يحاكي عملية التحديث بكفاءة عبر استخدام عمليات إدراج غير قابلة للتغيير.
يعتمد ذلك على قدرة محرك الجدول على التعرّف على الصفوف المكررة. ويتحقق هذا باستخدام عبارة ORDER BY لتحديد التفرّد، أي إذا كان صفّان يحملان القيم نفسها للأعمدة المحددة في ORDER BY، فيُعتبران مكررين. ويتيح عمود version، الذي يُحدَّد عند تعريف الجدول، الاحتفاظ بأحدث إصدار من الصف عند التعرّف على صفّين على أنهما مكرران، أي يُحتفَظ بالصف ذي قيمة الإصدار الأعلى.
نوضّح هذه العملية في المثال أدناه. هنا، تُعرَّف الصفوف بشكل فريد بواسطة العمود A (وهو ORDER BY للجدول). ونفترض أن هذه الصفوف أُدرجت على دفعتين، ما أدى إلى تكوّن جزأين من البيانات على القرص. ولاحقًا، خلال عملية خلفية غير متزامنة، تُدمَج هذان الجزآن معًا.
يتيح ReplacingMergeTree أيضًا تحديد عمود deleted إضافي. ويمكن أن يحتوي هذا العمود على 0 أو 1، حيث تشير القيمة 1 إلى أن الصف (ومكرراته) قد حُذف، بينما تُستخدم القيمة 0 في غير ذلك. ملاحظة: لن تُزال الصفوف المحذوفة وقت الدمج.
أثناء هذه العملية، يحدث ما يلي خلال دمج الأجزاء:
- الصف الذي تحدده القيمة 1 في العمود A يحتوي على صف تحديث بالإصدار 2 وصف حذف بالإصدار 3 (مع قيمة 1 في عمود deleted). لذلك يُحتفَظ بأحدث صف، والمعلَّم بأنه محذوف.
- الصف الذي تحدده القيمة 2 في العمود A يحتوي على صفّي تحديث. ويُحتفَظ بالصف الأحدث بقيمة 6 في عمود price.
- الصف الذي تحدده القيمة 3 في العمود A يحتوي على صف بالإصدار 1 وصف حذف بالإصدار 2. ويُحتفَظ بصف الحذف هذا.
لاحظ أن الصفوف المحذوفة لا تُزال أبدًا. ويمكن حذفها قسرًا باستخدام
OPTIMIZE table FINAL CLEANUP. ويتطلب ذلك الإعداد التجريبي allow_experimental_replacing_merge_with_cleanup=1. ولا ينبغي تنفيذ ذلك إلا في ظل الشروط التالية:
- يجب أن تكون متأكدًا من أنه لن تُدرج أي صفوف بإصدارات قديمة (بالنسبة إلى الصفوف التي يجري حذفها عبر التنظيف) بعد تنفيذ العملية. وإذا أُدرجت هذه الصفوف، فسيُحتفَظ بها بشكل غير صحيح، لأن الصفوف المحذوفة لن تعود موجودة.
- تأكد من أن جميع النسخ المتماثلة متزامنة قبل تنفيذ التنظيف. ويمكن تحقيق ذلك باستخدام الأمر:
لا يُنصح بمعالجة عمليات الحذف باستخدام ReplacingMergeTree إلا في الجداول التي يكون فيها عدد عمليات الحذف منخفضًا إلى متوسط (أقل من 10%)، ما لم يكن بالإمكان جدولة فترات للتنظيف وفق الشروط المذكورة أعلاه.
نصيحة: قد تتمكن أيضًا من تنفيذ OPTIMIZE FINAL CLEANUP على أقسام محددة لم تعد عرضة للتغييرات.
اختيار مفتاح أساسي/إزالة التكرار
ORDER BY الصفّ بشكل فريد عبر التغييرات. لذلك، عند الترحيل من قاعدة بيانات معاملات مثل Postgres، ينبغي تضمين المفتاح الأساسي الأصلي في Postgres ضمن عبارة ORDER BY في ClickHouse.
سيكون مستخدمو ClickHouse على دراية بكيفية اختيار الأعمدة في عبارة ORDER BY في جداولهم لتحسين أداء الاستعلامات. وبوجه عام، ينبغي اختيار هذه الأعمدة بناءً على الاستعلامات الشائعة وإدراجها بترتيب تصاعدي حسب الكاردينالية. والأهم من ذلك أن ReplacingMergeTree يفرض قيدًا إضافيًا: يجب أن تكون هذه الأعمدة غير قابلة للتغيير، أي إذا كنتم تنسخون البيانات من Postgres، فلا تضيفوا أعمدة إلى هذه العبارة إلا إذا كانت لا تتغير في بيانات Postgres الأساسية. وبينما يمكن أن تتغير أعمدة أخرى، يجب أن تظل هذه الأعمدة متسقة لضمان التعرّف الفريد على الصفوف.
بالنسبة إلى أحمال العمل التحليلية، فعادةً ما تكون فائدة المفتاح الأساسي في Postgres محدودة لأنكم نادرًا ما ستجرون عمليات بحث نقطية عن صفوف مفردة. ونظرًا لأننا نوصي بترتيب الأعمدة تصاعديًا حسب الكاردينالية، إضافةً إلى أن المطابقات على الأعمدة المدرجة مبكرًا في ORDER BY تكون عادةً أسرع، فينبغي إلحاق المفتاح الأساسي في Postgres بنهاية ORDER BY (ما لم تكن له قيمة تحليلية). وإذا كانت عدة أعمدة تكوّن مفتاحًا أساسيًا في Postgres، فينبغي إلحاقها بـ ORDER BY مع مراعاة الكاردينالية واحتمال فائدتها للاستعلامات. وقد ترغبون أيضًا في إنشاء مفتاح أساسي فريد باستخدام ربط القيم عبر عمود MATERIALIZED.
انظروا إلى جدول posts من مجموعة بيانات Stack Overflow.
ORDER BY بالقيمة (PostTypeId, toDate(CreationDate), CreationDate, Id). ويضمن العمود Id، وهو فريد لكل منشور، إمكانية إزالة التكرار من الصفوف. كما يُضاف العمودان Version وDeleted إلى المخطط حسب الحاجة.
الاستعلام عن ReplacingMergeTree
ORDER BY كمُعرّف فريد، ثم يحتفظ فقط بأعلى إصدار أو يزيل جميع التكرارات إذا كان أحدث إصدار يشير إلى الحذف. لكن هذا لا يوفّر سوى صحة نهائية للنتائج؛ فهو لا يضمن إزالة تكرار الصفوف، لذا لا ينبغي الاعتماد عليه.
استخدم FINAL لقراءة البيانات بعد إزالة التكرارلأن إزالة التكرار لا تحدث إلا أثناء عمليات الدمج في الخلفية، فقد يُرجع
SELECT عادي صفوفًا مكررة أو محذوفة. لقراءة نتائج صحيحة في وقت الاستعلام، استخدم المُعدِّل FINAL، الذي يُكمل إزالة التكرار واستبعاد الصفوف المحذوفة أثناء تنفيذ الاستعلام.INSERT INTO SELECT بسيطة:
deleted إلى 1. ويمكن أيضًا محاكاة ذلك باستخدام INSERT INTO SELECT بسيط.
FINAL على الجدول النتيجة الصحيحة.
أداء FINAL
FINAL زيادة طفيفة في كلفة الأداء على الاستعلامات.
ويظهر ذلك بشكل أوضح عندما لا تُجري الاستعلامات تصفيةً على أعمدة المفتاح الأساسي،
إذ يؤدي ذلك إلى قراءة المزيد من البيانات وزيادة كلفة إزالة التكرار. وإذا
أجريت التصفية على أعمدة المفتاح باستخدام شرط WHERE، فستقل كمية البيانات
المحمّلة والمُمرَّرة إلى عملية إزالة التكرار.
إذا كان شرط WHERE لا يستخدم عمود مفتاح، فإن ClickHouse لا يستفيد حاليًا من تحسين PREWHERE عند استخدام FINAL. ويهدف هذا التحسين إلى تقليل عدد الصفوف المقروءة للأعمدة غير المُصفّاة. ويمكن العثور هنا على أمثلة تحاكي PREWHERE هذا، وقد يُسهم ذلك في تحسين الأداء.
الاستفادة من الأقسام باستخدام ReplacingMergeTree
do_not_merge_across_partitions_select_final=1 لتحسين أداء الاستعلام FINAL. ويجعل هذا الإعداد الأقسام تُدمج وتُعالَج بشكل مستقل عند استخدام FINAL.
انظر إلى جدول posts التالي، حيث لا نستخدم أي تقسيم:
FINAL سيضطر إلى تنفيذ بعض المعالجة، نحدّث مليون صف - بزيادة AnswerCount فيها عبر إدراج صفوف مكررة.
FINAL:
do_not_merge_across_partitions_select_final=1.
اعتبارات سلوك الدمج
منطق اختيار الدمج
سلوك الدمج على الأجزاء الكبيرة
max_bytes_to_merge_at_max_space_in_pool، فلن يعود يُختار لمزيد من عمليات الدمج، حتى إذا كان min_age_to_force_merge_seconds مضبوطًا. ونتيجة لذلك، لم يعد بالإمكان الاعتماد على عمليات الدمج التلقائية لإزالة التكرارات التي قد تتراكم مع استمرار إدخال البيانات.
لمعالجة ذلك، يمكنك تنفيذ OPTIMIZE FINAL لدمج الأجزاء يدويًا وإزالة التكرارات. وعلى عكس عمليات الدمج التلقائية، يتجاوز OPTIMIZE FINAL عتبة max_bytes_to_merge_at_max_space_in_pool، ويدمج الأجزاء استنادًا فقط إلى الموارد المتاحة، ولا سيما مساحة القرص، إلى أن يبقى جزء واحد فقط في كل قسم. ومع ذلك، قد يستهلك هذا النهج قدرًا كبيرًا من الذاكرة في الجداول الكبيرة، وقد يتطلب تكرار التنفيذ مع إضافة بيانات جديدة.
وللحصول على حل أكثر استدامة يحافظ على الأداء، يُوصى بتقسيم الجدول. ويمكن أن يساعد ذلك في منع أجزاء البيانات من الوصول إلى الحد الأقصى لحجم الدمج، ويقلل من الحاجة إلى عمليات التحسين اليدوية المستمرة.
التقسيم والدمج عبر الأقسام
ضبط عمليات الدمج لتحسين أداء الاستعلام
min_age_to_force_merge_seconds مساوية لـ 0 وقيمة min_age_to_force_merge_on_partition_only مساوية لـ false، مما يعطّل هذه الميزات. في هذا الإعداد، سيطبّق ClickHouse سلوك الدمج القياسي من دون فرض عمليات دمج استنادًا إلى عمر القسم.
إذا تم تحديد قيمة لـ min_age_to_force_merge_seconds، فسيتجاوز ClickHouse آليات الدمج المعتادة للأجزاء الأقدم من الفترة المحددة. ومع أن هذا يكون فعّالًا عادةً فقط إذا كان الهدف هو تقليل العدد الإجمالي للأجزاء، فإنه قد يحسّن أداء الاستعلام في ReplacingMergeTree عبر تقليل عدد الأجزاء التي تحتاج إلى الدمج وقت تنفيذ الاستعلام.
يمكن ضبط هذا السلوك بمزيد من الدقة عبر تعيين min_age_to_force_merge_on_partition_only=true، بحيث يُشترط أن تكون جميع الأجزاء في القسم أقدم من min_age_to_force_merge_seconds حتى يتم الدمج المكثّف. يتيح هذا الإعداد للأقسام الأقدم أن تندمج تدريجيًا لتصبح جزءًا واحدًا مع مرور الوقت، مما يدمج البيانات ويحافظ على أداء الاستعلام.
الإعدادات الموصى بها
min_age_to_force_merge_seconds على قيمة منخفضة — أقل بكثير من فترة القسم. يقلل ذلك عدد الأجزاء ويمنع الدمج غير الضروري وقت الاستعلام باستخدام المُعامل FINAL.
على سبيل المثال، لنفترض قسمًا شهريًا تم دمجه بالفعل في جزء واحد. إذا أدت عملية insert صغيرة وعارضة إلى إنشاء جزء جديد داخل هذا القسم، فقد يتراجع أداء الاستعلام لأن ClickHouse سيضطر إلى قراءة عدة أجزاء إلى أن يكتمل الدمج. ويمكن أن يضمن ضبط min_age_to_force_merge_seconds دمج هذه الأجزاء بسرعة، مما يمنع تدهور أداء الاستعلام.