Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

محرك الجدول CollapsingMergeTree

الوصف

يرث المحرك CollapsingMergeTree من MergeTree ويضيف منطقًا لاختزال الصفوف أثناء عملية الدمج. يقوم محرك الجدول CollapsingMergeTree بحذف (اختزال) أزواج من الصفوف بشكل غير متزامن إذا كانت جميع الحقول في مفتاح الفرز (ORDER BY) متطابقة، باستثناء الحقل الخاص Sign، الذي يمكن أن تكون قيمته 1 أو -1. ويُحتفَظ بالصفوف التي لا يقابلها صف ذو قيمة Sign معاكسة.

لمزيد من التفاصيل، راجع قسم Collapsing في الوثيقة.

المعلمات

جميع معلمات محرك الجدول هذا، باستثناء المعلمة Sign، لها المعنى نفسه كما في MergeTree.

  • Sign — الاسم المُعطى لعمود يحدد نوع الصف، بحيث تكون القيمة 1 صف «حالة»، وتكون القيمة -1 صف «إلغاء». النوع: Int8.

إنشاء جدول

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
)
ENGINE = CollapsingMergeTree(Sign)
[PARTITION BY expr]
[ORDER BY expr]
[SAMPLE BY expr]
[SETTINGS name=value, ...]
الطريقة المُهمَلة لإنشاء جدول
CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
    name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
    ...
)
ENGINE [=] CollapsingMergeTree(date-column [, sampling_expression], (primary, key), index_granularity, Sign)

Sign — الاسم المُعطى لعمود من نوع الصف، حيث تمثل القيمة 1 صف "حالة" وتمثل القيمة -1 صف "إلغاء". Int8.

  • للحصول على وصف لمعلمات الاستعلام، راجع وصف الاستعلام.
  • عند إنشاء جدول CollapsingMergeTree، تكون بنود الاستعلام نفسها مطلوبة، كما هو الحال عند إنشاء جدول MergeTree.

اختزال

البيانات

افترض حالة تحتاج فيها إلى حفظ بيانات تتغير باستمرار لكائن معيّن. قد يبدو من المنطقي أن يكون هناك صف واحد لكل كائن وتحديثه كلما تغيّر شيء ما، لكن عمليات التحديث مكلفة وبطيئة بالنسبة إلى نظام إدارة قواعد البيانات، لأنها تتطلب إعادة كتابة البيانات في التخزين. إذا كنا بحاجة إلى كتابة البيانات بسرعة، فإن إجراء أعداد كبيرة من عمليات التحديث ليس نهجًا مقبولًا، لكن يمكننا دائمًا كتابة تغييرات الكائن بشكل تسلسلي. وللقيام بذلك، نستخدم العمود الخاص Sign.

  • إذا كانت قيمة Sign = 1 فهذا يعني أن الصف هو صف "حالة": صف يحتوي على حقول تمثل الحالة الحالية الصالحة.
  • إذا كانت قيمة Sign = -1 فهذا يعني أن الصف هو صف "إلغاء": صف يُستخدم لإلغاء حالة كائن له السمات نفسها.

على سبيل المثال، نريد حساب عدد الصفحات التي تصفحها المستخدمون على موقع ويب ما، والمدة التي قضوها في زيارتها. وفي لحظة زمنية معيّنة، نكتب الصف التالي الذي يمثل حالة نشاط المستخدم:

┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         5 │      146 │    1 │
└─────────────────────┴───────────┴──────────┴──────┘

لاحقًا، نسجّل التغيّر في نشاط المستخدم وندوّنه في الصفّين التاليين:

┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         5 │      146 │   -1 │
│ 4324182021466249494 │         6 │      185 │    1 │
└─────────────────────┴───────────┴──────────┴──────┘

يلغي الصف الأول الحالة السابقة للكائن (الذي يمثّل مستخدمًا في هذه الحالة). يجب أن ينسخ جميع حقول مفتاح الفرز الخاصة بالصف "الملغى" باستثناء Sign. يحتوي الصف الثاني أعلاه على الحالة الحالية.

وبما أننا نحتاج فقط إلى آخر حالة لنشاط المستخدم، يمكن حذف صف "الحالة" الأصلي وصف "الإلغاء" الذي أدرجناه، كما هو موضح أدناه، مع اختزال الحالة غير الصالحة (القديمة) للكائن:

┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         5 │      146 │    1 │ -- old "state" row can be deleted
│ 4324182021466249494 │         5 │      146 │   -1 │ -- "cancel" row can be deleted
│ 4324182021466249494 │         6 │      185 │    1 │ -- new "state" row remains
└─────────────────────┴───────────┴──────────┴──────┘

