Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

محرك الجدول MergeTree

يُعدّ المحرّك MergeTree وغيره من محركات عائلة MergeTree (مثل ReplacingMergeTree و AggregatingMergeTree) من أكثر محركات الجداول استخدامًا وأكثرها متانة في ClickHouse.

صُمِّمت محركات الجداول من عائلة MergeTree لدعم معدلات عالية من إدخال البيانات وأحجام بيانات هائلة. وتُنشئ عمليات الإدخال أجزاءً للجدول، ثم تدمجها عملية تعمل في الخلفية مع أجزاء جدول أخرى.

الميزات الرئيسية لمحركات الجداول من عائلة MergeTree.

  • يحدّد المفتاح الأساسي للجدول ترتيب الفرز داخل كل جزء من الجدول (فهرس عنقودي). كما أن المفتاح الأساسي لا يشير إلى صفوف فردية، بل إلى كتل من 8192 صفًا تُسمى حبيبات. وهذا يجعل المفاتيح الأساسية لمجموعات البيانات الضخمة صغيرة بما يكفي لتبقى محمّلة في الذاكرة الرئيسية، مع استمرار توفير وصول سريع إلى البيانات المخزّنة على القرص.

  • يمكن تقسيم الجداول باستخدام تعبير تقسيم عشوائي. ويضمن تقسيم pruning استبعاد الأقسام من القراءة عندما يتيح الاستعلام ذلك.

  • يمكن نسخ البيانات عبر عدة عقد في cluster لتحقيق التوفّر العالي، وfailover، والترقيات من دون downtime. راجع نسخ البيانات.

  • تدعم محركات الجداول MergeTree أنواعًا مختلفة من الإحصاءات وطرائق أخذ العينات للمساعدة في تحسين الاستعلامات.

إنشاء الجداول

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
    name1 [type1] [[NOT] NULL] [DEFAULT|MATERIALIZED|ALIAS|EPHEMERAL expr1] [COMMENT ...] [CODEC(codec1)] [STATISTICS(stat1)] [TTL expr1] [PRIMARY KEY] [SETTINGS (name = value, ...)],
    name2 [type2] [[NOT] NULL] [DEFAULT|MATERIALIZED|ALIAS|EPHEMERAL expr2] [COMMENT ...] [CODEC(codec2)] [STATISTICS(stat2)] [TTL expr2] [PRIMARY KEY] [SETTINGS (name = value, ...)],
    ...
    INDEX index_name1 expr1 TYPE type1(...) [GRANULARITY value1],
    INDEX index_name2 expr2 TYPE type2(...) [GRANULARITY value2],
    ...
    PROJECTION projection_name_1 (SELECT <COLUMN LIST EXPR> [GROUP BY] [ORDER BY]),
    PROJECTION projection_name_2 (SELECT <COLUMN LIST EXPR> [GROUP BY] [ORDER BY])
) ENGINE = MergeTree()
ORDER BY expr
[PARTITION BY expr]
[PRIMARY KEY expr]
[SAMPLE BY expr]
[TTL expr
    [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx' [, ...] ]
    [WHERE conditions]
    [GROUP BY key_expr [SET v1 = aggr_func(v1) [, v2 = aggr_func(v2) ...]] ] ]
[SETTINGS name = value, ...]

للاطلاع على وصف مفصل للمعلمات، راجع تعليمة CREATE TABLE

عبارات الاستعلام

ENGINE

ENGINE — اسم المحرّك ومعلماته. ENGINE = MergeTree(). لا يحتوي محرّك MergeTree على أي معلمات.

ORDER BY

ORDER BY — مفتاح الفرز.

Tuple من أسماء الأعمدة أو تعبيرات عشوائية. مثال: ORDER BY (CounterID + 1, EventDate).

إذا لم يكن هناك مفتاح أساسي مُعرّف (أي لم يتم تحديد PRIMARY KEY)، يستخدم ClickHouse مفتاح الفرز بوصفه المفتاح الأساسي.

إذا لم تكن هناك حاجة إلى الفرز، يمكنك استخدام الصياغة ORDER BY tuple(). وبدلاً من ذلك، إذا كان الإعداد create_table_empty_primary_key_by_default مفعّلًا، فستُضاف ORDER BY () ضمنيًا إلى عبارات CREATE TABLE. راجع Selecting a Primary Key.

PARTITION BY

PARTITION BYمفتاح التقسيم. وهو اختياري. في معظم الحالات، لا تحتاج إلى مفتاح تقسيم، وحتى إذا احتجت إلى التقسيم، فغالبًا لا تحتاج إلى مفتاح تقسيم أكثر دقة من التقسيم حسب الشهر. لا يسرّع التقسيم الاستعلامات (على عكس تعبير ORDER BY). يجب ألّا تستخدم مطلقًا تقسيمًا مفرط الدقة. لا تقسّم بياناتك حسب معرّفات العملاء أو الأسماء (وعوضًا عن ذلك، اجعل معرّف العميل أو الاسم هو العمود الأول في تعبير ORDER BY).

للتقسيم حسب الشهر، استخدم التعبير toYYYYMM(date_column)، حيث إن date_column هو عمود يحتوي على تاريخ من النوع Date. وتكون أسماء الأقسام هنا بالتنسيق "YYYYMM".

PRIMARY KEY

PRIMARY KEY — المفتاح الأساسي إذا كان يختلف عن مفتاح الفرز. وهو اختياري.

إن تحديد مفتاح فرز (باستخدام عبارة ORDER BY) يعني ضمنيًا تحديد مفتاح أساسي. وعادةً لا تكون هناك حاجة إلى تحديد المفتاح الأساسي إلى جانب مفتاح الفرز.

SAMPLE BY

SAMPLE BY — تعبير لأخذ العينات. اختياري.

إذا تم تحديده، فيجب أن يكون ضمن المفتاح الأساسي. ويجب أن يُنتج تعبير أخذ العينات عددًا صحيحًا غير موقّع.

مثال: SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID)).

TTL

TTL — قائمة بالقواعد التي تحدد مدة الاحتفاظ بالصفوف ومنطق نقل الأجزاء تلقائيًا بين الأقراص ووحدات التخزين. اختياري.

يجب أن يُرجِع التعبير قيمة من النوع Date أو DateTime، مثل TTL date + INTERVAL 1 DAY.

يحدد نوع القاعدة DELETE|TO DISK 'xxx'|TO VOLUME 'xxx'|GROUP BY الإجراء الذي يجب تنفيذه على الجزء عند تحقق التعبير (أي عند بلوغه الوقت الحالي): حذف الصفوف منتهية الصلاحية، أو نقل جزء (إذا تحقق التعبير لجميع الصفوف في الجزء) إلى القرص المحدد (TO DISK 'xxx') أو إلى وحدة التخزين (TO VOLUME 'xxx')، أو تجميع القيم في الصفوف منتهية الصلاحية. النوع الافتراضي للقاعدة هو الحذف (DELETE). يمكن تحديد قائمة تضم عدة قواعد، لكن يجب ألا تكون هناك أكثر من قاعدة DELETE واحدة.

لمزيد من التفاصيل، راجع TTL للأعمدة والجداول

الإعدادات

انظر إعدادات MergeTree.

مثال على إعداد Sections

ENGINE MergeTree() PARTITION BY toYYYYMM(EventDate) ORDER BY (CounterID, EventDate, intHash32(UserID)) SAMPLE BY intHash32(UserID) SETTINGS index_granularity=8192

في المثال، نضبط التقسيم حسب الشهر.

كما نضبط تعبيرًا لأخذ العينات على هيئة hash استنادًا إلى معرّف المستخدم. يتيح لك ذلك توزيع البيانات في الجدول توزيعًا شبه عشوائي لكل من CounterID وEventDate. وإذا حدّدت عبارة SAMPLE عند اختيار البيانات، فسيُرجع ClickHouse عيّنة بيانات شبه عشوائية ومتوازنة لمجموعة فرعية من المستخدمين.

يمكن حذف إعداد index_granularity لأن 8192 هي القيمة الافتراضية.

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

معلمات MergeTree()

  • date-column — اسم عمود من النوع Date. ينشئ ClickHouse الأقسام تلقائيًا حسب الشهر استنادًا إلى هذا العمود. وتكون أسماء الأقسام بالتنسيق "YYYYMM".
  • sampling_expression — تعبير لأخذ العينات.
  • (primary, key) — المفتاح الأساسي. النوع: Tuple()
  • index_granularity — دقة الفهرس. وهو عدد صفوف البيانات بين "العلامات" في الفهرس. القيمة 8192 مناسبة لمعظم المهام.

مثال

MergeTree(EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID)), 8192)

يُهيَّأ محرّك MergeTree بالطريقة نفسها المستخدَمة في المثال أعلاه ضمن طريقة تهيئة المحرّك الرئيسية.

تخزين البيانات

يتكوّن الجدول من أجزاء بيانات مرتّبة حسب المفتاح الأساسي.

عند إدراج البيانات في جدول، تُنشأ أجزاء بيانات منفصلة، ويُرتَّب كل جزء منها ترتيبًا معجميًا حسب المفتاح الأساسي. على سبيل المثال، إذا كان المفتاح الأساسي هو (CounterID, Date)، فإن البيانات داخل الجزء تُرتَّب حسب CounterID، وضمن كل CounterID تُرتَّب حسب Date.

