אופטימיזציה של שאילתות MongoDB: explain, אינדקסים, צינור אגרגציה

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

היכולות שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

אופטימיזציה של MongoDB: היכן שאילתות מאבדות מהירות

שאילתות איטיות ב-MongoDB לעיתים רחוקות מופיעות מיד: השירות עובד חודשים על קולקציה קטנה שבה כל מה שנדרש נקרא מהזיכרון. נקודת השבירה מגיעה עם גידול הנתונים — שליפה שהגיבה אתמול באופן מיידי מתחילה להיתקל במתכנן, וזמן התגובה גדל יחד עם נפח הקולקציה.

מקום האיבוד הראשון — סריקת קולקציה במקום גישה מבוססת אינדקס. explain() מציג COLLSCAN במקום שבו ציפינו ל-IXSCAN, ומכאן כל מנה חדשה של מסמכים מייקרת את הקריאה. קורה שאינדקס קיים פורמלית, אבל אינדקס מורכב נבנה בסדר שגוי — שוויון, מיון, טווח — והמתכנן פשוט לא לוקח אותו.

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

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

מה נותנת הגדרת אינדקסים ותוכניות ביצוע

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

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

בהמשך בונים מחדש את האינדקסים בהתאם לתבניות השאילתות בפועל. אינדקס מורכב נבנה בסדר "שוויון → מיון → טווח", כך שהסינון והמיון נסגרים במעבר אחד; במקומות שבהם האפליקציה שולפת רק כמה שדות, הופכים אותו לאינדקס מכסה — השרת מחזיר תשובה בלי לפנות למסמכים עצמם.

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

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

יציבות תחת עומס חשובה יותר מזמן ממוצע: מערכת שמחזיקה תגובה בזינוק פי עשרה לא מפילה תרחישי משתמש בשעות שיא.

תוכנית הביצוע היא величиה לא קבועה: הקולקציה גדלה, הסינונים משתנים, והאופטימייזר עשוי לבחור אסטרטגיה אחרת. לכן מגדירים בקרה קבועה על שאילתות איטיות וספי התראה, כדי שהרעה תיתפס לפני התלונות ולא אחריהן.

גישות להאצת שאילתות: השוואת אפשרויות

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

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

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

שלבי העבודה: מביקורת שאילתות עד מסירת ההגדרות

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

  1. פרופילינג שאילתות — מפעילים איסוף של פעולות איטיות ומקבצים אותן לפי תבנית משותפת. כך רואים צווארי בקבוק יציבים ולא חריגות בודדות; פניות של האפליקציה והאנליטיקה מסומנות בנפרד, כי מחיר הטעות שלהן שונה.

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

  3. תיקון אינדקסים ותבניות פנייה — אינדקסים מורכבים נבנים לפי הסדר האמיתי של השדות בסינון ובמיון, ומוסיפים אינדקסים מכסים היכן שהאפליקציה שולפת רק חלק מהשדות. בצינור האגרגציה מעבירים את הסינון להתחלה, לפני הקיבוץ ופריסת המערכים.

  4. סקירת מודל הנתונים — קינון וכפילות: לעיתים תיקון מבנה המסמך מסיר חבילה שלמה של פניות כבדות.

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

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

אינדקסים, סכמות ותקנונים: מה נשאר בפרויקט

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

  • תיאור האינדקסים — הרכב וסדר השדות, הסלקטיביות, לאיזה תרחיש קריאה נוצר. היצירה מעוצבת כמיגרציה ולא כפקודה ידנית על שרת הפרודקשן.
  • סכמות קולקציות וולידטורים — שדות חובה, טיפוסים מותרים, איסור על מבנים לא-אחידים באותה קולקציה, כדי שהשאילתות יישארו צפויות.
  • כללי גישה לנתונים — היכן הקריאה מתבצעת רק לפי שדה מאונדקס, אילו שליפות ללא סינון נחשבות לפגם, ואילו פעולות מותרות במסלול החם.
  • מדיניות גישה ותפקידים — חשבונות נפרדים לשירותים, הרשאות מינימליות נדרשות, והיעדר הרשאות אדמין לאפליקציה.
  • נוהל תחזוקה — באיזו תדירות מוציאים תוכניות ביצוע, באיזה פער מול התקן השאילתה יוצאת לשיפור ומי בודק זאת.
  • תוכניות תקן ושאילתות בקרה — סט שליפות עם התנהגות צפויה, שעליו בודקים רגרסיה לאחר שינויים בסכמה.
  • הוראה לתורן — מה לבדוק כשהשהיות גדלות, אילו מטריקות ובאיזה סדר.

כל הארטיפקטים מנוהלים בגרסאות לצד הקוד ומועברים לצוות שלכם. חודש אחרי המסירה מהנדס חדש יוסיף אינדקס בלי לשבור את הקיימים, והתורן ימצא את סיבת ההרעה לפי ההוראה — בלי לפנות אלינו.

קייס: האצנו קטלוג בלי לכתוב מחדש את הלוגיקה

מה היה

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

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

מה שינינו

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

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

מה נהיה

זמן התגובה התקצר בכפי שמונה בערך, האחוזון ה-95 חזר לשברי שנייה, וחריגות זמן ההמתנה נעלמו. נפח הנתונים ששאילתה אחת קוראת ירד בסדר גודל — עם אותה כמות מוצרים ובלי לכתוב מחדש את הלוגיקה.

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

מה לעשות אם אופטימיזציה של MongoDB לא נתנה אפקט

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

סדר השדות באינדקס מורכב — זו הטעות הנפוצה הראשונה. האינדקס מאיץ שליפה רק אם הקידומת שלו תואמת את תנאי הסינון; אם ראשון עומד שדה בעל סלקטיביות נמוכה או שהשדות הוחלפו, המתכנן מוותר על האינדקס. החלפת סדר השדות היא תיקון בקונפיגורציה, והרווח נמדד בכפולות.

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

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

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

ספקות טכניים: עונים על שאלות נפוצות

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

האם האינדקסים החדשים יאטו את הכתיבה?

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

אפשר להסתדר בלי לשנות את הסכמה?

ברוב המקרים כן. סדר השדות באינדקס מורכב, אינדקסים חלקיים ו-TTL, ורצף שלבים שנכתב מחדש באגרגציה נותנים שיפור בלי לתקן מסמכים. לשנות מבנה אנחנו מציעים רק היכן ששכתוב השליפה לא סוגר את המשימה — ואז עם תאימות לאחור ותוכנית העברה.

מה יקרה עם השרדינג?

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

באיזו סביבה בודקים את התוצאה?

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

נדון במשימה: נחזור עם הערכה ותוכנית עבודה

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

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

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