دليل يوضّح طرق التحديث المتاحة وكيفية اختيار الطريقة المناسبة لعبء العمل لديك.
عندما يتعلق الأمر بالتعامل مع التحديثات، تتبع قواعد البيانات التحليلية وقواعد بيانات المعاملات نهجًا مختلفًا في معالجتها، وذلك بسبب اختلاف فلسفات التصميم الأساسية وحالات الاستخدام المستهدفة.
يُعد ClickHouse قاعدة بيانات موجّهة بالأعمدة، وهو مُحسَّن للتحليلات كثيفة القراءة وعمليات الإلحاق فقط ذات الإنتاجية العالية.
في الممارسة العملية، يُعاد غالبًا تنظيم الجداول لتحويل عمليات الحذف والتحديث إلى عمليات إلحاق تُعالَج بشكل غير متزامن و/أو عند وقت القراءة، للاستفادة من نقاط قوة ClickHouse في استيعاب البيانات عالي الإنتاجية.
كما يدعم ClickHouse أيضًا عمليات تحديث وحذف قوية.يوفّر هذا الدليل نظرة عامة على طرق التحديث المتاحة في ClickHouse، ويساعدك على اختيار استراتيجية التحديث المناسبة لعبء العمل لديك.
ضمن كل فئة من الفئتين المذكورتين أعلاه، توجد عدة طرق لتحديث البيانات.
ولكل طريقة مزاياها وخصائص أدائها، لذا ينبغي اختيار الطريقة المناسبة وفقًا لنموذج بياناتك وحجم البيانات التي تنوي تحديثها.
تُعدّ محركات الجداول المتخصصة الخيار الأفضل عندما تكون لديك كميات كبيرة من التحديثات، أو تغييرات متكررة على مستوى الصفوف، أو تحتاج إلى معالجة دفق مستمر من أحداث التحديث والحذف.المحركات التي ستصادفها غالبًا هي:
استخدمه عند تحديث الصفوف الفردية بشكل متكرر، أو في الحالات التي تحتاج فيها إلى الاحتفاظ بأحدث حالة للكائنات التي تتغير بمرور الوقت. على سبيل المثال، تتبّع نشاط المستخدم أو إحصاءات المقالات.
وبما أن محركات الجداول من عائلة MergeTree تدمج أجزاء البيانات في الخلفية، فهي توفّر اتساقًا نهائيًا، ويجب استخدام الكلمة المفتاحية FINAL لضمان إزالة التكرار بشكل صحيح خلال هذه الفترة عند الاستعلام عن الجدول.
توجد أيضًا أنواع محركات أخرى، لكن هذه هي الأكثر شيوعًا.
قد تكون عبارات UPDATE التصريحية أكثر بساطة في عمليات التحديث البسيطة من دون تعقيد إدارة منطق إزالة التكرار، لكنها تكون عمومًا أنسب لتحديث عدد أقل من الصفوف وبوتيرة أقل مقارنةً بالمحركات المتخصصة.
استخدم هذا في معظم الحالات، وخصوصًا عند تنفيذ عمليات UPDATE صغيرة ومتكررة (حتى نحو 10% من الجدول) كجزء من تطبيقك أو سير العمل. على سبيل المثال، قد يرغب مستخدم في حذف سجل أحداثه، بينما تكون الأحداث موزعة ضمن جدول متعدد المستأجرين يضم عددًا كبيرًا من المستخدمين. ينشئ هذا النهج أجزاء تصحيح لتوفير ظهور فوري من دون إعادة كتابة أعمدة كاملة. ويضيف عبئًا إضافيًا إلى استعلامات SELECT، لكنه يوفر زمن استجابة يمكن التنبؤ به.
استخدم هذا عند تنفيذ إدارة بيانات على نطاق أوسع، وخصوصًا عندما يتوافق التحديث مع تقسيم الجدول. على سبيل المثال، إذا كنت بحاجة إلى تحديث عمود لكل الصفوف ضمن شهر واحد في جدول مُقسَّم حسب الشهر.
يقوم ReplacingMergeTree بإزالة تكرار الصفوف التي لها مفتاح فرز واحد أثناء عمليات الدمج في الخلفية، مع الاحتفاظ بأحدث إصدار فقط.
CREATE TABLE posts( Id UInt32, Title String, ViewCount UInt32, Version UInt32)ENGINE = ReplacingMergeTree(Version)ORDER BY Id
يُعد هذا المحرك مثاليًا للتحديثات المتكررة جدًا على الصفوف الفردية عندما تُعرَّف التحديثات بواسطة مفتاح ثابت.
وتُظهر اختبارات الأداء أنه قد يكون أسرع بما يصل إلى 4,700 مرة من عمليات mutations عند تحديث صف واحد.لتحديث صف، ما عليك سوى إدراج إصدار جديد بالقيم نفسها لمفتاح الفرز وبرقم إصدار أعلى. تُزال الإصدارات الأقدم أثناء عمليات الدمج في الخلفية. ونظرًا لأن إزالة التكرار ليست فورية (فهي لا تحدث إلا أثناء عمليات الدمج)، ينبغي استخدام المُعدِّل FINAL أو منطق استعلام مكافئ للحصول على نتائج صحيحة وخالية من التكرار. ويضيف المُعدِّل FINAL عبئًا إضافيًا على الاستعلام يتراوح بين 21% و550% بحسب البيانات.لا يمكن لـ ReplacingMergeTree تحديث قيم مفتاح الفرز. كما أنه يدعم عمود Deleted لعمليات الحذف المنطقي.اقرأ المزيد: دليل ReplacingMergeTree | مرجع ReplacingMergeTree
يقوم CoalescingMergeTree بدمج السجلات المتفرقة من خلال الاحتفاظ بأحدث قيمة غير NULL لكل عمود أثناء عمليات الدمج. ويتيح ذلك تنفيذ عمليات إدراج أو تحديث على مستوى الأعمدة بدلًا من استبدال الصف بالكامل.
CREATE TABLE electric_vehicle_state( vin String, -- vehicle identification number last_update DateTime64 Materialized now64(), -- optional (used with argMax) battery_level Nullable(UInt8), -- in % lat Nullable(Float64), -- latitude (°) lon Nullable(Float64), -- longitude (°) firmware_version Nullable(String), cabin_temperature Nullable(Float32), -- in °C speed_kmh Nullable(Float32) -- from sensor)ENGINE = CoalescingMergeTreeORDER BY vin;
صُمم هذا المحرك لسيناريوهات تصل فيها البيانات على شكل أجزاء من مصادر متعددة، أو تُملأ فيها أعمدة مختلفة في أوقات مختلفة. وتشمل حالات الاستخدام الشائعة بيانات القياس عن بُعد لإنترنت الأشياء من الأنظمة الفرعية المتجزئة، وإثراء ملفات تعريف المستخدمين، ومسارات ETL ذات الأبعاد المتأخرة.عند دمج الصفوف التي لها مفتاح الفرز نفسه، يحتفظ CoalescingMergeTree بأحدث قيمة غير NULL لكل عمود بدلًا من استبدال الصف بأكمله. يجب أن تكون الأعمدة غير المفتاحية من النوع Nullable لكي يعمل ذلك كما هو مقصود. وكما هو الحال مع ReplacingMergeTree، استخدم FINAL للحصول على نتائج صحيحة ومُوحَّدة.هذا المحرك متاح ابتداءً من ClickHouse 25.6.اقرأ المزيد: CoalescingMergeTree
انطلاقًا من فكرة أن التحديثات مكلفة، لكن يمكن الاستفادة من عمليات الإدراج لتنفيذها، يستخدم CollapsingMergeTree عمود Sign لإخبار ClickHouse بكيفية التعامل مع الصفوف أثناء عمليات الدمج. إذا أُدرجت القيمة -1 في عمود Sign، فسيُطوى الصف (أي يُحذف) عند إقرانه بصف مطابق ذي القيمة +1. تُحدَّد الصفوف المطلوب تحديثها استنادًا إلى مفتاح الفرز المستخدم في عبارة ORDER BY عند إنشاء الجدول.
CREATE TABLE user_activity( UserID UInt64, PageViews UInt8, Duration UInt8, Sign Int8)ENGINE = CollapsingMergeTree(Sign)ORDER BY UserID-- Initial stateINSERT INTO user_activity VALUES (4324182021466249494, 5, 146, 1)-- Cancel old row and insert new stateINSERT INTO user_activity VALUES (4324182021466249494, 5, 146, -1)INSERT INTO user_activity VALUES (4324182021466249494, 6, 185, 1)-- Query with proper aggregationSELECT UserID, sum(PageViews * Sign) AS PageViews, sum(Duration * Sign) AS DurationFROM user_activityGROUP BY UserIDHAVING sum(Sign) > 0
على عكس ReplacingMergeTree، يتيح لك CollapsingMergeTree تعديل قيم مفتاح الترتيب. وهو مناسب جدًا للعمليات القابلة للعكس التي تتضمن دلالات الإلغاء، مثل المعاملات المالية أو تتبّع حالة اللعبة.
يتطلّب نهج التحديث المذكور أعلاه أن يحتفظ تطبيقك بالحالة على جهة العميل لكي يتمكّن من إدراج صف الإلغاء. ورغم أن هذا هو النهج الأكثر كفاءة من منظور ClickHouse، فقد يكون العمل به على نطاق واسع معقّدًا. كما تحتاج الاستعلامات أيضًا إلى تجميع مع ضرب الإشارة لإنتاج نتائج صحيحة.
تؤدي عمليات التعديل (ALTER TABLE ... UPDATE) إلى إعادة كتابة جميع الأجزاء التي تحتوي على صفوف تطابق تعبير WHERE.
ALTER TABLE posts UPDATE AnswerCount = AnswerCount + 1 WHERE AnswerCount = 0
تُعدّ التعديلات كثيفة الاستهلاك لعمليات الإدخال/الإخراج، إذ تعيد كتابة جميع الأجزاء التي تطابق تعبير WHERE.
ولا تتسم هذه العملية بالذرية.
وتُستبدل الأجزاء بالأجزاء التي طُبِّق عليها التعديل بمجرد أن تصبح جاهزة، وسيشاهد استعلام SELECT الذي يبدأ تنفيذه أثناء عملية التعديل بيانات من أجزاء طُبِّق عليها التعديل بالفعل، إلى جانب بيانات من أجزاء لم يُطبَّق عليها التعديل بعد.
يمكنك تتبّع حالة التقدّم عبر جدول system.mutations.
التعديلات كثيفة الاستهلاك لعمليات الإدخال/الإخراج، ويجب استخدامها باعتدال لأنها قد تؤثر في أداء SELECT على مستوى العنقود. وإذا تراكمت التعديلات في الطابور بوتيرة أسرع من معالجتها، فسيتدهور أداء الاستعلامات. راقب الطابور عبر system.mutations.
عند إجراء تعديلات عبر ALTER TABLE ... UPDATE، قد تحتاج إلى الانتظار حتى تُطبَّق هذه التعديلات من خلال عملية في الخلفية لكي ترى القيم المعدَّلة منعكسة في استعلاماتك.
يوفّر ClickHouse طريقة لتغيير هذا السلوك من خلال “التعديلات الفورية”.
عندما تكون التعديلات الفورية مُمكَّنة، تُعلَّم الصفوف المحدَّثة فورًا، وتُرجِع استعلامات SELECT اللاحقة القيم المعدَّلة تلقائيًا.يمكن تمكين التعديلات الفورية للجداول من عائلة MergeTree عبر تفعيل الإعداد على مستوى الاستعلام apply_mutations_on_fly.
SET apply_mutations_on_fly = 1;
مثال
لنُنشئ جدولًا وننفّذ بعض عمليات mutation:
CREATE TABLE test_on_fly_mutations (id UInt64, v String)ENGINE = MergeTree ORDER BY id;-- تعطيل materialization في الخلفية لعمليات mutation لعرض-- السلوك الافتراضي عندما لا تكون on-the-fly mutations مُمكّنةSYSTEM STOP MERGES test_on_fly_mutations;SET mutations_sync = 0;-- إدراج بعض الصفوف في جدولنا الجديدINSERT INTO test_on_fly_mutations VALUES (1, 'a'), (2, 'b'), (3, 'c');-- تحديث قيم الصفوفALTER TABLE test_on_fly_mutations UPDATE v = 'd' WHERE id = 1;ALTER TABLE test_on_fly_mutations DELETE WHERE v = 'd';ALTER TABLE test_on_fly_mutations UPDATE v = 'e' WHERE id = 2;ALTER TABLE test_on_fly_mutations DELETE WHERE v = 'e';
لنتحقق من نتيجة التحديثات عبر استعلام SELECT:
-- تعطيل on-the-fly mutations صراحةًSET apply_mutations_on_fly = 0;SELECT id, v FROM test_on_fly_mutations ORDER BY id;
لاحظ أن قيم الصفوف لم تُحدَّث بعد عند الاستعلام عن الجدول الجديد:
┌─id─┬─v─┐│ 1 │ a ││ 2 │ b ││ 3 │ c │└────┴───┘
لنرَ الآن ما يحدث عند تمكين on-the-fly mutations:
-- تمكين on-the-fly mutationsSET apply_mutations_on_fly = 1;SELECT id, v FROM test_on_fly_mutations ORDER BY id;
يُرجع استعلام SELECT الآن النتيجة الصحيحة فورًا، من دون الحاجة إلى انتظار تطبيق عمليات mutation:
┌─id─┬─v─┐│ 3 │ c │└────┴───┘
تأثير الأداء
عند تمكين التعديلات الفورية، لا تُجسَّد التعديلات فورًا، بل لا تُطبَّق إلا أثناء استعلامات SELECT. ومع ذلك، يُرجى ملاحظة أن التعديلات لا تزال تُجسَّد بشكل غير متزامن في الخلفية، وهي عملية مكلفة.إذا ظل عدد التعديلات المُرسلة يتجاوز باستمرار عدد التعديلات التي تُعالَج في الخلفية خلال فترة زمنية معينة، فسيستمر طابور التعديلات غير المُجسَّدة المطلوب تطبيقها في النمو. وسيؤدي ذلك في النهاية إلى تراجع أداء استعلامات SELECT.نقترح تمكين الإعداد apply_mutations_on_fly مع إعدادات أخرى على مستوى MergeTree مثل number_of_mutations_to_throw و number_of_mutations_to_delay للحد من النمو غير المحدود للتعديلات غير المُجسَّدة.
دعم الاستعلامات الفرعية والدوال غير الحتمية
توفّر On-the-fly mutations دعمًا محدودًا للاستعلامات الفرعية والدوال غير الحتمية. ولا يُدعَم إلا الاستعلامات الفرعية التي تُرجِع قيمة واحدة إذا كان حجم النتيجة معقولًا (ويتحكم في ذلك الإعداد mutations_max_literal_size_to_replace). كما لا تُدعَم إلا الدوال غير الحتمية الثابتة (مثل الدالة now()).تخضع هذه السلوكيات للإعدادات التالية:
الإعداد
الوصف
الافتراضي
mutations_execute_nondeterministic_on_initiator
إذا كانت القيمة true، فستُنفَّذ الدوال غير الحتمية على الـ replica المُبادِرة، وتُستبدل بقيم حرفية في استعلامات UPDATE وDELETE.
false
mutations_execute_subqueries_on_initiator
إذا كانت القيمة true، فستُنفَّذ الاستعلامات الفرعية التي تُرجِع قيمة واحدة على الـ replica المُبادِرة، وتُستبدل بقيم حرفية في استعلامات UPDATE وDELETE.
false
mutations_max_literal_size_to_replace
الحد الأقصى لحجم القيم الحرفية المُسلسلة، بالبايت، التي يمكن استبدالها في استعلامات UPDATE وDELETE.
تستخدم التحديثات خفيفة الوزن “patch parts”—وهي أجزاء بيانات خاصة لا تتضمن سوى الأعمدة والصفوف المُحدَّثة—بدلاً من إعادة كتابة الأعمدة بالكامل كما يحدث في عمليات mutation التقليدية.
UPDATE posts SET AnswerCount = AnswerCount + 1 WHERE Id = 404346
يستخدم هذا النهج صياغة UPDATE القياسية، وينشئ الأجزاء التصحيحية فورًا من دون انتظار عمليات الدمج. تصبح القيم المحدَّثة مرئية فورًا في استعلامات SELECT عبر تطبيق التصحيحات، لكنها لا تُطبَّق ماديًا إلا أثناء عمليات الدمج اللاحقة. وهذا يجعل التحديثات خفيفة الوزن مثالية لتحديث نسبة صغيرة من الصفوف (حتى نحو 10% من الجدول) بزمن استجابة متوقَّع. وتُظهر اختبارات الأداء أنها قد تكون أسرع بما يصل إلى 23 مرة من الطفرات.لكن المقابل هو أن استعلامات SELECT تتحمّل كلفة إضافية عند تطبيق التصحيحات، كما تُحتسب الأجزاء التصحيحية ضمن حدود الأجزاء. وبعد تجاوز عتبة ~10%، تزداد الكلفة الإضافية الناتجة عن تطبيق التصحيحات عند القراءة بشكل متناسب، مما يجعل الطفرات المتزامنة أكثر كفاءة للتحديثات الأكبر.للمزيد: UPDATE خفيف الوزن
توفر الطفرات الفورية آليةً لتحديث الصفوف بحيث تُرجع استعلامات SELECT اللاحقة تلقائيًا القيم المعدّلة من دون انتظار المعالجة في الخلفية. ويعالج هذا عمليًا قصور الذرية في الطفرات العادية.
SET apply_mutations_on_fly = 1;SELECT ViewCount FROM posts WHERE Id = 404346
┌─ViewCount─┐│ 26762 │└───────────┘
-- Increment the countALTER TABLE posts UPDATE ViewCount = ViewCount + 1 WHERE Id = 404346-- The updated value is immediately visibleSELECT ViewCount FROM posts WHERE Id = 404346
┌─ViewCount─┐│ 26763 │└───────────┘
يتطلب كلٌّ من عملية mutation واستعلامات SELECT اللاحقة أن يكون الإعداد apply_mutations_on_fly = 1 مفعّلًا. تُخزَّن شروط mutation في ClickHouse Keeper، الذي يحتفظ بكل شيء في الذاكرة، وتُطبَّق آنيًا أثناء الاستعلامات.لاحظ أن mutation لا تزال تُستخدم لتحديث البيانات، لكنها فقط لا تُطبَّق ماديًا على الفور. وستظل mutation تُنفَّذ في الخلفية كعملية غير متزامنة، وتتحمل نفس الـ overhead المرتفع مثل mutation العادية. كما أن التعبيرات التي يمكن استخدامها مع هذه العملية محدودة أيضًا (راجع التفاصيل).
يجب استخدام on-the-fly mutations فقط لعدد صغير من العمليات—ربما بضع عشرات كحد أقصى. ويخزن Keeper الشروط في الذاكرة، لذا فإن الاستخدام المفرط يؤثر في استقرار العنقود. وقد يتسبب الحمل الكبير على Keeper في مهلات للجلسات تؤثر في جداول غير ذات صلة.
يلخّص الجدول التالي العبء الإضافي على أداء الاستعلامات استنادًا إلى اختبارات الأداء. وتُستخدم Mutations كخط أساس، لأن الاستعلامات تعمل بأقصى سرعة بمجرد اكتمالها وإعادة كتابة البيانات فعليًا.