القرار · فصل ٩

امتى تستخدمه، وامتى المعنى مش الكلمة

Tradeoffs · embeddings · semantic search · hybrid
🎯 الهدف: تقرر من متطلبات البحث مش من عدد الصفوف، وتعرف vector search بيكمل الكلمة ومش بيستبدلها.

مفيش رقم سحري

مفيش قاعدة تقول: مليون صف استخدم SQL، وعشر مليون استخدم Elasticsearch. العدد لوحده بيضيّع السؤال.

عشرون مليون، والبحث SKU

كل الاستعلامات تطابق كامل على كود. إندكس في SQL هيبقى أسرع وأبسط. محرك البحث هنا تكلفة من غير مكسب.

مئتا ألف، والبحث تجربة

عايز ترتيب، غلط إملائي، اقتراحات، facets، ومرادفات. محرك بحث منطقي حتى لو العدد “صغير” في نظر حد بيعد الصفوف.

عشر مليون منتج مع خانة بحث هي واجهة المتجر: الحالة الأولى في المذكرة. SQL تفضل للأوردر والمخزون، و Elasticsearch للقائمة والترتيب.

اللي هتكسبه واللي هتدفعه

بتكسب بحث نص سريع، ranking، fuzzy، aggregations، autocomplete، وإمكانية التوزيع. في المقابل:

infrastructureRAMmonitoringsynchronizationfailure handlingreindexingoperational complexity

Elasticsearch بيحب الرام، والـ cluster محتاج حد يشوفه وهو واقع. المزامنة مسار يتكسر. تغيير التحليل يعني إعادة بناء. متضيفوش عشان شركة كبيرة بتستخدمه. ضيفه لما المشكلة اللي في فصل ١ موجودة عندك، وانت مستعد تشغّل الخدمة دي.

Vector: البحث بالمعنى

لحد هنا البحث بالكلمات: التوكن موجود أو قريب إملائيًا. اليوزر ساعات يكتب النِيّة: “موبايل تصويره كويس بالليل”. الوصف ممكن يقول low light و night mode من غير كلمة “بالليل”.

Vector search يحوّل النص لـ embedding: قائمة أرقام يمثلها موديل. Elasticsearch يخزنها في حقل dense_vector ويدور على أقرب المتجهات. ده semantic search. الموديل هو اللي يطلّع الأرقام. متألفش المتجه في الكود.

src/embed.js
export async function embed(text) {  const res = await fetch(process.env.EMBED_URL, {    method: "POST",    headers: { "content-type": "application/json" },    body: JSON.stringify({ text }),  });  if (!res.ok) throw new Error("embed_failed");  const json = await res.json();  return json.vector;}
src/knn.js
export function knnQuery(vector) {  return {    field: "embedding",    query_vector: vector,    k: 20,    num_candidates: 100,  };}

طول dims في الـ mapping لازم يساوي طول متجه الموديل بالظبط. غيّرت الموديل؟ الإندكس القديم مش هيتقارن مع المتجه الجديد. ده reindex، مش زرار.

Hybrid: القائمتين يتلموا بالترتيب مش بالدرجة

درجة BM25 ودرجة القرب بين المتجهات مش على نفس المسطرة. تجمعهم رقم واحد بيفسد الاتنين. Hybrid search يشغّل البحث بالكلمة والبحث بالمتجه، وبعدين يدمج الترتيب بـ RRF (Reciprocal Rank Fusion): كل نتيجة تاخد نقاط من مكانها في كل قائمة، واللي فوق في الاتنين يطلع الأول.

src/hybrid.js
import { textQuery } from "./search-text.js";import { embed } from "./embed.js";export async function hybridSearch(es, q) {  const vector = await embed(q);  return es.search({    index: "products",    retriever: {      rrf: {        retrievers: [          { standard: { query: textQuery(q) } },          { knn: { field: "embedding", query_vector: vector, k: 30, num_candidates: 100 } },        ],        rank_window_size: 50,        rank_constant: 60,      },    },  });}

شكل الـ retriever اتظبط بين الإصدارات. راجع الـ docs قبل ما تنقله. لو الإصدار عندك لسه من غير RRF، نفّذ البحثين وادمّج الترتيب في Express بنفس الفكرة، ومتجمعش _score مع مسافة المتجه.

الفلاتر (مخزون، سعر، براند) لسه لازمة على الناحيتين. نتيجة “قريبة في المعنى” بس خلصت من المخزن، أو من قسم تاني، بتبوّظ الصفحة زي ما الكفر كان بيبوّظ الترتيب.

🧪 Lab

خد تلات أسئلة واكتب تحت كل واحد: keyword، vector، ولا hybrid.

  1. APL-IP15P-256
  2. ifone 15 pro
  3. “عايز لابتوب خفيف أسافر بيه ومش هألعب عليه”

الأول keyword وفي الغالب تطابق SKU. التاني keyword مع fuzzy، والـ vector ممكن يساعد بس مش أساسي. التالت semantic أو hybrid، لأن الكلمات الحرفية “خفيف” و “سفر” ممكن متبقاش في العنوان. لو التالت رجع شنطة لابتوب قبل اللابتوب، الفلاتر أو وزن القسم لسه مش مظبوطين.

✅ الخلاصة — Checklist

🧠 اختبر نفسك
١) عشرون مليون منتج والبحث كله برقم SKU. Elasticsearch؟
غالبًا لأ. إندكس SQL على الـ SKU يكفي، ومحرك البحث هيزود تشغيل من غير مكسب.
٢) ليه منجمعش درجة BM25 على درجة المتجه؟
مش نفس المقياس. الدمج الصح على الترتيب، زي RRF.
٣) غيّرت موديل الـ embeddings. إيه المطلوب؟
إعادة index. طول المتجه ومعناه اتغيروا، والمقارنة مع القديم مش صح.