إنشاء فهرس نصي
يمكن استخدام الفهارس النصية مع أي إصدار من ClickHouse >= 26.2، بغض النظر عن إعداد التوافق.
Query
- String وFixedString،
- Array(String) وArray(FixedString)،
- Map (باستخدام الدالتين mapKeys وmapValues)، و
- JSON (باستخدام الدالتين JSONAllPaths و
JSONAllValues).
Array(Nullable(String or FixedString)).
بدلًا من ذلك، لإضافة فهرس نصي إلى جدول موجود:
Query
Query
Query
tokenizer (إلزامية). تحدد الوسيطة tokenizer المُجزِّئ:
splitByNonAlphaيقسم السلاسل النصية عند محارف ASCII غير الأبجدية الرقمية (راجع الدالة splitByNonAlpha).splitByString(S)يقسم السلاسل النصية باستخدام سلاسل فاصلةSيحددها المستخدم (راجع الدالة splitByString). يمكن تحديد الفواصل باستخدام معلمة اختيارية، على سبيل المثال:tokenizer = splitByString([', ', '; ', '\n', '\\']). لاحظ أن كل سلسلة يمكن أن تتكون من عدة محارف (', 'في المثال). قائمة الفواصل الافتراضية، إذا لم تُحدَّد صراحةً (على سبيل المثال:tokenizer = splitByString)، هي مسافة بيضاء واحدة[' '].asciiCJKيقسم السلاسل النصية إلى رموز وفقًا لقواعد حدود الكلمات في Unicode (على غرار Unicode Text Segmentation (UAX #29)). وتُشكِّل محارف ASCII الأبجدية الرقمية والشرطات السفلية رموزًا مع الموصلات (ASCII:للحروف، و.و'للمحارف من النوع نفسه). أما محارف Unicode غير التابعة لـ ASCII، بما في ذلك محارف CJK، فتصبح رموزًا من محرف واحد.ngrams(N)يقسم السلاسل النصية إلى n-grams متساوية الحجم بطولN(راجع الدالة ngrams). يمكن تحديد طول ngram باستخدام معلمة عدد صحيح اختيارية بين 1 و8، على سبيل المثال:tokenizer = ngrams(3). حجم ngram الافتراضي، إذا لم يُحدَّد صراحةً (على سبيل المثال:tokenizer = ngrams)، هو 3.sparseGrams(min_length, max_length, min_cutoff_length)يقسم السلاسل النصية إلى n-grams متغيرة الطول، بحيث لا يقل طولها عنmin_lengthولا يزيد علىmax_length(شاملًا) من المحارف (راجع الدالة sparseGrams). ما لم يُحدَّد ذلك صراحةً، تكون القيم الافتراضية لـmin_lengthوmax_lengthهي 3 و100. إذا تم توفير المعلمةmin_cutoff_length، فلن تُعاد إلا n-grams التي يكون طولها أكبر من أو مساويًا لـmin_cutoff_length. مقارنةً بـngrams(N)، يُنتج المُجزِّئsparseGramsN-grams متغيرة الطول، مما يتيح تمثيلًا أكثر مرونة للنص الأصلي. على سبيل المثال،tokenizer = sparseGrams(3, 5, 4)يُنشئ داخليًا 3- و4- و5-grams من سلسلة الإدخال، لكن لا تُعاد إلا 4- و5-grams.arrayلا يُجري أي تجزئة، أي إن كل قيمة في row هي رمز (راجع الدالة array).
يطبّق المُجزِّئ
splitByString فواصل التقسيم من اليسار إلى اليمين.
وقد يؤدي ذلك إلى حدوث حالات التباس.
على سبيل المثال، ستؤدي سلاسل الفواصل ['%21', '%'] إلى تجزئة %21abc على هيئة ['abc']، بينما سيؤدي تبديل ترتيب سلسلتي الفواصل إلى ['%', '%21'] إلى إخراج ['21abc'].
في معظم الحالات، ستحتاج إلى أن تُفضِّل المطابقة الفواصل الأطول أولًا.
ويمكن تحقيق ذلك عمومًا عبر تمرير سلاسل الفواصل بترتيب تنازلي حسب الطول.
وإذا كانت سلاسل الفواصل تُشكّل prefix code، فيمكن تمريرها بأي ترتيب.Query
Response
asciiCJK لأنه يتعامل بشكل صحيح مع حدود الكلمات في Unicode، بما في ذلك محارف CJK.
:::
وسيطة المعالج المسبق (اختيارية). يشير المعالج المسبق إلى تعبير يُطبَّق على سلسلة الإدخال قبل تقسيمها إلى رموز.
تشمل حالات الاستخدام الشائعة لوسيطة المعالج المسبق
- تحويل الأحرف إلى صغيرة/كبيرة، أو طيّ الحالة لتمكين المطابقة غير الحساسة لحالة الأحرف، مثل lower، lowerUTF8، caseFoldUTF8.
- تطبيع UTF-8، مثل normalizeUTF8NFC، normalizeUTF8NFD، normalizeUTF8NFKC، normalizeUTF8NFKD، normalizeUTF8NFKCCasefold، toValidUTF8.
- إزالة المحارف أو السلاسل الفرعية غير المرغوب فيها أو تحويلها، مثل علامات التشكيل، مثل extractTextFromHTML، substring، idnaEncode، translate، removeDiacriticsUTF8.
Nullable(T) أو LowCardinality(T)، فيجب أن يقبل تعبير المعالج المسبق القيم القابلة للإبطال أو منخفضة الكاردينالية (أي ألّا يطرح استثناءً).
أمثلة:
INDEX idx col TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = lower(col))INDEX idx col TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = substringIndex(col, '\n', 1))INDEX idx col TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = lower(extractTextFromHTML(col)))INDEX idx col TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = removeDiacriticsUTF8(caseFoldUTF8(col)))
INDEX idx lower(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = upper(lower(col)))INDEX idx lower(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = concat(lower(col), lower(col)))- غير مسموح:
INDEX idx lower(col) TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = concat(col, col))
المعالجات المسبقة، من حيث المبدأ، تكافئ تغليف عمود الفهرس أو التعبير بتعبير المعالج المسبق.
على سبيل المثال، يمكن محاكاة المعالج المسبق
lower في INDEX idx col TYPE text(tokenizer = 'splitByNonAlpha', preprocessor = lower(col)) باستخدام INDEX idx lower(col) TYPE text(tokenizer = 'splitByNonAlpha').
وعيب الصيغة الأخيرة هو أن المعالج المسبق المُحاكى لا يُطبَّق إلا إذا طابق شرط التصفية في بند WHERE.
على سبيل المثال، يطابق WHERE hasAllTokens(lower(col), [...]) بينما لا يطابق WHERE hasAllTokens(col, [...]).
لذلك، ولأفضل تجربة استخدام، نوصي باستخدام تعبيرات المعالج المسبق.SETTINGS use_skip_indexes = 0).
على سبيل المثال،
Query
Query
Query
Query
Query
Response
استخدام فهرس نصي
نوصي باستخدام الدالتين
hasAnyTokens وhasAllTokens للبحث في الفهرس النصي؛ يُرجى الاطلاع على أدناه.
تعمل هاتان الدالتان مع جميع المجزئات المتاحة وجميع تعبيرات المعالجة المسبقة الممكنة.
ونظرًا إلى أن الدوال المدعومة الأخرى سبقت الفهرس النصي تاريخيًا، فقد كان عليها الاحتفاظ بسلوكها القديم في كثير من الحالات (مثلًا، عدم دعم المعالجة المسبقة).الدوال المدعومة
WHERE أو بنود PREWHERE:
=
= (equals) يطابق مصطلح البحث المعطى بالكامل.
مثال:
IN
IN (in) يشبه equals، لكنه يطابق كل مصطلحات البحث.
مثال:
NOT IN (notIn) غير مدعوم في الفهرس النصي.LIKE and match
تستخدم هذه الدوال حاليًا الفهرس النصي للتصفية فقط إذا كان الـ مجزئ في الفهرس هو
splitByNonAlpha أو ngrams أو sparseGrams.لا يدعم الفهرس النصي
NOT LIKE (notLike).LIKE (like) والدالة match مع الفهارس النصية، يجب أن يتمكن ClickHouse من استخراج رموز كاملة من عبارة البحث.
وبالنسبة إلى الفهرس الذي يستخدم مجزئ من النوع ngrams، يتحقق ذلك إذا كان طول السلاسل النصية المطلوب البحث عنها بين أحرف البدل مساويًا لطول ngram أو أكبر منه.
Example لفهرس نصي يستخدم مجزئ من النوع splitByNonAlpha:
support في المثال كلمات مثل support وsupports وsupporting وغيرها.
هذا النوع من الاستعلامات هو استعلام عن سلسلة فرعية، ولا يمكن تسريعه باستخدام فهرس نصي.
لاستفادة من فهرس نصي في استعلامات LIKE، يجب إعادة كتابة نمط LIKE على النحو التالي:
support ويمينه إمكانية استخراج هذا المصطلح على أنه رمز.
ولحسن الحظ، توجد حالة خاصة يمكن فيها لـ ClickHouse الاستفادة من الفهرس المقلوب لتسريع استعلامات LIKE بشكل كبير.
راجع قسم ضبط أداء LIKE/ILIKE لمزيد من التفاصيل.
multiSearchAny و multiMatchAny
LIKE و match (انظر أعلاه): يجب أن يتمكن ClickHouse من استخراج رموز كاملة من كل needle، ويجب أن تكون قائمة needles ثابتة.
تُقرأ granule إذا كان من المحتمل أن تحتوي على أي needle.
بالنسبة إلى multiMatchAny، إذا تعذر اختزال نمط واحد إلى متطلب رمز (على سبيل المثال .*، الذي يطابق أي مستند)، فلن يمكن استخدام الفهرس النصي، وسيرجع الاستعلام إلى فحص كامل.
كما هو الحال مع LIKE و match، يعمل البحث بالسلاسل الفرعية والتعبيرات النمطية بأفضل شكل مع مجزئات ngrams و sparseGrams.
تفهرس مجزئات النص هذه n-grams متداخلة من الأحرف، لذلك يُحلَّل needle إلى n-grams موجودة في الفهرس أينما ظهر needle كسلسلة فرعية، بغض النظر عمّا إذا كان يبدأ أو ينتهي في منتصف كلمة.
وبالتالي يمكن استخدام needle كما هو، ما دام طوله لا يقل عن حجم n-gram.
Example للفهرس النصي مع مجزئ ngrams:
splitByNonAlpha سوى الرموز الكاملة (أي الكلمات الكاملة).
ولأن needle قد يبدأ أو ينتهي في منتصف كلمة، فإن ClickHouse يتجاهل الرمزين الأول والأخير في كل needle، بحيث لا يمكن للفهرس تقليم الحبيبات إلا بالاعتماد على الرموز الكاملة.
ولكي يستخدم البحث عن substring والبحث بالتعبيرات النمطية الفهرس مع splitByNonAlpha، أَحِط كل needle بمحارف فاصلة (مثل المسافات) بحيث يُكوِّن رمزًا كاملًا واحدًا أو أكثر.
مثال على الفهرس النصي مع المُجزِّئ splitByNonAlpha:
startsWith and endsWith
LIKE، لا يمكن للدالتين startsWith وendsWith استخدام فهرس نصي إلا إذا أمكن استخراج رموز كاملة من عبارة البحث.
وبالنسبة إلى الفهرس الذي يستخدم مجزئ ngrams، يتحقق ذلك إذا كان طول السلاسل النصية المطلوب البحث عنها بين أحرف البدل مساويًا لطول ngram أو أكبر منه.
مثال على فهرس نصي يستخدم مجزئ splitByNonAlpha:
clickhouse إلا رمزًا واحدًا.
أما support فلا يُعدّ رمزًا لأنه يمكن أن يطابق support وsupports وsupporting وغيرها.
للعثور على جميع rows التي تبدأ بـ clickhouse supports، يُرجى إنهاء نمط البحث بمسافة لاحقة:
endsWith مع مسافة في البداية:
hasToken و hasTokenOrNull
يبدو استخدام الدالة
hasToken بسيطًا، لكنه ينطوي على بعض الجوانب التي قد تُسبب مشكلات عند استخدام مُجزِّئات غير افتراضية وتعبيرات المعالجة المسبقة.
نوصي باستخدام الدالتين hasAnyTokens و hasAllTokens بدلًا من ذلك.hasAnyTokens and hasAllTokens
hasPhrase
hasAllTokens، التي تشترط فقط وجود جميع الرموز في أي موضع، فإن hasPhrase تشترط أن تظهر كتسلسل متصل.
تُجزَّأ عبارة البحث إلى رموز باستخدام المجزئ نفسه المُعدّ لعمود الفهرس.
لاحظ أن الدالة تتطلب أحد مجزئات splitByNonAlpha أو splitByString أو ngrams أو asciiCJK.
مثال:
has
رمز واحدًا ضمن مصفوفة من السلاسل النصية.
مثال:
hasAny و hasAll
mapContains
mapContainsKey) مع الرموز المستخرجة من السلسلة النصية المطلوب البحث فيها ضمن مفاتيح الـ map.
ويشبه هذا السلوك الدالة equals مع عمود String.
ولا يُستخدَم فهرس نصي إلا إذا كان قد أُنشئ على expression mapKeys(map).
Example:
mapContainsValue
map.
ويشبه سلوكها سلوك الدالة equals مع عمود String.
ولا يُستخدم الفهرس النصي إلا إذا كان قد أُنشئ على التعبير mapValues(map).
مثال:
mapContainsKeyLike و mapContainsValueLike
operator[]
mapKeys(map) أو mapValues(map)، أو كليهما.
مثال:
Array(T) وMap(K, V) مع الفهرس النصي.
فهرسة أعمدة Array(String)
clickhouse) فحص جميع السجلات:
keywords في كل صف.
للتغلّب على مشكلة الأداء هذه، نُعرّف فهرسًا نصيًا للعمود keywords:
فهرسة أعمدة Map
فهرسة أعمدة JSON
JSON بثلاث طرق:
- فهارس على أعمدة فرعية محددة — أنشئ فهرسًا نصيًا على مسار JSON معروف، تمامًا كما تفعل مع عمود عادي. يؤدي ذلك إلى فهرسة القيم الموجودة في هذا المسار.
- فهارس قائمة على المسار باستخدام JSONAllPaths — تُفهرِس جميع المسارات الموجودة في كل granule لتخطي الـ granules التي لا يمكن أن تحتوي على المسار المطلوب في الاستعلام. وهذا مشابه لأعمدة
Map. - فهارس قائمة على القيم باستخدام JSONAllValues — تُفهرِس جميع القيم عبر جميع مسارات JSON لتسريع البحث النصي الكامل في أي عمود JSON فرعي باستخدام فهرس واحد.
فهارس على أعمدة فرعية محددة
- مسار محدد النوع مُعرَّف في تلميح نوع JSON — ويمكن الوصول إليه بالاسم مباشرةً:
json.a. - مسار Dynamic مع تحويل نوع صريح — استخدم صياغة تحويل النوع
:::json.b::String.
Query
Query
Response
Query
Response
الفهارس المستندة إلى المسار باستخدام JSONAllPaths
Map، يمكن إنشاء فهارس نصية على أعمدة JSON باستخدام JSONAllPaths.
يخزّن الفهرس مجموعة مسارات JSON الموجودة في كل حبيبة، ويستخدمها لتخطّي الحبيبات التي لا يحتوي فيها الاستعلام على المسار المطلوب.
مثال على تعريف الفهرس:
Query
EXPLAIN indexes = 1 للتحقق من استخدام فهرس التخطي.
عندما يكون المسار موجودًا في جزء واحد فقط، يتجاوز الفهرس الجزء الآخر.
مثال:
Query
Response
Query
Response
IS NOT NULL الفهرس أيضًا — إذ يتخطى الحبيبات التي يغيب فيها المسار (لأن القيمة ستكون NULL):
مثال:
Query
Response
الفهارس القائمة على القيم باستخدام JSONAllValues
JSONAllValues.
تعيد JSONAllValues جميع القيم من عمود JSON بصيغة Array(String).
وتُحوَّل قيم أنواع البيانات غير النصية (مثل الأعداد الصحيحة والمصفوفات) إلى تمثيلها النصي.
ويفهرس فهرس نصي مُنشأ باستخدام JSONAllValues هذه التمثيلات النصية عبر جميع مسارات JSON في كل صف.
ويمكن لهذا الفهرس بعد ذلك تسريع الاستعلامات التي تُطبِّق عامل تصفية على أعمدة JSON الفرعية الفردية.
وعندما يطبّق استعلام عامل تصفية على عمود فرعي محدد (مثل data.user_name = 'alice')، يمكن للفهرس النصي أن يتخطى بسرعة الصفوف (والحبيبات) التي لا تحتوي أيٌّ من قيم JSON فيها على رموز البحث.
قد يُنتج الفهرس نتائج إيجابية كاذبة عندما تحتوي مسارات JSON مختلفة على الرموز نفسها.
على سبيل المثال، إذا كان الصف 1 يحتوي على
{"a": "hello", "b": "world"} وكان الاستعلام يبحث عن data.a = 'world'، فلن يتمكن الفهرس النصي من تمييز أن world تنتمي إلى المسار b لا إلى a.
وفي مثل هذه الحالات، لن يتخطى الفهرس هذا الصف، وسيتولى عامل التصفية على بيانات العمود الفعلية إجراء التقييم النهائي.
وهذا هو السلوك نفسه في حالات استخدام الفهرس النصي الأخرى، حيث يعمل الفهرس كعامل تصفية تمهيدي سريع.إنشاء الفهرس
أنماط الاستعلام المدعومة
String، بالإضافة إلى الدالة equals لجميع الأعمدة.
الوصول إلى العمود الفرعي:
CAST الصريح:
IN:
البحث عن العبارات
hasPhrase.
يجب أن تظهر جميع الرموز في العبارة بشكل متتالٍ وبالترتيب نفسه داخل المستند.
يُسرّع فهرس النص البحث عن العبارات من خلال تقاطع قوائم الترحيل الخاصة بجميع الرموز في العبارة لتحديد الحبيبات المرشحة.
وداخل تلك الحبيبات، يتحقق ClickHouse بعد ذلك من التجاور الدقيق بين الرموز.
تتوافق hasPhrase مع المُجزِّئات splitByNonAlpha وsplitByString وngrams وasciiCJK.
تُجزَّأ سلسلة العبارة إلى رموز باستخدام المُجزِّئ المُهيأ في الفهرس.
ويتم تجاهل أحرف الفصل الخاصة بالـ المُجزِّئ في العبارة: hasPhrase(text, 'quick+brown') تكافئ hasPhrase(text, 'quick brown') بالنسبة إلى الـ المُجزِّئ splitByNonAlpha.
مثال
Query
Query
Response
'New weather in York') لا يتطابق لأن الرموز ليست بالترتيب الصحيح.
الصف 3 ('weather in New Orleans') لا يتطابق لأنه لا يحتوي على الرمز 'York'.
ضبط الأداء
القراءة المباشرة
direct read عن الاستعلام بالاعتماد حصريًا على فهرس النص (أي من خلال عمليات lookup في فهرس النص) من دون الوصول إلى عمود النص الأساسي.
وتقرأ عمليات lookup في فهرس النص قدرًا قليلًا نسبيًا من البيانات، لذا فهي أسرع بكثير من فهارس التخطي المعتادة في ClickHouse (التي تُجري lookup في فهرس التخطي، ثم تحميل الحبيبات المتبقية وتصفيتها).
يُتحكَّم في direct read عبر إعدادين:
- الإعداد query_plan_direct_read_from_text_index (وقيمته الافتراضية true) الذي يحدد ما إذا كانت
direct readمفعّلة بشكل عام. - كان الإعداد use_skip_indexes_on_data_read شرطًا مسبقًا لـ
direct readفي إصدارات ClickHouse الأقدم من < 26.4.
direct read الدوال hasToken وhasAllTokens وhasAnyTokens.
إذا كان فهرس النص معرّفًا باستخدام المُجزِّئ array، فإن direct read تدعم أيضًا الدوال equals وhas وhasAny وhasAll وmapContainsKey وmapContainsValue.
ويمكن أيضًا دمج هذه الدوال باستخدام عوامل التشغيل AND وOR وNOT.
كما يمكن أن تتضمن عبارتا WHERE أو PREWHERE عوامل تصفية إضافية لا تتعلق بدوال البحث النصي (لأعمدة النص أو الأعمدة الأخرى) - وفي هذه الحالة ستظل آلية تحسين direct read مستخدمة، ولكن بفعالية أقل (إذ إنها تنطبق فقط على دوال البحث النصي المدعومة).
للتحقق مما إذا كان الاستعلام يستخدم direct read، شغّل الاستعلام باستخدام EXPLAIN PLAN actions = 1.
وعلى سبيل المثال، استعلام مع تعطيل direct read
query_plan_direct_read_from_text_index = 1
__text_index_<index_name>_<function_name>_<id>.
إذا كان هذا العمود موجودًا، فهذا يعني أنه تم استخدام القراءة المباشرة.
إذا كانت عبارة التصفية في WHERE تحتوي فقط على دوال البحث النصي، فيمكن للاستعلام تجنّب قراءة بيانات العمود بالكامل وتحقيق أكبر فائدة أداء عبر القراءة المباشرة.
ومع ذلك، حتى إذا جرى الوصول إلى العمود النصي في موضع آخر من الاستعلام، فستظل القراءة المباشرة توفّر تحسينًا في الأداء.
القراءة المباشرة كتلميح
تعتمد القراءة المباشرة كتلميح على المبادئ نفسها التي تعتمد عليها القراءة المباشرة العادية، لكنها تضيف بدلًا من ذلك عامل تصفية إضافيًا مُنشأً من بيانات فهرس النص، من دون الاستغناء عن العمود النصي الأساسي.
وتُستخدم مع الدوال التي قد تؤدي فيها القراءة من فهرس النص فقط إلى مطابقات إيجابية كاذبة.
الدوال المدعومة هي: like, startsWith, endsWith, equals, has, hasPhrase, mapContainsKey, و mapContainsValue.
يمكن لعامل التصفية الإضافي أن يوفّر انتقائية إضافية لتقييد مجموعة النتائج بدرجة أكبر عند دمجه مع عوامل تصفية أخرى، مما يساعد على تقليل كمية البيانات المقروءة من الأعمدة الأخرى.
تخضع القراءة المباشرة كتلميح للإعداد query_plan_text_index_add_hint (مُمكّن افتراضيًا).
مثال على استعلام من دون تلميح:
query_plan_text_index_add_hint = 1
__text_index_...) إلى شرط التصفية.
وبفضل تحسين PREWHERE، يُقسَّم شرط التصفية إلى ثلاثة أجزاء اقترانية منفصلة، تُطبَّق بترتيب تصاعدي بحسب التعقيد الحسابي.
بالنسبة لهذا الاستعلام، يكون ترتيب التطبيق هو __text_index_...، ثم greaterOrEquals(...)، وأخيرًا like(...).
ويتيح هذا الترتيب تخطي عدد أكبر من حبيبات البيانات مقارنةً بما يتخطاه الفهرس النصي وشرط التصفية الأصلي، وذلك قبل قراءة الأعمدة الثقيلة المستخدمة في الاستعلام بعد عبارة WHERE، مما يقلل بدرجة أكبر كمية البيانات المطلوب قراءتها.
استعلامات LIKE/ILIKE
%<alpha-numeric-characters-without-spaces>% ويكون مُجزِّئ لفهرس النص text index هو splitByNonAlpha أو array، يستفيد ClickHouse من الفهرس المعكوس inverted index لتسريع استعلامات LIKE/ILIKE بشكل كبير. ولتحقيق ذلك، يفحص ClickHouse القاموس Dictionary الخاص بالفهرس المعكوس بدلًا من إجراء فحص كامل للجدول للعثور على النمط المطابق.
عند تمكين هذا التحسين، يُفترض أن تصبح استعلامات LIKE/ILIKE أسرع بكثير من الفحص الكامل للجدول. ومع ذلك، إذا كان النمط يطابق معظم الرموز tokens في القاموس، فقد يصبح الأداء أسوأ مقارنةً بالفحص الكامل للجدول. ولحسن الحظ، توجد آلية احتياطية fallback تمنع ذلك.
يخضع هذا التحسين لإعداد واحد:
وتخضع الآلية الاحتياطية fallback لإعدادين:
لا يدعم هذا التحسين إلا الدالتين like و ilike.
التخزين المؤقت
إعدادات ذاكرة التخزين المؤقت لرموز الفهرس النصي
إعدادات ذاكرة التخزين المؤقت للترويسات
إعدادات ذاكرة التخزين المؤقت لقوائم الترحيل
القيود
- قد يستهلك البناء المادي للفهرسة النصية التي تحتوي على عدد كبير من الرموز (مثلًا 10 مليارات رمز) كميات كبيرة من الذاكرة. ويمكن أن يحدث
البناء المادي للفهرس النصي مباشرةً (
ALTER TABLE <table> MATERIALIZE INDEX <index>) أو بصورة غير مباشرة أثناء عمليات دمج الأجزاء. - لا يمكن إجراء البناء المادي للفهرسة النصية على الأجزاء التي تحتوي على أكثر من 4.294.967.296 (= 2^32 = نحو 4.2 مليارات) صف. ومن دون فهرس نصي مُنشأ ماديًا، تلجأ الاستعلامات إلى البحث البطيء بالقوة الغاشمة داخل الجزء. وكتقدير لأسوأ الاحتمالات، افترض أن جزءًا يحتوي على عمود واحد من النوع String وأن إعداد MergeTree
max_bytes_to_merge_at_max_space_in_pool(القيمة الافتراضية: 150 جيجابايت) لم يتغير. في هذه الحالة، يحدث ذلك إذا كان العمود يحتوي في المتوسط على أقل من 29.5 حرفًا لكل صف. وعمليًا، تحتوي الجداول أيضًا على أعمدة أخرى، وتكون العتبة أقل من ذلك بعدة مرات (بحسب عدد الأعمدة الأخرى ونوعها وحجمها).
فهارس النص مقابل الفهارس المعتمدة على Bloom filter
bloom_filter وngrambf_v1 وtokenbf_v1 وsparse_grams)، إلا أن كليهما يختلفان اختلافًا جوهريًا من حيث التصميم وحالات الاستخدام المستهدفة:
فهارس Bloom filter
- تستند إلى هياكل بيانات احتمالية قد تؤدي إلى false positives.
- لا يمكنها إلا الإجابة عن أسئلة الانتماء إلى مجموعة، أي إن العمود قد يحتوي على الرمز X أو أنه بالتأكيد لا يحتوي على X.
- تخزّن معلومات على مستوى الحبيبة، مما يتيح تخطي نطاقات واسعة أثناء تنفيذ query.
- يصعب ضبطها على نحو صحيح (راجع هنا للاطلاع على مثال).
- وهي مدمجة نسبيًا (بضعة كيلوبايتات أو ميغابايتات لكل part).
- تبني فهرسًا معكوسًا حتميًا على الرموز. ولا يمكن أن تنتج عن الفهرس نفسه false positives.
- مُحسّنة خصيصًا لأعباء عمل البحث النصي.
- تخزّن معلومات على مستوى الصف، مما يتيح lookup فعّالًا للمصطلحات.
- وهي كبيرة نسبيًا (من عشرات إلى مئات الميغابايتات لكل part).
- فهي لا تدعم tokenization وpreprocessing المتقدمين.
- وهي لا تدعم البحث باستخدام عدة رموز.
- وهي لا توفر خصائص الأداء المتوقعة من فهرس معكوس.
- فهي توفر tokenization وpreprocessing
- وتوفر دعمًا فعّالًا لـ
hasAllTokensوLIKEوmatchووظائف البحث النصي المشابهة. - وتتمتع بقابلية توسّع أفضل بكثير مع المجموعات النصية الكبيرة.
تفاصيل التنفيذ
- قاموس يربط كل رمز بقائمة ترحيلات، و
- مجموعة من قوائم الترحيلات، تمثّل كل واحدة منها مجموعة من أرقام الصفوف.
dictionary_block_size).
ويتكوّن ملف كتل القاموس (.dct) من جميع كتل القاموس لكل index granules في الجزء.
ملف ترويسة الفهرس (.idx)
يحتوي ملف ترويسة الفهرس، لكل كتلة قاموس، على أول رمز في الكتلة وإزاحته النسبية داخل ملف كتل القاموس.
تشبه بنية sparse index هذه فهرس المفتاح الأساسي المتناثر) في ClickHouse.
ملف قوائم الترحيلات (.pst)
تُرتَّب قوائم الترحيلات الخاصة بجميع الرموز ترتيبًا تسلسليًا في ملف قوائم الترحيلات.
ولتوفير المساحة مع الحفاظ على سرعة عمليات التقاطع والاتحاد، تُخزَّن قوائم الترحيلات على هيئة roaring bitmaps.
إذا كانت قائمة الترحيلات أكبر من posting_list_block_size، فتُقسَّم إلى عدة كتل تُخزَّن تسلسليًا في ملف قوائم الترحيلات.
دمج الفهارس النصية
عند دمج data parts، لا يحتاج الفهرس النصي إلى إعادة بنائه من الصفر؛ بل يمكن دمجه بكفاءة في step منفصلة من merge process.
وخلال هذه step، تُقرأ القواميس المرتبة للفهارس النصية لكل جزء إدخال وتُدمج في قاموس موحّد جديد.
كما يُعاد حساب أرقام الصفوف في قوائم الترحيلات لتعكس مواضعها الجديدة في data part المدمج، باستخدام mapping من أرقام الصفوف القديمة إلى الجديدة يُنشأ خلال Phase الدمج الأولية.
وتشبه طريقة دمج الفهارس النصية هذه كيفية دمج projections التي تحتوي على العمود _part_offset.
وإذا لم يكن الفهرس materialized في الجزء المصدر، فيُبنى ويُكتب في ملف مؤقت ثم يُدمج مع الفهارس من الأجزاء الأخرى ومن ملفات الفهارس المؤقتة الأخرى.
تصحيح الأخطاء
يمكن استخدام table function mergeTreeTextIndex لفحص الفهارس النصية داخليًا.
مثال: مجموعة بيانات Hacker News
hackernews:
ALTER TABLE لإضافة فهرس نصي إلى عمود comment، ثم نطبّقه فعليًا:
hasToken وhasAnyTokens وhasAllTokens.
ستُظهر الأمثلة التالية الفرق الكبير في الأداء بين فحص فهرس تقليدي وتحسين القراءة المباشرة.
1. استخدام hasToken
hasToken مما إذا كان النص يحتوي على رمز واحد محدد.
سنبحث عن الرمز الحساس لحالة الأحرف ‘ClickHouse’.
القراءة المباشرة معطّلة (المسح القياسي)
بشكل افتراضي، يستخدم ClickHouse فهرس التخطي لتصفية الحبيبات، ثم يقرأ بيانات العمود الخاصة بهذه الحبيبات.
يمكننا محاكاة هذا السلوك من خلال تعطيل القراءة المباشرة.
2. استخدام hasAnyTokens
hasAnyTokens مما إذا كان النص يحتوي على واحدة على الأقل من الوحدات المعطاة.
سنبحث عن التعليقات التي تحتوي على ‘love’ أو ‘ClickHouse’.
تم تعطيل Direct read (Standard scan)
3. استخدام hasAllTokens
hasAllTokens من احتواء النص على جميع الرموز المعطاة.
سنبحث عن التعليقات التي تحتوي على كلٍّ من ‘love’ و’ClickHouse’.
القراءة المباشرة معطّلة (المسح القياسي)
حتى مع تعطيل القراءة المباشرة، يظل فهرس التخطي القياسي فعّالًا.
فهو يقلّص عدد الصفوف من 28.7 مليون صف إلى 147.46 ألف صف فقط، لكنه لا يزال مضطرًا إلى قراءة 57.03 ميغابايت من العمود.
4. البحث المركب: OR, AND, NOT, …
hasAnyTokens(comment, ['ClickHouse', 'clickhouse']) هي الخيار المفضل والأكثر كفاءة.
- مدونة: الإعلان عن الإتاحة العامة للبحث النصي الكامل في ClickHouse
- مدونة: بناء بحث نصي كامل عالي الأداء لتخزين الكائنات
- فيديو: مقدمة إلى البحث النصي الكامل في ClickHouse
- فيديو: ما وراء الكواليس: البحث النصي الكامل في ClickHouse على نطاق واسع وبسرعة عالية
- عرض تقديمي: نظرة داخلية على البحث النصي الكامل في ClickHouse: سريع وأصلي وعمودي
- عرض تقديمي: فهارس قواعد البيانات المقلوبة: لماذا، وما هي، وكيف تعمل، FOSDEM 2026