נושא ה Scalability הוא פופולרי-במיוחד בכמה השנים האחרונות.
לאחרונה עברתי על עשרות קורות חיים של מועמדים - ובכמעט כולם צוינו מושגים כמו "High Scalability", או "Big Data", "NoSQL", "Hadoop" - וכו'. כנראה שכל מי שעבד במערכת עם הרבה transcriptions per seconds או נפחים גדולים של נתונים - סיפר על כך בהרחבה, ומי שלא - התאמץ להראות איזו זיקה. זה "המשחק" היום, ונראה לי שהייתי עושה בעצמי את אותו הדבר בדיוק!
בפוסט הזה אני רוצה לספר על תהליך של Hyper-Scaling שאנו עוברים בחברת Gett - וכיצד הוא משפיע על עבודת הארכיטקטורה.
אני רוצה להזכיר שהמונח "Scalability", מתייחס בהנדסת תוכנה לשני סוגים של אתגרים:
אנו מגיעים ל Scale כאשר יש לנו כמות משאבים (למשל: שרתים) מסוימת, וכל שרת מבצע עבודה בקצב מסוים.
באופן דומה, גם בגדילה של גוף פיתוח - המפתח האחרון שנוסף נוטה להיות (בממוצע!) פחות יעיל מקודמו:
--
לאחרונה עברתי על עשרות קורות חיים של מועמדים - ובכמעט כולם צוינו מושגים כמו "High Scalability", או "Big Data", "NoSQL", "Hadoop" - וכו'. כנראה שכל מי שעבד במערכת עם הרבה transcriptions per seconds או נפחים גדולים של נתונים - סיפר על כך בהרחבה, ומי שלא - התאמץ להראות איזו זיקה. זה "המשחק" היום, ונראה לי שהייתי עושה בעצמי את אותו הדבר בדיוק!
בפוסט הזה אני רוצה לספר על תהליך של Hyper-Scaling שאנו עוברים בחברת Gett - וכיצד הוא משפיע על עבודת הארכיטקטורה.
No Scale |
אני רוצה להזכיר שהמונח "Scalability", מתייחס בהנדסת תוכנה לשני סוגים של אתגרים:
- Software Scalability - התמודדות עם יותר משתמשים, יותר פעילות, יותר נתונים.
- Development Scalability - היכולת להתנהל עם צוות פיתוח גדול יותר.
ב Gett יש לנו Software Scale מסוים, שהוא לא קטן - אבל גם לא ענק. ככה וככה נתונים, ככה וככה פעולות בשנייה.
ההתמודדות העכשווית שלנו היא דווקא יותר עם Development Scalability, שכמו שאנסה להראות במהלך הפוסט - יש לה דמיון לא-קטן ל Software Scalability.
לפני כחצי שנה, כשהגעתי ל Gett היו בצוות צד-השרת כשישה מתכנתים. הגעתי מעט לאחר גיוס ענק של 150M$ שהחברה ביצעה. עם הגיוס, החברה החליטה להגדיל משמעותית את קבוצת ה R&D - בכדי לקבל משמעותית יותר תפוקה. בעת כתיבת הפוסט יש לנו כבר עשרים וחמישה (!!!) מתכנתי צד-השרת - ואנחנו עוד מגייסים.
את הכלל של "לא להגדיל גוף פיתוח ביותר מ 50% בשנה" - שברנו כבר מזמן... מה עושים עכשיו? ואיך מתמודדים עם זה מצד הארכיטקטורה?
את הכלל של "לא להגדיל גוף פיתוח ביותר מ 50% בשנה" - שברנו כבר מזמן... מה עושים עכשיו? ואיך מתמודדים עם זה מצד הארכיטקטורה?
ההקבלה בין Scale של תוכנה ו Scale של קבוצות-פיתוח
נוסחה מקובלת בעולם ה Software Scale היא זו:
אנו מגיעים ל Scale כאשר יש לנו כמות משאבים (למשל: שרתים) מסוימת, וכל שרת מבצע עבודה בקצב מסוים.
גדילה ב Scale, כלומר: Scaling - מתבצעת ע"י הוספת שרתים או לחלופין ע"י שיפור הביצועים של כל מחשב בודד במערכת.
ככל שהמערכת גדלה - סביר שנחווה מצב בו כל מחשב נוסף שאנו מוסיפים הוא פחות יעיל מקודמו. מדוע? מכיוון ש:
- פעולות על כמות גדולה יותר של נתונים - אורכות יותר זמן. למשל: אינדקסים בבסיס נתונים רלציוני, הפחתת הרציפות בדיסק, caches פחות יעילים או סתם פעולות joins גדולות יותר (merge על work set גדול יותר).
- יותר תקשורת שנדרשת בין המחשבים השונים במערכת. יש הבדל בין הודעות עדכון שנשלחות ל 6 מחשבים - וכאלו שנשלחות ל 25 מחשבים. פעם נתקלתי במערכת שהוספה של מחשבים למערכת, מעל 16 מחשבים, כבר לא הגדילה את ה scale - בגלל ריבוי של הודעות כאלו.
- כמות הקוד שלא ניתן למקבל (parallelism) באופן טבעי תגדל, ולא תקטן - אלא אם הייתה השקעה משמעותית בצמצום קוד שכזה.
- חוסרי יעילות שונים - שצצים במערכת באקראיות טבעית.
- כל עבודה רוחבית במערכת (למשל: Refracting גדול), הופכים להיות קשים וארוכים פי כמה - כאשר כמות הקוד גדולה יותר.
- יותר תקשורת וסנכרון נדרשת בין המפתחים בקבוצה. אם פעם היה מספיק להרים את הראש מבהייה במסך - בכדי ליצור קשר עם מתכנת שמכיר היטב היבט מסוים של המערכת, היום כבר צריך לקום מהמקום, לחפש - ולעתים לגלות שצריך לדבר עם כמה אנשים בכדי לקבל תמונה מלאה.
- תמונות-העולם של המפתחים בארגון מתבזרות במהירות: בניגוד לצוות שהיה לו זמן להתגבש במשך תקופה ארוכה - כעת יש זרימה של אנשים חדשים, שכל אחד רגיל לשיטות שונות וגישות שונות.
הגישות הללו, עבור כל אחד, "הוכיחו את עצמן מעל ספק סביר - בעבר". אמת. אבל מה עושים כאשר הגישות הפוכות זו לזו? האם ORM הוא טוב להכל, טוב רק לקונפיגורציה, או "רעה-חולה שיש להסיר מהמערכת בהקדם האפשרי!"? - יותר ידיים עובדות => יותר קוד => מערכת מורכבת יותר. כל שינוי במערכת מורכבת יותר - אורך יותר זמן בעצמו (מגמה לחוסר יעילות מובנה).
- נוצרים יותר צווארי בקבוק ("רק משה מכיר את הקוד הזה") - שהולכים ומקשים יותר ויותר על התקדמות הפיתוח.
- יותר ישיבות, יותר המוניות, יותר אנשים שיש להכיר ולהתרגל לעבוד איתם - חוסרי יעילות שונים, שצצים במערכת באקראיות כל-כך טבעית.
ההשקעה ב Development Scale בפיתוח אמנם עוסקת במידה רבה בגיוס עובדים ("Capacity"), אבל לא פחות מכך - בשיפור היעילות של כל עובד ("Performance"). תהליכי ה Continuous Integration (ליתר דיוק: on-going integration) - מוגדרים מחדש, אנו משקיעים יותר בשיתוף הידע - ופישוט שלו, וכאן יש לצוות הארכיטקטים תפקיד חשוב.
אנו חותרים ל Continuous Delivery (הפעם: באמת) - בכדי לשפר את יכולת התגובה לתקלות במערכת, וכדי לעשות אותה יציבה יותר. באופן פרדוקסלי משהו, הניסיון בתעשייה מראה שדווקא העלאת תכיפות ה deployments מגדיל את יציבות המערכת - בטווח הבינוני-ארוך. יותר deploys = יותר "שבירות", אבל אז גם יותר לקחים, יותר מנגנוני-התאוששות ובקרה, ויותר אוטומציה. כל עוד אין מנגנון ארגוני שמותיר "לעצור את הקצב, ולצמצם את קצב ה deploys" - האנרגיות ינותבו לשיפור המערכת, ומנגנוני הייצוב שלה.
--
ב Software Scale, יש את השאיפה התמידית ל Linear Scalability: האידאל שלפיו הוספה של כל מכונה למערכת, תתרום את החלק היחסי שלה. למשל: הכפלת כמות השרתים - תכפיל את הספק העבודה (למשל: כמות בקשות בשנייה).
לא ממש Linear Scaling: ככל שמספר הבקשות עולה - יש להוסיף חלק יחסי גדול יותר של שרתים בכדי לענות על הביקוש. יש במערכת הזו צווארי בקבוק מסוימים ל scalability. |
בקבוצת ה R&D כולנו מבינים שככל שהמערכת גדלה - היעילות של המתכנים הולכת וקטנה. אין לנו שאיפות ל Linear Development Scalability. אנחנו גם מכירים במגבלות המקבילות האנושית ("תשע נשים לא יכולות ללדת ילד בחודש אחד").
בשונה מאיתנו ל Business דווקא יש ציפיות ל Linear Scalability - מפורשות יותר או פחות.
"פי-2 אנשי support עונים לפי-1.9 קריאות במוקד?" - הם מספרים, "כן... אנחנו מבינים שהנדסה זה קצת יותר מורכב. הגדלנו את הפיתוח פי 4, ואי-אפשר לקבל פי-4 פיצ'רים - אבל גם אי אפשר כבר לצפות לפחות לפי-3 יותר פיצ'רים, או לקבל אותם לפחות פי-3 יותר מהר?"
הלחץ מצד הביזנס הוא אולי משני, אבל הוא משפיע - וגורם לנו להתייעל יותר בכל הנוגע ל Development Scalability של הפיתוח. בעיקר ע"י צמצום חוסר-היעילות שהמערכת יוצרת למפתח הבודד.
--
ב Software Scale, יש "קסם" שיכול לסייע למערכת לצמוח ב Scale שהוא יותר מלינארי: מצב בו פי-2 שרתים, משרתים יותר מפי-2 משתמשים. כיצד זה קורה? יש כמה דרכים, אבל ה"קסם" הנפוץ ביותר הוא Cache (או memoization - בגרסה התאורטית שלו).
כאשר אנו יכולים לבצע חישוב מורכב רק פעם ב 5 דקות, ואז להפיץ את התוצאות לעוד ועוד משתמשים - כמות גדולה אפילו יותר של משתמשים תגדיל את הלחץ רק על ערוץ ההפצה (CDN?) - ולא על יצירת התוכן (החישוב).
ככל שנתכנן את המערכת שלנו בצורה בה ניתן יהיה להשתמש יותר ויותר ב Caches שכאלו - נשפר את ה Scalability של המערכת. תכנון שכזה כולל, הרבה פעמים - משא ומתן עם אנשי הביזנס ("תקבלו את זה מעודכן פעם בשעה - לא כל הזמן").
(פתרונות אחרים כוללים העברת עבודה ל Clients, או צמצום העבודה לקצב קטן מבו עולה ה scale, למשל: ביצוע חישוב על 500 משתמשי-מדגם, ללא קשר למספר כלל-המשתמשים במערכת).
ב Development Scaling יש גם כמה "קסמים" שכאלו. הבולט בהם - הוא code re-usability: היכולת להשתמש בקוד שנכתב בעבר - עבור פיצ'ר חדש.
פתרון שונה-דומה הוא Generalization: כתיבת קוד כללי יותר - שיכול לשרת מטרות דומות, אך שונות.
הקסמים האלו - הם חמקמקים ביותר!
הם באמת לפעמים "עושים את הקסם" - אבל פעמים אחרות "יוצא מהם כל האוויר" ברגע האמת: אנו משקיעים עוד זמן ועבודה בקוד כללי יותר / קוד המתוכנן לשימוש חוזר - אבל אז השימוש החוזר פשוט לא מתאים. אם השקענו בקוד כללי - ואין לו שימוש, אנו נשארים עם קוד יקר יותר, מסובך יותר, ועם הצורך עדיין לספק פתרון לפיצ'ר השני (או, חס וחלילה: לאנוס את הפיצ'ר השני להיות משהו אחר - בכדי להתאים לקוד שכבר קיים).
מהנדסים צעירים, ואולי אף מהנדסים בכלל - נוטים לבצע הערכת יתר (גסה?) ליכולת שלהם לייצר קוד יעיל לשימוש חוזר / קוד כללי יעיל. משם נוצר הכלל You Ain't Gonna Need It (בקיצור: YAGNI) המציע פשוט לא לנסות לחזות מקרים אלו מראש, אלא לעשות Refactoring לקוד כללי רק ברגע שהוכח הצורך - מעל ספק סביר.
בכל מקרה: שימוש חוזר בקוד והכללה, גם אם נעשים בדיעבד - הם כלים חשובים מאוד לשיפור ה Development Scalability.
אולי אתם מאוכזבים מעט מהפוסט: יש בו הרבה דיבורים כללים, ואין בו Hadoop, HPC או Big Data משום צורה!
אני אנסה לתמצת:
הפיתוח של Gett עובר כרגע תהליך של Development Hyper-Scaling. יש גם בעיות של Software-Scaling - אבל הן (עדיין) פחות מאתגרות - אולי אזכיר לקחים משם בפוסט אחר.
הארכיטקטורה, או תוכנית-העל שלנו להתמודד עם בעיות ה Development Hyper Scaling הן כאלו:
אני מודע לכך שעדיין אין פה Design Patterns הנדסיים משמעותיים (אולי מלבד MSA) - אבל זה מה שעובד, ואנו עושים את מה שעובד - ולא רק מה שמתאים לציפיה מסוימת (ארכיטקטורה = "תרשימים של ריבועים"). סורי! :)
זהו... מקווה שנהניתם, ואולי אף השכלתם. כרגיל - אשמח לתגובות.
שיהיה לנו בהצלחה!
"פי-2 אנשי support עונים לפי-1.9 קריאות במוקד?" - הם מספרים, "כן... אנחנו מבינים שהנדסה זה קצת יותר מורכב. הגדלנו את הפיתוח פי 4, ואי-אפשר לקבל פי-4 פיצ'רים - אבל גם אי אפשר כבר לצפות לפחות לפי-3 יותר פיצ'רים, או לקבל אותם לפחות פי-3 יותר מהר?"
הלחץ מצד הביזנס הוא אולי משני, אבל הוא משפיע - וגורם לנו להתייעל יותר בכל הנוגע ל Development Scalability של הפיתוח. בעיקר ע"י צמצום חוסר-היעילות שהמערכת יוצרת למפתח הבודד.
--
ב Software Scale, יש "קסם" שיכול לסייע למערכת לצמוח ב Scale שהוא יותר מלינארי: מצב בו פי-2 שרתים, משרתים יותר מפי-2 משתמשים. כיצד זה קורה? יש כמה דרכים, אבל ה"קסם" הנפוץ ביותר הוא Cache (או memoization - בגרסה התאורטית שלו).
כאשר אנו יכולים לבצע חישוב מורכב רק פעם ב 5 דקות, ואז להפיץ את התוצאות לעוד ועוד משתמשים - כמות גדולה אפילו יותר של משתמשים תגדיל את הלחץ רק על ערוץ ההפצה (CDN?) - ולא על יצירת התוכן (החישוב).
ככל שנתכנן את המערכת שלנו בצורה בה ניתן יהיה להשתמש יותר ויותר ב Caches שכאלו - נשפר את ה Scalability של המערכת. תכנון שכזה כולל, הרבה פעמים - משא ומתן עם אנשי הביזנס ("תקבלו את זה מעודכן פעם בשעה - לא כל הזמן").
(פתרונות אחרים כוללים העברת עבודה ל Clients, או צמצום העבודה לקצב קטן מבו עולה ה scale, למשל: ביצוע חישוב על 500 משתמשי-מדגם, ללא קשר למספר כלל-המשתמשים במערכת).
Scaling שהוא טוב מ Linear-Scaling: הוספת שרת למערכת - מוסיפה יכולת לספק קצת יותר משתמשים מחלקו במערכת. |
ב Development Scaling יש גם כמה "קסמים" שכאלו. הבולט בהם - הוא code re-usability: היכולת להשתמש בקוד שנכתב בעבר - עבור פיצ'ר חדש.
פתרון שונה-דומה הוא Generalization: כתיבת קוד כללי יותר - שיכול לשרת מטרות דומות, אך שונות.
הקסמים האלו - הם חמקמקים ביותר!
הם באמת לפעמים "עושים את הקסם" - אבל פעמים אחרות "יוצא מהם כל האוויר" ברגע האמת: אנו משקיעים עוד זמן ועבודה בקוד כללי יותר / קוד המתוכנן לשימוש חוזר - אבל אז השימוש החוזר פשוט לא מתאים. אם השקענו בקוד כללי - ואין לו שימוש, אנו נשארים עם קוד יקר יותר, מסובך יותר, ועם הצורך עדיין לספק פתרון לפיצ'ר השני (או, חס וחלילה: לאנוס את הפיצ'ר השני להיות משהו אחר - בכדי להתאים לקוד שכבר קיים).
מהנדסים צעירים, ואולי אף מהנדסים בכלל - נוטים לבצע הערכת יתר (גסה?) ליכולת שלהם לייצר קוד יעיל לשימוש חוזר / קוד כללי יעיל. משם נוצר הכלל You Ain't Gonna Need It (בקיצור: YAGNI) המציע פשוט לא לנסות לחזות מקרים אלו מראש, אלא לעשות Refactoring לקוד כללי רק ברגע שהוכח הצורך - מעל ספק סביר.
בכל מקרה: שימוש חוזר בקוד והכללה, גם אם נעשים בדיעבד - הם כלים חשובים מאוד לשיפור ה Development Scalability.
אז מה עם ארכיטקטורה ל Hyper-Scaling?!
אולי אתם מאוכזבים מעט מהפוסט: יש בו הרבה דיבורים כללים, ואין בו Hadoop, HPC או Big Data משום צורה!
אני אנסה לתמצת:
הפיתוח של Gett עובר כרגע תהליך של Development Hyper-Scaling. יש גם בעיות של Software-Scaling - אבל הן (עדיין) פחות מאתגרות - אולי אזכיר לקחים משם בפוסט אחר.
הארכיטקטורה, או תוכנית-העל שלנו להתמודד עם בעיות ה Development Hyper Scaling הן כאלו:
- בראש ובראשונה - מעבר ל Micro-Services: הפיכת מערכת אחת מורכבת - לכמה מערכות קטנות יותר, ומורכבות פחות. המעבר הוא אינטנסיבי, אבל הוא מאפשר להבין ביתר קלות את כלל המערכת - ולדעת להיכן לצלול בעת הצורך. כמו כן - הוא מצמצם במידה רבה את הצורך בתקשורת מרובה, לטובת תקשורת בסיסית יותר וממוקדת יותר, למשל: סך האחריויות של כל שירות, וה APIs שלו - שמוגדרים היטב (אנחנו משתמשים ב Swagger לתיעוד - כלי שמשרת אותנו היטב).
את השימוש ב MSA להתמודדות עם Development Hyper-Scaling לא המצאנו בעצמנו: למדנו מ case-studies על חברות שעמדו באתגר דומה (למשל: אמזון). - שימוש-חוזר בקוד, הוא רעיון שקשה לממש (מעבר לפונקציה פה ושם). דווקא Micro-services, בכך שאנו מגדירים שירותים עם שימוש עסקי ברור, ו APIs מוגדרים היטב - מסייעים לנו ליצור יחידות גדולות של קוד שמתאימות לשימוש-חוזר. כבר בחצי-שנה האחרונה, היו לנו כמה הצלחות יפות.
- אנו עוסקים בצורה פרואקטיבית בארגון בשיתוף ידע על חלקי המערכת השונים, האחריויות שלהם, וה flows העיקריים במערכת. לא עובר כמעט שבוע שאני לא עושה session שכזה, לצוות כלשהו בפיתוח - ואני לא היחידי. עוד פעם ועוד פעם - עד שלכולם כבר יימאס (אנחנו עוד רחוקים משם...).
שמות פשוטים, מטפורות טובות, וסיפורים קליטים - הם מרכיב עקרי בבניית והפצת הידע. - צוות הארכיטקטים לוקח תפקיד קצת יותר ריכוזי מהרגיל (אולי: יותר מהאידאל האישי שלי?!) בהגדרת superflows חדשים במערכת. כן! אנחנו רוצים לעבוד יותר agile ולתת לאנשים יותר ויותר אחריות והשפעה, אבל בנקודת הזמן הזו - תוצאות טובות יותר מושגות כאשר לפחות את ה flows העיקרים - מוגדרים מרכזית ע"י הארכיטקטים.
כאשר מפתחים עושים שינויים ושיפורים ב flows - זו סיבה לשמחה (אלא אם בכך הם סותרים עקרונות או flows אחרים במערכת). - אנו מנסים לקדם בקוד כמה עקרונות:
- קידום תרבות של הצגת פתרונות - ולא רק בעיות (בכל ה R&D).
- קוד פשוט להבנה - עדיף יותר על פני קוד קצר או מתוחכם. אם אתם קוראים של הבלוג זמן רב, אתם אולי יודעים שזו הנטייה הטבעית שלי - אבל זה לא הסטנדרט הברור של ריילס (שם עקרונות של קוד קצר ו DRY - מושרשים עמוק בקהילה).
- יותר כלי monitoring ו supportability עבור הפיתוח. בכתיבה של כל פיצ'ר - לחשוב איזו השקעה תהיה משתלמת כאשר לפיצ'ר הזה תהיה בעיה ב production. כלי supportability יכולים להציג חווית שימוש עלובה למדי - כל עוד הם עוזרים.
- הכנסה של אוטומציה / בדיקות יחידה / בדיקות-API / בדיקות-אינטגרציה. בכל ארגון שראיתי בעשור האחרון זו הייתה המגמה - אבל אנחנו עכשיו צריכים את זה יותר.
אני מודע לכך שעדיין אין פה Design Patterns הנדסיים משמעותיים (אולי מלבד MSA) - אבל זה מה שעובד, ואנו עושים את מה שעובד - ולא רק מה שמתאים לציפיה מסוימת (ארכיטקטורה = "תרשימים של ריבועים"). סורי! :)
זהו... מקווה שנהניתם, ואולי אף השכלתם. כרגיל - אשמח לתגובות.
שיהיה לנו בהצלחה!