• בלוג
  • איך להתמודד עם בעיות ביצועים במערכת

איך להתמודד עם בעיות ביצועים במערכת

30/09/2018

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

1. הרבה סיכות קטנות

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

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

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

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

2. טיפ 1: אפשרו הצגת מידע בצורה ברורה לגבי כל פעולה

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

אני משתמש בקובץ לוג שכל שורה בו נראית בערך כך:

I, [2018-09-29T12:09:20.722606 #27886]  INFO -- : method=GET path=/s/2911/metaplot/1
6 format=json controller=Schools::LessonsController action=show status=200 duration=
393.97 view=20.70 db=84.63 time=2018-09-29 12:09:20 +0000 host=my.schooler.biz user_
id=62354 params={"school"=>"2911", "bundle_id"=>"metaplot"}

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

לעומת זאת כשהמספרים האלה מתחילים לעלות זה נותן לי כיוון על מה להתחיל להסתכל ואיפה לחפש את הסיכות: האם השליפות מבסיס הנתונים לוקחות יותר מדי זמן? (ואז נלך להוסיף אינדקסים או לשפר את השליפה) האם בניית ה JSON או ה HTML היא התהליך האיטי? או שחישובים אחרים בצד השרת לוקחים יותר מדי זמן ואז אפשר להוסיף תיזמונים והדפסות על הפונקציות הרלוונטיות.

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

3. טיפ 2: בנו מנגנונים שיאפשרו לכם להפריד פעולות כבדות לשרתים או תהליכים אחרים

בפעם הראשונה ששולחים קוד לביצוע על ידי תהליך חיצוני מחוץ לרצף ה Request-Response של בקשת רשת זה הרבה עבודה. צריך לבנות תשתית של שליחת קוד לרכיב חיצוני שמורכבת מתור משימות שהשרת יכול להוסיף עליו משימות וצריך לבנות רכיב אחר שמוציא משימות מהתור ומבצע אותן. אבל אחרי שתבנו כזו תשתית תוכלו להשתמש בה בקלות כדי להוריד עומסים מפונקציות שזיהיתם שעושות יותר מדי עבודה.

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

current_user.visit(@lesson.id) if current_user.real?

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

current_user.delay.visit(@lesson.id) if current_user.real?

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

4. טיפ 3: בנו מנגנונים שיאפשרו לכם להוסיף משאבים מהר (גדילה אופקית)

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

אבל אם השרת ובסיס הנתונים נמצאים כבר עכשיו על מכונות שונות ויש לכם Load Balancer שכבר עכשיו מוגדר לפני השרת והגדרתם כמו שצריך את כל נושא ה Sticky Sessions עם העוגיות אז כשתצטרכו עוד שרת יהיה לכם ממש קל להרים אחד שיתחבר לאותו בסיס נתונים וישב מאחורי אותו Load Balancer.

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

5. טיפ 4: בדיקות עומסים

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

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