معظم فرق SaaS تلجأ إلى Elasticsearch فور أن يصبح البحث متطلباً. لكن تشغيله في الإنتاج يعني كتلة أخرى للتوفير وخط نشر آخر ومزامنة بيانات للحفاظ عليها. لمعظم منتجات SaaS دون 10 ملايين سجل لكل مستأجر، البحث النصي في PostgreSQL يكفي.
معظم فرق SaaS تلجأ إلى Elasticsearch فور أن يصبح البحث متطلباً في المنتج. إنه الحل الصناعي القوي، لكنه يأتي بتكاليف تشغيلية حقيقية: كتلة أخرى للتوفير، ومجموعة اتصالات أخرى للإدارة، ومزامنة بيانات للحفاظ عليها بين قاعدة البيانات الأساسية وفهرس البحث. لمعظم منتجات SaaS دون 10 ملايين سجل لكل مستأجر، البحث النصي الكامل في PostgreSQL يتعامل مع بحث المنتجات وبحث العملاء واسترجاع المستندات بشكل كافٍ تماماً.
كيف يعمل البحث النصي الكامل في PostgreSQL؟
البحث النصي في PostgreSQL يمركز حول نوعين من البيانات: tsvector وهو قائمة معالجة ومُسوَّية من الكلمات القابلة للبحث، وtsquery وهو استعلام البحث المُحلَّل الذي يُطابق مع المتجهات.
SELECT to_tsvector('english', 'Organic Lebanese olive oil from Bekaa Valley');
-- النتيجة: 'bekaa':5 'lebanes':2 'oil':4 'oliv':3 'organ':1 'valley':6
تُحذف الكلمات الشائعة مثل "from"، ويُختزل "Lebanese" إلى "lebanes" حتى تتطابق عمليات البحث عن "lebanon" مع "Lebanese".
استراتيجية الفهرسة في SaaS متعدد المستأجرين
في مخطط PostgreSQL متعدد المستأجرين، الجداول التي تحتاج بحثاً تحصل على عمود tsvector يُملأ بمشغّل. يجب أن يدعم فهرس GIN مرشح المستأجر الذي يرافق دائماً استعلام البحث:
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
name TEXT NOT NULL,
description TEXT,
sku TEXT,
search_vec TSVECTOR
);
CREATE INDEX products_search_gin ON products USING GIN(search_vec);
يُحتفظ بـ tsvector بواسطة مشغّل:
CREATE OR REPLACE FUNCTION update_product_search_vec() RETURNS TRIGGER AS $$
BEGIN
NEW.search_vec :=
setweight(to_tsvector('english', coalesce(NEW.name, '')), 'A') ||
setweight(to_tsvector('english', coalesce(NEW.sku, '')), 'B') ||
setweight(to_tsvector('english', coalesce(NEW.description, '')), 'C');
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
setweight() يُعيّن الأولوية: تطابقات الاسم تتفوق على تطابقات الـ SKU التي تتفوق على تطابقات الوصف.
الاستعلام من Go مع sqlc
-- name: SearchProducts :many
SELECT id, tenant_id, name, sku,
ts_rank(search_vec, query) AS rank
FROM products,
websearch_to_tsquery('english', @search_term) query
WHERE tenant_id = @tenant_id
AND search_vec @@ query
ORDER BY rank DESC
LIMIT @limit_count OFFSET @offset_count;
websearch_to_tsquery() تُحلل مدخلات اللغة الطبيعية وتتعامل مع العبارات المقتبسة وAND وOR والنفي تلقائياً.
التعامل مع المحتوى العربي
لمنتجات SaaS التي تخدم أسواقاً ناطقة بالعربية في لبنان والخليج، النهج الأبسط والأكثر موثوقية هو استخدام الإعداد البسيط simple للنص العربي:
CREATE TEXT SEARCH CONFIGURATION arabic_simple (COPY = simple);
نمط عملي للمنتجات ثنائية اللغة هو تخزين عمودَي tsvector: واحد للمحتوى الإنجليزي وآخر للمحتوى العربي:
ALTER TABLE products ADD COLUMN search_vec_ar TSVECTOR;
الاستعلام يستخدم كليهما:
WHERE tenant_id = @tenant_id
AND (search_vec @@ websearch_to_tsquery('english', @search_term)
OR search_vec_ar @@ to_tsquery('arabic_simple', @search_term))
خصائص الأداء على نطاق واسع
فهارس GIN على tsvector سريعة لنمط البحث ضمن صفوف مستأجر واحد. عند مليون منتج لكل مستأجر، استعلام بحث مع بحث فهرس GIN يليه مرشح المستأجر يعمل في أقل من 20ms على مثيل RDS من db.t3.medium.
نقطة الاختناق تنتقل إلى حساب ts_rank عندما تكون مجموعات النتائج كبيرة (أكثر من 10,000 تطابق)، وليس إلى مسح الفهرس.
متى تنتقل إلى نظام بحث مخصص؟
البحث النصي في PostgreSQL ليس الأداة المناسبة لكل حالة. انتقل إلى Typesense أو Meilisearch عندما:
- تحتاج مطابقة فازية "هل تقصد؟" على نطاق واسع
- محتواك غير منظم: ملفات PDF، رسائل إلكترونية، ملاحظات حرة
- تحتاج بحثاً مواجهياً مع أعداد ديناميكية عبر ملايين السجلات
دروس من الإنتاج
البحث النصي الكامل في PostgreSQL حل عملي وجاهز للإنتاج لمعظم احتياجات البحث في نطاق 0 إلى 5 ملايين سجل لكل مستأجر. الميزة التشغيلية للبقاء داخل PostgreSQL تفوق الفجوة في الميزات مقابل Elasticsearch حتى تتجاوز متطلبات البحث ما يتعامل معه tsvector.
هل تحتاج إلى بحث في منتجك دون كتلة بحث مخصصة؟
فريق Voxire يبني خلفيات Go SaaS ونماذج بيانات PostgreSQL للشركات في لبنان والمنطقة.
https://voxire.com/get-a-quote/



