لماذا ترتيب النتائج يقتل أداء تطبيقك؟ حكاية مشكلة شائعة والحل الذي لا ينتبه له أحد

يا جماعة الخير، خلوني أحكيلكم قصة صارت معي زمان، في بدايات عملي على مشروع كان بمثابة “ابني البكر”. كان تطبيقًا اجتماعيًا بسيطًا، فيه لوحة تحكم (Dashboard) بتعرض للمستخدمين “أكثر المنشورات تفاعلًا”. في الأيام الأولى، لما كان عدد المستخدمين والبيانات قليل، كان كل شيء سريعًا “والوضع لوز”. الصفحة بتحمّل بغمضة عين، والخادم مرتاح ومبسوط.

مرت الشهور، والتطبيق نجح، وعدد المستخدمين زاد بشكل كبير. وفجأة، بدأت الشكاوى توصلني. “يا أبو عمر، الداشبورد بطيئة جدًا!”، “الصفحة بتعلّق!”. دخلت أفحص الخادم، لقيت مؤشر استهلاك المعالج (CPU) ضارب في السقف، وزمن الاستجابة (Response Time) طالع نازل زي البورصة في يوم متقلب. قضيت ساعات طويلة وأنا “أنبش” في الكود وأحلل سجلات الأخطاء (Logs)، وكاسات القهوة تتكدس على مكتبي. كنت على وشك أستسلم وأقول المشكلة من استضافة السيرفر… لحد ما وقعت عيني على سطر بريء جدًا، سطر واحد مسؤول عن ترتيب البيانات. كان هو المجرم الصامت الذي يغتال أداء تطبيقي مع كل طلب جديد.

المشهد المألوف: الكود البريء الذي يقتل الأداء

السيناريو الذي وقعت فيه هو سيناريو يتكرر في آلاف التطبيقات كل يوم. لدينا صفحة تحتاج لعرض “أفضل 10” أو “الأكثر نشاطًا” أو أي شيء من هذا القبيل. منطق العرض غالبًا ما يكون كالتالي:

  1. نجلب البيانات من قاعدة البيانات (مثلاً، منشورات المستخدمين).
  2. نجلب بيانات أخرى من مصدر خارجي أو خدمة أخرى (مثلاً، عدد المشاركات من واجهة برمجة تطبيقات أخرى).
  3. نقوم بدمج كل هذه البيانات في مصفوفة واحدة كبيرة.
  4. نحسب “درجة” (Score) لكل عنصر بناءً على التفاعلات، المشاهدات، إلخ.
  5. نرتب المصفوفة بأكملها ترتيبًا تنازليًا حسب هذه الدرجة.
  6. نأخذ أول 10 عناصر من المصفوفة المرتبة لعرضها.

الكود قد يبدو بسيطًا وبريئًا، كهذا المثال في لغة PHP:


// 1. جلب البيانات من مصادر مختلفة
$posts = getPostsFromDatabase(); // قد تكون 5000 منشور
$shares = getSharesFromAPI(); // بيانات لـ 5000 منشور

// 2. دمج وحساب الدرجة
$data = [];
foreach ($posts as $post) {
    $score = $post['likes'] + $post['comments'] + ($shares[$post['id']] ?? 0);
    $data[] = ['id' => $post['id'], 'title' => $post['title'], 'score' => $score];
}

// 3. الترتيب الكامل (هنا تكمن الكارثة)
// نرتب 5000 عنصر لكي نحصل على 10 فقط!
usort($data, fn($a, $b) => $b['score']  $a['score']);

// 4. أخذ أفضل 10 عناصر
$topTen = array_slice($data, 0, 10);

// 5. عرض النتائج
return $topTen;

عندما يكون عدد العناصر 100 أو 200، لن تلاحظ أي مشكلة. ولكن عندما يصبح العدد 10,000 أو 100,000، فإن دالة usort هذه تتحول إلى وحش يلتهم موارد المعالج والذاكرة مع كل تحديث للصفحة.

المشكلة ليست في اللغة… بل في المنطق

أول رد فعل طبيعي للمبرمج هو لوم الأداة: “PHP بطيئة”، “Node.js لا يتحمل الضغط”، “يجب أن نستخدم لغة أخرى!”. ولكن الحقيقة، يا جماعة الخير، أن المشكلة ليست في اللغة ولا في إطار العمل (Framework) ولا حتى في قاعدة البيانات.

