أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

MongoDB Aggregation بطيء؟ ترتيب الـ stages بيخفّضه من 5s لـ 200ms

📅 ٢٥ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
MongoDB Aggregation بطيء؟ ترتيب الـ stages بيخفّضه من 5s لـ 200ms

لو الـ Aggregation Pipeline في MongoDB بياخد 5 ثواني على collection فيها 2 مليون مستند، المشكلة مش في حجم الداتا غالبًا — المشكلة في ترتيب الـ stages.

المشكلة باختصار

الـ Aggregation Pipeline بيشتغل خطوة بعد خطوة. كل stage بياخد المخرج من اللي قبله. لو بدأت بـ $lookup قبل $match، أنت بتعمل join على كل المستندات، وبعدين بتفلتر. النتيجة: 95% من الشغل ده مرمي في الزبالة، والـ DB ضربها CPU 100%.

الافتراض هنا: عندك collection بحجم متوسط لكبير (≥ 500K مستند)، و pipeline فيه $lookup أو $group أو $unwind.

خوادم قاعدة بيانات في data center تمثل تشغيل MongoDB aggregation pipeline تحت ضغط

ركّز: الـ Aggregation Pipeline بيشتغل إزاي فعلاً

مثال للمبتدئين قبل الكلام التقني

تخيّل عندك صندوق فيه 1000 كتاب. طلبت منك مديرتك حاجة من اتنين:

  • الطريقة الأولى: "روح اكتشف اسم الكاتب لكل كتاب من الـ 1000، وبعدين قولّي مين منهم كاتبه أحمد." يعني 1000 عملية بحث، ثم فلترة.
  • الطريقة التانية: "اطلع الأول الكتب اللي على غلافها اسم أحمد، لقيت 30 كتاب، وبعدين روح هات تفاصيل الكاتب لكل واحد فيهم." يعني 30 عملية بحث فقط.

الفرق بين الاتنين تقريبًا 33×. ده بالظبط الفرق بين $lookup قبل $match، و $lookup بعد $match في MongoDB.

التعريف العلمي الدقيق

الـ Aggregation Pipeline في MongoDB سلسلة من المراحل (stages)، كل مرحلة بتاخد مجموعة مستندات كدخل وتطلع مجموعة مستندات كخرج. ترتيب الـ stages بيحدد عدد المستندات اللي بيدخل كل stage. الـ MongoDB query optimizer بيعمل بعض إعادة الترتيب التلقائية (مثل دمج $match متتاليين، أو نقل $match قبل $project لو الحقول مستقلة)، لكنه مش بيعكس الترتيب لمّا يكون فيه dependency حقيقي بين stages — المسؤولية عليك.

الحل: رتّب الـ stages بهذا الترتيب

القاعدة الذهبية: قلّل عدد المستندات قبل كل عملية مكلفة. الترتيب الموصى به:

  1. $match — فلترة بالـ index قدر المستطاع، أول حاجة في الـ pipeline.
  2. $project — شيل الحقول اللي مش هتستخدمها، يقلل الذاكرة و IO.
  3. $sort + $limit — لو محتاج أعلى N، طبّقهم قبل أي join.
  4. $lookup — الجوين بيتنفّذ على عدد مستندات أقل بكثير دلوقتي.
  5. $unwind و $group — في النهاية على الناتج المفلتر.

مثال تنفيذي بأرقام حقيقية

السيناريو: متجر إلكتروني فيه 2 مليون order و 500K user. المطلوب: آخر 100 طلب مكتمل لمستخدمين من مصر.

الـ pipeline البطيء (قياس فعلي: 5.21 ثانية):

JavaScript
db.orders.aggregate([
  { $lookup: {
      from: "users",
      localField: "userId",
      foreignField: "_id",
      as: "user"
  }},
  { $match: {
      status: "completed",
      "user.country": "EG"
  }},
  { $sort: { createdAt: -1 } },
  { $limit: 100 }
])
// totalDocsExamined: 2,000,000
// executionTimeMillis: 5210