ينفّذ CollapsingMergeTree سلوك الاختزال هذا تحديدًا أثناء دمج أجزاء البيانات.

خصائص هذا النهج

  1. يجب أن يتذكّر البرنامج الذي يكتب البيانات حالة الكائن حتى يتمكّن من إلغائها. ويجب أن يحتوي صف "إلغاء" على نُسخ من حقول مفتاح الفرز الخاصة بـ "حالة"، مع قيمة Sign معاكسة. يزيد هذا من الحجم الأولي للتخزين، لكنه يتيح كتابة البيانات بسرعة.
  2. تؤدي المصفوفات الطويلة والمتزايدة في الأعمدة إلى تقليل كفاءة المحرّك بسبب زيادة العبء عند الكتابة. وكلما كانت البيانات أبسط، ارتفعت الكفاءة.
  3. تعتمد نتائج SELECT بدرجة كبيرة على اتساق سجل تغييرات الكائن. توخَّ الدقة عند إعداد البيانات لإدراجها. فقد تحصل على نتائج غير متوقعة إذا كانت البيانات غير متسقة. على سبيل المثال، قيم سالبة لمقاييس غير سالبة مثل عمق الجلسة.

الخوارزمية

عندما يدمج ClickHouse أجزاء البيانات، تُختزل كل مجموعة من الصفوف المتتالية التي لها مفتاح الفرز نفسه (ORDER BY) إلى ما لا يزيد على صفّين: صف "حالة" حيث Sign = 1، وصف "إلغاء" حيث Sign = -1. وبعبارة أخرى، في ClickHouse تُختزل الإدخالات.

لكل جزء بيانات ناتج، يحفظ ClickHouse ما يلي:

1. أول صف "إلغاء" وآخر صف "حالة"، إذا كان عدد صفوف "حالة" و"إلغاء" متساويًا وكان الصف الأخير صف "حالة".
2. آخر صف "حالة"، إذا كان عدد صفوف "حالة" أكبر من عدد صفوف "إلغاء".
3. أول صف "إلغاء"، إذا كان عدد صفوف "إلغاء" أكبر من عدد صفوف "حالة".
4. لا يحفظ أي صف، في جميع الحالات الأخرى.

بالإضافة إلى ذلك، عندما يكون هناك صفّان إضافيان على الأقل من نوع "حالة" مقارنةً بصفوف "إلغاء"، أو صفّان إضافيان على الأقل من نوع "إلغاء" مقارنةً بصفوف "حالة"، تستمر عملية الدمج. ومع ذلك، يتعامل ClickHouse مع هذا الوضع على أنه خطأ منطقي ويسجّله في سجل الخادم. قد يحدث هذا الخطأ إذا أُدرجت البيانات نفسها أكثر من مرة. ولذلك، يجب ألا يغيّر الاختزال نتائج حساب الإحصاءات. وتُختزل التغييرات تدريجيًا بحيث لا تبقى في النهاية إلا الحالة الأخيرة لكل كائن تقريبًا.

العمود Sign مطلوب لأن خوارزمية الدمج لا تضمن أن تكون جميع الصفوف ذات مفتاح الفرز نفسه في جزء البيانات الناتج نفسه، أو حتى على الخادم الفعلي نفسه. يعالج ClickHouse استعلامات SELECT باستخدام خيوط تنفيذ متعددة، ولا يمكنه التنبؤ بترتيب الصفوف في النتيجة.

يلزم التجميع إذا كانت هناك حاجة إلى الحصول على بيانات "مختزلة" بالكامل من جدول CollapsingMergeTree. ولإتمام الاختزال، اكتب استعلامًا باستخدام عبارة GROUP BY ودوال التجميع التي تراعي الإشارة. فعلى سبيل المثال، لحساب العدد، استخدم sum(Sign) بدلًا من count(). ولحساب مجموع قيمة ما، استخدم sum(Sign * x) مع HAVING sum(Sign) > 0 بدلًا من sum(x) كما في المثال أدناه.

يمكن حساب الدوال التجميعية count وsum وavg بهذه الطريقة. ويمكن حساب الدالة التجميعية uniq إذا كان للكائن حالة واحدة غير مختزلة على الأقل. أما الدالتان التجميعيتان min وmax فلا يمكن حسابهما لأن CollapsingMergeTree لا يحفظ سجل الحالات المختزلة.

أمثلة

مثال للاستخدام

إذا كانت لدينا بيانات المثال التالية:

┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         5 │      146 │    1 │
│ 4324182021466249494 │         5 │      146 │   -1 │
│ 4324182021466249494 │         6 │      185 │    1 │
└─────────────────────┴───────────┴──────────┴──────┘

لنُنشئ جدول UAct باستخدام CollapsingMergeTree:

