קצת מבוא: אני כותב תוכנת שרת בJava, בתוכנה הרבה קוד שנורש מגירסאות קודמות.
התוכנה בת כ-12 שנים, ומתקדמת בגירסאות שמשתדלות להיות תואמות לאחור שככל שניתן.
מטרת התוכנה היא התאמת משאבים לעבודות.
ביום בהיר אחד, נמצאה בעיית ביצועים באחת ההתקנות, עבודות לא נכנסו לריצה מספיק מהר או בכלל.
שינסנו מותניים והתחלנו לבדוק את מקור הבעייה.
קשה מאוד לשחזר את הבעייה בתנאי מעבדה, לכן היינו צריכים לנטר מערכת ייצור בזמן עבודה.
לא יכולנו להשתמש במאבחנים (profiler) כיוון שהדבר היה מחמיר את בעיית הביצועים.
הדרך שבה ניטרנו הייתה לייצר קבצי מחסנית קריאות (stack) בעזרת "kill -3" או בעזרת jstack, וניתוחם.
למי שלא יודע כל קריאה לkill -3 מדפיסה את מחסנית הקריאות הנוכחית של כל החוטים במערכת לפלט הסטנדרטי של התוכנה שרצה, jstack מדפיסה אותם לפלט הסטנדרטי של מקום הקריאה.
כך יצרנו קבצים רבים כאלה, בערך אחד לחצי שניה במשך כדקה או שתיים, מה שנתן לנו מידע לנתח אבל לא העמיס על התוכנה יותר מדי שכן התדירות די נמוכה יחסית למאבחן, אפילו אחד שמנסה לא להעמיס יותר מדי.
מכיוון שרק חוט אחד עניין אותנו בודדנו את המחסניות שלו מכל שאר המחסניות, ובדקנו אותם, בדרך הזאת:
התוכנה בת כ-12 שנים, ומתקדמת בגירסאות שמשתדלות להיות תואמות לאחור שככל שניתן.
מטרת התוכנה היא התאמת משאבים לעבודות.
ביום בהיר אחד, נמצאה בעיית ביצועים באחת ההתקנות, עבודות לא נכנסו לריצה מספיק מהר או בכלל.
שינסנו מותניים והתחלנו לבדוק את מקור הבעייה.
קשה מאוד לשחזר את הבעייה בתנאי מעבדה, לכן היינו צריכים לנטר מערכת ייצור בזמן עבודה.
לא יכולנו להשתמש במאבחנים (profiler) כיוון שהדבר היה מחמיר את בעיית הביצועים.
הדרך שבה ניטרנו הייתה לייצר קבצי מחסנית קריאות (stack) בעזרת "kill -3" או בעזרת jstack, וניתוחם.
למי שלא יודע כל קריאה לkill -3 מדפיסה את מחסנית הקריאות הנוכחית של כל החוטים במערכת לפלט הסטנדרטי של התוכנה שרצה, jstack מדפיסה אותם לפלט הסטנדרטי של מקום הקריאה.
כך יצרנו קבצים רבים כאלה, בערך אחד לחצי שניה במשך כדקה או שתיים, מה שנתן לנו מידע לנתח אבל לא העמיס על התוכנה יותר מדי שכן התדירות די נמוכה יחסית למאבחן, אפילו אחד שמנסה לא להעמיס יותר מדי.
מכיוון שרק חוט אחד עניין אותנו בודדנו את המחסניות שלו מכל שאר המחסניות, ובדקנו אותם, בדרך הזאת:
שורת sed זאת לא מוחקת, כלומר מוצאת, את כל המקטעים שמתחילים בשם החוט, ומסתיימים בשיגרת ההרצה שלו, כלומר המחסניות של החוט שמעניין אותנו, אותם נמיין לפי שורה כדי למצוא את השורה הכבדה ביותר.sed -e "/SchedulerThread/,/SchedulingProcessorV4.run/\!d" | sort | uniq -c | sort -n
אם מעניין אותנו רק שיגרה ולא שורה, צריך להסיר מהמחסנית את מספרי השורות.
לפעמים אנו רוצים למצוא רק חלק מהמחסניות, לא כל המחסניות של החוט זה אלא רק כאלה שמגיעות לשיגרה מסויימת, אז נשתמש בsed זה:
sed -e "/$BEGIN/,/$END/!d" |tac | sed -e "/$PATTERN/,/$BEGIN/!d"| sort | uniq -c | sort -n
זה נראה מסובך, כי בsed אם פותחים מקטע ולא מוצאים את סופו, ממשיכים לסוף הקובץ, לכן קודם כל נמצא רק את המחסניות של החוט שלנו, ואז בתוכם נמצא את קטעי המחסנית שמעניינים אותנו, שימו לב שבתוצאה לא יהיו שורות שקראו לשיגרה שאנחנו מחפשים.
המחלקה הפוגענית הייתה איזשהי לוגיקה מעל מפה מסונכרנת, שניגשים אליה הרבה מאוד פעמים מחוטים שונים.
לפעמים כאשר כותבים קטע קוד מסויים, נדמה שהוא יקר, ולכן מוסיפים מטמון, איני יודע מה היו בדיוק השיקולים בעת כתיבת מחלקה זו אבל באופן כללי כדאי מאוד להיזהר בהוספת מטמון, וכדאי לעשות את זה רק בעת בדיקות ביצועים, אסור להסתמך על הנחות, ואמרו את זה כבר גדולים ממני.
מכיוון שהייתה לוגיקה בהכנסה למטמון, לא יכולנו פשוט לוותר על מעבר בקטע קוד זה.
אז החלטנו להחליף את המפה בConcurrentHashMap, הבעיה הייתה שמחלקה זו לא תומכת בnull.
לכן היינו צריכים לכתוב מחלקה עוטפת שכן תומכת בקוד זה.
בתחילה עשינו מחלקה שבבונה (constructor) שלה מקבלים את הNullObject אבל זה מימוש מסורבל.
אז עברנו למחלקה שמכניסים אליה את המחרוזת "null" במקום null בכל מקום שנדרש.
למחלקה קראנו ConcurrentHashMapWithNullSupport, לעיונכם.
למחלקה קראנו ConcurrentHashMapWithNullSupport, לעיונכם.
הדבר שיפר את הביצועים אבל עוד הרבה עבודה לפנינו ...