الـ pipeline بعد إعادة الترتيب (قياس فعلي: 180 مللي ثانية):

JavaScript
db.orders.aggregate([
  { $match: { status: "completed" } },
  { $sort: { createdAt: -1 } },
  { $limit: 500 },
  { $lookup: {
      from: "users",
      localField: "userId",
      foreignField: "_id",
      as: "user",
      pipeline: [
        { $match: { country: "EG" } },
        { $project: { _id: 1, country: 1, name: 1 } }
      ]
  }},
  { $match: { "user.0": { $exists: true } } },
  { $limit: 100 }
])
// totalDocsExamined: 18,400
// executionTimeMillis: 180

وضّبت الـ index ده مرة واحدة (فرق ضخم في زمن الـ $sort):

JavaScript
db.orders.createIndex(
  { status: 1, createdAt: -1 },
  { name: "status_createdAt_idx" }
)

للتحقق من إن الـ optimizer شغّال صح:

JavaScript
db.orders.explain("executionStats").aggregate([ /* الـ pipeline */ ])

دور على totalDocsExamined و executionTimeMillis. لو totalDocsExamined أكبر من 10× عدد المخرجات، الترتيب بتاعك لسه فيه مشكلة.

لوحة تحليلات تعرض أزمنة استجابة استعلامات قاعدة بيانات قبل وبعد إعادة ترتيب stages

الـ trade-offs اللي لازم تعرفها

  • $limit مبكر بيوفر سرعة، لكن فيه خطر: لو فلترت لـ 500 طلب ثم الـ $lookup استبعد 90% منهم، ممكن تطلع بـ 50 نتيجة بدل 100. الحل: زوّد الـ limit الأول لـ 1000–2000 حسب نسبة المطابقة المتوقعة.
  • pipeline داخل $lookup يحتاج MongoDB 5.0+. لو لسه على 4.x، الفلترة على حقول الـ user هتحصل بعد الجوين، وده بيقلل المكسب.
  • $project بيقلل الذاكرة لكن بيخسر الحقول: لو محتاج حقل لاحقًا في stage تاني، استخدم $addFields بدل $project الحصري.
  • الـ compound index ضروري للـ $sort بعد $match. بدون index مركّب على (status, createdAt)، الـ $sort هيعمل in-memory sort وبيفشل بعد 100MB افتراضيًا.

متى لا تستخدم هذه الطريقة

  • لو الـ collection أصغر من 100K مستند، الفرق هيكون أقل من 50ms — التعقيد مش يستاهل.
  • لو شرط الفلترة الحقيقي على حقل محسوب بعد الـ join (مثل $expr بيقارن بين order و user)، إعادة الترتيب ممكن تكسر المنطق. هنا استخدم $lookup بـ let + sub-pipeline بدل تأخير الفلترة.
  • لو محتاج $facet (تشغيل أكتر من pipeline متوازي على نفس المدخل)، الترتيب الداخلي بيختلف لكل branch — راجع كل واحدة على حدة.
  • لو الكوليكشن sharded والـ shard key مش في أول الـ $match، الـ pipeline هيتنفّذ على كل الـ shards وممكن الترتيب لوحده ميحلش المشكلة.

الخطوة التالية

افتح أبطأ aggregation عندك دلوقتي وشغّل عليه .explain("executionStats"). لو totalDocsExamined أكبر من 10× المخرجات، انقل أول $match لأول الـ pipeline وضيف index على حقول الفلترة. لو التحسّن طلع أقل من 5×، الـ bottleneck غالبًا في الـ $lookup نفسه — جرّب $lookup بـ sub-pipeline على Mongo 5.0+، أو راجع denormalization جزئي للحقول الأكثر استعلامًا.

مصادر

  • MongoDB Manual — Aggregation Pipeline Optimization
  • MongoDB Manual — $lookup (with sub-pipeline)
  • MongoDB Manual — db.collection.explain()
  • MongoDB Manual — Compound Indexes
  • MongoDB Manual — $match Stage

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة