דילוג לתוכן המרכזי
מודול 10 · שיעור 4 מתוך 440/57 בקורס~12 min
שיעורי מודול (4/4)

בעיית N+1

בפיתוח אחורי, אחת הטעויות השכיחות ביותר שמגבירות בצורה קטסטרופלית את ביצועי ה-API היא בעיית "N + 1 שאילתות" הידועה לשמצה.

זה בדרך כלל זוחל לא ידוע למפתח שמשתמש בהפשטות ספרייה כמו "ORMs" (Object-Relational Mappers: למשל Prisma או Sequelize) כדי לדבר עם מסד הנתונים מ-Node.js.

האנטומיה של האסון

תארו לעצמכם שאפליקציה רוצה להחזיר את כל רישום דף הבית של מסחר אלקטרוני: כל המוצרים יחד עם המחרוזת של שם הקטגוריה שכל אחד מהם שייך. במבט מהצד של Node.js, מתכנת לא מנוסה היה כותב את הלולאה האיטרטיבית:

  1. products = await db.query("SELECT * FROM products"); (פעולה אחת גדולה ומגושמת 1)
  2. המפתח חוזר (for..of) על כל product במערך של 100 שנמצאו
  3. בכל שלב, ב-Node.js הם ממתינים לשאילתת משנה איטרטיבית categoryName = await db.query("SELECT name FROM categories WHERE id = " + product.category_id); ומטמיעים אותה בתוצאה (N איטרציות משניות של שאילתות איטיות וקטנות!)

סה"כ בקשות לדמון Postgres: 1 + 100 איטרציות מקומיות = 101 רשתות מיקרו הלוך ושוב בין Node.js ל-DB. 101 נסיעות הלוך ושוב גורמות לתקורה נוראית של רשת ולהשהייה מכוערת. בקנה מידה אמיתי, ייתכן ש-N אינו 100 אלא 10,000 שורות של חשבוניות הכוללות לבדיקה! ממשק ה-API לא יחזור לפני שפגע בפסק זמן של 5xx.

התשובה המקורית האמיתית: JOIN או Aggregation בשאילתה בודדת

שימוש מופתי בהפשטות DBMS שנכתבו ביד, במקום פקודות ORM טריוויאליות ב-Node, ממזג את הביצועים יחד באלפיות שניות בשאילתה אחת ובלתי מנוצחת שפותרת את הבלגן "N+1":

SQL
SELECT
  p.id,
  p.name,
  c.name AS category_name
FROM products p
JOIN categories c ON p.category_id = c.id;

סה"כ נסיעות הלוך ושוב של API/DB: שאילתה ייחודית אחת, מותכת, אופטימלית מתמטית על ידי האינדקסים של Postgres עצמה, מטופלת במלואה עם 0 אלפיות השנייה של זמן אחזור רשת נוסף.

חלופה לצבירה של JSON

גרסאות Postgres מודרניות אפילו מאפשרות לך להחזיר את כל החיבור המורכב מקונן כ-JSON עם json_agg, מה שמאפשר ל-JavaScript שלך לקבל את מערך products כולל תת-מערך הביקורות המעוצבות ללא כל דבק סקריפט מקומי! (בנתיבים והאתגרים המתקדמים תלמדו את סעיף JSONb של Postgres.)

התור שלך

פעילות גופנית#sql.m10.l4.e1
ניסיונות: 0טוען...

בוא נפתור בעיה אמיתית של N+1 בהתהוות.\nצומת היה מפעיל Select עולמי ולאחר מכן חוזר על כל לקוח כדי למצוא את שמותיו אם הוא רצה תקציר של הזמנות שנשלחו הבוקר.\nפתור זאת על ידי הדפסת לי בצילום מקורי יחיד JOIN בין 'לקוחות' (השתמש בכינוי הטקסטואלי המקורי 'c') ו-'extracto's trial's\n. (כפיית קידומת כינוי רשמית): 'c.first_name' (מהלקוח) ו-'o.status' (מההזמנה הממתינה).

טוען עורך...
הצג רמז

תחביר: SELECT c.first_name, o.status FROM לקוחות c JOIN הזמנות o ON c.id = o.customer_id;

הפתרון זמין לאחר 3 ניסיונות

בחינת יעילות GROUP BY

פעילות גופנית#sql.m10.l4.e2
ניסיונות: 0טוען...

אם לא משתמשים ב-Group-Bys לוגיים בתוך מסד הנתונים כדי לקבל את הספירה הכוללת לכל שורה וקטגוריה, Node.js יקרא ל-DB בלי סוף, ויפוצץ ל-N+1.\nכתוב שאילתה בודדת שבוחרת את השדה 'עיר' מ'לקוחות' ואת ספירת Postgres הסטנדרטית 'COUNT(id)' באמצעות מילת המפתח הייחודית של הטרמינל כדי לצבור אותם.

טוען עורך...
הצג רמז

טריוויאלי: SELECT city, COUNT(id) FROM customers GROUP BY city;

הפתרון זמין לאחר 3 ניסיונות