المشكلة في طريقة تفكيرنا. نحن نقوم بعمل هائل لا نحتاجه أصلًا. نحن نرتب كل شيء… بينما نحتاج القليل فقط.

لنفهم حجم الكارثة: خوارزميات الترتيب الشائعة مثل Quicksort (المستخدمة غالبًا خلف الكواليس في دوال مثل usort) لها تعقيد زمني يُعرف بـ O(N log N). بلغة بسيطة، هذا يعني أن الجهد المطلوب للترتيب لا يزداد بشكل خطي مع زيادة عدد العناصر (N)، بل يزداد بشكل أسرع. ترتيب 100,000 عنصر ليس أسوأ بـ 10 مرات من ترتيب 10,000 عنصر، بل هو أسوأ بكثير. وكل هذا الجهد الهائل، الذي يتكرر مع كل طلب، يُرمى 99.9% منه في سلة المهملات لأننا لا نحتاج سوى لأول 10 عناصر.

تغيير السؤال يغيّر كل شيء

هنا تأتي الخبرة لتعلمنا الدرس الأهم في هندسة البرمجيات. بدلًا من أن نسأل:

“كيف يمكنني أن أرتب هذه المصفوفة الضخمة بشكل أسرع؟”

يجب أن نسأل السؤال الصحيح:

“كيف يمكنني أن أجد أفضل 10 عناصر بدون ترتيب المصفوفة بأكملها؟”

بمجرد أن تعيد صياغة السؤال، تظهر الحلول الذكية تلقائيًا. أنت لا تريد قائمة مرتبة، أنت تريد مجموعة صغيرة من “الفائزين”.

الحل الذكي: استخدم “كومة” بدل الفرز الكامل (Heap to the Rescue)

الحل الأمثل لهذه المشكلة هو استخدام بنية بيانات (Data Structure) رائعة تسمى “الكومة” (Heap). لا تدع الاسم يخيفك، فكرتها بسيطة جدًا وعملية.

للعثور على أفضل 10 عناصر (Top 10)، سنستخدم نوعًا معينًا من الكومة يسمى “الكومة الصغرى” (Min-Heap). وإليكم كيف تعمل بطريقة مبسطة:

  1. أنشئ “كومة” فارغة بحجم أقصى يساوي 10 عناصر.
  2. ابدأ بالمرور على جميع عناصرك (الـ 100,000 عنصر) واحدًا تلو الآخر.
  3. إذا كانت الكومة لم تمتلئ بعد (فيها أقل من 10 عناصر): أضف العنصر الحالي إليها مباشرة.
  4. إذا كانت الكومة ممتلئة (فيها 10 عناصر): قارن العنصر الحالي مع أصغر عنصر في الكومة. (ميزة الكومة الصغرى أنها تجعل الوصول لأصغر عنصر فيها سريعًا جدًا).
    • إذا كان العنصر الحالي أكبر من أصغر عنصر في الكومة، فهذا يعني أنه يستحق أن يكون من أفضل 10. لذا، “اطرد” العنصر الأصغر من الكومة وأدخل العنصر الحالي مكانه.
    • إذا كان العنصر الحالي أصغر أو يساوي، فتجاهله ببساطة وأكمل طريقك.

بعد المرور على جميع العناصر، ماذا ستحتوي الكومة في النهاية؟ ستحتوي بالضبط على أفضل 10 عناصر! والجميل في الأمر أننا لم نرتب المصفوفة كاملة أبدًا.

مثال عملي: من الكود السيء إلى الكود الأفضل

لحسن الحظ، معظم لغات البرمجة توفر تنفيذًا جاهزًا للكومة. في PHP، لدينا SplMinHeap. لنعد كتابة الكود السابق باستخدام هذا الحل الذكي:


// لنفترض أن لدينا مصفوفة $data الضخمة من المثال السابق

// 1. إنشاء كومة صغرى
$topTenHeap = new class extends SplMinHeap {
    // نحتاج لمقارنة العناصر بناءً على 'score'
    protected function compare($value1, $value2): int {
        return $value2['score']  $value1['score'];
    }
};

// 2. المرور على جميع العناصر
foreach ($data as $item) {
    // ببساطة، أدخل كل عنصر في الكومة
    $topTenHeap->insert($item);
}

