لو متجرك العربي فيه 38 ألف منتج وبتستخدم WHERE name LIKE '%موبايل%'، انت بتدفع ثانيتين على كل استعلام، وعمرك ما هترجع نتيجة لو المستخدم كتب "موبيل" بدون ياء. PostgreSQL عنده full-text search مدمج بيحل المشكلة دي في 12ms بدون ما تنزّل Elasticsearch ولا تدفع $50 شهرياً.
بحث عربي ذكي في PostgreSQL: من LIKE البطيء لـ tsvector السريع
المشكلة باختصار
الـ LIKE في SQL بيعمل sequential scan على كل صف. على جدول فيه 38K منتج، ده معناه قراءة كل سطر من القرص ومقارنته. الأسوأ: LIKE '%كلمة%' ميقدرش يستخدم أي index، لأن الـ B-tree بيرتب من البداية مش من النص الجوّاني.
زي القصة دي: متجر إلكتروني مصري عنده 38K منتج. كان الاستعلام بياخد 1,840ms في المتوسط، و54% بس من البحثات بترجّع نتيجة صحيحة (لأن المستخدمين بيكتبوا "موبيل" أو "مُوبايل" أو "تليفون"). بعد التطبيق: 12ms، و recall 91%.
المفهوم الأساسي: tsvector زي فهرس الكتاب
تخيل إنك في مكتبة فيها 10,000 كتاب وحد سألك "فين كتب التاريخ؟". مش هتفتح كل كتاب وتقرا. هتروح للفهرس في آخر كل كتاب، تشوف الصفحات اللي فيها كلمة "تاريخ"، وتجيب الكتاب على طول. tsvector بيعمل نفس الفكرة بالظبط: بياخد النص ويحوّله لقائمة من lexemes (جذور الكلمات بدون حركات وبدون أحرف مكررة)، ويخزنهم في GIN index اللي مصمم خصيصاً لقوائم الـ tokens.
علمياً: PostgreSQL بيمر النص على text search configuration. الـ configuration دي بتقرر 3 حاجات: إيه الـ stopwords (كلمات تتجاهل زي "في" و "من")، إيه الـ stemming algorithm (يرجع "كتاب", "كتب", "مكتبة" لجذر واحد)، وإيه الـ normalization (شيل الحركات والتشكيل).
الحل: 4 خطوات
1) إنشاء configuration عربي
PostgreSQL مفيهوش Arabic configuration افتراضي، لكن فيه Snowball stemmer عربي مدمج من نسخة 13+. هنبنيه:
CREATE TEXT SEARCH CONFIGURATION arabic (COPY = simple);
CREATE TEXT SEARCH DICTIONARY arabic_stem (
TEMPLATE = snowball,
Language = arabic
);
ALTER TEXT SEARCH CONFIGURATION arabic
ALTER MAPPING FOR asciiword, word, numword, asciihword, hword
WITH arabic_stem;2) دالة تطبيع النص
مستخدم بيكتب "مُوبايل" بضمة، الثاني "موبيل" بدون ياء، الثالث "إلكترونيات" بهمزة. لازم نوحّدهم قبل ما نخزن.