كانت صفحاتنا تموت ببطء: كيف أنقذنا ‘التحميل المسبق’ (Eager Loading) من جحيم استعلامات N+1؟

يا أهلاً بكل المبرمجين والمبرمجات، المبتدئين منهم والمحترفين. اسمي أبو عمر، واليوم بدي أحكيلكم قصة صارت معي ومع فريقي قبل كم سنة، قصة علّمتنا درس ما بننساه عن أهمية فهم كيف الكود تبعنا بحكي مع قاعدة البيانات.

كنا وقتها شغالين على مشروع كبير، منصة اجتماعية بتشبه الفيسبوك نوعاً ما. بعد شهور من التعب والسهر، أطلقنا النسخة التجريبية. في البداية، كل إشي كان تمام التمام والسرعة ممتازة. لكن مع زيادة عدد المستخدمين والبيانات، بلشت الشكاوى توصلنا: “الصفحة الرئيسية بطيئة كثير!”، “صفحة المنشورات بتعلّق!”

قعدنا نضرب أخماس بأسداس. السيرفرات مواصفاتها عالية، والكود مكتوب بأحدث التقنيات، وين المشكلة يا جماعة؟ واحد من الشباب اقترح نزيد موارد السيرفر، وآخر قال يمكن لازم نعمل Caching. بس أنا كان إحساسي بقول إنه المشكلة أعمق من هيك. قعدت مع فنجان قهوة، وفتحت الـ logs بتاعت السيرفر، وخصوصاً الـ SQL logs… وهون كانت الصدمة.

لكل طلب لصفحة واحدة، كنت أشوف مئات، وأحياناً آلاف، استعلامات الـ SQL الصغيرة والسريعة! منظرها كان زي سرب نمل ماشي ورا بعضه، كل نملة حاملة فتفوتة صغيرة. صحيح كل استعلام لحاله سريع، بس لما تجمعهم مع بعض بصيروا كابوس. وقتها ولعت اللمبة براسي وقلت للفريق: “يا شباب، إحنا غرقانين في جحيم الـ N+1”.


ما هي مشكلة N+1 بالضبط؟ ولماذا هي “الجحيم”؟

قبل ما نكمل القصة، خلوني أشرحلكم ببساطة شو هي مشكلة الـ “N+1 Query”. تخيل إنك بدك تعرض قائمة فيها 100 منشور (Posts)، وكل منشور إله كاتب (Author) واحد. بدك تعرض عنوان المنشور واسم الكاتب جنبه.

الطريقة الساذجة أو “الكسولة” (Lazy Loading) اللي بيعملها الـ ORM (Object-Relational Mapper) زي Eloquent في Laravel أو Hibernate في Java، هي كالتالي:

  1. الاستعلام الأول (The “1”): بيعمل استعلام واحد كبير عشان يجيب كل المنشورات المية.
    SELECT * FROM posts LIMIT 100;
  2. الاستعلامات الإضافية (The “N”): بعدين، لما تيجي تعرض البيانات في الصفحة، لكل منشور من المية، الكود بيحتاج اسم الكاتب. فبيقوم الـ ORM بعمل استعلام جديد ومنفصل عشان يجيب معلومات الكاتب المرتبط بالمنشور هاد.
    SELECT * FROM authors WHERE id = 1; -- للمنشور الأول
    SELECT * FROM authors WHERE id = 5; -- للمنشور الثاني
    SELECT * FROM authors WHERE id = 1; -- للمنشور الثالث (ممكن يتكرر)
    ... وهكذا 100 مرة
        

المحصلة النهائية؟ عشان تجيب 100 منشور، عملت 1 + 100 = 101 استعلام لقاعدة البيانات! هلأ تخيل لو عندك 500 منشور في الصفحة؟ بتصير 501 استعلام. هذا هو جحيم الـ N+1، وهو واحد من أشهر أسباب بطء التطبيقات اللي بتستخدم ORM.

“مشكلة N+1 مثل أن تذهب إلى السوبرماركت 101 مرة. مرة لتحديد قائمة المشتريات، ثم 100 مرة أخرى، كل مرة لشراء غرض واحد فقط من القائمة.”

العلاج السحري: التحميل المسبق (Eager Loading)

بعد ما شخصنا المشكلة، كان لازم نلاقي الحل. والحل، الحمد لله، كان أبسط مما توقعنا. الحل هو مفهوم اسمه “التحميل المسبق” أو “Eager Loading”.