// 3. استخراج أفضل 10 عناصر
// الكومة ستحتفظ تلقائيًا بالأفضل، لكنها غير مرتبة.
// لنستخرجها ونرتبها (ترتيب 10 عناصر فقط لا يكلف شيئًا!)
$topTen = [];
$count = min(10, $topTenHeap->count()); // تأكد من عدم طلب أكثر مما هو موجود
for ($i = 0; $i extract();
}

return $topTen;

/*
ملاحظة: SplHeap لا تسمح بتحديد حجم أقصى مباشرة،
لكنها داخليًا أكثر كفاءة من فرز مصفوفة كاملة.
لتحقيق الكفاءة القصوى O(N log K)، يمكن بناء التنفيذ يدويًا
أو استخدام مكتبة، لكن حتى SplHeap الجاهزة تقدم تحسنًا هائلاً.
*/

الفرق في الأداء بين الطريقتين ليس مجرد تحسين بسيط، بل هو فرق هائل. التعقيد الزمني لهذا الحل هو O(N log K) حيث N هو العدد الإجمالي للعناصر و K هو العدد الذي نريده (10 في حالتنا). عندما تكون K صغيرة جدًا مقارنة بـ N، يكون هذا الحل أسرع بأضعاف مضاعفة.

متى نستخدم هذا النمط؟ وأين لا نستخدمه؟

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

  • لوحات الصدارة (Leaderboards).
  • المحتوى الرائج (Trending Content).
  • أنظمة التوصيات (Recommendation Systems).
  • لوحات التحكم والتقارير (Dashboards & Analytics Widgets).

ولكن، كن حذرًا، فالهندسة ليست وصفة واحدة تناسب الجميع. لا تستخدم هذا الحل:

  • عندما يكون الترتيب مسؤولية قاعدة البيانات: إذا كانت كل بياناتك في جدول واحد في قاعدة البيانات، فالحل الأفضل والأسرع هو ترك المهمة لها: SELECT * FROM posts ORDER BY score DESC LIMIT 10;. قاعدة البيانات مصممة ومحسّنة للقيام بذلك بكفاءة فائقة. مشكلتنا تظهر عند دمج بيانات من مصادر متعددة في طبقة التطبيق.
  • عندما يكون حجم البيانات صغيرًا جدًا: إذا كنت تتعامل مع 50 أو 100 عنصر، فإن استخدام دالة الترتيب العادية usort أسهل وأوضح، والفرق في الأداء لا يكاد يذكر. “مش كل إشي بده بازوكا”، لا تفرط في الهندسة (Over-engineering).
  • عندما تحتاج إلى ترتيب مستقر (Stable Sort): في حالات نادرة جدًا، قد يهمك ترتيب العناصر التي لها نفس الدرجة. الكومة لا تضمن هذا الاستقرار.

الخلاصة: المشكلة لم تكن في الأداء 💡

في نهاية المطاف، المشكلة التي واجهتها في تطبيقي لم تكن حقًا مشكلة “أداء”، بل كانت مشكلة “طريقة تفكير”. لقد كنا نقوم بعمل لا لزوم له، وكنا ندفع ثمنه غاليًا من موارد الخادم وتجربة المستخدم.

عندما غيرنا السؤال من “كيف نرتب؟” إلى “ماذا نحتاج حقًا؟”، تغير الحل بأكمله، وتحسن الأداء بشكل جذري، واختفى الضغط عن الخوادم.

وهنا تكمن المفارقة الجميلة:

  • لم نحتج إلى خادم أقوى.
  • لم نحتج إلى نظام تخزين مؤقت (Cache) معقد.
  • لم نحتج إلى تغيير لغة البرمجة.

كل ما احتجناه كان قرارًا معماريًا أفضل، مبنيًا على فهم أعمق للمشكلة الحقيقية.

في تطبيقات الويب، الخوارزميات لا تُعرض في الواجهة… لكنها من يحدد إن كانت الواجهة ستُفتح أصلًا.

أبو عمر

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

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

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

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

آخر المدونات

أتمتة العمليات

قهوتك الصباحية مع ملخص الإنجازات: كيف تبني داشبورد يومي يصلك على الموبايل باستخدام n8n والذكاء الاصطناعي

كف عن تشتيت نفسك كل صباح بين Jira وGitHub والإيميلات. تعلم معي، أبو عمر، كيف تبني ورك فلو أتمتة يرسل لك ملخصاً ذكياً ومنسقاً بإنجازات...

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