دروس الوحدة (4/4)
مشكلة N+1
في تطوير الواجهة الخلفية، أحد الأخطاء الأكثر شيوعًا التي تدمر أداء واجهة برمجة التطبيقات (API) بشكل كارثي هي مشكلة "N + 1 Queries" سيئة السمعة.
عادةً ما يتسلل إلى مكان غير معروف للمطور الذي يستخدم تجريدات المكتبة مثل "ORMs" (مخططو الكائنات العلائقية: على سبيل المثال Prisma أو Sequelize) للتحدث إلى قاعدة البيانات من Node.js.
تشريح الكارثة
تخيل أن أحد التطبيقات يريد عرض قائمة الصفحة الرئيسية للتجارة الإلكترونية بالكامل: جميع المنتجات بالإضافة إلى سلسلة اسم الفئة التي ينتمي إليها كل منتج. من ناحية Node.js، يمكن للمبرمج عديم الخبرة أن يكتب الحلقة التكرارية:
products = await db.query("SELECT * FROM products");(عملية واحدة كبيرة وضخمة 1)- يكرر المطور (
for..of) فوق كلproductفي المصفوفة المكونة من 100 عنصر تم العثور عليها - في كل خطوة، في Node.js، ينتظرون استعلامًا فرعيًا متكررًا
categoryName = await db.query("SELECT name FROM categories WHERE id = " + product.category_id);ويقومون بتضمينه في النتيجة (N تكرارات ثانوية للاستعلامات البطيئة والصغيرة!)
إجمالي الطلبات المقدمة إلى برنامج Postgres: 1 + 100 تكرار محلي = 101 رحلات ذهابًا وإيابًا للشبكة الصغيرة بين Node.js وقاعدة البيانات. 101 رحلة ذهابًا وإيابًا تسبب حملًا رهيبًا للشبكة ووقت استجابة قبيحًا. على النطاق الحقيقي، قد لا يكون N 100 بل 10000 صف من إجمالي الفواتير لاستكشافها! لن تعود واجهة برمجة التطبيقات (API) قبل انتهاء مهلة 5xx.
الإجابة الأصلية الحقيقية: الانضمام أو التجميع في استعلام واحد
باستخدام تجريدات DBMS المكتوبة يدويًا ببراعة، بدلاً من أوامر ORM التافهة في Node، يتم دمج عمليات التنفيذ معًا بالمللي ثانية في استعلام واحد لا يهزم والذي يحل فوضى "N+1":
SELECT
p.id,
p.name,
c.name AS category_name
FROM products p
JOIN categories c ON p.category_id = c.id;إجمالي الرحلات ذهابًا وإيابًا لواجهة برمجة التطبيقات/قاعدة البيانات: استعلام واحد فريد، مدمج، ومُحسَّن رياضيًا بواسطة فهارس Postgres الخاصة، ويتم التعامل معه بالكامل مع 0 مللي ثانية من زمن الوصول الإضافي للشبكة.
بديل تجميع JSON
تتيح لك إصدارات Postgres الحديثة أيضًا إعادة الارتباط المعقد بأكمله المتداخل كـ JSON مع json_agg، مما يسمح لجافا سكريبت الخاص بك باستقبال المصفوفة products بما في ذلك مصفوفتها الفرعية من المراجعات المنسقة دون أي لصق نصي محلي! (في المسارات والتحديات المتقدمة، ستدرس جملة JSONb الخاصة بـ Postgres.)
دورك
دعونا نحل مشكلة N+1 حقيقية قيد الإنشاء.\nستقوم العقدة بتشغيل تحديد عام ثم تكراره على كل عميل للعثور على أسمائهم إذا أراد تلخيصًا للطلبات التي تم شحنها هذا الصباح.\nقم بحلها عن طريق طباعتي في لقطة أصلية واحدة JOIN بين 'customers' (استخدم الاسم المستعار النصي الأصلي 'c') و'orders' (الاسم المستعار 'o').\nاستخرج حقلين تافهين (فرض الاسم المستعار الرسمي) البادئة): "c.first_name" (من العميل) و"o.status" (من الطلب المعلق).
إظهار التلميح
بناء الجملة: حدد c.first_name، o.status من العملاء c أوامر الانضمام o ON c.id = o.customer_id؛
الحل متاح بعد 3 من المحاولات
استكشاف كفاءة المجموعة
إذا لم يتم استخدام Group-Bys المنطقية داخل قاعدة البيانات للحصول على العدد الإجمالي لكل صف وفئة، فسيقوم Node.js باستدعاء قاعدة البيانات إلى ما لا نهاية، مما يؤدي إلى N+1.\nاكتب استعلامًا واحدًا يحدد حقل "المدينة" من "العملاء" وعدد Postgres القياسي "COUNT(id)" باستخدام الكلمة الأساسية الطرفية لتجميعها بشكل فريد بالاسم.
إظهار التلميح
تافهة: اختر المدينة، COUNT(id) من العملاء GROUP BY city؛
الحل متاح بعد 3 من المحاولات