شو يعني Eager Loading؟

ببساطة، بدل ما تخلي الـ ORM “كسول” ويجيب البيانات المرتبطة عند الطلب فقط، إنت بتصير “نشيط” وبتحكيله من البداية: “اسمع، أنا رح أحتاج المنشورات مع الكُتّاب تبعونهم، فلو سمحت جهّزلي إياهم كلهم مع بعض”.

اللي بصير تحت الغطاء هو تحوّل ذكي في الاستعلامات. بدل 101 استعلام، العملية كلها بتصير باستعلامين اثنين فقط، مهما كان عدد المنشورات!

  1. الاستعلام الأول: نفس الاستعلام الأصلي لجلب كل المنشورات.
    SELECT * FROM posts LIMIT 100;
  2. الاستعلام الثاني: الـ ORM بياخد كل “IDs” بتاعت الكُتاب من المية منشور اللي جابهم، وبيعمل استعلام واحد بس عشان يجيب كل الكُتاب المطلوبين باستخدام جملة IN.
    SELECT * FROM authors WHERE id IN (1, 5, 8, ...); -- قائمة بكل الـ IDs الفريدة للكُتاب

المحصلة: استعلامين فقط! الفرق في الأداء بين 101 استعلام واستعلامين هو فرق بين السما والأرض. هو الفرق بين صفحة بتحمّل في 10 ثواني وصفحة بتحمّل في 200 ميلي ثانية.

أمثلة بالكود (يا مبرمجين، ركزوا معي)

خلونا نشوف كيف بنطبق هالحكي في إطار عمل مشهور زي Laravel باستخدام Eloquent ORM.

الكود السيء (يسبب مشكلة N+1):


// في الـ Controller
$posts = Post::take(100)->get();

// في الـ View (Blade)
@foreach ($posts as $post)
    <p>{{ $post->title }} - كتبه: {{ $post->author->name }}</p> 
    // ^^^ كل لفة هنا تطلق استعلاماً جديداً لجلب الكاتب
@endforeach

الكود الصح (باستخدام Eager Loading):

شوفوا ما أبسط التعديل! مجرد إضافة دالة with().


// في الـ Controller
$posts = Post::with('author')->take(100)->get(); // هاد هو السحر كله!

// في الـ View (Blade)
@foreach ($posts as $post)
    <p>{{ $post->title }} - كتبه: {{ $post->author->name }}</p>
    // ^^^ لا يوجد أي استعلام جديد هنا، البيانات جاهزة مسبقاً
@endforeach

بمجرد تطبيق هذا التعديل البسيط على كل الأماكن اللي فيها المشكلة في مشروعنا، كانت النتيجة فورية. الصفحات اللي كانت “بتموت” رجعت للحياة وصارت أسرع من البرق. إشي برفع الراس عنجد.


نصائح عملية من خبرة أبو عمر

هالمشكلة علمتني دروس مهمة، وبحب أشارككم بعض النصائح العملية:

  • استخدم أدوات المراقبة دائماً: لا تنتظر شكوى المستخدم. استخدم أدوات مثل Laravel Telescope أو Laravel Debugbar في بيئة التطوير. هاي الأدوات بتعرضلك عدد الاستعلامات لكل صفحة، وبتخليك تصيد مشكلة الـ N+1 من أولها.
  • لا تفرط في التحميل المسبق: الـ Eager Loading عظيم، لكن لا تستخدمه لتحميل كل العلاقات الممكنة. حمّل فقط ما تحتاجه للعرض في الصفحة الحالية. إذا عندك منشور مرتبط بكاتب وتعليقات وتاجات، وحضرتك بدك تعرض بس اسم الكاتب، اكتب Post::with('author')->get() وليس Post::with('author', 'comments', 'tags')->get(). التحميل الزائد هو الوجه الآخر لمشكلة الأداء.
  • فكر بعقلية قاعدة البيانات: قبل ما تكتب أي كود بجيب بيانات، اسأل حالك: “كم استعلام رح يتنفذ عشان هذا الكود يشتغل؟”. هذا السؤال الصغير رح يغير طريقة تفكيرك ويخليك تكتب كود أكثر كفاءة بشكل تلقائي.
  • تعلم Eager Loading للعلاقات المتداخلة: ماذا لو أردت جلب المنشورات مع مؤلفيها، وعنوان كل مؤلف؟ يمكنك فعل ذلك بسهولة:
    $posts = Post::with('author.country')->get();

    هذا الكود سيقوم بتنفيذ 3 استعلامات فقط (واحد للمنشورات، واحد للمؤلفين، وواحد للدول) بدلاً من مئات الاستعلامات.

