MS

מאמרים הנדסיים

הגדלת API ב־Node.js מ־200 אלף למיליון משתמשים עם תגובות מהירות ב־60%

מאת Muhammad Shahid · · 3 דקות קריאה

תשובה קצרה

הבקאנד של CV Maker – Resume Builder, אפליקציית Android ב־Google Play, גדל מ־200 אלף ליותר ממיליון משתמשים בזמן שזמן התגובה הממוצע של ה־API ירד בכ־60%. העבודה כללה אינדקסים ב־MongoDB, החלפת שאילתות חוזרות ב־aggregation pipelines, קאש לתשובות, pagination, ולידציה של בקשות וטיפול מרוכז בשגיאות, כשהשחרורים נשארו יציבים.

עובדות עיקריות
פרויקטCV Maker – Resume Builder ‏(Google Play)
צמיחהמ־200 אלף ליותר ממיליון משתמשים
מהירותזמן תגובה ממוצע מהיר בכ־60%
הפתרוןאינדקסים, aggregation pipelines, קאש, pagination
טכנולוגיותNode.js,‏ Express,‏ MongoDB

לאן הולך הזמן ב־API איטי ב־Node.js?

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

מה אינדקסים משנים?

אינדקס מאפשר ל־MongoDB לקפוץ ישר למסמכים שהשאילתה צריכה במקום לקרוא את כל האוסף. מוסיפים אינדקסים מורכבים שמתאימים לסינון ולסדר המיון של השאילתות החמות ביותר, ובודקים עם explain() שכל שאילתה בודקת בערך כמה מסמכים שהיא מחזירה. זה בדרך כלל השיפור הגדול ביותר.

למה להחליף שאילתות חוזרות ב־aggregation pipeline?

דפוס נפוץ הוא לשלוף רשימה, לעבור עליה בלולאה ב־Node.js ולהריץ עוד שאילתה לכל פריט. עם 50 פריטים אלה 51 סבבים מול מסד הנתונים. ‏aggregation pipeline עושה את הסינון, הצירוף והספירה בתוך MongoDB בבקשה אחת:

const resumes = await Resume.aggregate([
  { $match: { userId } },
  { $sort: { updatedAt: -1 } },
  { $limit: 20 },
  { $lookup: { from: "templates", localField: "templateId", foreignField: "_id", as: "template" } },
  { $project: { title: 1, updatedAt: 1, "template.name": 1 } },
])

שמים את $match ו־$limit כמה שיותר מוקדם, כדי שהשלבים הבאים יעבדו על פחות מסמכים, ובוחרים עם $project רק את השדות שהאפליקציה מציגה.

מה כדאי לשמור בקאש?

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

איך API צריך לחלק רשימות ארוכות לעמודים?

אף פעם לא מחזירים אוסף שלם. מחזירים עמוד של תוצאות ודרך לקבל את הבא. ‏skip ו־limit בסדר להיסטים קטנים; ברשימות ארוכות, pagination לפי סמן על שדה עם אינדקס נשאר מהיר בכל עומק:

// Next page: everything older than the last item the client has
const page = await Resume.find({ userId, updatedAt: { $lt: cursor } })
  .sort({ updatedAt: -1 })
  .limit(20)

איך ולידציה וטיפול מרוכז בשגיאות מצמצמים תקלות בפרודקשן?

מוודאים כל גוף בקשה בכניסה, כך שקלט שגוי נדחה עם 400 ברור במקום להיכשל עמוק בתוך הקוד. אחר כך מעבירים כל שגיאה דרך handler אחד ב־Express שרושם אותה עם הקשר ומחזיר תשובה עקבית. השגיאות מפסיקות להיות מפוזרות, ורואים אילו קורות הכי הרבה ומתקנים אותן.

app.use((err, req, res, next) => {
  logger.error({ err, path: req.path, user: req.user?.id })
  const status = err.status || 500
  res.status(status).json({ error: status === 500 ? "Something went wrong" : err.message })
})

איך שומרים על שחרורים יציבים כשהתנועה גדלה?

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

שאלות נפוצות

בכמה ה־API של CV Maker נעשה מהיר יותר?

זמן התגובה הממוצע ירד בכ־60%, בזמן שבסיס המשתמשים גדל מ־200 אלף ליותר ממיליון.

מה זו בעיית N+1 שאילתות?

שליפת רשימה ואז הרצת שאילתה נוספת לכל פריט. זה מכפיל את הסבבים מול מסד הנתונים; ‏aggregation pipeline או שאילתה אחת עם $in בדרך כלל מחליפים את זה.

להשתמש ב־skip ו־limit ל־pagination?

ברשימות קצרות כן. ברשימות ארוכות עדיף pagination לפי סמן על שדה עם אינדקס, כמו updatedAt, שנשאר מהיר בכל עומק.

Node.js מספיק מהיר למיליון משתמשים?

כן. בפועל מסד הנתונים ומבנה השאילתות קובעים את הביצועים הרבה יותר מסביבת הריצה.

למה לרכז את הטיפול בשגיאות ב־Express?

handler אחד רושם כל שגיאה עם הקשר ומחזיר תשובה עקבית, כך שרואים אילו שגיאות קורות הכי הרבה ומתקנים אותן.

אתה יכול להגדיל את הבקאנד של האפליקציה שלי?

כן. הגדלתי בקאנד של אפליקציית מובייל ליותר ממיליון משתמשים. ראו את עמוד שירות הבקאנד ב־Node.js.