إنشاء فهرس نصي
tokenizer. تحدد وسيطة tokenizer أداة التقسيم إلى رموز:
splitByNonAlphaيقسم السلاسل النصية عند محارف ASCII غير الأبجدية الرقمية (راجع أيضًا الدالة splitByNonAlpha).splitByString(S)يقسم السلاسل النصية باستخدام سلاسل الفواصلSالتي يحددها المستخدم (راجع أيضًا الدالة splitByString). يمكن تحديد الفواصل باستخدام معلمة اختيارية، على سبيل المثالtokenizer = splitByString([', ', '; ', '\n', '\\']). لاحظ أن كل سلسلة يمكن أن تتكون من عدة محارف (', 'في المثال). قائمة الفواصل الافتراضية، إذا لم تُحدَّد صراحةً (على سبيل المثال،tokenizer = splitByString)، هي مسافة بيضاء واحدة[' '].ngrams(N)يقسم السلاسل النصية إلىN-grams متساوية الحجم (راجع أيضًا الدالة ngrams). يمكن تحديد طول ngram باستخدام معلمة عددية صحيحة اختيارية بين 2 و8، على سبيل المثالtokenizer = ngrams(3). الحجم الافتراضي لـ ngram، إذا لم يُحدَّد صراحةً (على سبيل المثال،tokenizer = ngrams)، هو 3.arrayلا يجري أي تقسيم إلى رموز، أي إن قيمة كل صف هي رمز واحد (راجع أيضًا الدالة array).sparseGrams(min_length, max_length, min_cutoff_length)— يستخدم الخوارزمية نفسها كما في الدالة sparseGrams لتقسيم سلسلة نصية إلى جميع ngrams ذات الطولmin_lengthوعدة ngrams بأطوال أكبر حتىmax_length، شاملًا. إذا تم تحديدmin_cutoff_length، فلن تُحفَظ في الفهرس إلا N-grams التي يكون طولها أكبر من أو مساويًا لـmin_cutoff_length. بخلافngrams(N)، التي تولّد فقط N-grams بطول ثابت، تنتجsparseGramsمجموعة من N-grams ذات أطوال متغيرة ضمن النطاق المحدد، مما يتيح تمثيلًا أكثر مرونة لسياق النص. على سبيل المثال،tokenizer = sparseGrams(3, 5, 4)سيولّد 3- و4- و5-grams من سلسلة الإدخال، ولن يحفظ في الفهرس إلا 4- و5-grams.
تطبّق أداة التقسيم
splitByString فواصل التقسيم من اليسار إلى اليمين.
وقد يؤدي ذلك إلى حالات التباس.
على سبيل المثال، سلاسل الفواصل ['%21', '%'] ستؤدي إلى تقسيم %21abc إلى ['abc']، بينما سيؤدي تبديل ترتيب سلسلتي الفواصل إلى ['%', '%21'] إلى إخراج ['21abc'].
في معظم الحالات، ستحتاج إلى أن تُفضَّل مطابقة الفواصل الأطول أولًا.
ويمكن تنفيذ ذلك عمومًا بتمرير سلاسل الفواصل بترتيب تنازلي حسب الطول.
وإذا كانت سلاسل الفواصل تشكّل prefix code، فيمكن تمريرها بأي ترتيب.preprocessor هي تعبير يحوّل سلسلة الإدخال قبل تقسيمها إلى رموز.
تشمل حالات الاستخدام الشائعة لوسيطة المُعالج المسبق ما يلي
- تحويل سلاسل الإدخال إلى أحرف صغيرة (أو كبيرة) لتمكين المطابقة غير الحساسة لحالة الأحرف، مثل lower وlowerUTF8، انظر المثال الأول أدناه.
- تطبيع UTF-8، مثل normalizeUTF8NFC وnormalizeUTF8NFD وnormalizeUTF8NFKC وnormalizeUTF8NFKD وtoValidUTF8.
- إزالة المحارف أو السلاسل الفرعية غير المرغوب فيها أو تحويلها، مثل extractTextFromHTML وsubstring وidnaEncode.
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))
معلمات متقدمة اختيارية
معلمات متقدمة اختيارية
ستعمل القيم الافتراضية للمعلمات المتقدمة التالية جيدًا في جميع الحالات تقريبًا.
ولا نوصي بتغييرها.تحدد المعلمة الاختيارية
dictionary_block_size (الافتراضي: 128) حجم كتل القاموس بالصفوف.تحدد المعلمة الاختيارية dictionary_block_frontcoding_compression (الافتراضي: 1) ما إذا كانت كتل القاموس تستخدم الترميز الأمامي كآلية ضغط.تحدد المعلمة الاختيارية max_cardinality_for_embedded_postings (الافتراضي: 16) عتبة cardinality التي يجب دونها تضمين قوائم الترحيل داخل كتل القاموس.تحدد المعلمة الاختيارية bloom_filter_false_positive_rate (الافتراضي: 0.1) معدل الإيجابيات الكاذبة لمرشح bloom الخاص بالقاموس.استخدام فهرس نصي
SELECT أمرًا مباشرًا، إذ تستخدم دوال البحث النصي الشائعة الفهرس تلقائيًا.
إذا لم يكن هناك فهرس، فسترجع دوال البحث النصي الواردة أدناه إلى عمليات مسح بطيئة بالقوة الغاشمة.
الدوال المدعومة
WHERE ضمن استعلام SELECT:
= and !=
= (equals) و != (notEquals ) يطابقان مصطلح البحث المُعطى بالكامل.
مثال:
= و!=، لكن البحث بالمساواة أو عدم المساواة لا يكون ذا معنى إلا عند استخدام أداة التقسيم إلى رموز array (إذ يجعل الفهرس يخزّن قيم الصف كاملة).
IN and NOT IN
IN (in) وNOT IN (notIn) مع الدالتين equals وnotEquals، لكنهما تطابقان جميع عبارات البحث (IN) أو لا تطابقان أيًا منها (NOT IN).
مثال:
= و !=، أي إن IN و NOT IN لا يكون لهما معنى إلا عند استخدام أداة التقسيم إلى رموز array.
LIKE وNOT LIKE وmatch
تستخدم هذه الدوال حاليًا الفهرس النصي للتصفية فقط إذا كانت
أداة التقسيم إلى رموز الخاصة بالفهرس هي splitByNonAlpha أو ngrams.LIKE like وNOT LIKE (notLike) والدالة match مع الفهارس النصية، يجب أن يتمكن ClickHouse من استخراج وحدات كاملة من عبارة البحث.
مثال:
support في المثال كلمات مثل support وsupports وsupporting وما إلى ذلك.
هذا النوع من الاستعلامات هو استعلام عن سلسلة فرعية، ولا يمكن تسريعه باستخدام فهرس نصي.
للاستفادة من فهرس نصي في استعلامات LIKE، يجب إعادة كتابة نمط LIKE على النحو التالي:
support ويمينها إمكانية استخراج هذا المصطلح بوصفه رمز.
startsWith و endsWith
LIKE، لا يمكن للدالتين startsWith و endsWith استخدام فهرس نصي إلا إذا أمكن استخراج رموز كاملة من عبارة البحث.
مثال:
clickhouse.
ولا يُعدّ support رمز لأنه قد يطابق support وsupports وsupporting وما إلى ذلك.
للعثور على جميع الصفوف التي تبدأ بـ clickhouse supports، أنهِ نمط البحث بمسافة في النهاية:
endsWith مع مسافة في البداية:
hasToken و hasTokenOrNull
رمز واحدًا محددًا.
وخلافًا للدوال المذكورة سابقًا، فإنهما لا تُجزِّئان مصطلح البحث إلى رمزات (إذ تفترضان أن المُدخل عبارة عن رمز واحد).
مثال:
hasToken وhasTokenOrNull هما الأكثر كفاءةً للاستخدام مع فهرس text.
hasAnyTokens and hasAllTokens
has
mapContains
mapContainsKey) تطابق رمزًا واحدًا ضمن مفاتيح الخريطة.
مثال:
operator[]
Array(T) وMap(K, V) مع الفهرس النصّي.
أمثلة على دعم Array وMap في الفهرس النصي.
فهرسة Array(String)
clickhouse) فحص جميع السجلات:
keywords في كل صف.
للتغلب على مشكلة الأداء هذه، يمكننا تعريف فهرس نصي لـ keywords ينشئ بنية مُحسّنة للبحث تُعالج جميع الكلمات المفتاحية مسبقًا، مما يتيح عمليات بحث فورية:
مهم: بعد إضافة الفهرس النصي، يجب إعادة بنائه على البيانات الموجودة:
فهرسة Map
- يعثر على جميع السجلات التي تحتوي على تقييد المعدل:
- يعثر على جميع السجلات الواردة من عنوان IP محدد:
مهم: بعد إضافة الفهرس النصي، يجب إعادة بنائه للبيانات الحالية:
- اعثر على جميع الطلبات التي طُبِّق عليها تحديد المعدل:
- يعرض جميع السجلات الواردة من عنوان IP محدد:
التنفيذ
بنية الفهرس
- قاموس يربط كل token بـ posting list، و
- مجموعة من posting lists، تمثّل كلٌّ منها مجموعة من أرقام الصفوف.
dictionary_block_size).
ويتكوّن ملف كتل القاموس (.dct) من جميع كتل القاموس لكل index granules ضمن part.
ملف index granules (.idx)
يحتوي ملف index granules، لكل كتلة قاموس، على أول token في الكتلة، وإزاحتها النسبية في ملف كتل القاموس، وbloom filter لجميع الـ tokens في الكتلة.
وتشبه بنية sparse index هذه فهرس المفتاح الأساسي المتناثر في ClickHouse).
ويتيح bloom filter تخطّي كتل القاموس مبكرًا إذا لم يكن token المطلوب البحث عنه موجودًا في كتلة القاموس.
ملف posting lists (.pst)
تُرتَّب posting lists لجميع الـ tokens ترتيبًا تسلسليًا داخل ملف posting lists.
ولتوفير المساحة مع السماح في الوقت نفسه بإجراء عمليتَي intersect وunion بسرعة، تُخزَّن posting lists على هيئة roaring bitmaps.
وإذا كانت cardinality الخاصة بـ posting list أقل من 16 (وقابلة للتهيئة عبر parameter max_cardinality_for_embedded_postings)، فسيتم تضمينها داخل القاموس.
القراءة المباشرة
- الإعداد query_plan_direct_read_from_text_index (الافتراضي: 1) الذي يحدد ما إذا كانت القراءة المباشرة مفعّلة بشكل عام.
- الإعداد use_skip_indexes_on_data_read (الافتراضي: 1)، وهو شرط مسبق آخر للقراءة المباشرة. لاحظ أنه في قواعد بيانات ClickHouse التي تكون فيها compatibility < 25.10، يكون
use_skip_indexes_on_data_readمعطّلًا، لذا تحتاج إما إلى رفع قيمة إعداد compatibility أو تنفيذSET use_skip_indexes_on_data_read = 1صراحةً.
ALTER TABLE ... MATERIALIZE INDEX لهذا الغرض).
الدوال المدعومة
تدعم آلية تحسين القراءة المباشرة الدوال hasToken وhasAllTokens وhasAnyTokens.
ويمكن أيضًا دمج هذه الدوال باستخدام العوامل AND وOR وNOT.
كما يمكن أن تحتوي عبارة WHERE على مرشحات إضافية لا تتعلق بدوال البحث النصي (لأعمدة النص أو الأعمدة الأخرى) — وفي هذه الحالة ستظل آلية تحسين القراءة المباشرة مستخدمة، لكنها ستكون أقل فعالية (إذ إنها تنطبق فقط على دوال البحث النصي المدعومة).
لمعرفة ما إذا كان الاستعلام يستخدم القراءة المباشرة، شغّل الاستعلام مع EXPLAIN PLAN actions = 1.
وكمثال، استعلام مع تعطيل القراءة المباشرة
query_plan_direct_read_from_text_index = 1
__text_index_<index_name>_<function_name>_<id>.
إذا كان هذا العمود موجودًا، فهذا يعني أنه تم استخدام القراءة المباشرة.
مثال: مجموعة بيانات Hacker News
hackernews:
ALTER TABLE ونضيف فهرسًا نصيًا إلى عمود comment، ثم نُطبّقه فعليًا:
hasToken وhasAnyTokens وhasAllTokens.
ستوضّح الأمثلة التالية الفارق الكبير في الأداء بين فحص الفهرس القياسي وتحسين القراءة المباشرة.
1. استخدام hasToken
hasToken مما إذا كان النص يحتوي على token واحد محدد.
سنبحث عن token حساس لحالة الأحرف وهو ‘ClickHouse’.
تعطيل القراءة المباشرة (Standard scan)
بشكل افتراضي، يستخدم ClickHouse فهرس التخطي لتصفية الحبيبات، ثم يقرأ بيانات الأعمدة الخاصة بهذه الحبيبات.
يمكننا محاكاة هذا السلوك من خلال تعطيل القراءة المباشرة.
2. استخدام hasAnyTokens
hasAnyTokens مما إذا كان النص يحتوي على توكن واحد على الأقل من التوكنات المحددة.
سنبحث عن التعليقات التي تحتوي على ‘love’ أو ‘ClickHouse’.
القراءة المباشرة معطّلة (المسح القياسي)
3. استخدام hasAllTokens
hasAllTokens مما إذا كان النص يحتوي على جميع الرموز المميّزة المحددة.
سنبحث عن التعليقات التي تحتوي على كلٍّ من ‘love’ و’ClickHouse’.
القراءة المباشرة معطّلة (المسح القياسي)
حتى مع تعطيل القراءة المباشرة، يظل فهرس التخطي القياسي فعّالًا.
فهو يقلّص عدد الصفوف من 28.7 مليون صف إلى 147.46 ألف صف فقط، لكنه لا يزال بحاجة إلى قراءة 57.03 ميغابايت من العمود.
4. البحث المركّب: OR، AND، NOT، …
hasAnyTokens(comment, ['ClickHouse', 'clickhouse']) هي المفضلة والأكثر كفاءة.