الستیک سرچ بخش سوم: کوئریهای Bool، هایلایتینگ و صفحهبندی
اگه این سری مقالهها رو دنبال کرده باشین، تا الان میدونین که:
- در بخش اول: کوئریها در برابر فیلترها (Queries vs Filters)، مدل ذهنی پشت کوئریهای الاستیکسرچ و اینکه چرا «مپینگ» (Mapping) انقدر حیاتیه رو بررسی کردیم.
- در بخش دوم: انواع کوئریهای پرکاربرد (Practical Query Types)، رایجترین مدلهای کوئری مثل
match
،term
وrange
رو قدم به قدم دیدیم.
حالا وقتشه بریم سراغ یک بخش مهم و حیاتی: بول کوئری (Bool Query).
چرا؟ چون هیچ مشکل جستجوی واقعیای توی دنیای واقعی فقط با یه شرط حل نمیشه. کاربرها هم «ربط داشتن» (Relevance) رو میخوان و هم «محدودیت» (Restrictions):
- یه جستجو با کلمه کلیدی (یعنی ربط داشتن).
- اما در عین حال، فیلتر کردن بر اساس مجوزها، بازه زمانی، دستهبندیها، موجودی کالا یا قیمت.
بول کوئری همون ابزاریه که همه این شرطها رو به هم میچسبونه و به یه نتیجهی واحد میرسونه.
راستی این رو هم بگم که من این سری رو بیشتر برای تثبیت یادگیری خودم و به عنوان یک رفرنس برای مراجعههای آتی نوشتم. پس اگه حس کردین خیلی خلاصه و کوتاه هست یه جاهایی بگین بیشتر توضیح بدم.
چرا بول کوئری همه جا هست؟
الاستیکسرچ به صورت خودکار، اکثر کوئریهای ساده رو پشت صحنه توی یک bool
میپیچونه. چرا؟ چون جستجوی واقعی = لایههای منطقی مختلف.
bool
رو مثل موتوری ببین که وظیفهاش ترکیب کردن قوانین و رتبهبندی نتایجه:
must
: هسته اصلی معنی کوئری.filter
: محدودیتهای سفت و سخت (سریعتر، کَش میشه، امتیازی نداره).should
: چیزهای ‘اگه باشه چه بهتر’ که امتیاز رو بالا میبرن.must_not
: استثناهایی که باید حذف بشن.
یه مثال برای درک بهتر: اگه جستجو مثل یه فرآیند استخدام باشه:
must
: مهارتهای ضروری مورد نیاز.filter
: معیارهای صلاحیت (مثل سن، موقعیت مکانی).should
: امتیازات مثبت و اضافی (مثل تسلط به زبان انگلیسی، مشارکت در پروژههای اوپن سورس).must_not
: «خط قرمزها» یا دلایل حذف از فرآیند (ساخت رزومه فیک).
چهار ستون اصلی بول کوئریها
۱. must
: مطابقت و تأثیرگذاری روی امتیاز (Score)
اینو وقتی استفاده کن که شرط مورد نظر باید هم نتایج رو فیلتر کنه و هم روی رتبهبندی تأثیر بذاره.
مثال: پیدا کردن پستهای وبلاگی که کلمه «python» توی عنوانشون هست.
{ "query": { "bool": { "must": [ { "match": { "title": "python" } } ] } } }
نکات:
- دادههایی که «python» توی عنوانشون نباشه، اصلاً نشون داده نمیشن.
- دادههایی که مطابقت قویتری دارن، رتبه بالاتری میگیرن.
کاربرد واقعی:
- پورتال کاریابی: باید حتماً «data engineer» توی عنوان شغلی مطابقت داشته باشه.
- جستجوی محصول: باید حتماً «لپتاپ» توی اسم محصول مطابقت داشته باشه.
۲. filter: محدود کردن بدون امتیازدهی (Scoring)
بندهای filter
مجموعه نتایج رو محدود میکنن، اما روی امتیاز و رتبهبندی تأثیر نمیذارن.
- مزیت بزرگ: این فیلترها کَش (Cached) میشن و فوقالعاده سریع هستن.
مثال: فقط مقالههای منتشر شده (Published) رو نشون بده.
{ "query": { "bool": { "must": [ { "match": { "content": "python" } } ], "filter": [ { "term": { "status": "published" } } ] } } }
نکات:
- از این برای فیلترهایی مثل تاریخها، دستهبندیها، یا مجوزهای دسترسی استفاده کنین.
filter
ها رو به عنوان “موارد غیرقابل مذاکره” در نظر بگیرین.
کاربرد واقعی:
- فروشگاه اینترنتی (E-commerce): فیلتر کردن محصولاتی که
in_stock = true
دارن (موجودی دارن). - سایت خبری: فیلتر کردن مقالههایی که
published_at >= now-30d
(در ۳۰ روز اخیر منتشر شدن).
۳. should: امتیاز اضافی برای مطابقتهای ترجیحی (Boost)
بندهای should
اختیاری هستن؛ اما اگه مطابقت پیدا کنن، امتیاز ربط داشتن رو بالاتر میبرن.
مثال: به آموزشها (Tutorials) و محتوای جدیدتر اولویت بده.
{ "query": { "bool": { "must": [ { "match": { "content": "python" } } ], "should": [ { "match": { "tags": "tutorial" } }, { "range": { "published_at": { "gte": "2024-01-01" } } } ], "minimum_should_match": 0 } } }
نکات:
- بدون مطابقت
should
هم، اسناد همچنان نمایش داده میشن. - با مطابقت
should
، اسناد رتبه بالاتری میگیرن. - از
minimum_should_match
استفاده کنین تا حداقل N تا شرطshould
حتماً مطابقت پیدا کنن (در مثال بالا، ۰ گذاشتیم تا کاملاً اختیاری باشه).
کاربرد واقعی:
- جستجوی «python» اما بالا بردن امتیاز اسنادی که تگ «beginner-friendly» دارن.
- جستجوی «لپتاپ» اما بالا بردن امتیاز مدلهای «۲۰۲۴».
۴. must_not: قوانین حذف سختگیرانه
برای حذف کردن اسنادی که نمیخواین نشون داده بشن.
مثال: پیشنویسها (Drafts) رو حذف کن.
{ "query": { "bool": { "must": [ { "match": { "content": "python" } } ], "must_not": [ { "term": { "status": "draft" } } ] } } }
نکات:
- عالی برای حذف اسپم، کاربرهای بلاکشده، یا محصولات مخفی.
کاربرد واقعی:
- مارکتپلیس:
must_not
نشون دادن محصولاتی که به عنوان «ممنوعه» تگ خوردن. - جستجوی داخلی:
must_not
نشون دادن اسنادی که «محرمانه» (confidential) علامت خوردن.
Must در برابر Filter: قانون طلایی
یکی از جاهایی که کسایی که تازه الستیکسرچ رو شروع کردن گیر میکنن همینجاست. به طور کلی یادتون باشه:
- از
must
استفاده کنین وقتی شرط باید روی رتبهبندی ربط داشتن تأثیر بذاره. - از
filter
استفاده کنین وقتی شرط فقط یک محدودیت ساده است.
مثال:
- لپتاپ زیر ۱,۰۰۰ دلار →
filter
. (محدودیت قیمت، ربطی به امتیاز نداره) - لپتاپ با کلمه «گیمینگ» توی عنوان →
must
. (مؤثر روی امتیاز و ربط داشتن)
این انتخاب هم روی عملکرد (Performance) و هم روی رضایت کاربر تأثیر میذاره.
هایلایتینگ (Highlighting): نشون بده چرا نتیجه مطابقت داشت
یه وقتایی خوبه خودمون ببینیم که چرا یک نتیجه نشون داده شده. همچنین میتونین کلی کارای جذاب توی فرانت بکنین و به کاربر قسمتهای مرتبط با سرچش رو نشون بدیم
مثال:
{ "query": { "match": { "content": "python tutorial" } }, "highlight": { "fields": { "content": {} } } }
نمونه خروجی:
"highlight": { "content": [ "... توی این <em>آموزش </em>، یاد بگیرین که چطور قدم به قدم < em> (python) </em> رو یاد بگیرین ..." ] }
چرا هایلایتینگ مهمه؟
- نرخ کلیک (Click-through rates) رو بهتر میکنه.
- اعتماد کاربر رو به نتایج جستجو جلب میکنه.
- کمک میکنه کاربر سریعتر محتوا رو مرور کنه.
صفحهبندی (Pagination) و مرتبسازی (Sorting)
۱. صفحهبندی معمولی (from + size)
{ "from": 0, "size": 10, "query": { "match": { "content": "python" } } }
برای صفحههای اول خوبه. اما اگه بخواین به صفحههای خیلی عمیق برین (مثلاً from = 10000
)، خیلی هزینهبر و کُند میشه.
۲. صفحهبندی عمیق با search_after
این روش برای اسکرول بینهایت (Infinite Scroll) یا دکمههای «بارگذاری بیشتر» (Load More) خیلی کارآمد و بهینه است.
{ "size": 10, "query": { "match": { "content": "python" } }, "sort": [{ "published_at": "desc" }], "search_after": ["2024-07-10T10:15:00"] // این تاریخ آخرین سند در صفحه قبله }
نکته مهم: یادتون باشه، وقتی از search_after
استفاده میکنید، باید مقادیر آرایهی sort
آخرین سندی که توی صفحهی قبل دیدید رو بهش بدید. حالا ممکنه بپرسید، “اگه چند فیلد برای مرتبسازی داشته باشیم چی؟”
جواب ساده است: شما باید کل مقادیر فیلدهای مرتبسازی (sort values) رو به صورت یه تاپل (tuple) توی search_after
بدید و ترتیب مقادیر باید دقیقاً با ترتیب فیلدها در sort
مطابقت داشته باشه:
{ "size": 10, "sort": [ { "timestamp": "asc" }, { "id": "desc" } ], "search_after": ["2025-09-26T12:00:00Z", 1234] // مقدار timestamp و id آخرین سند }
۳. مرتبسازی بر اساس چند فیلد
"sort": [ { "_score": "desc" }, { "published_at": "desc" } ]
این کار مرتبسازی پایدار (Stable Sorting) رو تضمین میکنه (مخصوصاً وقتی که تعداد زیادی سند امتیاز _score
یکسان دارن، مهمه).
جمعبندی نهایی: یک مثال واقعی
سناریو: فرض کنید از الستیک سرچ توی یه موتور جستجوی وبلاگ استفاده کردین و:
- باید «python» توی عنوانش باشه. (
must
) - فقط مقالههای منتشر شده. (
filter
) - پیشنویسها حذف بشن. (
must_not
) - آموزشها و محتوای جدید امتیاز بالاتری بگیرن. (
should
) - اسنیپتهای هایلایت شده رو نشون بده.
- بر اساس امتیاز و بعد تاریخ انتشار مرتبسازی کن.
- صفحهبندی ۱۰ تا در هر صفحه.
{ "query": { "bool": { "must": [ { "match": { "title": "python" } } ], "filter": [ { "term": { "status": "published" } } ], "must_not": [ { "term": { "category": "draft" } } ], "should": [ { "match": { "tags": "tutorial" } }, { "range": { "published_at": { "gte": "2024-01-01" } } } ], "minimum_should_match": 0 } }, "highlight": { "fields": { "content": {} } }, "sort": [ { "_score": "desc" }, { "published_at": "desc" } ], "from": 0, "size": 10 }
خلاصه
بول کوئری (bool query) مثل آچار فرانسه الاستیکسرچ میمونه. با تسلط بر اون میتونین:
- شرطهای مختلف رو به آسونی به هم بچسبونین.
- بین ربط داشتن (
must
,should
) و محدودیتها (filter
,must_not
) تعادل برقرار کنین. - تجربه کاربری (UX) رو با هایلایتینگ بهتر کنین.
- با صفحهبندی و مرتبسازی درست، نتایج رو سریع و مقیاسپذیر نگه دارین.
در بخش بعدی این سری مقالات:
در مورد تجمیعها (Aggregations) – تبدیل جستجو به تحلیل دادهها صحبت میکنیم و احتمالا یک معرفی از داشبورد سازی با kibana رو خواهیم داشت