بنية خدمة البحث: تطبيق Flask منفرد مع Valkey
هذا المقال يشرح كيف تعمل خدمة البحث الخاصة بنا كتطبيق Flask منفرد على خادم منفصل، باستخدام Valkey (فرع من Redis) للبحث المتجه عالي الأداء، والتخزين المؤقت، والإكمال التلقائي.
المشكلة: أداء وقابلية توسيع البحث
عمليات البحث مكلفة حسابياً:
-
استخراج الفلاتر: مطابقة 2500+ عبارة ضد كل استعلام
-
عمليات البحث ذات الصلة: حساب التشابه عبر 65 ألف استعلام
-
الإكمال التلقائي: مطابقة البادئة على 65 ألف استعلام
-
تصفية المنتجات: تصفية 5000+ منتج بمعايير متعددة
تشغيل هذه العمليات على خادم الويب الرئيسي يتسبب في:
-
تباطؤ تحميل الصفحات: البحث يحجب الطلبات الأخرى
-
ضغط على الذاكرة: التضمينات تشغل مساحة ذاكرة ضخمة
-
تنافس على وحدة المعالجة المركزية: حساب التشابه كثيف الاستخدام لوحدة المعالجة المركزية
-
صعوبة التوسع: لا يمكن توسيع نطاق البحث بشكل مستقل
نحتاج إلى خدمة بحث مخصصة يمكن توسيع نطاقها بشكل مستقل.
الحل: خدمة بحث منفردة
نشغل تطبيق Flask منفصل على خادم مخصص:
خادم الويب الرئيسي
↓ مكالمات واجهة برمجة تطبيقات HTTP
خدمة البحث
↓ استعلامات Valkey
خادم Valkey
هذه البنية توفر:
-
توسع مستقل: توسيع نطاق خدمة البحث دون التأثير على خادم الويب الرئيسي
-
عزل الموارد: عمليات البحث لا تؤثر على خادم الويب الرئيسي
-
التخزين المؤقت: يقوم Valkey بتخزين النتائج مؤقتاً للاستعلامات المتكررة السريعة
-
التوافر العالي: يمكن إعادة تشغيل خدمة البحث دون التأثير على خادم الويب الرئيسي
مكونات خدمة البحث
1. واجهة برمجة تطبيقات استخراج الفلاتر
-
النقطة النهائية:
/api/extract_filters -
الغرض: استخراج فلاتر منظمة من استعلامات اللغة الطبيعية
-
مثال:
GET /api/extract_filters?q=mini+pc+16gb+ram
الاستجابة:
{
"Form Factor": "Mini PC",
"Main Memory": "16"
}
التنفيذ:
-
تحميل مطابقة العبارات للفلاتر من Valkey أو JSON
-
مطابقة العبارات باستخدام تعبير عادي لحدود الكلمات
-
إرجاع الفلاتر المنظمة
التخزين المؤقت: يتم تخزين مطابقة العبارات مؤقتاً في Valkey (مدة صلاحية 30 يوم)
2. واجهة برمجة تطبيقات عمليات البحث ذات الصلة
-
النقطة النهائية:
/api/related -
الغرض: إيجاد استعلامات متشابهة دلالياً باستخدام البحث المتجه
-
مثال:
POST /api/related
{
"query": "mini pc",
"limit": 10
}
الاستجابة:
{
"related": [
{"query": "small computer", "similarity": 0.92},
{"query": "compact desktop", "similarity": 0.89},
{"query": "mini pc 8gb", "similarity": 0.87}
]
}
التنفيذ:
-
تضمين الاستعلام باستخدام all-mpnet-base-v2
-
استعلام RediSearch في Valkey للجيران الأقرب
-
إرجاع أفضل N نتيجة مرتبة حسب التشابه
التخزين المؤقت: يتم تخزين النتائج مؤقتاً في Valkey (مدة صلاحية 7 أيام)
3. واجهة برمجة تطبيقات الإكمال التلقائي
-
النقطة النهائية:
/api/autocomplete -
الغرض: اقتراح استعلامات أثناء كتابة المستخدم
-
مثال:
GET /api/autocomplete?q=mini+p&limit=5
الاستجابة:
{
"suggestions": [
"mini pc",
"mini pc 16gb",
"mini pc 8gb ram",
"mini pc fanless",
"mini pc windows 11"
]
}
التنفيذ:
-
استعلام RediSearch في Valkey بمطابقة البادئة
-
التصنيف حسب الشعبية (درجة الظهور + النقر)
-
إرجاع أفضل N اقتراح
التخزين المؤقت: فهرس الإكمال التلقائي في Valkey (يتم تحديثه يومياً)
4. واجهة برمجة تطبيقات الاستعلامات الشائعة
-
النقطة النهائية:
/api/popular -
الغرض: الحصول على الاستعلامات الأكثر شيوعاً
-
مثال:
GET /api/popular?limit=10
الاستجابة:
{
"queries": [
"mini pc",
"thin client",
"industrial pc",
"all in one pc"
]
}
التنفيذ:
-
تحميل الاستعلامات من Valkey أو JSON
-
الترتيب حسب درجة حركة المرور (الظهورات + النقرات)
-
إرجاع أفضل N استعلام
التخزين المؤقت: يتم تخزين الاستعلامات الشائعة مؤقتاً في Valkey (مدة صلاحية 30 يوم)
تكامل Valkey
Valkey هو فرع من Redis يوفر:
-
البحث المتجه: وحدة RediSearch للبحث عن التشابه
-
التخزين المؤقت: مخزن مفتاح-قيمة سريع في الذاكرة
-
الإكمال التلقائي: مطابقة البادئة مع مجموعات مرتبة
-
الثبات: AOF (ملف الإلحاق فقط) للمتانة
البحث المتجه مع RediSearch
نستخدم وحدة RediSearch في Valkey للبحث عن التشابه المتجه:
إنشاء الفهرس:
client.ft("queries_idx").create_index([
VectorField("embedding", "FLAT", {
"TYPE": "FLOAT32",
"DIM": 768,
"DISTANCE_METRIC": "COSINE"
}),
TextField("query"),
NumericField("score")
])
البحث المتجه:
query_embedding = model.encode(query)
results = client.ft("queries_idx").search(
Query("*=>[KNN 10 @embedding $vec AS score]")
.sort_by("score")
.return_fields("query", "score")
.dialect(2),
query_params={"vec": query_embedding.tobytes()}
)
هذا يُرجع أقرب 10 جيران حسب تشابه جيب التمام.
استراتيجية التخزين المؤقت
نخزن أنواعاً متعددة من البيانات مؤقتاً في Valkey:
مطابقة العبارات (مدة صلاحية 30 يوم):
client.setex(
"seo:phrase_mappings",
30 * 24 * 3600,
json.dumps(phrase_mappings)
)
عمليات البحث ذات الصلة (مدة صلاحية 7 أيام):
cache_key = f"related:{query_hash}"
client.setex(cache_key, 7 * 24 * 3600, json.dumps(results))
الاستعلامات الشائعة (مدة صلاحية 30 يوم):
client.setex(
"seo:popular_queries",
30 * 24 * 3600,
json.dumps(popular_queries)
)
فهرس الإكمال التلقائي (يتم تحديثه يومياً):
for query, score in queries:
client.zadd("autocomplete:mini", {query: score})
الإكمال التلقائي مع المجموعات المرتبة
نستخدم مجموعات Valkey المرتبة للإكمال التلقائي:
هيكل الفهرس:
autocomplete:m → ["mini pc": 5000, "mini computer": 3000]
autocomplete:mi → ["mini pc": 5000, "mini computer": 3000]
autocomplete:min → ["mini pc": 5000, "mini computer": 3000]
autocomplete:mini → ["mini pc": 5000, "mini computer": 3000]
البحث عن البادئة:
prefix = "mini"
results = client.zrevrange(f"autocomplete:{prefix}", 0, 9, withscores=True)
هذا يُرجع أفضل 10 استعلامات تبدأ بـ "mini"، مرتبة حسب النقاط.
اتصال واجهة برمجة التطبيقات
يستدعي خادم الويب الرئيسي خدمة البحث عبر HTTP:
استخراج الفلاتر
from app.shared.filter_service import extract_filters_from_query
filters = extract_filters_from_query("mini pc 16gb ram")
# داخلياً يستدعي: GET SEARCH_SERVICE_URL/api/extract_filters?q=...
عمليات البحث ذات الصلة
import requests
response = requests.post(
"SEARCH_SERVICE_URL/api/related",
json={"query": "mini pc", "limit": 10},
timeout=2
)
related = response.json()["related"]
الإكمال التلقائي
response = requests.get(
"SEARCH_SERVICE_URL/api/autocomplete",
params={"q": "mini p", "limit": 5},
timeout=1
)
suggestions = response.json()["suggestions"]
معالجة الأخطاء والبدائل الاحتياطية
يتعامل خادم الويب الرئيسي مع فشل خدمة البحث بأسلوب أنيق:
try:
filters = extract_filters_from_query(query)
except Exception as e:
logger.error(f"Search service failed: {e}")
filters = {} # العودة إلى فلاتر فارغة
هذا يضمن استمرار عمل خادم الويب الرئيسي حتى لو تعطلت خدمة البحث.
### تكوين الشبكة
جميع الخوادم على شبكة خاصة:
- **خادم الويب الرئيسي**: يمكنه الوصول إلى خدمة البحث و Valkey
- **خدمة البحث**: يمكنها الوصول إلى Valkey
- **Valkey**: يمكن الوصول إليه فقط من خادم الويب الرئيسي وخدمة البحث
لا يوجد وصول خارجي لخدمة البحث أو Valkey.
## التكامل مع خط أنابيب تحسين محركات البحث
تتكامل خدمة البحث مع خط أنابيب تحسين محركات البحث:
### الخطوة 11: الهجرة إلى Valkey
يقوم خط أنابيب تحسين محركات البحث بتحميل البيانات إلى Valkey:
```python
# تحميل تضمينات الاستعلامات
for query, embedding in zip(queries, embeddings):
client.hset(f"query:{query_hash}", mapping={
"query": query,
"embedding": embedding.tobytes(),
"score": score
})
# إنشاء فهرس RediSearch
client.ft("queries_idx").create_index([...])
راجع هجرة Valkey للحصول على التفاصيل.
تسجيل الاستعلامات
تقوم خدمة البحث بتسجيل الاستعلامات لخط أنابيب تحسين محركات البحث:
log_entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"query": query,
"filters_extracted": filters,
"results_count": len(results)
}
with open(SEO_LIVE_QUERIES_LOG, "a") as f:
f.write(json.dumps(log_entry) + "\n")
تتغذى هذه السجلات مرة أخرى في الخطوة 1د: جلب الاستعلامات الحية.
المراجع
المفاهيم التقنية
-
Valkey - الموقع الرسمي
-
Redis - الموقع الرسمي (فرع Valkey)
-
RediSearch - وحدة البحث المتجه
-
تشابه جيب التمام - ويكيبيديا
مقالات ذات صلة
-
استخراج الفلاتر - كيف يتم استخراج الفلاتر
-
إنشاء البحث ذي الصلة - كيف يتم إنشاء عمليات البحث ذات الصلة
-
استراتيجية التضمين - كيف يتم إنشاء التضمينات
-
نظرة عامة على خط أنابيب تحسين محركات البحث - بنية خط الأنابيب الكاملة
-
هجرة Valkey - تحميل البيانات في Valkey
الملخص
تعمل خدمة البحث الخاصة بنا كتطبيق Flask منفرد على خادم منفصل:
البنية:
-
تطبيق Flask منفرد على خادم مخصص
-
Valkey (فرع من Redis) للتخزين المؤقت والبحث المتجه
-
واجهة برمجة تطبيقات HTTP للاتصال بخادم الويب الرئيسي
واجهات برمجة التطبيقات:
-
/api/extract_filters- استخراج فلاتر من الاستعلامات -
/api/related- إيجاد استعلامات متشابهة (بحث متجه) -
/api/autocomplete- اقتراح استعلامات (مطابقة البادئة) -
/api/popular- الحصول على الاستعلامات الشائعة
ميزات Valkey:
-
البحث المتجه (وحدة RediSearch)
-
التخزين المؤقت (مدة صلاحية 30 يوم لمطابقة العبارات)
-
الإكمال التلقائي (مجموعات مرتبة)
-
الثبات (AOF)
المزايا:
-
توسع مستقل
-
عزل الموارد
-
أداء عالي (التخزين المؤقت في Valkey)
-
تحمل الأخطاء (تدهور أنيق)
هذه البنية تمكن من بحث سريع وقابل للتوسع مع الحفاظ على استجابة خادم الويب الرئيسي.