تُفصل البيانات التابعة لتقسيمات مختلفة في أجزاء مختلفة. وفي الخلفية، يدمج ClickHouse أجزاء البيانات لتحقيق تخزين أكثر كفاءة. ولا تُدمج الأجزاء التابعة لتقسيمات مختلفة. كما أن آلية الدمج لا تضمن وجود جميع الصفوف التي لها المفتاح الأساسي نفسه ضمن جزء البيانات نفسه.

يمكن تخزين أجزاء البيانات بتنسيق Wide أو Compact. ففي تنسيق Wide يُخزَّن كل عمود في ملف منفصل ضمن نظام الملفات، أما في تنسيق Compact فتُخزَّن جميع الأعمدة في ملف واحد. ويمكن استخدام تنسيق Compact لتحسين أداء عمليات الإدراج الصغيرة والمتكررة.

يتحكم في تنسيق تخزين البيانات الإعدادان min_bytes_for_wide_part وmin_rows_for_wide_part الخاصان بمحرك الجدول. فإذا كان عدد البايتات أو الصفوف في جزء البيانات أقل من قيمة الإعداد المقابل، يُخزَّن الجزء بتنسيق Compact. وإلا فيُخزَّن بتنسيق Wide. وإذا لم يُضبط أيّ من هذين الإعدادين، فتُخزَّن أجزاء البيانات بتنسيق Wide.

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

يُقيَّد حجم الحبيبة بالإعدادين index_granularity وindex_granularity_bytes الخاصين بمحرك الجدول. ويقع عدد الصفوف في الحبيبة ضمن النطاق [1, index_granularity]، وذلك بحسب حجم الصفوف. وقد يتجاوز حجم الحبيبة index_granularity_bytes إذا كان حجم صف واحد أكبر من قيمة الإعداد. وفي هذه الحالة، يكون حجم الحبيبة مساويًا لحجم الصف.

المفاتيح الأساسية والفهارس في الاستعلامات

خذ المفتاح الأساسي (CounterID, Date) على سبيل المثال. في هذه الحالة، يمكن توضيح الفرز والفهرس كما يلي:

Whole data:     [---------------------------------------------]
CounterID:      [aaaaaaaaaaaaaaaaaabbbbcdeeeeeeeeeeeeefgggggggghhhhhhhhhiiiiiiiiikllllllll]
Date:           [1111111222222233331233211111222222333211111112122222223111112223311122333]
Marks:           |      |      |      |      |      |      |      |      |      |      |
                a,1    a,2    a,3    b,3    e,2    e,3    g,1    h,2    i,1    i,3    l,3
Marks numbers:   0      1      2      3      4      5      6      7      8      9      10

إذا كان استعلام البيانات يحدد ما يلي:

  • CounterID in ('a', 'h')، يقرأ الخادم البيانات ضمن نطاقات العلامات [0, 3) و[6, 8).
  • CounterID IN ('a', 'h') AND Date = 3، يقرأ الخادم البيانات ضمن نطاقات العلامات [1, 3) و[7, 8).
  • Date = 3، يقرأ الخادم البيانات ضمن نطاق العلامات [1, 10].

تُظهر الأمثلة أعلاه أن استخدام فهرس يكون دائمًا أكثر فعالية من مسح كامل.

يسمح الفهرس المتناثر بقراءة بيانات إضافية. عند قراءة نطاق واحد من المفتاح الأساسي، يمكن قراءة ما يصل إلى index_granularity * 2 من الصفوف الإضافية في كل كتلة بيانات.

تتيح لك الفهارس المتناثرة العمل مع عدد كبير جدًا من صفوف الجدول، لأن مثل هذه الفهارس تتسع في معظم الحالات داخل RAM الخاصة بالحاسوب.

لا يتطلب ClickHouse مفتاحًا أساسيًا فريدًا. يمكنك إدراج عدة صفوف بالمفتاح الأساسي نفسه.

يمكنك استخدام تعبيرات من النوع Nullable في عبارتي PRIMARY KEY وORDER BY، ولكن لا يُنصح بذلك بشدة. للسماح بهذه الميزة، فعِّل الإعداد allow_nullable_key. وينطبق مبدأ NULLS_LAST على قيم NULL في عبارة ORDER BY.

اختيار مفتاح أساسي

عدد الأعمدة في المفتاح الأساسي غير محدود صراحةً. وبحسب بنية البيانات، يمكنك تضمين عدد أكبر أو أقل من الأعمدة في المفتاح الأساسي. وقد يترتب على ذلك ما يلي:

  • تحسين أداء الفهرس.

    إذا كان المفتاح الأساسي هو (a, b)، فإن إضافة عمود آخر c ستحسّن الأداء إذا تحققت الشروط التالية:

    • وجود استعلامات تتضمن شرطًا على العمود c.
    • شيوع نطاقات بيانات طويلة (أطول عدة مرات من index_granularity) ذات القيم المتطابقة لـ (a, b). وبعبارة أخرى، عندما تتيح لك إضافة عمود آخر تخطي نطاقات بيانات طويلة نسبيًا.
  • تحسين ضغط البيانات.

    يرتّب ClickHouse البيانات حسب المفتاح الأساسي، لذا كلما زاد التجانس كان الضغط أفضل.

  • توفير منطق إضافي عند دمج أجزاء البيانات في المحرّكين CollapsingMergeTree وSummingMergeTree.

    في هذه الحالة، من المنطقي تحديد مفتاح فرز يختلف عن المفتاح الأساسي.

يؤثر المفتاح الأساسي الطويل سلبًا في أداء الإدراج واستهلاك الذاكرة، لكن الأعمدة الإضافية في المفتاح الأساسي لا تؤثر في أداء ClickHouse أثناء استعلامات SELECT.

يمكنك إنشاء جدول من دون مفتاح أساسي باستخدام الصياغة ORDER BY tuple(). في هذه الحالة، يخزّن ClickHouse البيانات بترتيب إدراجها. وإذا كنت تريد الحفاظ على ترتيب البيانات عند إدراجها بواسطة استعلامات INSERT ... SELECT، فاضبط max_insert_threads = 1.

لاختيار البيانات بالترتيب الأصلي، استخدم استعلامات SELECT أحادية الخيط.

اختيار مفتاح أساسي يختلف عن مفتاح الفرز

يمكن تحديد مفتاح أساسي (تعبير بقيم تُكتب في ملف الفهرس لكل علامة) يختلف عن مفتاح الفرز (تعبير لفرز الصفوف في أجزاء البيانات). في هذه الحالة، يجب أن تكون مجموعة تعبيرات المفتاح الأساسي بادئةً لمجموعة تعبيرات مفتاح الفرز.

تكون هذه الميزة مفيدة عند استخدام محركات الجداول SummingMergeTree و AggregatingMergeTree. في الحالة الشائعة عند استخدام هذه المحركات، يحتوي الجدول على نوعين من الأعمدة: الأبعاد والمقاييس. عادةً ما تُجمِّع الاستعلامات قيم أعمدة المقاييس باستخدام GROUP BY عشوائي، مع التصفية بحسب الأبعاد. ولأن SummingMergeTree وAggregatingMergeTree يدمجان الصفوف التي لها القيمة نفسها لمفتاح الفرز، فمن الطبيعي إضافة جميع الأبعاد إليه. ونتيجةً لذلك، يتكوّن تعبير المفتاح من قائمة طويلة من الأعمدة، ويجب تحديث هذه القائمة باستمرار عند إضافة أبعاد جديدة.

في هذه الحالة، من المنطقي الإبقاء على بضعة أعمدة فقط في المفتاح الأساسي لتوفير مسح نطاقات بكفاءة، وإضافة بقية أعمدة الأبعاد إلى مجموعة مفتاح الفرز.

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

استخدام الفهارس والتقسيمات في الاستعلامات

بالنسبة إلى استعلامات SELECT، يحلّل ClickHouse ما إذا كان يمكن استخدام فهرس. ويمكن استخدام الفهرس إذا كانت عبارة WHERE/PREWHERE تتضمن تعبيرًا (إما كأحد عناصر الاقتران أو بالكامل) يمثّل عملية مقارنة مساواة أو عدم مساواة، أو إذا كانت تتضمن IN أو LIKE مع بادئة ثابتة على أعمدة أو تعبيرات موجودة في المفتاح الأساسي أو مفتاح التقسيم، أو على بعض الدوال الجزئية التكرار لهذه الأعمدة، أو على العلاقات المنطقية بين هذه التعبيرات.

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

لنلقِ نظرة على الـ محرّك المُعَدّ كما يلي:

ENGINE MergeTree()
PARTITION BY toYYYYMM(EventDate)
ORDER BY (CounterID, EventDate)
SETTINGS index_granularity=8192

في هذه الحالة، ضمن الاستعلامات:

SELECT count() FROM table
WHERE EventDate = toDate(now())
AND CounterID = 34

SELECT count() FROM table
WHERE EventDate = toDate(now())
AND (CounterID = 34 OR CounterID = 42)