CREATE TABLE UAct
(
    UserID UInt64,
    PageViews UInt8,
    Duration UInt8,
    Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID

بعد ذلك، سنُدرِج بعض البيانات:

INSERT INTO UAct VALUES (4324182021466249494, 5, 146, 1)
INSERT INTO UAct VALUES (4324182021466249494, 5, 146, -1),(4324182021466249494, 6, 185, 1)

نستخدم استعلامَي INSERT لإنشاء جزأَي بيانات مختلفين.

يمكننا الاستعلام عن البيانات باستخدام:

SELECT * FROM UAct
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         5 │      146 │   -1 │
│ 4324182021466249494 │         6 │      185 │    1 │
└─────────────────────┴───────────┴──────────┴──────┘
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         5 │      146 │    1 │
└─────────────────────┴───────────┴──────────┴──────┘

لنلقِ نظرة على البيانات المُعادة أعلاه ولنرَ ما إذا كان الاختزال قد حدث… باستخدام استعلامَي INSERT، أنشأنا جزأين من البيانات. نُفِّذ استعلام SELECT على خيطَي تنفيذ، وحصلنا على ترتيب عشوائي للصفوف. ومع ذلك، لم يحدث الاختزال لأنه لم يكن هناك أي دمج لأجزاء البيانات بعد، كما أن ClickHouse يدمج أجزاء البيانات في الخلفية في وقت غير معلوم لا يمكننا التنبؤ به.

لذلك نحتاج إلى تجميع، ونُجري ذلك باستخدام دالة التجميع sum وعبارة HAVING:

SELECT
    UserID,
    sum(PageViews * Sign) AS PageViews,
    sum(Duration * Sign) AS Duration
FROM UAct
GROUP BY UserID
HAVING sum(Sign) > 0
┌──────────────UserID─┬─PageViews─┬─Duration─┐
│ 4324182021466249494 │         6 │      185 │
└─────────────────────┴───────────┴──────────┘

إذا لم نكن بحاجة إلى التجميع وأردنا فرض عملية الاختزال، فيمكننا أيضًا استخدام المُعدِّل FINAL مع عبارة FROM.

SELECT * FROM UAct FINAL
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         6 │      185 │    1 │
└─────────────────────┴───────────┴──────────┴──────┘

مثال على نهج آخر

تقوم فكرة هذا النهج على أن عمليات الدمج لا تأخذ في الحسبان سوى حقول المفتاح. لذلك يمكننا، في الصف "إلغاء"، تحديد قيم سالبة تلغي أثر الإصدار السابق من الصف عند الجمع من دون استخدام العمود Sign.

في هذا المثال، سنستخدم بيانات العينة الواردة أدناه:

┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         5 │      146 │    1 │
│ 4324182021466249494 │        -5 │     -146 │   -1 │
│ 4324182021466249494 │         6 │      185 │    1 │
└─────────────────────┴───────────┴──────────┴──────┘

في هذا النهج، يلزم تغيير أنواع البيانات للحقلين PageViews وDuration بحيث يمكن تخزين القيم السالبة. لذلك نغيّر نوعَي هذين العمودين من UInt8 إلى Int16 عند إنشاء الجدول UAct باستخدام collapsingMergeTree:

CREATE TABLE UAct
(
    UserID UInt64,
    PageViews Int16,
    Duration Int16,
    Sign Int8
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY UserID

لنختبر هذا النهج عبر إدخال البيانات في جدولنا.

ومع ذلك، يُعد ذلك مقبولًا في الأمثلة أو مع الجداول الصغيرة:

INSERT INTO UAct VALUES(4324182021466249494,  5,  146,  1);
INSERT INTO UAct VALUES(4324182021466249494, -5, -146, -1);
INSERT INTO UAct VALUES(4324182021466249494,  6,  185,  1);

SELECT * FROM UAct FINAL;
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         6 │      185 │    1 │
└─────────────────────┴───────────┴──────────┴──────┘
SELECT
    UserID,
    sum(PageViews) AS PageViews,
    sum(Duration) AS Duration
FROM UAct
GROUP BY UserID
┌──────────────UserID─┬─PageViews─┬─Duration─┐
│ 4324182021466249494 │         6 │      185 │
└─────────────────────┴───────────┴──────────┘
SELECT COUNT() FROM UAct
┌─count()─┐
│       3 │
└─────────┘
OPTIMIZE TABLE UAct FINAL;

SELECT * FROM UAct
┌──────────────UserID─┬─PageViews─┬─Duration─┬─Sign─┐
│ 4324182021466249494 │         6 │      185 │    1 │
└─────────────────────┴───────────┴──────────┴──────┘
Navigation