الخلاصة: الكود السريع مش سحر، هو فهم

في النهاية يا جماعة، تحسين الأداء مش مجرد إضافة سيرفرات أو استخدام أدوات معقدة. هو في جوهره فهم عميق لكيفية عمل الأدوات اللي بين إيدينا. مشكلة N+1 هي مثال صارخ على كيف ممكن سطر كود واحد بريء المظهر إنه يقتل أداء تطبيق كامل.

الحل، كما رأينا، لم يكن معقداً. كان مجرد معرفة وتطبيق لمفهوم الـ Eager Loading. لهذا السبب، نصيحتي الدائمة لكم: لا تتوقفوا عن التعلم والغوص في تفاصيل التقنيات التي تستخدمونها. افهموا كيف يعمل الـ ORM، وكيف يتحدث مع قاعدة البيانات، ولن تفاجئكم مثل هذه “الوحوش الخفية” مرة أخرى.

يلا يا شباب، شدوا حيلكم، وخلي كودكم دايماً سريع ونظيف! 🚀

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

تجربة المستخدم والابداع البصري

كانت واجهاتنا تتحدث بلغة الروبوتات: كيف أنقذنا ‘فن الكتابة لتجربة المستخدم’ (UX Writing) من جحيم حيرة المستخدم؟

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

22 مايو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كان ملفي على GitHub مقبرة للمشاريع المنسية: كيف أنقذني ‘التنظيم القصصي’ من جحيم الانطباع الأول السيء؟

ملف GitHub الفوضوي كاد أن يكلّفني وظيفة أحلامي. في هذه المقالة، أشارككم كـ"أبو عمر" كيف حوّلت 'مقبرة المشاريع' هذه إلى قصة احترافية تروي مسيرتي التقنية...

22 مايو، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

كانت قاعدة بياناتنا تختنق: كيف أنقذتنا “النسخ المتماثلة للقراءة” (Read Replicas) من جحيم بطء الاستعلامات؟

أشارككم قصة حقيقية من قلب المعركة، عندما كاد تطبيقنا أن ينهار تحت ضغط المستخدمين. سأشرح لكم كيف كانت "النسخ المتماثلة للقراءة" (Read Replicas) طوق النجاة...

21 مايو، 2026 قراءة المزيد
التكنلوجيا المالية Fintech

من كابوس التحقق اليدوي إلى onboarding بدقائق: كيف أنقذت eKYC شركات التكنولوجيا المالية

كان التحقق من هوية العميل كابوساً يدوياً يهدد بفشل المشاريع الناشئة. في هذه المقالة، أشارككم قصة حقيقية من قلب المعركة، وكيف أنقذتنا تقنية "اعرف عميلك...

21 مايو، 2026 قراءة المزيد
البنية التحتية وإدارة السيرفرات

كان إعداد كل خادم جديد كابوساً يدوياً: كيف أنقذتنا ‘البنية التحتية كشيفرة’ (Terraform) من جحيم عدم الاتساق؟

أنا أبو عمر، وأروي لكم حكايتي مع الليالي الطوال في إعداد الخوادم يدوياً، وكيف كانت 'البنية التحتية كشيفرة' باستخدام Terraform هي طوق النجاة الذي نقلنا...

21 مايو، 2026 قراءة المزيد
ادارة الفرق والتنمية البشرية

كان أفضل مهندسينا يرحلون أو يصبحون مدراء سيئين: كيف أنقذنا ‘المسار الوظيفي المزدوج’ من نزيف المواهب؟

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

21 مايو، 2026 قراءة المزيد
اختبارات الاداء والجودة

كنا نظن أن تغطية اختباراتنا 100%: كيف كشف ‘الاختبار الطفري’ (Mutation Testing) عن نقاط ضعفنا الخفية؟

كنا في الفريق فخورين جدًا بتحقيق تغطية اختبارات بنسبة 100%، لكن الأخطاء كانت لا تزال تظهر في المنتج النهائي. اكتشف كيف كشف لنا الاختبار الطفري...

21 مايو، 2026 قراءة المزيد
البودكاست