SELECT count() FROM table
WHERE ((EventDate >= toDate('2014-01-01')
AND EventDate <= toDate('2014-01-31')) OR EventDate = toDate('2014-05-01'))
AND CounterID IN (101500, 731962, 160656)
AND (CounterID = 101500 OR EventDate != toDate('2014-05-01'))

سيستخدم ClickHouse فهرس المفتاح الأساسي لاستبعاد البيانات غير ذات الصلة، ومفتاح التقسيم الشهري لاستبعاد الأقسام الواقعة ضمن نطاقات تاريخ غير ذات صلة.

تُظهر الاستعلامات أعلاه أن الفهرس يُستخدم حتى مع التعبيرات المعقدة. وتُنظَّم القراءة من الجدول بحيث لا يمكن أن يكون استخدام الفهرس أبطأ من مسح كامل.

في المثال أدناه، لا يمكن استخدام الفهرس.

SELECT count() FROM table WHERE CounterID = 34 OR URL LIKE '%upyachka%'

للتحقق مما إذا كان ClickHouse يستطيع استخدام الفهرس عند تنفيذ استعلام، استخدم الإعدادين force_index_by_date وforce_primary_key.

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

استخدام الفهرس مع التعبيرات الحتمية في المفاتيح الأساسية

يمكن أن يحتوي المفتاح الأساسي على تعبيرات، لا على أسماء الأعمدة فقط. ولا تقتصر هذه التعبيرات على سلاسل دوال بسيطة، بل يمكن أن تكون أشجار تعبيرات كيفما كانت (مثل الدوال المتداخلة والتعبيرات المركبة)، ما دامت حتمية.

يُعدّ التعبير حتميًا إذا كان يعيد دائمًا النتيجة نفسها لقيم الإدخال نفسها (على سبيل المثال: length(), toDate(), lower(), left(), cityHash64(), toUUID()؛ بخلاف now() أو rand()). وإذا كان المفتاح الأساسي يحتوي على تعبيرات حتمية، يمكن لـ ClickHouse تطبيقها على القيم الثابتة في الاستعلام واستخدام النتيجة لبناء شروط على فهرس المفتاح الأساسي. وهذا يتيح تخطّي البيانات لعبارات الشرط مثل =, IN, و has.

ومن حالات الاستخدام الشائعة إبقاء المفتاح الأساسي مضغوطًا (مثل تخزين قيمة hash بدلًا من String طويل)، مع الاستمرار في إتاحة استخدام الفهرس لعبارات الشرط على العمود الأصلي.

مثال على مفتاح أساسي حتمي (لكن غير حقني):

ENGINE = MergeTree()
ORDER BY length(user_id)

أمثلة على شروط التصفية التي يمكن أن تستخدم الفهرس:

SELECT * FROM table WHERE user_id = 'alice';
SELECT * FROM table WHERE user_id IN ('alice', 'bob');
SELECT * FROM table WHERE has(['alice', 'bob'], user_id);

في هذه الحالات، يحسب ClickHouse قيمة length('alice') (وسائر الثوابت) مرةً واحدة، ثم يستخدم قيم الأطوال لتضييق النطاقات في فهرس المفتاح الأساسي. وبما أن طول السلسلة ليس حقنيًا، فقد تتشارك سلاسل user_id المختلفة الطول نفسه، لذلك قد يقرأ الفهرس حبيبات إضافية (مطابقات إيجابية كاذبة). ومع ذلك، تظل النتيجة صحيحة لأن الشرط الأصلي (user_id = ... وIN وما إلى ذلك) يظل مطبَّقًا بعد القراءة.

إذا كان التعبير الحتمي أيضًا حقنيًا (أي إن المدخلات المختلفة لا يمكن أن تنتج المخرج نفسه لأنواع argument المستخدمة)، فبوسع ClickHouse أيضًا استخدام الفهرس بفاعلية مع الصيغ المنفية: != وNOT IN وNOT has(...). على سبيل المثال، الدالتان reverse(p) وhex(p) حقنيتان بالنسبة إلى String.

مثال على مفتاح أساسي حقني:

ENGINE = MergeTree()
ORDER BY hex(p)

كما تُدعَم أيضًا التعبيرات الحقنية الأكثر تعقيدًا، على سبيل المثال:

ENGINE = MergeTree()
ORDER BY reverse(tuple(reverse(p), hex(p)))

أمثلة على الشروط التي يمكن أن تستخدم الفهرس:

SELECT * FROM table WHERE p != 'abc';
SELECT * FROM table WHERE p NOT IN ('abc', '12345');
SELECT * FROM table WHERE NOT has(['abc', '12345'], p);

استخدام الفهرس مع المفاتيح الأساسية الرتيبة جزئيًا

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

لا يمكن لـ ClickHouse استخدام الفهرس إذا كانت قيم المفتاح الأساسي ضمن نطاق معلمات الاستعلام لا تمثل تسلسلاً رتيبًا. في هذه الحالة، يستخدم ClickHouse أسلوب المسح الكامل.

يستخدم ClickHouse هذا المنطق ليس فقط مع تسلسلات أيام الشهر، بل مع أي مفتاح أساسي يمثّل تسلسلاً رتيبًا جزئيًا.

فهارس تخطي البيانات

يرد تعريف الفهرس في قسم الأعمدة ضمن استعلام CREATE.

INDEX index_name expr TYPE type(...) [GRANULARITY granularity_value]

بالنسبة إلى الجداول من عائلة *MergeTree، يمكن تحديد فهارس تخطي البيانات.

تجمع هذه الفهارس بعض المعلومات عن التعبير المحدد على مستوى الكتل، التي تتكوّن من granularity_value من الحبيبات (ويُحدَّد حجم الحبيبة باستخدام الإعداد index_granularity في محرك الجدول). ثم تُستخدم هذه القيم المجمّعة في استعلامات SELECT لتقليل كمية البيانات المطلوب قراءتها من القرص، وذلك عبر تخطي كتل كبيرة من البيانات التي لا يمكن لعبارة where أن تستوفي الشرط فيها.

يمكن حذف العبارة GRANULARITY، والقيمة الافتراضية لـ granularity_value هي 1.

مثال

CREATE TABLE table_name
(
    u64 UInt64,
    i32 Int32,
    s String,
    ...
    INDEX idx1 u64 TYPE bloom_filter GRANULARITY 3,
    INDEX idx2 u64 * i32 TYPE minmax GRANULARITY 3,
    INDEX idx3 u64 * length(s) TYPE set(1000) GRANULARITY 4
) ENGINE = MergeTree()
...

يمكن لـ ClickHouse استخدام الفهارس الواردة في المثال لتقليل حجم البيانات المقروءة من القرص في الاستعلامات التالية:

SELECT count() FROM table WHERE u64 == 10;
SELECT count() FROM table WHERE u64 * i32 >= 1234
SELECT count() FROM table WHERE u64 * length(s) == 1234

يمكن أيضًا إنشاء فهارس لتخطي البيانات على الأعمدة المركبة:

-- on columns of type Map:
INDEX map_key_index mapKeys(map_column) TYPE bloom_filter
INDEX map_value_index mapValues(map_column) TYPE bloom_filter

-- on columns of type JSON:
INDEX json_paths_index JSONAllPaths(json_column) TYPE bloom_filter

-- on columns of type Tuple:
INDEX tuple_1_index tuple_column.1 TYPE bloom_filter
INDEX tuple_2_index tuple_column.2 TYPE bloom_filter

-- on columns of type Nested:
INDEX nested_1_index col.nested_col1 TYPE bloom_filter
INDEX nested_2_index col.nested_col2 TYPE bloom_filter

أنواع فهارس التخطي

يدعم محرك الجدول MergeTree الأنواع التالية من فهارس التخطي. لمزيد من المعلومات حول كيفية استخدام فهارس التخطي لتحسين الأداء، راجِع "فهم فهارس تخطي البيانات في ClickHouse".

فهرس التخطي MinMax

لكل حبيبة فهرس، تُخزَّن القيمتان الصغرى والكبرى لتعبير ما. (إذا كان التعبير من النوع tuple، فإنه يخزّن القيمتين الصغرى والكبرى لكل عنصر من عناصر الـ tuple.)

Syntaxtext
minmax

Set

يُخزَّن لكل كتلة فهرس ما يصل إلى max_rows من القيم الفريدة للتعبير المحدد. ويعني max_rows = 0 "تخزين جميع القيم الفريدة".

Syntaxtext
set(max_rows)

مرشح Bloom

يُخزَّن مرشح Bloom لكل حبيبة فهرس للأعمدة المحددة.

Syntaxtext
bloom_filter([false_positive_rate])

يمكن أن تأخذ المعلمة false_positive_rate قيمة بين 0 و1 (القيمة الافتراضية: 0.025) وتحدّد احتمال توليد نتيجة إيجابية، مما يزيد كمية البيانات المقروءة.

أنواع البيانات التالية مدعومة:

  • (U)Int*
  • Float*
  • Enum
  • Date
  • DateTime
  • String
  • FixedString
  • Array
  • LowCardinality
  • Nullable
  • UUID
  • Map

مرشح Bloom لـ N-gram (مهمل)

يُخزِّن كل حبيبة فهرس مرشح Bloom لـ n-grams الخاصة بالأعمدة المحددة.

Syntaxtext
ngrambf_v1(n, size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed)
Parameter Description
n حجم ngram
size_of_bloom_filter_in_bytes حجم مرشح bloom بالبايتات. يمكنك استخدام قيمة كبيرة هنا، على سبيل المثال 256 أو 512، لأنه يمكن ضغطه بكفاءة).
number_of_hash_functions عدد دوال hash المستخدمة في مرشح bloom.
random_seed البذرة لدوال hash الخاصة بمرشح bloom.

لا يعمل هذا الفهرس إلا مع أنواع البيانات التالية:

لتقدير معلمات ngrambf_v1، يمكنك استخدام الدوال المعرّفة من قبل المستخدم (UDFs) التالية.

UDFs for ngrambf_v1sql
CREATE FUNCTION bfEstimateFunctions [ON CLUSTER cluster]
AS
(total_number_of_all_grams, size_of_bloom_filter_in_bits) -> round((size_of_bloom_filter_in_bits / total_number_of_all_grams) * log(2));

CREATE FUNCTION bfEstimateBmSize [ON CLUSTER cluster]
AS
(total_number_of_all_grams, probability_of_false_positives) -> ceil((total_number_of_all_grams * log(probability_of_false_positives)) / log(1 / pow(2, log(2))));

CREATE FUNCTION bfEstimateFalsePositive [ON CLUSTER cluster]
AS
(total_number_of_all_grams, number_of_hash_functions, size_of_bloom_filter_in_bytes) -> pow(1 - exp(-number_of_hash_functions/ (size_of_bloom_filter_in_bytes / total_number_of_all_grams)), number_of_hash_functions);

CREATE FUNCTION bfEstimateGramNumber [ON CLUSTER cluster]
AS
(number_of_hash_functions, probability_of_false_positives, size_of_bloom_filter_in_bytes) -> ceil(size_of_bloom_filter_in_bytes / (-number_of_hash_functions / log(1 - exp(log(probability_of_false_positives) / number_of_hash_functions))))

لاستخدام هذه الدوال، تحتاج إلى تحديد معلَمتين على الأقل:

  • total_number_of_all_grams
  • probability_of_false_positives

على سبيل المثال، إذا كان هناك 4300 ngrams في الـ حبيبة، وكنت تتوقع أن تكون الإيجابيات الكاذبة أقل من 0.0001، فيمكن بعد ذلك تقدير المعلَمات الأخرى بتنفيذ الاستعلامات التالية:

--- estimate number of bits in the filter
SELECT bfEstimateBmSize(4300, 0.0001) / 8 AS size_of_bloom_filter_in_bytes;

وبالطبع، يمكنك أيضًا استخدام تلك الدوال لتقدير المعلمات الخاصة بشروط أخرى. وتستند الدوال المذكورة أعلاه إلى حاسبة bloom filter هنا.

مرشح bloom للرموز

Syntaxtext
tokenbf_v1(size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed)

مرشح Bloom لـ sparse grams

مرشح Bloom لـ sparse grams مشابه لـ ngrambf_v1، لكنه يستخدم رموز sparse grams بدلًا من ngrams.

Syntaxtext
sparse_grams(min_ngram_length, max_ngram_length, min_cutoff_length, size_of_bloom_filter_in_bytes, number_of_hash_functions, random_seed)

فهرس نصي

ينشئ فهرسًا معكوسًا على بيانات نصية مُجزّأة إلى رموز، مما يتيح بحثًا نصيًا كاملًا بكفاءة وبشكل حتمي. راجع هنا لمزيد من التفاصيل.

تشابه المتجهات

يدعم البحث التقريبي عن أقرب الجيران، راجع هنا للاطلاع على التفاصيل.

دعم الدوال

تتضمن الشروط في عبارة WHERE استدعاءات لدوال تتعامل مع الأعمدة. وإذا كان العمود جزءًا من فهرس، يحاول ClickHouse استخدام هذا الفهرس عند تطبيق هذه الدوال. يدعم ClickHouse مجموعات فرعية مختلفة من الدوال التي يمكن معها استخدام الفهارس.

يمكن استخدام الفهارس من النوع set مع جميع الدوال. أما أنواع الفهارس الأخرى، فهي مدعومة على النحو التالي:

الدالة (المعامل) / الفهرس المفتاح الأساسي minmax ngrambf_v1 tokenbf_v1 bloom_filter sparse_grams text
equals (=, ==)
notEquals(!=, <>)
like
notLike
match
startsWith
endsWith
multiSearchAny
multiSearchAnyUTF8
multiMatchAny
in
notIn
less (<)
greater (>)
lessOrEquals (<=)
greaterOrEquals (>=)
empty
notEmpty
has
hasAny
hasAll
hasToken
hasTokenOrNull
hasTokenCaseInsensitive (*)
hasTokenCaseInsensitiveOrNull (*)
hasAnyTokens
hasAllTokens
pointInPolygon
mapContains (mapContainsKey)
mapContainsKeyLike
mapContainsValue
mapContainsValueLike

لا يمكن لـ ngrambf_v1 استخدام الدوال التي تحتوي على وسيطة ثابتة أصغر من حجم ngram لتحسين الاستعلامات.

(*) لكي تكون hasTokenCaseInsensitive وhasTokenCaseInsensitiveOrNull فعّالتين، يجب إنشاء الفهرس tokenbf_v1 على بيانات محوّلة إلى أحرف صغيرة، على سبيل المثال INDEX idx (lower(str_col)) TYPE tokenbf_v1(512, 3, 0).

الإسقاطات

تشبه الإسقاطات العروض المُجسَّدة، لكنها تُعرَّف على مستوى الجزء. وهي توفّر ضمانات للاتساق إلى جانب استخدامها تلقائيًا في الاستعلامات.

لا تُدعَم الإسقاطات في عبارات SELECT التي تستخدم المُعدِّل FINAL.

استعلام الإسقاط

استعلام الإسقاط هو ما يعرّف الإسقاط. وهو يختار البيانات ضمنيًا من الجدول الأصلي. البنية

SELECT <column list expr> [GROUP BY] <group keys expr> [ORDER BY] <expr>

يمكن تعديل الإسقاطات أو حذفها باستخدام تعليمة ALTER.

فهارس الإسقاط

تُوسّع فهارس الإسقاط النظام الفرعي للإسقاط عبر توفير طريقة خفيفة وواضحة لتعريف فهارس على مستوى الإسقاط. ومن الناحية الظاهرية، يظل فهرس الإسقاط إسقاطًا، ولكن بصياغة أبسط وغرض أوضح: إذ يعرّف تعبيرًا مخصصًا للتصفية، بدلًا من تقديم بيانات مُحتسَبة ومخزَّنة مسبقًا. أما داخليًا، فلا يقوم فهرس الإسقاط بتمثيل الجدول الأصلي فعليًا بترتيب صفوف مُبدَّل كما يفعل الإسقاط العادي. وبدلًا من ذلك، يُخزَّن هذا التبديل في صورة عمود رقمي للتبديل _part_offset، أي SELECT _part_offset ORDER BY <index_expr>.

الصياغة

PROJECTION <name> INDEX <index_expr> TYPE <index_type>

مثال:

CREATE TABLE example
(
    id UInt64,
    region String,
    user_id UInt32,
    PROJECTION region_proj INDEX region TYPE basic,
    PROJECTION uid_proj INDEX user_id TYPE basic
)
ENGINE = MergeTree
ORDER BY id;

أنواع الفهارس

الأنواع المدعومة حاليًا:

  • basic: يعادل فهرس MergeTree عاديًا على التعبير.

يوفّر هذا الإطار إمكانية إضافة المزيد من أنواع الفهارس مستقبلًا.

تخزين الإسقاطات

تُخزَّن الإسقاطات داخل دليل الجزء. وهي تشبه الفهرس، لكنها تتضمن دليلًا فرعيًا يخزّن جزءًا من جدول MergeTree مجهول الاسم. ويُشتق هذا الجدول من استعلام تعريف الإسقاط. إذا وُجد بند GROUP BY، يصبح محرك التخزين الأساسي هو AggregatingMergeTree، وتُحوَّل جميع الدوال التجميعية إلى AggregateFunction. وإذا وُجد بند ORDER BY، يستخدمه جدول MergeTree كتعبير المفتاح الأساسي. وأثناء عملية الدمج، يُدمَج جزء الإسقاط عبر إجراء الدمج الخاص بالتخزين التابع له. ويُدمَج المجموع الاختباري لجزء الجدول الأصل مع جزء الإسقاط. أما مهام الصيانة الأخرى فهي مشابهة لفهارس التخطي.

تحليل الاستعلام

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

الوصول المتزامن إلى البيانات

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

تُنفَّذ القراءة من الجدول بالتوازي تلقائيًا.

TTL للأعمدة والجداول

يحدّد مدة بقاء القيم.

يمكن تعيين عبارة TTL للجدول بأكمله ولكل عمود على حدة. ويمكن أيضًا لـ TTL على مستوى الجدول تحديد آلية نقل البيانات تلقائيًا بين الأقراص ووحدات التخزين، أو إعادة ضغط الأجزاء التي انتهت صلاحية جميع البيانات فيها.

يجب أن ينتج عن تقييم التعبيرات نوع البيانات Date أو Date32 أو DateTime أو DateTime64.

البنية

تعيين مدة بقاء لعمود:

TTL time_column
TTL time_column + interval

لتعريف interval، استخدم معاملات الفواصل الزمنية، على سبيل المثال:

TTL date_time + INTERVAL 1 MONTH
TTL date_time + INTERVAL 15 HOUR

TTL للعمود

عندما تنتهي صلاحية القيم في العمود، يستبدلها ClickHouse بالقيم الافتراضية لنوع بيانات العمود. وإذا انتهت صلاحية جميع قيم العمود في جزء البيانات، يحذف ClickHouse هذا العمود من جزء البيانات على نظام الملفات.

لا يمكن استخدام عبارة TTL مع الأعمدة المفتاحية.

أمثلة

إنشاء جدول باستخدام TTL:

CREATE TABLE tab
(
    d DateTime,
    a Int TTL d + INTERVAL 1 MONTH,
    b Int TTL d + INTERVAL 1 MONTH,
    c String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d;

إضافة TTL إلى عمود في جدول موجود

ALTER TABLE tab
    MODIFY COLUMN
    c String TTL d + INTERVAL 1 DAY;

تعديل TTL للعمود

ALTER TABLE tab
    MODIFY COLUMN
    c String TTL d + INTERVAL 1 MONTH;

TTL للجدول

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

TTL expr
    [DELETE|RECOMPRESS codec_name1|TO DISK 'xxx'|TO VOLUME 'xxx'][, DELETE|RECOMPRESS codec_name2|TO DISK 'aaa'|TO VOLUME 'bbb'] ...
    [WHERE conditions]
    [GROUP BY key_expr [SET v1 = aggr_func(v1) [, v2 = aggr_func(v2) ...]] ]

قد يأتي بعد كل تعبير TTL نوعٌ من قواعد TTL. ويحدد ذلك الإجراء الذي يجب تنفيذه عند تحقق التعبير (أي عند وصوله إلى الوقت الحالي):

  • DELETE - حذف الصفوف منتهية الصلاحية (الإجراء الافتراضي)؛
  • RECOMPRESS codec_name - إعادة ضغط جزء البيانات باستخدام codec_name؛
  • TO DISK 'aaa' - نقل الجزء إلى القرص aaa؛
  • TO VOLUME 'bbb' - نقل الجزء إلى القرص bbb؛
  • GROUP BY - تجميع الصفوف منتهية الصلاحية.

يمكن استخدام الإجراء DELETE مع عبارة WHERE لحذف بعض الصفوف منتهية الصلاحية فقط استنادًا إلى شرط تصفية:

TTL time_column + INTERVAL 1 MONTH DELETE WHERE column = 'value'

يجب أن يكون تعبير GROUP BY بادئةً للمفتاح الأساسي للجدول.

إذا لم يكن العمود جزءًا من تعبير GROUP BY ولم تُعيَّن له قيمة صراحةً في العبارة SET، فسيحتوي الصف الناتج على قيمة عشوائية من الصفوف المجمّعة (كما لو طُبِّقت عليه الدالة التجميعية any).

أمثلة

إنشاء جدول مع TTL:

CREATE TABLE tab
(
    d DateTime,
    a Int
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d
TTL d + INTERVAL 1 MONTH DELETE,
    d + INTERVAL 1 WEEK TO VOLUME 'aaa',
    d + INTERVAL 2 WEEK TO DISK 'bbb';

تعديل TTL للجدول:

ALTER TABLE tab
    MODIFY TTL d + INTERVAL 1 DAY;

إنشاء جدول، حيث تنتهي صلاحية الصفوف بعد شهر واحد. وتُحذف الصفوف منتهية الصلاحية التي توافق تواريخها يوم الاثنين:

CREATE TABLE table_with_where
(
    d DateTime,
    a Int
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(d)
ORDER BY d
TTL d + INTERVAL 1 MONTH DELETE WHERE toDayOfWeek(d) = 1;

إنشاء جدول يُعاد فيه ضغط الصفوف منتهية الصلاحية:

CREATE TABLE table_for_recompression
(
    d DateTime,
    key UInt64,
    value String
) ENGINE MergeTree()
ORDER BY tuple()
PARTITION BY key
TTL d + INTERVAL 1 MONTH RECOMPRESS CODEC(ZSTD(17)), d + INTERVAL 1 YEAR RECOMPRESS CODEC(LZ4HC(10))
SETTINGS min_rows_for_wide_part = 0, min_bytes_for_wide_part = 0;

إنشاء جدول تُجمَّع فيه الصفوف منتهية الصلاحية. في صفوف النتيجة، تحتوي x على القيمة العظمى عبر الصفوف المجمَّعة، وy — على القيمة الصغرى، وd — على أي قيمة عشوائية من الصفوف المجمَّعة.

CREATE TABLE table_for_aggregation
(
    d DateTime,
    k1 Int,
    k2 Int,
    x Int,
    y Int
)
ENGINE = MergeTree
ORDER BY (k1, k2)
TTL d + INTERVAL 1 MONTH GROUP BY k1, k2 SET x = max(x), y = min(y);

إزالة البيانات منتهية الصلاحية

تُزال البيانات التي انتهت صلاحية TTL الخاصة بها عندما يدمج ClickHouse أجزاء البيانات.

عندما يكتشف ClickHouse أن البيانات منتهية الصلاحية، يُجري عملية دمج خارج الجدول الزمني. وللتحكم في وتيرة عمليات الدمج هذه، يمكنك تعيين merge_with_ttl_timeout. وإذا كانت القيمة منخفضة جدًا، فسينفّذ العديد من عمليات الدمج خارج الجدول الزمني، ما قد يستهلك قدرًا كبيرًا من الموارد.

إذا نفّذت استعلام SELECT بين عمليات الدمج، فقد تحصل على بيانات منتهية الصلاحية. ولتجنب ذلك، استخدم استعلام OPTIMIZE قبل SELECT.

انظر أيضًا

أنواع الأقراص

بالإضافة إلى أجهزة التخزين الكتلية المحلية، يدعم ClickHouse أنواع التخزين التالية:

استخدام عدة أجهزة تخزين كتلية لتخزين البيانات

المقدمة

يمكن لمحركات الجداول من عائلة MergeTree تخزين البيانات على عدة أجهزة تخزين كتلية. على سبيل المثال، قد يكون ذلك مفيدًا عندما تُقسَّم بيانات جدول معيّن ضمنيًا إلى بيانات "ساخنة" وأخرى "باردة". تُطلَب أحدث البيانات بانتظام، لكنها لا تتطلب سوى مساحة صغيرة. في المقابل، نادرًا ما تُطلَب البيانات التاريخية الممتدة على مدى زمني طويل. وإذا توفرت عدة أقراص، فيمكن وضع البيانات "الساخنة" على أقراص سريعة (مثل أقراص NVMe SSD أو الذاكرة)، بينما تُحفَظ البيانات "الباردة" على أقراص أبطأ نسبيًا (مثل HDD).

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

يُعد جزء البيانات أصغر وحدة قابلة للنقل في الجداول التي تستخدم المحرك MergeTree. وتُخزَّن البيانات التابعة لجزء واحد على قرص واحد. ويمكن نقل أجزاء البيانات بين الأقراص في الخلفية (وفقًا لإعدادات المستخدم)، وكذلك باستخدام استعلامات ALTER.

المصطلحات

  • القرص — جهاز تخزين كتلي مُركَّب على نظام الملفات.
  • القرص الافتراضي — القرص الذي يخزّن المسار المحدد في إعداد الخادم path.
  • وحدة التخزين — مجموعة مرتبة من أقراص متماثلة (مشابهة لـ JBOD).
  • سياسة التخزين — مجموعة من وحدات التخزين والقواعد الخاصة بنقل البيانات بينها.

يمكن العثور على الأسماء المخصَّصة للكيانات الموصوفة في جداول النظام system.storage_policies وsystem.disks. لتطبيق إحدى سياسات التخزين المُعدّة على جدول، استخدم الإعداد storage_policy في جداول عائلة المحرك MergeTree.

التكوين

يجب تعريف الأقراص ووحدات التخزين وسياسات التخزين داخل الوسم <storage_configuration>، سواء في ملف ضمن الدليل config.d.

بنية التكوين:

<storage_configuration>
    <disks>
        <disk_name_1> <!-- disk name -->
            <path>/mnt/fast_ssd/clickhouse/</path>
        </disk_name_1>
        <disk_name_2>
            <path>/mnt/hdd1/clickhouse/</path>
            <keep_free_space_bytes>10485760</keep_free_space_bytes>
        </disk_name_2>
        <disk_name_3>
            <path>/mnt/hdd2/clickhouse/</path>
            <keep_free_space_bytes>10485760</keep_free_space_bytes>
        </disk_name_3>

        ...
    </disks>

    ...
</storage_configuration>

الوسوم:

  • <disk_name_N> — اسم القرص. يجب أن تختلف الأسماء بين جميع الأقراص.
  • path — المسار الذي سيخزّن الخادم البيانات تحته (مجلدا data وshadow)، ويجب أن ينتهي بـ '/'.
  • keep_free_space_bytes — مقدار مساحة القرص الحرة التي يجب الاحتفاظ بها.

ترتيب تعريف القرص غير مهم.

بنية إعداد سياسات التخزين:

<storage_configuration>
    ...
    <policies>
        <policy_name_1>
            <volumes>
                <volume_name_1>
                    <disk>disk_name_from_disks_configuration</disk>
                    <max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
                    <load_balancing>round_robin</load_balancing>
                </volume_name_1>
                <volume_name_2>
                    <!-- configuration -->
                </volume_name_2>
                <!-- more volumes -->
            </volumes>
            <move_factor>0.2</move_factor>
        </policy_name_1>
        <policy_name_2>
            <!-- configuration -->
        </policy_name_2>

        <!-- more policies -->
    </policies>
    ...
</storage_configuration>

الوسوم:

  • policy_name_N — اسم السياسة. يجب أن تكون أسماء السياسات فريدة.
  • volume_name_N — اسم وحدة التخزين. يجب أن تكون أسماء وحدات التخزين فريدة.
  • disk — قرص ضمن وحدة تخزين.
  • max_data_part_size_bytes — الحد الأقصى لحجم الجزء الذي يمكن تخزينه على أيٍّ من أقراص وحدة التخزين. إذا كان الحجم التقديري لجزء مدموج أكبر من max_data_part_size_bytes، فسيُكتب هذا الجزء في وحدة التخزين التالية. تتيح هذه الميزة أساسًا الاحتفاظ بالأجزاء الجديدة/الصغيرة على وحدة تخزين سريعة (SSD) ثم نقلها إلى وحدة تخزين بطيئة (HDD) عندما يصبح حجمها كبيرًا. لا تستخدم هذا الإعداد إذا كانت سياستك تحتوي على وحدة تخزين واحدة فقط.
  • move_factor — عندما تنخفض المساحة المتاحة عن هذه النسبة، تبدأ البيانات تلقائيًا بالانتقال إلى وحدة التخزين التالية إن وُجدت (القيمة الافتراضية 0.1). يرتّب ClickHouse الأجزاء الموجودة حسب الحجم من الأكبر إلى الأصغر (ترتيبًا تنازليًا)، ثم يحدّد الأجزاء التي يكفي حجمها الإجمالي لتحقيق شرط move_factor. وإذا لم يكن الحجم الإجمالي لجميع الأجزاء كافيًا، فستنقل جميع الأجزاء.
  • perform_ttl_move_on_insert — يعطّل TTL move عند تنفيذ INSERT لجزء بيانات. افتراضيًا (إذا كان مُمكّنًا)، إذا أدرجنا جزء بيانات انتهت صلاحيته بالفعل وفق قاعدة TTL move، فإنه ينتقل فورًا إلى وحدة التخزين/القرص المعرَّف في قاعدة النقل. وقد يؤدي ذلك إلى إبطاء عملية INSERT بشكل كبير إذا كانت وحدة التخزين/القرص الوجهة بطيئة (مثل S3). وإذا كان معطّلًا، فسيُكتب جزء البيانات المنتهي الصلاحية أولًا في وحدة التخزين الافتراضية، ثم يُنقل مباشرة بعد ذلك إلى وحدة تخزين TTL.
  • load_balancing - سياسة موازنة الأقراص، round_robin أو least_used.
  • least_used_ttl_ms - يضبط مهلة التحديث (بالملي ثانية) للمساحة المتاحة على جميع الأقراص (0 - التحديث دائمًا، -1 - عدم التحديث مطلقًا، والقيمة الافتراضية هي 60000). لاحظ أنه إذا كان القرص مستخدمًا من ClickHouse فقط وغير خاضع لتوسيع/تقليص نظام الملفات أثناء التشغيل، فيمكنك استخدام -1. أما في جميع الحالات الأخرى فلا يُنصح بذلك، لأنه سيؤدي في النهاية إلى توزيع غير صحيح للمساحة.
  • prefer_not_to_merge — يجب ألا تستخدم هذا الإعداد. فهو يعطّل دمج أجزاء البيانات على وحدة التخزين هذه (وهذا ضار ويؤدي إلى تراجع الأداء). عند تمكين هذا الإعداد (لا تفعل ذلك)، لا يعود دمج البيانات على وحدة التخزين هذه مسموحًا به (وهذا أمر سيئ). يتيح هذا الإعداد (لكنك لا تحتاج إليه) التحكّم (وإذا كنت تريد التحكّم في شيء هنا، فأنت ترتكب خطأً) في كيفية تعامل ClickHouse مع الأقراص البطيئة (لكن ClickHouse يعرف الأفضل، لذا يُرجى عدم استخدام هذا الإعداد).
  • volume_priority — يحدّد الأولوية (الترتيب) الذي تُملأ به وحدات التخزين. القيمة الأقل تعني أولوية أعلى. يجب أن تكون قيم المعلَمة أعدادًا طبيعية، وأن تغطي مجتمعةً النطاق من 1 إلى N (بحيث تُمنح أقل أولوية) من دون تخطي أي أرقام.
    • إذا كانت جميع وحدات التخزين موسومة، فستُرتَّب أولوياتها وفق الترتيب المحدد.
    • إذا كانت بعض وحدات التخزين فقط موسومة، فستكون الوحدات غير الموسومة ذات الأولوية الأدنى، وتُرتَّب حسب ترتيب تعريفها في config.
    • إذا لم تكن أي من وحدات التخزين موسومة، فتُحدَّد أولويتها وفق ترتيب التصريح بها في التهيئة.
    • لا يمكن أن تكون لوحدتي تخزين قيمة الأولوية نفسها.

أمثلة على التهيئة:

<storage_configuration>
    ...
    <policies>
        <hdd_in_order> <!-- policy name -->
            <volumes>
                <single> <!-- volume name -->
                    <disk>disk1</disk>
                    <disk>disk2</disk>
                </single>
            </volumes>
        </hdd_in_order>

        <moving_from_ssd_to_hdd>
            <volumes>
                <hot>
                    <disk>fast_ssd</disk>
                    <max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
                </hot>
                <cold>
                    <disk>disk1</disk>
                </cold>
            </volumes>
            <move_factor>0.2</move_factor>
        </moving_from_ssd_to_hdd>

        <small_jbod_with_external_no_merges>
            <volumes>
                <main>
                    <disk>jbod1</disk>
                </main>
                <external>
                    <disk>external</disk>
                </external>
            </volumes>
        </small_jbod_with_external_no_merges>
    </policies>
    ...
</storage_configuration>

في المثال المذكور، تنفّذ سياسة التخزين hdd_in_order أسلوب التوزيع بالتناوب. لذلك لا تحدد هذه السياسة سوى وحدة تخزين واحدة (single)، وتُخزَّن أجزاء البيانات على جميع أقراصها بالتتابع الدوري. يمكن أن تكون هذه السياسة مفيدة جدًا إذا كانت هناك عدة أقراص متشابهة مركّبة على النظام، ولكن RAID غير مهيأ. ضع في اعتبارك أن كل قرص على حدة غير موثوق، وقد تحتاج إلى تعويض ذلك بعامل replication مقداره 3 أو أكثر.

إذا كانت هناك أنواع مختلفة من الأقراص متاحة في النظام، فيمكن استخدام سياسة moving_from_ssd_to_hdd بدلًا من ذلك. تتكوّن وحدة التخزين hot من قرص SSD (fast_ssd)، ويبلغ الحد الأقصى لحجم جزء يمكن تخزينه على هذه الوحدة 1GB. ستُخزَّن جميع الأجزاء التي يزيد حجمها على 1GB مباشرةً على وحدة التخزين cold، التي تحتوي على قرص HDD باسم disk1. كذلك، بمجرد أن تتجاوز نسبة امتلاء القرص fast_ssd 80%، ستُنقل البيانات إلى disk1 بواسطة عملية تعمل في الخلفية.

يُعد ترتيب سرد وحدات التخزين داخل سياسة التخزين مهمًا إذا كانت واحدة على الأقل من وحدات التخزين المدرجة لا تحتوي على المعامل volume_priority بشكل صريح. وبمجرد امتلاء إحدى وحدات التخزين بشكل زائد، تُنقل البيانات إلى الوحدة التالية. ويُعد ترتيب سرد الأقراص مهمًا أيضًا لأن البيانات تُخزَّن عليها بالتناوب.

عند إنشاء table، يمكن تطبيق إحدى سياسات التخزين المهيأة عليها:

CREATE TABLE table_with_non_default_policy (
    EventDate Date,
    OrderID UInt64,
    BannerID UInt64,
    SearchPhrase String
) ENGINE = MergeTree
ORDER BY (OrderID, BannerID)
PARTITION BY toYYYYMM(EventDate)
SETTINGS storage_policy = 'moving_from_ssd_to_hdd'

تعني سياسة التخزين default استخدام وحدة تخزين واحدة فقط، تتكوّن من قرص واحد فقط محدَّد في <path>. يمكنك تغيير سياسة التخزين بعد إنشاء الجدول باستخدام استعلام [ALTER TABLE … MODIFY SETTING]، ويجب أن تتضمن السياسة الجديدة جميع الأقراص ووحدات التخزين القديمة نفسها وبالأسماء نفسها.

يمكن تغيير عدد الخيوط التي تنفّذ عمليات نقل أجزاء البيانات في الخلفية عبر الإعداد background_move_pool_size.

التفاصيل

في حالة جداول MergeTree، تصل البيانات إلى القرص بطرق مختلفة:

  • نتيجةً لعملية insert (استعلام INSERT).
  • أثناء عمليات الدمج في الخلفية والطفرات.
  • عند التنزيل من نسخة متماثلة أخرى.
  • نتيجةً لتجميد القسم ALTER TABLE … FREEZE PARTITION.

في جميع هذه الحالات، باستثناء الطفرات وتجميد القسم، يُخزَّن الجزء على وحدة تخزين وقرص وفقًا لسياسة التخزين المحددة:

  1. تُختار أول وحدة تخزين (بحسب ترتيب تعريفها) تتوفر فيها مساحة قرص كافية لتخزين جزء (unreserved_space > current_part_size) وتسمح بتخزين أجزاء بهذا الحجم (max_data_part_size_bytes > current_part_size).
  2. ضمن وحدة التخزين هذه، يُختار القرص الذي يلي القرص المستخدم لتخزين الجزء السابق من البيانات، والذي تكون فيه المساحة الحرة أكبر من حجم الجزء (unreserved_space - keep_free_space_bytes > current_part_size).

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

في الخلفية، تُنقَل الأجزاء بين وحدات التخزين استنادًا إلى مقدار المساحة الحرة (المعامل move_factor) وفقًا لترتيب تعريف وحدات التخزين في ملف الإعدادات. ولا تُنقَل البيانات مطلقًا من الأخيرة إلى الأولى. يمكن استخدام جداول النظام system.part_log (الحقل type = MOVE_PART) وsystem.parts (الحقلان path وdisk) لمراقبة عمليات النقل في الخلفية. كما يمكن العثور على معلومات تفصيلية في سجلات الخادم.

يمكن للمستخدم فرض نقل جزء أو قسم من وحدة تخزين إلى أخرى باستخدام الاستعلام ALTER TABLE … MOVE PART|PARTITION … TO VOLUME|DISK …، مع مراعاة جميع القيود الخاصة بالعمليات التي تعمل في الخلفية. يبدأ الاستعلام عملية النقل بنفسه ولا ينتظر اكتمال العمليات في الخلفية. سيتلقى المستخدم رسالة خطأ إذا لم تتوفر مساحة حرة كافية أو إذا لم تتحقق أي من الشروط المطلوبة.

لا يتداخل نقل البيانات مع تكرار البيانات. لذلك، يمكن تحديد سياسات تخزين مختلفة للجدول نفسه على نسخ متماثلة مختلفة.

بعد اكتمال عمليات الدمج في الخلفية والطفرات، لا تُزال الأجزاء القديمة إلا بعد مرور مدة زمنية معينة (old_parts_lifetime). وخلال هذه المدة، لا تُنقَل إلى وحدات تخزين أو أقراص أخرى. لذلك، وحتى تُزال الأجزاء نهائيًا، فإنها تظل محسوبة عند تقييم مساحة القرص المشغولة.

يمكن للمستخدم إسناد الأجزاء الكبيرة الجديدة إلى أقراص مختلفة ضمن وحدة تخزين من نوع JBOD بطريقة متوازنة باستخدام الإعداد min_bytes_to_rebalance_partition_over_jbod.

استخدام التخزين الخارجي لتخزين البيانات

يمكن لمحركات الجداول من عائلة MergeTree تخزين البيانات في S3 وAzureBlobStorage وHDFS باستخدام قرص من النوع s3 أو azure_blob_storage أو hdfs على الترتيب. راجع إعداد خيارات التخزين الخارجي لمزيد من التفاصيل.

مثال على استخدام S3 كتخزين خارجي باستخدام قرص من النوع s3.

صيغة التكوين:

<storage_configuration>
    ...
    <disks>
        <s3>
            <type>s3</type>
            <support_batch_delete>true</support_batch_delete>
            <endpoint>https://clickhouse-public-datasets.s3.amazonaws.com/my-bucket/root-path/</endpoint>
            <access_key_id>your_access_key_id</access_key_id>
            <secret_access_key>your_secret_access_key</secret_access_key>
            <region></region>
            <header>Authorization: Bearer SOME-TOKEN</header>
            <server_side_encryption_customer_key_base64>your_base64_encoded_customer_key</server_side_encryption_customer_key_base64>
            <server_side_encryption_kms_key_id>your_kms_key_id</server_side_encryption_kms_key_id>
            <server_side_encryption_kms_encryption_context>your_kms_encryption_context</server_side_encryption_kms_encryption_context>
            <server_side_encryption_kms_bucket_key_enabled>true</server_side_encryption_kms_bucket_key_enabled>
            <proxy>
                <uri>http://proxy1</uri>
                <uri>http://proxy2</uri>
            </proxy>
            <connect_timeout_ms>10000</connect_timeout_ms>
            <request_timeout_ms>5000</request_timeout_ms>
            <retry_attempts>10</retry_attempts>
            <single_read_retries>4</single_read_retries>
            <min_bytes_for_seek>1000</min_bytes_for_seek>
            <metadata_path>/var/lib/clickhouse/disks/s3/</metadata_path>
            <skip_access_check>false</skip_access_check>
        </s3>
        <s3_cache>
            <type>cache</type>
            <disk>s3</disk>
            <path>/var/lib/clickhouse/disks/s3_cache/</path>
            <max_size>10Gi</max_size>
        </s3_cache>
    </disks>
    ...
</storage_configuration>

راجع أيضًا تهيئة خيارات التخزين الخارجي.

استخدام أقراص S3 مع وحدات تخزين متعددة

يمكن استخدام أقراص S3 (وغيرها من أقراص التخزين الكائني) في سياسات التخزين متعددة الأقراص ومتعددة الوحدات بالطريقة نفسها المستخدمة مع الأقراص المحلية. يتيح لك ذلك توزيع البيانات عبر عدة حاويات S3 ضمن وحدة تخزين واحدة (على نمط JBOD)، أو إعداد سياسات تخزين متعددة الطبقات باستخدام وحدات S3.

على سبيل المثال، لتوزيع البيانات عبر حاويتَي S3 بأسلوب round-robin:

<storage_configuration>
    <disks>
        <s3_bucket1>
            <type>s3</type>
            <endpoint>https://s3.amazonaws.com/bucket-1/data/</endpoint>
            <access_key_id>your_access_key_id</access_key_id>
            <secret_access_key>your_secret_access_key</secret_access_key>
        </s3_bucket1>
        <s3_bucket2>
            <type>s3</type>
            <endpoint>https://s3.amazonaws.com/bucket-2/data/</endpoint>
            <access_key_id>your_access_key_id</access_key_id>
            <secret_access_key>your_secret_access_key</secret_access_key>
        </s3_bucket2>
    </disks>
    <policies>
        <s3_multi_bucket>
            <volumes>
                <main>
                    <disk>s3_bucket1</disk>
                    <disk>s3_bucket2</disk>
                </main>
            </volumes>
        </s3_multi_bucket>
    </policies>
</storage_configuration>

يمكنك أيضًا الجمع بين وحدات التخزين المحلية ووحدات تخزين S3 ضمن سياسة تخزين متدرجة، على سبيل المثال نقل البيانات من قرص SSD محلي إلى S3 كلما تقادمت:

<storage_configuration>
    <disks>
        <local_ssd>
            <path>/mnt/fast_ssd/clickhouse/</path>
        </local_ssd>
        <s3_cold>
            <type>s3</type>
            <endpoint>https://s3.amazonaws.com/cold-storage/data/</endpoint>
            <access_key_id>your_access_key_id</access_key_id>
            <secret_access_key>your_secret_access_key</secret_access_key>
        </s3_cold>
    </disks>
    <policies>
        <local_to_s3>
            <volumes>
                <hot>
                    <disk>local_ssd</disk>
                    <max_data_part_size_bytes>1073741824</max_data_part_size_bytes>
                </hot>
                <cold>
                    <disk>s3_cold</disk>
                </cold>
            </volumes>
            <move_factor>0.2</move_factor>
        </local_to_s3>
    </policies>
</storage_configuration>

يمكن إعداد جداول MergeTree غير المكررة وفق سيناريو كاتب واحد وعدة قرّاء على وحدة تخزين مشتركة. ويتحقق ذلك عبر التحديث التلقائي لقائمة الأجزاء، الذي يمكن إعداده على القرّاء. لاحظ أن ذلك يتطلب بيانات تعريف نظام ملفات مشتركة عبر النسخ المتماثلة (أو table_disk = true مع قرص محلي على مستوى الجدول). راجع refresh_parts_interval and table_disk.

الأعمدة الافتراضية

  • _part — اسم الجزء.
  • _part_index — الفهرس التسلسلي للجزء في نتيجة الاستعلام.
  • _part_starting_offset — صف البداية التراكمي للجزء في نتيجة الاستعلام.
  • _part_offset — رقم الصف في الجزء.
  • _part_granule_offset — رقم الـ granule في الجزء.
  • _partition_id — اسم القسم.
  • _part_uuid — المعرّف الفريد للجزء (إذا كان إعداد MergeTree ‏assign_part_uuids مفعّلًا).
  • _part_data_version — إصدار بيانات الجزء (إما الحد الأدنى لرقم block أو إصدار mutation).
  • _partition_value — القيم (Tuple) الخاصة بتعبير partition by.
  • _sample_factor — عامل العينة (من الاستعلام).
  • _block_number — الرقم الأصلي للـ block الخاص بالصف الذي أُسنِد عند insert، ويُحفَظ عبر merges عندما يكون الإعداد enable_block_number_column مفعّلًا.
  • _block_offset — الرقم الأصلي للصف داخل block الذي أُسنِد عند insert، ويُحفَظ عبر merges عندما يكون الإعداد enable_block_offset_column مفعّلًا.
  • _disk_name — اسم الـ قرص المستخدم للتخزين.

إحصاءات الأعمدة

يرد تعريف الإحصاءات في قسم الأعمدة ضمن استعلام CREATE للجداول التابعة لعائلة *MergeTree*:

CREATE TABLE tab
(
    a Int64 STATISTICS(tdigest, uniq),
    b Float64
)
ENGINE = MergeTree
ORDER BY a

يمكننا أيضًا تعديل الإحصاءات باستخدام تعليمات ALTER:

ALTER TABLE tab ADD STATISTICS b TYPE tdigest, uniq;
ALTER TABLE tab DROP STATISTICS a;

توفّر هذه الإحصاءات خفيفة الوزن معلومات مجمّعة عن توزيع القيم في الأعمدة. وتُخزَّن الإحصاءات في كل جزء وتُحدَّث مع كل عملية إدراج. ولا يمكن استخدامها لتحسين PREWHERE إلا إذا فعّلنا set use_statistics = 1.

تقليم الأجزاء باستخدام الإحصاءات

عند تمكين use_statistics_for_part_pruning، يمكن استخدام الإحصاءات في تقليم الأجزاء. حاليًا، لا يدعم تقليم الأجزاء إلا إحصاءات basic (وإحصاءات minmax المهملة). وعند تعريف مثل هذه الإحصاءات لعمود، يتتبّع ClickHouse القيمتَين الصغرى والكبرى لذلك العمود في كل جزء. يتيح تقليم الأجزاء تخطي قراءة أجزاء البيانات كاملةً عندما يتعذر على شرط التصفية في الاستعلام مطابقة أي صفوف في ذلك الجزء.

مثال:

-- Create a table with basic statistics on the 'value' column
CREATE TABLE test_stats
(
    id UInt64,
    value Int64 STATISTICS(basic)
)
ENGINE = MergeTree
ORDER BY id;

SYSTEM STOP MERGES test_stats;

-- Insert data in separate inserts to create multiple parts
INSERT INTO test_stats SELECT number, number FROM numbers(1000); -- Part 1: value range [0, 999]
INSERT INTO test_stats SELECT number, number + 10000 FROM numbers(1000); -- Part 2: value range [10000, 10999]

SET use_statistics_for_part_pruning = 1;

-- This query will skip Part 1 entirely because its max value (999) < 5000
SELECT count() FROM test_stats WHERE value > 5000;

-- Use EXPLAIN to see the pruning effect
EXPLAIN indexes = 1 SELECT count() FROM test_stats WHERE value > 5000;
-- The output will show "Parts: 1/2" indicating one part was pruned

الأنواع المتاحة من إحصاءات الأعمدة

  • basic

    حزمة مضغوطة من الملخصات ذات القيمة الواحدة المستمدة من عمود. وبحسب نوع العمود، تُعبَّأ العناصر التالية:

    • لأي عمود: عدد الصفوف التي تساوي القيمة الافتراضية للنوع (0 للأعداد الصحيحة والأعداد فاصلة عائمة، و'' لـ String، و[] لـ Array، وNULL لـ Nullable، إلخ)، مما يتيح للمُحسِّن تقدير مسندات col = <default> وعوامل التصفية IS NULL؛

    • لأي عمود تُمثَّل قيمه كأعداد (أعداد صحيحة، أعداد فاصلة عائمة، Decimal*، Date*، DateTime*، Enum*، IPv4، …): القيمة الدنيا والقيمة القصوى، مما يتيح تقدير انتقائية عوامل تصفية النطاق وتمكين تقليم الأجزاء؛

    • لأعمدة String وFixedString: إجمالي طول القيم غير NULL بالبايت (الذي يمكن اشتقاق متوسط طول السلسلة منه).

      يمكن لإحصائية basic واحدة أن تعبّئ عدة عناصر من هذه في الوقت نفسه — فعلى سبيل المثال، في عمود Nullable(UInt32)، تتتبّع كلاً من الحد الأدنى/الأقصى الرقمي وعدد قيم NULL. ولأن عدد القيم الافتراضية مُعرَّف لكل نوع من أنواع الأعمدة، يمكن تعريف basic على أي عمود، بما في ذلك الأنواع المركبة مثل Array وTuple وMap.

  • minmax (مهمل)

  • tdigest

هياكل TDigest التقريبية التي تتيح حساب النسب المئوية التقريبية (مثل المئين التسعين) للأعمدة الرقمية.

  • uniq

    هياكل BJKST التقريبية التي توفّر تقديراً لعدد القيم المميزة التي يحتويها العمود. وتستخدم داخلياً uniq.

  • uniq_v2

    مشابه لـ uniq، لكنه يستخدم داخلياً uniqCombined(12) (وهو أحد أشكال HyperLogLog). يستهلك ذاكرة أقل من uniq ويمكن بناؤه بسرعة أكبر.

  • countmin

هياكل CountMin التقريبية التي توفّر عدّاً تقريبياً لتكرار كل قيمة في العمود.

أنواع البيانات المدعومة

(U)Int*, Float*, Decimal(), Date, Boolean, Enum* IPv4 String أو FixedString أي نوع آخر
basic ✔ (عدد القيم الافتراضية فقط)
countmin
minmax
tdigest
uniq
uniq_v2

تقبل جميع ما سبق أيضًا الأغلفة Nullable وLowCardinality(Nullable) للأنواع المذكورة. ويمكن أيضًا تعريف basic لأي نوع آخر (بما في ذلك الأنواع المركبة مثل Array وTuple وMap)، وفي هذه الحالة لا يسجّل سوى عدد القيم الافتراضية.

العمليات المدعومة

عوامل تصفية المساواة (==) عوامل تصفية النطاق (>, >=, <, <=)
basic ✔ (القيمة الافتراضية فقط) ✔ (الأعمدة الرقمية فقط)
countmin
minmax ✔ (الأعمدة الرقمية فقط)
tdigest ✔ (الأعمدة الرقمية فقط)
uniq
uniq_v2

يستجيب basic لعامل تصفية المساواة بدقة فقط عندما تطابق القيمة المُقارنة القيمة الافتراضية للتخزين الداخلي للعمود (العدد الصحيح الخام 0 للأنواع الرقمية، و'' / N من البايتات الصفرية لـ String / FixedString، وNULL للأعمدة من النوع Nullable، إلخ). وبالنسبة إلى أنواع Enum على وجه الخصوص، لا يُستخدم المسار السريع إلا عندما تُطابق القيمة الحرفية للمُعدِّد العدد الصحيح الخام 0؛ وإذا لم تكن لأي مُعدِّد قيمة خام قدرها 0، فلن تُسهم الإحصائية في تقدير المساواة. أما بالنسبة إلى القيم الأخرى، فيلجأ المُحسِّن إلى إحصاءات أخرى. بالنسبة إلى basic في أعمدة String / FixedString، تسجل الإحصائية إجمالي طول البايتات غير NULL (المستخدم لتقدير متوسط طول السلسلة) بالإضافة إلى عدد القيم الافتراضية/NULL؛ ولا تعتمد عليها عوامل تصفية النطاق أو تقليم الأجزاء.

إعدادات على مستوى العمود

يمكن تجاوز بعض إعدادات MergeTree على مستوى العمود:

  • max_compress_block_size — الحد الأقصى لحجم كتل البيانات غير المضغوطة قبل ضغطها لكتابتها في جدول.
  • min_compress_block_size — الحد الأدنى لحجم كتل البيانات غير المضغوطة المطلوب لضغطها عند كتابة العلامة التالية.

مثال:

CREATE TABLE tab
(
    id Int64,
    document String SETTINGS (min_compress_block_size = 16777216, max_compress_block_size = 16777216)
)
ENGINE = MergeTree
ORDER BY id

يمكن تعديل إعدادات مستوى العمود أو إزالتها باستخدام ALTER MODIFY COLUMN، على سبيل المثال:

  • إزالة SETTINGS من تعريف العمود:
ALTER TABLE tab MODIFY COLUMN document REMOVE SETTINGS;
  • عدّل إعدادًا:
ALTER TABLE tab MODIFY COLUMN document MODIFY SETTING min_compress_block_size = 8192;
  • إعادة تعيين إعداد واحد أو أكثر، مع إزالة تعريف الإعداد أيضًا من تعبير العمود في استعلام CREATE الخاص بالجدول.
ALTER TABLE tab MODIFY COLUMN document RESET SETTING min_compress_block_size;
Navigation