الإكمال التّلقائيّ والتّسامح مع الأخطاء وES|QL: بحث Elasticsearch متقدّم
على Karim تسليم شريط بحث منتج Veille: يقترح عناوين أثناء الكتابة، ويسامح على أخطاء الطّباعة، ويُهيّئ الأرضيّة للغة أوضح من Query DSL حين يريد محلّل تجميعًا سريعًا. أمّا Léa فتريد تحويل mapping في الإنتاج دون قطع الخدمة. تجمع هذه الوحدة أربعة أوراش: completion على headline.suggest، وfuzziness: AUTO، وES|QL، والـalias، وإعادة الفهرسة دون انقطاع.
الإكمال التّلقائيّ: الـsuggester من نوع completion
تذكير بالـmapping (الوحدة 4): يملك headline حقلًا فرعيًّا headline.suggest من نوع completion بـmax_input_length: 120. هذا النّوع الخاصّ ليس فهرسًا معكوسًا كلاسيكيًّا — إنّه مُحوِّل الحالات المنتهية (FST) يُطابق فورًا بادئةً مع كلّ المصطلحات المُ فهرسة. المقابل: يعيش في الذّاكرة الحيّة، وكتابته مُكلِفة.
لماذا max_input_length: 120
القيمة الافتراضيّة لـcompletion هي 50 حرفًا. على مدوَّنة HuffPost، يقطع ذلك عنوانًا واحدًا من كلّ اثنين: «The 20 Best Vegan Recipes You'll Actually Want To Cook This Weekend» (66 حرفًا) يُقطع إلى «The 20 Best Vegan Recipes You'll Actually Want To». المستخدم الذي يكتب «weekend» لن يجد شيئًا. بالانتقال إلى 120 نُغطّي المدوَّنة كلّها مع بقاء FST معقولًا.
لا يُعيد completion خطأً على العناوين المقطوعة: يُفهرسها ببساطة إلى الطّول الأقصى. إن «فوَّت» الإكمال يومًا اقتراحات بديهيّة على عناوين طويلة، فأوّل معامل يجب التّحقّق منه بـGET news/_mapping هو max_input_length.
استعلام _search مع suggest
في Kibana Dev Tools:
GET news/_search
{
"_source": false,
"suggest": {
"titres": {
"prefix": "trum",
"completion": {
"field": "headline.suggest",
"size": 5,
"skip_duplicates": true
}
}
}
}
الاستجابة المتوقَّعة (مقتطف من options):
'Truman Show' Delusion: Believing Your Life Is A Reality TV Show
Trump Abandons Commitment To 2-State Solution In Press Conference With Netanyahu
Trump Signs Order Ordering Federal Agencies To Cut Two Regulations For Every New One
Truman Capote's Ashes Sold For $43,750 At Auction
Truman Show Syndrome, Or When People Think Their Life Is A TV Show
ثلاث نقاط لتتذكّرها من هذا الاستعلام:
- البادئة
trumتطابقTrumpوTrumanمعًا: ينظرcompletionإلى بداية المصطلح، لا إلى معناه. skip_duplicates: trueيمنع تكرار عنوانَين متطابقَين في الاقتراحات (مفيد حين تُنشر الدّفعة مرّتَين)._source: falseيحذف الـhits الكلاسيكيّة؛ نريد فقط المفتاحsuggest.titres. هذا يُخفِّف الاستجابة.
زمن الاستجابة من رتبة جزء من الميليّة ثانية على هذه المدوَّنة — هذا وعد الـFST.
search_as_you_type كبديل
بديل لـcompletion: النّوع search_as_you_type، الذي يُنشئ تلقائيًّا حقولًا فرعيّة ._2gram و._3gram و._index_prefix. يُسامح على الأخطاء في وسط الكلمة ويُطابق عدّة حقول دفعةً واحدة، بثمن فهرس أثقل. لشريط Veille يكفي completion؛ نحتفظ بـsearch_as_you_type للمدوَّنات المتعدّدة اللّغات أو حين نريد «البحث ونحن نكتب» بدلًا من «اقتراح عنوان مضبوط».
التّسامح مع الأخطاء: fuzziness: AUTO
قارئ يبحث عن «climat chnage» (حرف «n» قبل «a») يجب أن يجد رغم ذلك «Climate Change». هذا دور fuzziness — مسافة تحرير Damerau-Levenshtein — التي يقبلها match مباشرةً.
GET news/_search
{
"query": {
"match": {
"headline": {
"query": "climat chnage",
"fuzziness": "AUTO"
}
}
},
"size": 3,
"_source": ["headline"]
}
الاستجابة المتوقَّعة: عدّة آلاف من النّتائج (قد يختلف رقمك قليلًا حسب الرّموز)، وفي المقدّمة عناوين تحوي «climate change». تُطبِّق القيمة AUTO قاعدة ذكيّة: صفر تحرير مسموح به لمصطلح من 1 إلى 2 حرف، وواحد لـ3 إلى 5 أحرف، واثنان لـ6 أحرف فأكثر. هذا هو الضّبط الافتراضيّ الذي يجب الاحتفاظ به.
phrase suggester: «Did you mean»
حين تقع الأخطاء على عدّة كلمات، يُولِّد suggester مخصَّص أرجح جملة مصحَّحة:
GET news/_search
{
"suggest": {
"correction": {
"text": "climat chnage",
"phrase": {
"field": "headline",
"size": 3,
"gram_size": 3,
"direct_generator": [
{ "field": "headline", "suggest_mode": "always" }
]
}
}
}
}
الاستجابة (مقتطف):
climate change (score élevé)
climate changes
climat change
يستعمل phrase suggester نموذج لغة على n-grams للحقل لترتيب التّصحيحات حسب المعقوليّة. هذا ما تعرضه المحرّكات وراء الجملة الكلاسيكيّة «هل تقصد: …».
fuzziness: AUTO على terms ضخم أو على بادئة قصيرة ("a" أو "le") يصبح بطيئًا ويُلوّث النّتائج. احتفظ به للحقل الرّئيسيّ (headline) وعلى استعلامات مؤلَّفة من مصطلحَين فأكثر. على الحقول الأخرى ابقَ على match صارم.
match_phrase مع slop: الجملة المرنة
يبحث match_phrase عن العبارة بالتّرتيب ومتجاورة. يسمح slop لـElasticsearch بقبول بضع كلمات بين المصطلحات (أو انقلاب) مع احترام فكرة الجملة.
GET news/_search
{
"query": {
"match_phrase": {
"headline": {
"query": "climate change",
"slop": 2
}
}
},
"size": 3
}
بـslop: 0 (القيمة الافتراضيّة)، يطابق الاستعلام «climate change» متجاورة فقط. بـslop: 2، يطابق كذلك «climate is changing» و«change in climate» و«climate rapid change». ينخفض score كلّما زاد عدد الإزاحات اللّازمة. هذا ما تريده الشّاشة الرّئيسيّة في Veille: نسمح ببعض المرونة حول العبارة، دون الوقوع في match مُنفلت.
multi_match مع الوزن
عنوان يحمل معنى أكبر من ملخّص: نُعزِّز headline مقارنةً بـshort_description.
GET news/_search
{
"query": {
"multi_match": {
"query": "climate change",
"fields": ["headline^3", "short_description"],
"type": "best_fields",
"fuzziness": "AUTO"
}
},
"size": 5,
"_source": ["headline", "category"]
}
اللّاحقة ^3 تضرب مساهمة الحقل headline في score بثلاثة. مقرونًا بـfuzziness: AUTO، هذا هو الاستعلام «القويّ» لشريط البحث: يسامح على الأخطاء، ويُفضّل العناوين، ويعبر عبر عدّة حقول. هذا ما يُوَصِّله Karim في النّسخة الأولى من الواجهة البرمجيّة.
ES|QL: لغة الأنابيب من Elasticsearch
ES|QL (Elasticsearch Query Language) لغة أنابيب (شبيهة بـSplunk وSQL) ظهرت في Elasticsearch 8 واستقرّت في 9. تُكمل Query DSL: بينما DSL هو JSON تعريفيّ مصمَّم للبحث الموزون، تربط ES|QL FROM وWHERE وSTATS وSORT وLIMIT في سطر واحد قابل للقراءة، مصمَّمة للتّحليل.
ثلاث طرق لتنفيذها:
- في Kibana Dev Tools عبر نقطة النّهاية
POST _query({"query": "..."}). - في Discover (Kibana) بتبديل مُنتقي اللّغة من KQL إلى ES|QL.
- من Python (الوحدة 13) عبر العميل الرّسميّ
elasticsearch.
مثال 1 — عدّ المقالات حسب الصّنف
POST _query
{
"query": "FROM news | STATS n = COUNT(*) BY category | SORT n DESC | LIMIT 10"
}
الاستجابة (أوّل أسطر values):
POLITICS 32739
WELLNESS 17827
ENTERTAINMENT 16058
TRAVEL 9887
STYLE & BEAUTY 9649
سطر واحد، أنبوب واحد، نتيجة جدوليّة مباشرة. في Query DSL الكلاسيكيّ، الشّيء نفسه كان يتطلّب size: 0 وterms بـfield: category وترتيبًا، إضافةً إلى قليل من ضجيج JSON.
مثال 2 — تصفية وتجميع وترتيب
POST _query
{
"query": "FROM news | WHERE category == \"POLITICS\" | STATS n = COUNT(*) BY category | SORT n DESC | LIMIT 10"
}
النّتيجة: POLITICS 32 739. نحافظ هنا على البنية STATS ... BY category لإظهار البنية النّحويّة؛ على مجموعة واحدة، يكفي STATS n = COUNT(*) بسيط.
مثال 3 — أفضل الكتّاب على مدى تواريخ
POST _query
{
"query": "FROM news | WHERE date >= \"2017-01-01\" AND date < \"2018-01-01\" | STATS articles = COUNT(*) BY authors.raw | SORT articles DESC | LIMIT 5"
}
النّتيجة المتوقَّعة (قد يختلف رقمك قليلًا):
Reuters 1 900+
Lee Moran 600+
Ed Mazza 500+
Ron Dicker 450+
Cole Delbyck 350+
يبقى الأنبوب قابلًا للقراءة حتّى حين نُكدِّس عدّة مصفّيات، وهي بالضّبط ورقة البيع لـES|QL عند Léa (التي تكتب ثلاثين استعلام تحليل أسبوعيًّا).
مثال 4 — استخراج سنة وعمل pivot
POST _query
{
"query": "FROM news | EVAL annee = DATE_EXTRACT(\"year\", date) | STATS n = COUNT(*) BY annee, category | SORT annee ASC, n DESC | LIMIT 20"
}
EVAL يُنشئ حقلًا محسوبًا (annee) على كلّ سطر، وSTATS ... BY annee, category يُجري تجميعًا متقاطعًا. النّتيجة: الأصناف السّائدة في كلّ سنة، من 2012 إلى 2018. هذا هو الاستعلام الذي نكتبه في ثلاثين ثانية لتحضير رسم بيانيّ.
Query DSL وKQL وES|QL: متى نستعمل ماذا
| المعيار | Query DSL | KQL | ES|QL |
|---|---|---|---|
| الصّيغة | JSON تعريفيّ | سلسلة مضغوطة (category : "POLITICS" and headline : trump) | أنبوب FROM ... | ... |
| أين | REST، عميل، Dev Tools | Discover وLens وAlerting (Kibana) | Dev Tools وDiscover وعميل |
الملاءمة والـ_score | نعم (BM25، explain) | نعم (طبقة رقيقة على DSL) | ليست مصمَّمة لذلك (نتيجة جدوليّة) |
| التّجميعات المعقّدة | نعم (مُطنِبة) | لا | نعم، قابلة جدًّا للقراءة |