בעיות של מערכות מבו…זרות

לאחרונה שמתי לב שהנושא של "מערכות מבוזרות" הוא (שוב?) באופנה. אנשים רבים מציינים שהם מתעסקים "במערכות מבוזרת" – במה באמת מדובר? האם מדובר במערכות ווב שרבים מאיתנו עוסקים בהן? אולי רק בפרויקטים מחקריים אולטרא-מורכבים שרובנו בכלל לא נבין?

האמת… כרגיל – נמצאת איפהשהו באמצע, ובנוסף היא גם איננה חד-משמעית.

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

אם תחפשו באמזון (אני משוטט בין הספרים הטכניים שם בתור תחביב קבוע) – אין הרבה ספרות על מערכות מבוזרות. זהו תחום מצד אחד "נחשב" (נראה לי) ומצד שני שכיסוי הידע בו נמצא בחסר – מול הצורך האמיתי בשוק. יש כמה ספרים שעוסקים באלגוריתמים מבוזרים (צד אחד נקודתי של העניין) ו 3-4 ספרים שמנסים לספק תמונה מקיפה על התחום, על כלל מרכיביו – אבל הם לרוב אקדמיים למדי. לא תמצאו בהם מילה על Hadoop, קסנדרה, או Zookeeper – שהן אולי המערכות המבוזרות החשובות של תקופתנו. הם מכסים לכל היותר מערכות של תקשורת P2P.

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

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

A Collection of independent computers that appear to its users as one computer — Andrew Tannenbaum

כאשר:

  • המחשבים במערכת פועלים בו-זמנית (concurrently).
  • המחשבים במערכת כושלים באופן בלתי-תלוי אחד מהשני.
  • השעון של המחשבים במערכת לא מכוונים לאותה השעה.
התנאי השלישי היא המעניין: למה שלא יהיו מכוונים לאותה השעה? ובכן:
  • אנו, בני-אדם, מחשיבים שעונים כמכוונים לאותה השעה גם כאשר יש ביניהם הפרש של כמה שניות [א]. עבור מחשבים, הפרש של כמה מליוניות שניה – יכול לעשות את ההבדל.
  • יש להניח שהתקשורת בין המחשבים במערכת עוברת ברשת – רשת בה יש אקראיות. אפילו אם המחשבים קרובים זה לזה פיסית, הרשת תגרום לזה שהודעה שנשלחה בזמן t לשני מחשבים שונים תגיע למחשבי היעד בזמנים שונים ובסדר אקראי – אקראיות שניתן להקביל, ל "שעונים שלא מכוונים אותו הדבר".
כלומר: אם נניח מראש שהשעונים אינם מכוונים, הסיכוי שלנו להיכשל בהנחות שגויות לגבי התנהגות המערכת – יפחת, ולכן אולי כדאי לנו להניח שזה המצב הנתון.
מחשב אישי (PC) הוא לא מערכת מבוזרת בדיוק מהסיבה הזו.
חשבו על זה: המחשב האישי בנוי מכמה רכיבים דיי חכמים ("מחשבים"?), שפועלים במקביל – ויכולים להיכשל באופן בלתי תלוי: CPU, GPU (מעבד או "כרטיס" גראפי), כונן כשיח, כרטיס רשת, וכו'.

האם המחשב האישי הוא מערכת מבוזרת?לא. הסיבה לכך היא שהמעבד הראשי (CPU) הוא הרכיב שמכתיב את קצב העבודה ("מספק שעון", מאות אלפי פעמים בשנייה) לכל הרכיבים האחרים במערכת – וכל הרכיבים הם מסונכרנים בדיוק לאותו השעון. ההבדל סמנטי, לכאורה – אבל חשוב.

הערה חשובה: מערכות מבוזרות הן מורכבות יותר ממערכות שאינן מבוזרות. אם תצליחו לפתור בעיה נתונה בעזרת מערכת לא-מבוזרת – כנראה שזה יהיה פתרון טוב יותר.

בעיות "אקדמיות" של מערכות מבוזרות

אלו לרוב בעיות שהן יותר low level, בליבה של המערכת המבוזרת – שאולי רבים מהמשתמשים של המערכות הללו יכולים לא להכיר.

Multicast – שליחת הודעה לכל המחשבים בקבוצה

אמנם פרוטוקול IP כולל יכולות Multi-cast כחלק מהפרוטוקול – אבל: א. המחיר של multicast הוא גבוה למדי, ב. ברוב הפעמים קונפיגורציה הרשת (הפרדה בין רשתות, firewalls, וכו') והעובדה שחלק מהמכונות לא זמין לקבל את ההודעה באותו הרגע (בשל כשל / עומס) – הופך multicast ברמת הרשת לכמעט לא רלוונטי.

את בעיית ה Multicast פותרים לרוב ב-2 דרכים עיקריות:

  • Messaging – כאשר יש מתווך (Message Bus, Message Broker וכו') שדואג לכך שההודעות יגיעו גם למחשב שכרגע לא זמין. המתווך צריך להיות Highly Available ובעל יכולת גישה לכל המחשבים במערכת – על מנת לספק רמת שירות גבוהה.
  • Gossip-based transmitting (נקרא גם Gossip Protocol) – משפחה של פרוטוקולים שמעבירים את ההודעה כחיקוי הדפוס של התפשטות מגיפות (אפידמיה): כל מחשב שולח הודעה לחבר אקראי, אחת לכמה זמן, לאורך טווח זמן שנקבע. סטטיסטית, בסבירות גבוהה מאוד – ניתן להגדיר התנהגות שבסופה כל המחשבים ברשת יקבלו לבסוף את ההודעה, על אף כשלים, שינוי בתוואי הרשת, או קושי להגיע למחשבים מסוימים. החיסרון של גישה זו היא שטווח הזמן של פעפוע ההודעות איננו מהיר, ויש "בזבוז" של משאבי הרשת בהעברת הודעות כפולות. לרוב משתמשים בדפוס זה להעברת הודעות קטנות.
    יישומים מקובלים הם עדכון קונפיגורציה או איתור ועדכון על כשלים במערכת.
Remote Procedure Call (בקיצור RPC)
הבעיה של הפעלת מתודה על מחשב מרוחק, בצורה אמינה וקלה – גם היא נחשבת לסוג של בעיה של מערכות מבוזרות, אבל מכיוון שהדומיין הזה מפותח למדי – לא אכנס להסברים.
Naming – היכולת של כל המחשבים במערכת לתת שם אחיד למשאב מסוים
דוגמה נפוצה אחת יכולה להיות הכרת כל המחשבים במערכת (למשל hostnames), בעוד מחשבים עולים ונופלים כל הזמן.
דוגמה נפוצה אחרת היא כאשר נתונים זזים לאורך הזמן (בשל רפליקציה, כשלים, ו partitioning) בין אמצעי אכסון שונים – ואנו רוצים להיות מסוגלים לאתר אותם.
יש גישות רבות לפתרון הבעיה, אציין כמה מהן בזריזות:
  • Naming Server מרכזי (או מבוזר עם רפליקציות לקריאה-בלבד, דמוי DNS -שהוא גם היררכי) – שמנהל את השמות במערכת וכולם פונים אליו.
  • Client Side Directory (קליינט = מחשב במערכת) – גישה בה משכפלים את ה Directory לכל המחשבים במערכת ע"י multi-cast (למשל עבור client-side load balancing – כאשר כל מחשב ברשת בוחר אקראית למי לפנות בכדי לבקש שירות. סוג של פתרון לבעיית ה Fault Tolerance).
  • Home-Based Approaches – כאשר הקונפיגורציה היא כבדה מדי מכדי לשכפל כל הזמן בין כל המחשבים במערכת, ניתן לשכפל (ב multi-cast) רק את רשימת שרתי הקונפיגורציה (עם אורנטצייה גאוגרפית, או של קרבה – ומכאן המונח "Home"). אם שרת קונפיגורציה אחד לא זמין (כשל בשרת או בתוואי הרשת) – ניתן לפנות לשרת קונפיגורציה חלופי.
  • Distributed Hash Table (בקיצור DHT) – שזה בעצם השם האקדמי למבנה הנתונים הבסיסי של שרת Naming משתכפל עצמית – לכמה עותקים, בצורה אמינה, scalable, וכו'. בסיסי-נתונים מבוזרים מסוג K/V כמו Cassandra או Riak – נחשבים למשתייכים לקטגוריה זו, ויכולים (ואכן משמשים) כפתרון לבעיית Naming במערכת מבוזרת.
    למיטב הבנתי, Cassandra מתבססת על מבנה מעט שונה שנקרא Consistent Hashing, שיעיל ביותר בפעולות ה Lookup של keys, על חשבון מחיר שינויים ברפליקציה שהם יקרים יותר. הנה מצגת שמצאתי שמסבירה את ההבדל בין מבני-נתונים.
סנכרון שעונים 
אמנם אמרתי בהקדמה שהנחת היסוד של מערכות מבוזרות היא שהשעונים אינם מסונכרנים – אך זה לא אומר שלא ניתן לשאוף למצב כזה, או לקירוב שלו. העניין הוא גם שכל המחשבים במערכת יגיעו להסכמה על שעון אחיד (בעיית הקונצנזוס – נדון בה מיד) וגם היכולת לקחת בחשבון את ה Latency של הרשת ו"לבטל אותו", כלומר: בקירוב מסוים.
לעתים קרובות, הידיעה שלמחשבים במערכת יש שעות אחיד בסטייה ידועה (נניח עד 100ns, בסבירות של 99.99%) – יכול להיות בסיס לפישוט תהליכים משמעותי. גישות להתמודדות בעיה זו:
  • הפתרון הבסיסי ביותר הוא פתרון כמו Network Time Protocol (בקיצור NTP) שמקובל על מערכות לינוקס / יוניקס – שקורא את השעה משרת זמן מרכזי, ותוך כדי הקריאה מבצע מדידות של ה latency לשרת המרכזי. באופן זה, הוא יכול להסתנכרן, בסטייה שניתנת לחישוב – לזמן של השרת המרכזי.
  • פרוטוקולים משוכללים יותר, כמו Reference broadcast synchronization (בקיצור RBS) אינם מניחים על קיומו של שרת זמן מרכזי, אלא מודדים latencies לשאר המחשבים במערכת – וכך מייצרים זמן משוערך יחסית למחשבים אחרים במערכת.
  • חשוב לציין שהשעון לא חייב להיות שעון של בני-אדם (שעה, דקה, שנייה, מיקרו שנייה), אלא יכול להיות שעון לוגי, שהוא בעצם counter עולה של אירועים. כל פעם שיש אירוע בעל משמעות – מעלים את ה counter באחד ומסנכרים את כלל המערכת על רצף האירועים (ולאו דווקא זמן ההתרחשות המדויק שלהם). מימוש מקובל: Lamport's Clock.
    הרעיון דומה לרעיון ה Captain's log stardate הדמיוני מסדרת המדע הבדיוני StarTrek: מכיוון שמסע במהירות האור (או קרוב לו) משפיע על מרחב הזמן, לא ניתן להסתמך על שעון עקבי עם שאר היקום, ולכן הקפטן מנהל יומן ע"פ תאריכים לוגיים. (סיבה נוספת למנגנון התאריכים בסדרה: לנתק את הצופה מרצף הזמן המוכר לו, סיבה קולנועית גרידא).
יצירת קונצנזוס
בעיית הקונצנזוס היא הבעיה לייצר תמונת עולם אחידה, לגבי פריט מידע כלשהו – עבור כל המחשבים במערכת.
ברמה הבסיסית יש את בעיית ה Leader Election: כיצד בוחרים מחשב יחיד בקבוצה (נקרא "מנהיג" – אבל עושה את העבודה השחורה) לבצע פעולה כלשהי (למשל: לשלוח מייל למשתמש, על אירוע שכל המחשבים במערכת יודעים עליו).
אנו רוצים שרק מחשב יחיד יבצע את הפעולה בכדי למנוע כפילויות.
כיצד בוחרים מנהיג?
  • לנסות לדמות בחירות אנושיות – זה לא כיוון פעולה מוצלח. נדלג. 🙂
  • אלגוריתם פשוט (The Bully Algorithm) מציע לכל מחשב במערכת לשלוח ב broadcast מספר אקראי שהגדיר, ובעל המספר הגבוה ביותר – הוא המנהיג.
  • ישנם מספר אלגוריתמים מורכבים יותר (מבוססי Ring או DHT) – שהם אמינים יותר, ומתאימים יותר למערכות עם מחשבים רבים.
  • בכל מקרה, אחד העקרונות המקובלים הוא לבחור מנהיג קבוע (כלומר: עד שהוא מת) – בכדי לחסוך את הפעלת האלגוריתם שוב ושוב. זה יכול להיות "מנהיג אזורי" או "מנהיג גלובאלי".
סוג אחר יש יצירת קנוזנזוס נעשית ע"י טרנזקציה מבוזרת. בד"כ מבצעים טרנזקציה מבוזרת ע"י רעיון / אלגוריתם שנקרא two-phase commit (או בקיצור 2PC). האלגוריתם עובד כך:

  1. ה coordinator הוא מי שיוזם את הפעולה. הוא שולח vote request לכל המחשבים המיועדים להשתתף בטרנזקציה המבוזרת.
  2. כל מחשב שמקבל את ה vote request עונה vote-commit (אם הוא מסוגל לבצע את הפעולה) או vote-abort (אם הוא לא מסוגל לבצע אותה).
  3. ה coordinator אוסף את כל הקולות. אם יש לפחות מישהו אחד שענה vote-abort, או פשוט לא ענה – הוא מפרסם global-abort וכל המחשבים מבצעים rollback.
  4. אם כולם ענו ב vote-commit – ה coordinator שולח הודעת global-commit – וכל המחשבים מבצעים commit מקומי על הטרנזצקיה.
יש גם גרסה מתוחכמת יותר של הפרוטוקול, בשם three-phase-commit (בקיצור 3PC) – המתמודדת עם מצב ביניים בו ה coordinator כשל. מעולם לא שמעתי מערכת שממשת את 3PC, וכנראה ש 2PC טוב לרוב הגדול של השימושים.
עוד אלגוריתם מקובל להשגת קונצנזוס הוא paxos שדורש שרוב (quorum) של המחשבים במערכת יסכים על עובדה – ואז הם מעדכנים את השאר המחשבים על ההחלטה.סוג אחרון של קונזנזוס, או תאום הוא Distributed Mutual Exclusion (שנקרא בתעשייה פשוט Distributed Lock) בו כמה מחשבים מתאימים ביניהם מי ייגש למשאב מסוים בלעדית, ייתכן ובמצב שלמשאב הזה יש כמה עותקים במערכת (בשל רפליקציה).

Distributed Fault Tolerance
הכלל השחוק כמעט בעניין זה הוא "Avoid a single point of failure" – על מנת שמערכת מבוצרת תהיה אמינה, יש לדאוג שאין שום רכיב יחיד במערכת שכישלון שלו – מכשיל את כלל המערכת.
תחום זה כולל נושאים של:
  • גילוי כשלים במערכת. אם מחשב א' לא מצליח ליצור קשר עם מחשב ב' – האם זו בעיה במחשב א', במחשב ב'? או בשילוב שלהם (גרסאות לא מתואמות, בעיות רשת)?
    • הבסיס לפתרונות הוא לרוב בעזרת coordinator (או קבוצה של coordinators) שדוגמים את כלל המחשבים במערכת בעזרת הודעות heartbeat (או alive) – בהן כל מחשב מדווח על מצבו, וע"פ כללים מסוימים מגיעים להחלטה.
    • גישות מתוחכמות יותר מתבססות על מידע שמגיע ממחשבים על חברים שלהם במערכת – ואז לקיחת החלטה מבוזרת (למשל: הצבעה בנוסח paxos האם מחשב מסוים הוא תקין או לא).
  • תקשורת P2P שהיא כבר פתרון לקשיים בין מחשבים במערכת לתקשר זה עם זה. כאשר מתקינים מערכת ברשת ארגונית – ייתכנו מצבים (ע"פ הגדרה) בהם אין לכל המחשבים ברשת תקשורת ישירה זה עם זה. מצב בעייתי אחר שאיתו P2P מתמודד הוא כשלים ברשת האינטרנט (ויעילות בהעברת כמויות גדולות של מידע, למשל: סרטים).
  • נושא נוסף הוא אלגוריתמים מתייצבים עצמית, שזה הרעיון של כתיבת אלגוריתמים שבהינתן מצב לא תקין רנדומלי באחד המחשבים – האלגוריתם, תוך כדי פעולה, יפצה על הכשל ויתקן אותו עם הזמן. פרוטוקולי תקשורת, ופרוטוקולים של רפליקציית נתונים – כוללים אלמנטים של התייצבות-עצמית.
כיום, ניתן למצוא כמה מערכות שמספקות את ה primitives הנ"ל. מערכות כמו:
  • Zookeeper (של Hadoop)
  • Consul (מבית היוצר של Vagrant)
  • Etcd
  • Eureka (של נטפליקס)
מספקות בבסיסן מערכת Naming מבוזרת (הם קוראים לזה לרוב Service Discovery) וקונפיגורציה מבוזרת (משהו בין Naming מבוזר לניהול קונצנזוס), אבל חלקן כוללות גם פרימיטיביים של Leader Election, Voting, 2 Phase-Commit וכו'.
אם אתם מזהים במערכת שלכם את הצורך באחד מהפרימיטיביים הללו – שווה אפשר לשקול לקחת מערכת מוכנה.
האמת: פעם בחיים פיתחנו Leader Election, ופעם 2PC – ודווקא היה בסדר (בהקשר לפוסט "התשתית הטובה ביותר").

בעיות "מעשיות" של מערכות מבוזרות

כאשר ניגשים למימוש מערכות מבוזרות "רגילות" (למשל: מערכות ווב ב Scale), אנו כנראה ניתקל בכמה בעיות שונות מהבעיות הנ"ל: בעיות ברמת הפשטה גבוהה יותר – שניתן לפתור בעזרת כלים שהתמודדו בעצמם עם חלק מהבעיות הנ"ל.

רק בשלבים מאוחרים יותר של ההתמודדות עם המערכת, אנו נדרשים ל fine-tuning שחושף אותנו להתמודדות עם הבעיות "האקדמיות" שהצגתי.

הבעיות המעשיות עליהן אני מדבר הן:

בעיות Scale

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

  1. שכפול "כוח-עבודה זול" – מעבר ל cluster של מחשבים שיבצע את העבודה (דאא). מה שנקרא "ציר-x" של ה Scale Cube.
  2. העברת עבודה ל Clients (למשל קוד javaScript). מכיוון שעל כל שרת יש עשרות עד אלפי מחשבי Client – יש הגיון רב לנצל את כח החישוב שלהם או האכסון שלהם.
  3. יצירת עותקים נוספים לקריאה-בלבד (Read Replicas) כפי שנהוג ב MySQL, למשל.
    1. וריאציה אחרת של רעיון זה הוא שימוש ב caches.
  4. פירוק המערכת למספר תתי-מערכות שכל אחת מבצעת תת-פונקציה. מה שנקרא "ציר ה-y" של ה Scale cube. חלוקת מערכת "מונוליטית" למיקרו-שירותים היא סוג של פירוק מערכת לתתי-מערכות.
  5. ביצוע Partioning (של מידע או עבודת חישוב). למשל: מחשבים בקבוצה א' יעבדו את הלקוחות ששמם מתחיל באותיות א-י, והמחשבים בקבוצה ב' יעבדו את הלקוחות ששמת מתחיל באות כ-ת. חלוקה זו מסומנת כ "ציר-z" ב Scale Cube, ובד"כ מגיעים אליה לאחר ששאר המאמצים מוצו.
    שימוש ב Partitioning מקל על ה Scalability, אבל יוצר בעיות חדשות של Consistency וסנכרון.
ה Scale Cube הוא מודל תאורטי, אך שימושי – המאפיין את הדרכים להגדיל את אופן פעולתה של מערכת (בעיקר צד-שרת).
בד"כ מתחילים לגדול בציר x, לאחר מכן ציר y, ורק לבסוף בציר z (המפרך יותר).

כאשר עוסקים ב Scale גדול המעבר ל Partitioning הוא כנראה בלתי-נמנע, מה שמציב כמה בעיות אחרות.
בעיה אחת לדוגמה היא הבעיה הידועה כ CAP theorem. הטענה שבהינתן Partitioning, לא ניתן לקבל גם Availability (זמינות) ו Consistency (עקביות הנתונים) באותו הזמן – ויש לבחור ביניהם.

או שנתשאל את המערכת ותמיד נקבל תשובה (Availability גבוה) – אבל התשובה לא בהכרח תהיה זהה מכל המחשבים שנפנה אליהם – כלומר: ביצענו פשרה ב Consistency, מה שנקרא AP.
או שנתשאל את המערכת ותמיד נקבל תשובה זהה מכל המחשבים במערכת (Consistency גבוה) – אבל לפעמים לא תהיה תשובה, כלומר התשובה תהיה "לא בטוחים עדיין, אנא המתן) (פשרה ב Availability), מה שנקרא CP.

הערה1: בסיס נתונים רלציוני נחשב כ "CA" כי הבחירה שלו היא ב Consistency ו Availability – אך ללא יכולת ל Partitioning (ולכן אומרים ש RDBMs הוא "לא Sacalable").
הערה2: פרויקט Cassandra הצליח לכאורה "לרמות" את ה CAP Theorem. כיצד? הנה הסבר מהיר: כל פריט מידע נשמר (נניח) כ 5 פעמים במערכת. בכל פעולת קריאה אנו מציינים כמה עותקים נדרשים לצורך הקריאה. אם נבחר 1 – נקבל AP, אם נבחר 5 – נקבל CP – ויש לנו את הטווח באמצע. כלומר: לא באמת רימינו את ה CAP Theorem, אבל אפשרנו לכל פעולת קריאה לבצע את ה trafeoff עבור עצמה.

עוד הקבלה מעניינת של ה CAP Theorem נמצאת בפעולת multi-casting השונות:

פרוטוקול Gossip הוא סוג של AP, פרוטוקול Paxos הוא סוג של CP, ופרוטוקול 2PC הוא סוג של CA – הוא לא יכול להתבצע על חלק מהמחשבים במערכת ולהשיג את התוצאה.

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

סיכום

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

שיהיה בהצלחה!

—-

קישורים רלוונטיים

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

—–

[א] אני מכיר דווקא בחור אחד שלא. אצלו "מכוונים לאותה השעה" הוא הפרש של עד חצי שנייה – ואל תשאלו איך הוא מכוון אותם….

משרת ארכיטקט ב Gett

היי,

כפי שרבים מכם בוודאי שמתם לב, עברתי לפני מספר חודשים (הממ… בעצם חצי שנה – הזמן עובר מהר!) לחברת Gett (לשעבר GetTaxi).

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

בקיצור: אני מגייס עוד ארכיטקט לצוות.

כרגע יש בצוות שלושה ארכיטקטים: רועי שגיב (שמגיע בעיקר מעולם ה Client), מיקי שירן (שמגיע מעולם השרת / ריילס), ואני – שמנהל את הצוות, אבל גם מבצע משימות כמו שאר החברים הצוות. אין לי פריווילגיה לנהל בלבד, וגם אם תהיה – לא נראה לי שאהיה מעוניין בה.

חברת Gett היא חברה בעלת הכנסות גבוהות, רווחית, ושנמצאת בצמיחה מהירה מאוד.

שנה שעברה (על פי פרסומים זרים) החברה הכניסה כ 200 מיליון דולר, והשנה הכוונה היא להכניס 500 מיליון דולר – פי 2 וחצי.

צוות השרת, ביום שהגעתי, כלל כ 6 מפתחים – והיום יש בו כבר 12 מפתחים ועוד 3 קבוצות outsource המגבות את חוסר היכולת שלנו לגייס לצוות בקצב בו היינו רוצים. וכן – אנחנו מגייסים:

  • מפתחי שרת (ריילס וגו)
  • מפתחי מובייל (אנדרואיד / iOS)
  • DevOps
  • וכמובן – גם עוד ארכיטקט.

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

התנועה במערכת הולכת וגדלה ובעיות Scale ו Stability צצות באופן תדיר – בעיות שיש לפתור, לעומק.

קבוצת הפיתוח של Gett (חלק גדול ממנה) במשחק באולינג

הארכיטקט שאני מחפש ידרש:

  • לתמוך וללוות את צוותי הפיתוח בעת פיתוח פיצ\'רים מורכבים / מרכזיים לחברה.
  • לעזור לבנות ב R&D תמונת עולם משותפת, אודות הארכיטקטורה של המוצר – ולאן היא הולכת.
  • מדי פעם, לעשות Designs מפורטים לפיצ\'רים כאלו ואחרים – שדורשים זאת.
  • לעזור לשפר את קבוצת הפיתוח – לתרום לרמת הידע והמקצועיות של הצוותים.
  • לתרום למאמץ הכללי, ליצור תשתית Highly Scalable, Highly Available ו Highly Maintainable (מלים גדולות – אבל זה המצב) המתהווה בתקופה זו – תשתית שמריצה את הביזנס של חברת Gett. זה אומר גם מעורבות ב DevOps, באיכות המוצר, ובתהליכי הפיתוח בכלל.

העולם הטכנולוגי שלנו מבוסס בעיקר על AWS, רובי on ריילס, שפת Go, פיתוח אפליקציות נייטב לאנדואיד ואייפון, PostgreSQL,  רדיס, וקצת Hadoop.
אנו באמצע תהליך של שבירת אפליקציה \"מונוליטית\" (סוג של MVC) – למיקרו-שירותים. יש לנו כבר כ 14 מיקרו-שירותים, ונוצרים אחד או שניים חדשים כל חודש, מאז שהגעתי.

אני מחפש מישהו:

  • עם לפחות 5 שנות ניסיון בפיתוח משמעותי – עדיף שכבר ממלא תפקיד של ארכיטקט כמה שנים.
  • בעל עומק מקצועי: מישהו שנכנס לעומק העניין, ומבין את הבעיות הטכנולוגיות (או הארגוניות) עד השורש.
  • מישהו בעל רקע טוב בצד השרת (שם רוב האתגרים שלנו, כרגע): מבין בסיסי נתונים, ותכנון של מערכות.
    • ייתרון משמעותי הוא הכרות מעמיקה עם ה Stack הטכנולוגי של ריילס.
    • ייתרון נוסף יכול לנבוע מניסיון עבודה במערכות מבוזרות (דברים כמו leader election, sharding או circuit breakers), במובייל, במערכות Big Data (אנחנו עוד רק בהתחלה), ופיתוח צד לקוח וובי.
  • מישהו שמתאים ונהנה לעבוד בסביבה אינטנסיבית, בה לוקחים החלטות מהר, ולעתים בוחרים בפתרונות פחות מקיפים – בכדי להתקדם מהר יותר / לללמוד מהר יותר.
  • מישהו שיכול לזקק בעיה טכנולוגית מורכבת להסבר פשוט – ולהנחיל אותו בקרב הסובבים אותו.
  • מישהו בעל יחסי-אנוש טובים, צנוע (לפחות, במידה), ושיודע לשמוח שהדבר הנכון נעשה – גם אם לא הוא יזם אותו. זוהי תרבות ארגונית לא שגרתית שקיימת ב Gett – ומאוד מנסים לשמר אותה.
הכוונה היא לא לחפש רק מישהו ותיק ומנוסה שיודע לעשות הכל (זה יהיה נחמד – אבל לא חובה), אלא גם מישהו באמצע הדרך שיכול ללמוד ולהתפתח בתוך החברה. הכוונה היא להשקיע במועמד (כנסים, יש לנו מסלול \"הכשרת ארכיטקטים\" פנימי, וכו\'). סה\"כ נראה לי שהסביבה ב Gett היא סביבה לימודית מצויינת בלי קשר – בגלל כל ההתרחשויות בחברה.

אם אתם מעוניינים באחת המשרות, ובמיוחד במשרת הארכיטקט – פנו אלי במייל: liorb[at]gett.com. כנ\"ל לגבי חברים / מכרים.
כשאתם כותבים אלי אתם יכולים להיות קצת פחות רשמיים – ולספק קורות חיים \"freestyle\". אפשר גם לשלוח מייל רק בכדי להתייעץ. עבורכם או עבור חברים.

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

שיהיה לכולנו בהצלחה!

ליאור

על Circuit Breakers ויציבות של מערכות מבוזרות

דמיינו 2 חנויות מכולת כמעט זהות: "המכולת של שמואל" ו"המכולת של הלל".

בשתי חנויות המכולת יש מוצרים איכותיים שאתם אוהבים, מוזיקה טובה מתנגנת, השירות מצוין, הן פתוחות 24/7, והמחירים – הוגנים. אתם אוהבים את המכולות הללו, ואתם אוהבים לקנות בהן.

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

במכולת של הלל – המצב הפוך. הם מוכנים לכך שלא יהיו מוצרים מסוימים זמינים. הם קוראים למצב הזה partial service או degraded service (הם קצת גיקים עם המונחים שלהם). המכולת פתוחה 24/7 למעט מקרים נדירים בהם הם סוגרים אותה (כאשר הלל צריך לצאת לאיזה סידור ואין לו מחליף), אבל כאשר אני הולך לקנות משהו – ייתכן ואחזור ללא קוטג' הביתה. לפעמים לא אכפת לי, אבל לפעמים זה מבאס. אם זה ממש חשוב לי אפילו אקפוץ למכולת אחרת להביא את הקוטג'. אבל בגלל שאני אוהב את החוויה במכולת – אני עדיין אחזור אליה בקנייה הבאה.
במקום לדאוג ל"אפס חוסרים במלאי", החבר'ה של הלל עסוקים בכך שחוסר של מוצר מסוים במלאי – לא יגרום למכולת להיסגר מעצמה. זה מאוד מוזר אבל הם מספרים שאם לא יעשו שום דבר, מחסור בטונה פתאום יגרום למכולת להסגר מעצמה. גיקים – אמרנו כבר?

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

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

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

Circuit Breakers

המנגנון הבסיסי המאפשר את מודל "המכולת של הלל" הוא ה Circuit Breaker.
לצורך הדיון אניח שמדובר במערכת בארכיטקטורת Micro-Services (בקיצור: MSA) והשירותים השונים הם הרזולוציה לבידוד בפני תקלות. ניתן לעשות אותו הדבר במערכת "מונוליטית", אם כי המשמעת העצמית הנדרשת היא לעתים מעט גבוהה יותר.

ה Circuit Breaker הוא מן Proxy לשירות מרוחק – אשר רק דרכו עושים את הקריאות. השירות המרוחק יכול להיות:
א. זמין
ב. לא זמין
ג. זמין – אך כושל (יש errors)
ד. זמין אבל אטי

אם ה Circuit Breaker מזהה שהשירות המרוחק אינו עובד כמו שצריך – הוא "יוצר נתק" (מטאפורה: פותח את המעגל) ולא מאפשר לקריאות נוספות לעבור. הפונקציות השונות מקבלות אינדיקציה על המצב הזה ופועלות כמיטב יכולתן לספק שירות חלקי – שעדיף על חוסר שירות.

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

מקורות: "דפוס העיצוב" של ה Circuit Breaker הוצג לראשונה ב 2007 ע"י מייקל נייגארד, בספר המצויין (!!): !Release It.
ב 2014 מרטין פאולר כתב פוסט בבלוג על דפוס העיצוב (הוא לא חידש שם, רק הסביר יותר פשוט), ומאז זהו ה reference המקובל.

המימוש הפנימי של ה Circuit Breaker הוא לרוב כמכונת מצבים פשוטה:

מקור: orgsync/stoplight (מימוש ברובי של Circuit Breaker)
  • המצב ההתחלתי הוא "מעגל סגור" (ירוק).
  • אם יש מספר מסוים של תקלות (מוצג בתרשים כ "fail") – עוברים למצב "אדום".
    • בד"כ לא מדובר בתקלה יחידה אלא threshold של סדרת תקלות. למשל: רצף של 5 exceptions, כאשר ה threshold הוא של 5.
  • במצב "אדום" – כל ניסיון קריאה לשירות המרוחק ייענה בערך החזרה מסוים (או Exception) מצדו של ה Circuit Breaker שאומר "תסתדרו בלי!" (כלומר: בלי השירות).
  • לאחר זמן מה (נניח: 120 שניות) של מצב אדום, ה circuit breaker יעבור למצב צהוב – ניסיון להחזיר שירות.
    • הוא יאפשר למספר נתון של קריאות לעבור לשירות המרוחק כדי לבדוק את התגובה. למשל: 10 קריאות. לכל שאר הקריאות הוא עדיין יחזיר את התשובה "תסתדרו בלי!".
    • אם ב 10 הקריאות הללו, הוא מזהה treshhold מסוים בעייתי, למשל: רצף של 3 exceptions מצד השירות המרוחק (לרוב ה threshold של המצב הצהוב הוא יותר מחמיר מזה של המצב הירוק) – הוא חוזר למצב אדום.
    • אחרת – הוא מחזיר את המערכת למצב ירוק.
כמו שאתם מבינים – יש המון וריאציות אפשריות של התנהגות של Circuit Breakers:
  • אפשר לשים thresholds שונים ומשונים. למשל, משהו שלא הזכרנו: שניסיונות חזרה למצב הצהוב יקרו בתדירות משתנה: יש גישה של המתנה של 2 דקות ואז כל 30 שניות (כאשר השירות המרוחק הוא חשוב) ויש גישה של להאריך את זמני הניסיון, למשל: פעם ראשונה 2 דקות, אח"כ 5 דקות, וכל פעם נוספת – 10 דקות (כאשר השירות המרוחק פחות חשוב ודווקא שגיאה ממנו היא לא נעימה).
  • כאשר circuit breaker מופעל – כנראה שתרצו שסוג של alert יעלה ל monitoring, הרי מדובר בהחלטה לתת שירות פחות טוב. מתי ואיך להעלות את ה alert – עניין לבחירה.
  • לעתים יש אפשרות של override ידני – אפשרות לקבע מצב ירוק/אדום/צהוב של ה circuit breaker בהתערבות ידנית.
  • אולי הכי חשוב: כיצד ה circuit breaker מאתר שגיאה של השירות המרוחק?
    • האם ע"י ניטור ה responses של ההודעות שחזרו מהשירות המרוחק (למשל: HTTP 5xx)?
    • האם ע"י ביצוע בדיקה יזומה (proactive) לשרת המרוחק (למשל: שליחת pinging או בדיקת health-check)?
    • אם מדובר על אטיות, ה circuit breaker יכול למדוד את מהירות החזרה של קריאות מהשרת המרוחק. אפשר להגיב לממוצע של קריאות אטיות, או לעתים להסתכל על אחוזון מסוים. למשל: אם 5% מהקריאות אטיות מ 10 שניות, אנו רוצים לנתק – מכיוון שזה אומר שירות גרוע למשתמש הקצה. האם מנתקים רק את הקריאות האטיות או את כולן?!
    • וכו'
תוכלו למצוא מספר מימושים שונים של Circuit Breakers, בכל שפת תכנות כמעט (לא ראיתי באסמבלי ;-)), אבל לא נדיר המקרה בהם תרצו לממש גם וריאציה משלכם – במידה ויש לכם מקרה חשוב שלא מטופל ע"י המימושים הזמינים.

לא נדיר גם מצב בו אתם משתמשים בכמה circuit breakers שונים בתוך המערכת. תלוי כמה גדולה ומורכבת היא.
ההתעסקות עם circuit breakers גם היא יכולה להיות משמעותית, וכדאי לשים לב ש:

  • אתם לא נסחפים לאזור ה over-optimization שאיננו משתלם מבחינת ההשקעה.
  • אתם יוצרים מערכת של circuit breaker שהיא מורכבת מדי לניטור ושליטה בזמן אירוע אמת ב Production (כאשר אתם לא יכולים לענות על שאלות כמו: "מדוע x התנתק"? או "אילו ניתוקים היו בזמן נתון").

אלו דוגמאות מהעולם האמיתי של ל Partial Service ניתן לתת? הנה כמה שאני נתקלתי בהן:

  • לוגים, לוגים, לוגים! בעם הייתה לנו מערכת עם שירות ירוד כמעט יומיים (!!) עד שהבנו שהיא נופלת כל הזמן כי הדיסק מלא ופעולות כתיבה ללוג נכשלות. אם כתיבה ללוג נכשלת – עדיף לא לכתוב לוגים, מאשר לגרום ל IO exceptions שמשבשים תהליכים שלמים במערכת.
    • בווריאציה אחרת מערכת מרוחקת לדיווח של בעיות (סוג של alerts) הגיבה ב latency של 4 שניות, וגרמה לשיבושים רבים בשירות שדיווח לה על בעיות זניחות, יחסית.
  • שירות ש"מצייר" מסלול נסיעה על המפה של החשבונית (בעולם המוניות). אם הוא לא זמין / מגיב היטב – שלח חשבוניות בלי ציור של המסלול, מה הבעיה?
  • שירות שמבצע סליקה של תשלומים. עדיף לשמור את סכום העסקה ולנסות לחייב כמה דקות מאוחר יותר (תחת סיכון של חיובים, עד סכום מסוים, ללא כיסוי) – מאשר לדחות על הסף את כל העסקאות, אפילו בפרק זמן קצר יחסית (כמה דקות).
  • וכו' וכו'

Throttling

עוד וריאציה דומה של Circuit Breaker היא מנגנון throttling ("להחזיק אצבע על הקשית כך שלא יהיה זרם חזק מדי").

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

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

המנגנון הוא דיי פשוט (אתאר את המימוש הספציפי) – עוקבים אחר מספר הבקשות הפעילות לשרת המרוחק בעזרת distributed, non-blocking lock (קל יותר באנגלית) על בסיס רדיס: לפני קריאה לשרת המרוחק מנסים "לקבל" Lock – אם מצליחים – מבצעים את הקריאה. אם לא מצליחים (כי כבר יש 10 קריאות פתוחות) – מחזירים הודעת "תסתדרו בלי!" לפונקציה שביקשה את השירות, ונותנים לה לספק שירות חלקי.

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

TCP timeouts

עוד נקודה ששווה להזכיר היא ה timeouts שאנו קובעים לתקשורת.
Timeouts הם מנגנון הגנה ממעלה ראשונה: אם שרת מרוחק לא מגיב – לא נרצה לתקוע thread (או אפילו request) לאורך זמן. תסריט נפוץ הוא שירות אטי, שגורם לשירות שקורא לו גם להיות אטי (כי הוא עסוק בלהמתין), שגורם גם הוא לשירותים האחרים התלויים בו – להיות אטיים באותו האופן.

למרות ש TCP הוא "reliable protocol", גם לו יש תקלות: בעיקר התנתקויות. כאשר קצב הקריאות בין שירותים הולך גדל – אנו נחווה תקלות אלו יותר ויותר.

התנהגות נפוצה היא לספק אותו ה timeout ליצירת ה connection וביצוע הקריאה עצמה (פעולת "קריאה"). אבל:

  • פעולת ה connection היא פעולה פשוטה ומהירה – היא אורכת, באופן טיפוסי, שבריר של שנייה (בניכוי network latency).
  • פעולת הקריאה לרוב גורמת לשירות השני לעבוד: לקרוא מידע מבסיס הנתונים, לקרוא לשירותים אחרים, לבצע עבודת CPU משמעותית וכו. זמן מקובל לקריאה שכזו הוא כ 100ms וגם לא נדיר להיתקל במצב של 1000ms ויותר (שוב: בניכוי network latency).

לכן, אם נגדיר timeouts באופן הבא:

  • עבור פעולת ה connection של ה TCP – נגדיר timeout בסך ה: tolerable latency
  • עבור פעולת ה read של ה TCP – נגדיר timeout בסך: tolerable latency + tolerable server time

נוכל לצמצם בצורה מורגשת זמני המתנה מיותרים.

כמו כן, אם יש לנו timeout על connection – כדאי לשקול בחיוב פעולת retry מיידית.
ניתן להראות ש timeout קצר ליצירת connection + ביצוע retry יחיד – היא מדיניות שברוב המקרים תהיה עדיפה בהרבה על יצירת connection עם timeout ארוך.

הסבר (דרוש רקע תאורטי בעבודה של TCP): אם ההודעה על לחיצת היד השלישית של TCP אובדת – אין מנגנון (מלבד timeout) לדעת זו. בעיה זו במדעי-המחשב ידועה כבעיית הגנרלים, ומשמעותה שלא ניתן לייצר מנגנון אמין ל handshake. הפתרון של TCP הוא לבצע 3 קריאות handshake בתקווה שזו נקודת איזון טובה בין עלות (מספר הקריאות) / סיכון לכישלון.

אחד הנתונים הידועים ב AWS הוא ש latency בין AZs יכול להיות עד 10ms. אם מסתכלים על הנתונים עצמם רואים ש 10ms הוא לא ממוצע, אלא אירוע נדיר יחסית: בבדיקות שערך Matthew Barlocker (חברת Lucid Software) – הוא ראה שבאחוזון ה 99.85% מקבלים latency בין AZ של 3ms בלבד:

הערה: אמזון מתעדפת נמוך (de-prioritize) קריאות ICMP (פרוטוקול השליטה של TCP/IP, הכולל גם את פקודת ה ping) – ולכן לא כדאי להסתמך על Ping להערכת ה latency ב AWS.

מסקנה אפשרית אחת היא שסביר לקבוע TCP Timeout של 3ms כאשר ליצירת connection באותו ה AWS region.
על פעולות HTTP GET ניתן לשקול (תלוי במקרה) מדיניות דומה של מתן timeouts קצרים יחסית (יש לקחת בחשבון את זמן השרת + network latency) – עם אפשרות ל retry.

"Be Resilient" vs. "Fail Fast"

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

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

מדיניות טובה אחרת אומרת: Fail Fast – אם המערכת מזהה שגיאה או סבירות גבוהה לשגיאה, עליה "לשבור את הכלים באותו הרגע" – וכך יהיה קל יותר לאתר את התקלה. אם התקלה נגררת ונגררת – ה"שבר" צפוי להיות בשלב מאוחר יותר במערכת, בשלב שיהיה קשה מאוד להבין ממנו מה מקור הבעיה האמיתי (root cause).

כיצד 2 גישות חכמות אלו משתלבות זו עם זו בעולם של מערכות מבוזרות, השאופות ליציבות גבוהה?

למרות הסתירה הבסיסית, ניתן לשלב את שתי הגישות:

  • אם מזהים כשל שנראה שניתן "לספוג" – אפשר לבצע Alert Fast, בשאיפה שה Alert מספיק ברור ומשמעותי כדי שיהיה ניתן לנטר אותו ולטפל בו בזמן סביר.
  • אם מזהים כשל שנראה שלא ניתן "לספוג" אותו – זהו באמת מצב שכדאי לעשות Fail Fast.
פעמים רבות, מה שמתחיל כ"כשל חלקי" שניתן לספוג אותו – מתגלגל בהמשך לכשל משמעותי יותר שלא ניתן עוד לספוג ("כמות הופכת לאיכות"  – של הכשל). אם אתם בוחרים בכיוון של Alerts – כדאי שאלו יהיו Alerts שתוכלו, ואכן תתייחסו אליהם – בזמן סביר.

סיכום

בעוד שמערכת יחידה ("מונוליטית") מתעסקים באופטימיזציה של ה up-time (מדד ברור למדי) – במערכות מבוזרות, המציאות יכולה להיות מורכבת / גמישה ברבה.

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

היכולת של מערכת להימצא במצבים שונים של כשלים-חלקיים מוסיפה מורכבות למערכת – אך מאפשרת להגיע לזמינות (availability) גבוהה יותר כאשר מדובר במערכת מורכבת.
העצה של מרטין פאוולר ל MonolithFirst היא טובה בהיבט הזה: מערכת מבוזרת היא בהחלט מורכבת יותר – ומבחינת זמינות  משתלם לעבור למודל מבוזר רק כאשר כלל המערכת הגיע לסף מסוים של מורכבות.
זמינות גבוהה של מערכת מושגת לא רק על ידי תכנון נבון, אלא גם ע"י אימונים ושיפור תמידי.
Chaos Monkey, למשל, הוא כלי Open Source ש"יפיל" לכם בצורה יזומה אך אקראית שירותים (או שרתים) במערכת – כדי שתוכלו לבדוק את ההתמודדות שלכם, ולשפר את צורות התגובה שלכם לכישלון שכזה.
בשלב ראשון ניתן להפעיל אותו בסביבת בדיקות – בה נפילה היא למידה ללא נזק, ובהדרגה ניתן להתקדם לסביבות יותר ויותר מציאויתיות ומחייבות. אם אתם מסוגלים להפעיל אותו ב production, בשעות העומס שלכם, ולשרוד עם שירות סביר – אז אתם בליגה העולמית!
שיהיה בהצלחה!

על Performance Monitoring ו New Relic

New Relic (בקיצור NR) הוא כלי ה Application Performance Monitoring (בקיצור APM) מהמוערכים שזמינים בשוק היום.
Relic הוא שריד קדום, והשם New Relic נבחר כמעט כבדרך אגב: היזם (Lew Cirne) השתעשע בתוכנה שמוצאת צמדי-מלים המורכבות מהאותיות של שם המשתמש, ובעת רישום החברה זה השם שנבחר, כמעט באקראיות. מאז הוא נותר.NR הוא לא יחיד: יש את AppDynamics (שמכוון יותר ל Enterprise), או Nagios (פתרון Open Source) נפוץ למדי – ויש עוד רבים.

NR איננו זול: הוא עולה כ 100-150$ ל host לחודש. אם מנטרים כמה עשרות שרתים יכולים להגיע בקלות לכמה אלפי דולרים בחודש, אבל אנחנו (כמו עוד לקוחות רבים של פתרונות ה Premium) – החלטנו שזו השקעה משתלמת.

אנו ב Gett משתמשים ב NewRelic, ובכלי משלים בשם Graphite, שהוא יותר תשתית ל"הרכבה עצמית" של monitoring. יש גם את StackDriver. דיי נפוץ לראות ארגונים שמשתמשים ביותר מכלי אחד ל APM.

ל APM יש שני שימושים עיקריים:

ניטור בעיות ב production בזמן אמת
הרבה תקלות טכניות ניתן לזהות ע"י מדידת שיוניים בביצועי המערכת.
לעתים, לא נזרקות הודעות שגיאה חריגות, ושום Alerts מרכזי שכיוונתם ואתם מנטרים לא מזהה משהו חריג – אבל משהו רע קורה למערכת. לפני שהתקלה ממש משפיעה על המשתמשים – יש סימנים מקדימים: לעתים אלה יהיו spikes של עומס על המערכת, ולעתים דווקא ירידת עומס בלתי מוסברת: כמו צונאמי שמתחיל בירידת מפלס הים… וקצת אח"כ הוא מכה.
אם העסק שלכם מבוסס על זמינות המערכת, בוודאי תרצו לאתר את הסימנים המקדימים הללו – ולהגיב בהקדם.
שיפור ביצועי המערכת
מדי פעם, מחליטים שהגיע הזמן לשפר ביצועים. אולי בגלל feature חדש שמעמיס על המערכת מעבר לצפוי, ולעתים בגלל שהמערכת כבר "לא סוחבת" כפי שסחבה פעם (או שסתם נמאס לכם לשלם עשרות אלפי דולרים בחודש על Infrastructure).
NR (וכלים דומים) יכולים לספק במהירות ובקלות ניתוחים התחלתיים טובים לצווארי הבקבוק העיקריים במערכת. לפעמים הניתוח של NR כמו שהוא – מספיק בכדי להבין את השיפור המדויק שיש לבצע  (למשל: שאילתת SQL יעילה יותר), לעתים אחרות NR מצביע על אזור הבעיה, ואז מתחילים באיטרציות של ניסויים / הוספת monitors (לעתים ב new relic, ולעתים בכלים קרובים יותר לקוד) – עד לאיתור המדויק של הבעיה ומציאת הפתרון.

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

הטריגר לפוסט, אגב, הוא כוננות "Tier 1" שעשיתי השבוע. זו כוננות ב Gett לקבל טלפון אם יש Crisis במערכת, באחת מהמדינות, לבצע ניטור ראשוני ולהעיר את הצוות שיטפל במשבר. New Relic הוא כנראה המקום הראשון שאבדוק בו, על מה המהומה. בלילות כאלו – זהו חבר קרוב.

צורת העבודה של New Relic

New Relic תומך בסביבות קוד מסוימות:

  • רובי
  • ג'אווה
  • NET.
  • פייטון
  • PHP
  • node.js

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

על כל שרת שאנו מנטרים בעזרת NR, מתקינים agent. ה agent הזה יודע לאסוף נתונים מתוך מערכת ההפעלה, מתוך הקוד שלנו (שלו הוספנו את הספרייה הקטנה של NR) ועוד סדרה של שרתים – בעזרת מערכת ה Plugins של NR. ישנם Plugins לעשרות אפליקציות מעניינות שנרצה לנטר: שרתי ווב (כמו nginx), בסיסי נתונים, memcached, רדיס, AWS, ועוד

בכדי לצמצם את ההשפעה של ה agent על השרת עצמו, NR מתמקדת באיסוף נתונים בחשיבת עלות (ערך לניתוח ביצועים) / תועלת (ההשפעה על המערכת באיסוף שלהם). כמו כן ה agent מפחית את ההשפעה שלו על השרת בו הוא רץ בכך שהוא אוסף buffer של נתונים ושולח אותם כ batch פעם בדקה (זו החלטה סבירה מכיוון שהניתוח עצמו נעשה כמה דקות אחורה, ולא באמת ב realtime). שליחת Alerts מה Agent, כמובן – מתבצעת בו במקום.

השרתים של NR אוספים את כל הנתונים שה agents שולחים ומבצעים קורולציה (תיאום) ביניהם – לתמונה אחת ואחידה.

חלק מה"קסם" שגרם ל New Relic להיות מאוד פופולארי הוא בהצגת מדדים שימושיים (ולא סתם spam של נתונים) בצורה מאוד נוחה לגישה, ומאוד אינטואטיבית. מרגע שאני מחבר את השרתים שלי ל NR – אני יכול לעבוד מייד, ויש לי סט מסודר והרמוני של כלים ותצוגות לעבוד איתו.

למשל: בצפייה ב Dashboard הראשי, ניתן ללחוץ על כפתור שמציג את הנתונים הנוכחיים, מול נתונים באותה השעה אתמול, ומול נתונים באותה השעה לפני שבוע.

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

זהו Tradeoff בין פשטות ושימושיות, על חשבון יכולת להתאמה אישית.

NR יודע לנטר גם זמן רנדור של אפליקציות ווב בדפדפן ואפילו אפליקציות Native Mobile. יש גם מוצר ל Analytics עמוקים יותר על הנתונים הסטטיסטיים, בשם "Insights". למרות הכל – בפוסט זה אתמקד רק במוצר ה APM לניתוח נתוני ביצועים מהשרת.

על מדד ה Apdex

מדד ה Apdex (קיצור של Application Performance Index), הוא מדד שמזוהה מאוד עם NR, מכיוון שזו עושה בו שימוש אינטנסיבי במוצר.
Apdex לא הומצא ע"י NR, אך בגלל השימוש הנרחב שלו במוצר, כדאי מאוד להבין מה הוא מתאר וכיצד הוא מתנהג.
עצם הרעיון של Apdex נובע מהחסרונות המובנים של מדדים כגון ממוצע או Median. מדדים שכאלו יכולים להסתיר בקלות התנהגויות חריגות ובעייתיות במערכת.

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

כאשר אנו "מסכמים" את ההתנהגות תחת מדדים כמו ממוצע או Variance – אנו מאבדים מידע חשוב.

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

מדוע "Apdex עובד"? – אני לא יודע להסבר. אך הוא נחשב מדד מוצלח.
כמובן שגם הוא לא מושלם.

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

בנוסף ל"בולענים סטטיסטיים", NR עשוי "לעגל פינות" בגרפיים המכילים מידע רב – כך שהתמונה האמיתית תאבד לגמרי. אל תתעצלו: תעשו Zoom In על ציר הזמן (טיפ: בחירת זמן עם העכבר על הגרף) ותפלטרו שוב ושוב את הנתונים לקבלת תמונה יותר ויותר מדוייקת.

מדד Appdex עובד כך:
עבור כל שרת (או טרנזקציה מרכזית) אנו מגדירים את הזמן הממוצע שאנו מצפים מהשרת להגיב. זוהי "נקודת הייחוס", והיא מסומנת כ Apdex-T.

  • כל טרנזקציה שתסתיים בזמן שהוגדר או מהר יותר – תחשב ל"משביעת רצון" מבחינת הביצועים. (בירוק בתרשים למעלה)
  • כל טרנזקציה שתסתיים בטווח שהוא בין הזמן שהוגדר כ"משביע רצון", עד לפי-4 מכך – תחשב כ "נסבלת" מבחינת ביצועים (בכתום / ירוק זית – בתרשים למעלה).
  • כל טרנזקציה ארוכה מכך, או טרנזקציה שנסתיימה בשגיאה (למשל: HTTP 500) – תחשב ל "מתסכלת".
מדד Apdex הוא אחוז הפעמים בהן הגענו לטרנזקציות "משביעות רצון" ועוד חצי מהפעמים (משקל פחות) בהן הגענו לטרנזקציות "נסבלות". טווחי הערכים של Apdex נעים בין 0.0 (אסון) ל 1.0 (מעולה)הנה דוגמה:

בשרת הזה אנו מצפים לביצוע טרנזקציה תוך 125ms, ומקבלים זאת ברוב הזמן.
מדד של 0.87 הוא טוב – אך לא מעולה. ניתן לשאוף לקצת יותר.

במדידות הדפדפן (NR מסמלצת גם קריאות מדפדפן לשרת – בכדי לבדוק את זמן התגובה של דף באתר, כולל הרינדור) הגדרנו נקודת ייחוס של 12 שניות (פשוט לא הגדרנו נקודת ייחוס) – ולכן אנו נמצאים ב Apdex 1.0 עגול. מעולה! ;-).

במעבר עם העכבר על הגרף אני נחשף למידע נוסף, ויכול לראות שהתנודות ב Apdex שלנו נעו בין 0.84 ל 0.92 בשש שעות האחרונות (נקודת החיתוך) – יציבות סבירה לכל הדעות.

rpm (קיצור של requests per minutes) הוא המקבילה של tps המקובל יותר (transactions per minute) ואנו כרגע עומדים (אני רואה במעבר עם העכבר) על כ 8.43k rpm 130tps, או כ 140tps, עם מגמה קלה של עליה (יש בד"כ מחזוריות ברורה, יומית ושבועית, לשימוש בשירותים)

מדדים עיקריים ש New Relic מספק

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

הנה ה Dashboard העיקרי של NR:

  1. זהו הגרף הראשי המציג "לאן הלך הזמן במערכת". הוא מציג את הממוצע של הזמן שהושקע בבסיס הנתונים, ה Application Server, או קוד האפליקציה שלנו (במקרה הזה – רובי). בגלל ש NR מכיר את ריילס, הוא מסוגל לבצע חיתוך ולהראות לנו כמה זמן הושקע ב ActiveRecords (ספריית ה ORM של Ruby on Rails).
    במבט חטוף ניתן לראות שזהו שירות שמשתמש בתכיפות בבסיס הנתונים (צהוב) – אך יש לו גם הרבה עבודת CPU (תכלת).
  2. זהו הגרף שהצגתי קודם לכן, של Apdex ו Throughput.
  3. זוהי רשימת הטרנזקציות היקרות ביותר (שקלול של זמן ביצוע הטרנזקציה x שכיחות הטרנזקציה). אם נרצה לבצע שיפורי ביצועים בשרת – זה המקום להתחיל בו.
  4. Error rate של השרת – כמות הטרנזקציות שלא הסתיימו כשורה.
  5. רשימת ה alerts שעלו מהמערכת.
  6. רשימת השרתים שב cluster. ניתן לראות CPU, disk-usage, צריכת זכרון ו Apdex – לכל שרת בנפרד.
כשאנו רוצים לשפר ביצועים, הכי הגיוני יהיה להתמקד בטרנזקציות שגוזלות הכי הרבה זמן (משוקלל). ניתן לבחור טרנזקיות ע"פ מדדים שונים (הכי אטיות, גוזלות הכי הרבה זמן, בעלות Apdex הנמוך ביותר, וכו') – ואך לעשות Drill down לטרנזקציה:

  1. אנו יכולים לראות את השונות בזמני התגובה של הטרנזקציות. האם יש הרבה אטיות / מהירות במיוחד – או שיש סוג של זמן קבוע שמסביבו כולן סבות? (צהוב – האחוזון ה 95% האטי, אדום – ה media, ירוק – הממוצע)
  2. לאן הולך הזמן בטרנזקציה: GC, קוד, בסיס נתונים, מערכות 3rd Party וכו'.
  3. כמה טרנזקציות לדוגמה (לרוב מהאזור הפחות טוב). הנה טרנזקציה שלקחה 1.3 שניות.
  4. למרות שמפתה למדוד את הטרנזקציה הגרועה ביותר (#3), לרוב זהו מקרה קצה שעלול להטעות (למשל: בדיוק היה אירוע Full GC).
    אני מעדיף לבחור את השורה השלישית, שהיא קצת יותר נורמטיבית – ולחקור אותה. להזכיר: הזמנים המדוברים הם Wall time clock – וזמן בו הקוד נמצא ב block גם הוא נספר.
והנה כבר ה Trace ש NR אוספת על הטרנזקציה:
לעתים ניתן לזהות מתוך ה trace את הבעיה. בעיות נפוצות הן:

  • "בעיית n+1" – בה יש קשר master-detail בבסיס הנתונים, עושים שאילתה אחת לאובייקט האב ועוד n שאילתות – אחת לכל אובייקט בן (במקום שאילתה אחת עם inner join)
  • שאילתה בודדת יקרה ביותר – ניתן ללחוץ על שורה ולראות את השאילתה הקונקרטית שבוצעה.
  • בעיית קוד (מתאפיינת לעתים קרובות ב GC גבוה).
בתמונה למעלה ניתן לקראות דוגמה שנראית כמו בעיית קוד – 2 פעולות "תשתית" לכאורה, שכל אחת לוקחת יותר מ 300ms. סימן השאלה מסביר ש NR לא עשתה Drill down ומנחה מה לעשות הלאה.

Drill down לתוך הקוד NR בחר במודע לא לעשות – כדי לא להשפיע לרעה על ביצועי האפליקציה. Drill Down שכזה עשוי להיות יקר בצורה מטרידה. במקום זאת, ניתן להוסיף לקוד custom monitors. למשל, הוספת הפקודה הבאה בקוד:

add_method_tracer  :my_method  'Custom/MyClass::my_method'

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

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

  1. ייתכן וניסיון לשחזר את הבעיה שלא ב production, עם הגדרות, נתונים, ועומסים ששונים מסביבת production – פשוט לא יצליח.
  2. כלי שמבצע ניטור "שתי וערב" על הקוד – אתם כנראה לא רוצים להביא ל production. הכלי עצמו יכול להכפיל בכמה מונים את כל זמני הריצה של המערכת…

אם אנו רוצים להיעזר ב NR לבצע ניתוח מקיף יותר וממוקד, אנו יכולים להתחיל "X-Ray session" שעוקב אחר דגימה גדולה יחסית של טרנזקציות (נאמר 50 בשעה), למשך זמן מוגבל (נאמר: חצי שעה). על טרנזקציות אלו יאסף כל המידע האפשרי – והמידע ייתן לנו מידע על מגוון טרנזקציות (ולא דווקא האטיות יותר).

עוד יכולות מעניינות (בקיצור)

Developer Mode

ה Developer Mode הוא גרסה רזה של NR שניתן להפעיל On-Premises – על מכונת הפיתוח, ויכולה לשמש לצורך ניטור ראשוני של הקוד לפני שהוא מגיע ל production. למשל, Queries בעייתיים ניתן לאתר בשלב מוקדם כבר כך – ולפני שמגיעים ל production. כמובן שהתנהגות המערכת ב production ועל מחשב של מפתח היא שונה – ולא כדאי לנסות להסיק יותר מדי מתוך ה Developer Mode.

ניתן להפעיל את ה Developer Mode מתוך קובץ הקונפיגורציה, newrelic.yml, ע"י הפיכת הערך developer_mode ל true – ואז יהיה ניתן לגשת ל dashboard דרך http://localhost:3000/newrelic.
ה Dashboard של ה Developer Mode הוא הרבה פחות אטרקטיבי ועשיר – אבל הוא מכיל את נתוני הבסיס החשובים על הטרנזקציות.

דו"חות מוכנים

ל NR יש סדרה של דוחות מגניבים – חלקם אפילו שימושיים! למשל:

  • חישוב ה up-time של המערכת (למשל: 99.9734%) בתקופת זמן נתונה.
  • בניית גרף ה Scalability של המערכת – עלייה בזמני התגובה של השרת ככל שמספר הבקשות גדל (ואולי יש עוד nodes ב cluster בכדי לשרת). גרף זה עוזר לזהות צווארי בקבוק פוטנציאליים.
  • Speed Index – השוואה של הנתונים של השרת שלנו מול אתרים אחרים בתעשייה (לא ראיתי כיצד זה יכול להיות מועיל)
  • מיפוי ויזואלי של התקשורת העיקרית בין השרתים שלכם.
ניתוח אוטומטי של NR על תלות בין כמה שרתים שלנו
Insights
את המידע העצום ש NR אוספת על השרתים שלכם ניתן לתחקר באופן חופשי (יחסית) ובעזרת שפת NRQL (שפה דומה ל SQL) – על מנת לבצע ניתוחים ש NR לא מספקת "Out of the box"

סיכום

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

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

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

שיהיה בהצלחה!

—–

קישורים מעניינים

הפלטפורמה של NR (מתוך הבלוג הרשמי)
NR ב highscalability.com (פוסט מ 2011)

Crash Course ב NR (וידאו של חצי שעה). מוצלח יותר מרוב החומרים שמצאתי בנושא ברשת
New Relic vs. AppDynamics (פוסט מבית טאקיפי)

על אבולוציה של ארכיטקטורה ו Conway's Law

חברת גטט (Gett, עד לפני שבוע בערך – עוד נקראה גט-טקסי) החברה בה אני עובד, עוברת כרגע שינוי ארגוני משמעותי.

זהו סוג של תהליך שרוב הארגונים עוברים פעם בשנה-שנתיים (אני מניח ששינויים יותר קטנים מתרחשים בערך אחת – לחצי שנה).

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

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

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

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

נתאר את המצב הצפוי הבא, בצוות האחראי על פונקציונליות x:

  • את הקוד שהצוות אחראי עליו (להלן: "מודול A") – יהיה קל ונגיש לשנות.
  • את הקוד שאחראי עליו צוות אחר (להלן "מודול B") – יהיה מורכב יותר לשנות: צריך לתאם, לתקשר יותר, ולהשתלב בעבודה של הצוות השני.

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

חוק קונווי

חוק ידוע בעולם התוכנה הוא Conway's Law שטבע איש מדעי-המחשב מלווין קונווי במאמר משנת 1968. למרות שזמן רב עבר – החוק עדיין נכון ולרלוונטי מתמיד.

החוק אומר כך:

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

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

בואו ואמקד למה אני מתכוון כאשר אני מדבר על "מבנה התוכנה" או "הארכיטקטורה":

  • חלוקה של הקוד למודולים ברורים – כאשר לכל מודול יש אחריות על פונקציה מסוימת של המערכת.
  • הגדרת מגבלות / תלויות על כתיבת הקוד – בכדי לשמר את המבנה הנ"ל לאורך זמן.
    למשל: כלל שמודול X לא מורשה לבצע פניות למודול Y, או שמודול Z לא מטפל בכלל במפונציונליות שהוגדרה לאחריותו של מודול X.

ההכרה בחוק מותירה לנו 3 ברירות עיקריות:

  1. להלחם במציאות הארגונית – בכדי לגרום למימוש "הנכון" של התכנון, כפי שהוגדר. "לא מעניין אותי שזה בצוות אחר. נכון יותר שזה יקרה כך וכך".
  2. לשנות את המבנה הארגוני, כשיקוף של התכנון שהוגדר – בכדי לאפשר ולתמוך בתכנון שהוגדר.
  3. לשנות את תכנון המערכת, כדי שתשתלב במבנה הארגוני הנתון.
אופציה ראשונה היא סוג של קרב אבוד, סוג של "להיות צודק, אך לא חכם".
אופציה שנייה היא ואלידית ונכונה – כל עוד התכנון המדובר הוא חשוב מספיק בכדי שהארגון יסתדר לפיו. (כלומר: משהו מאוד אסטרטגי לארגון).
אופציה שלישית היא אופציית ברירת המחדל: במקום להלחם בחוקי התכנותיקה ("חוקי הפיסיקה של עולם התוכנה") – לקבל אותם ולהסתגל אליהם.המצב שלנו היום הוא כזה: יש לנו ארכיטקטורה / מבנה של התוכנה שמתנהל יפה – אך מתאים למבנה הארגון הקיים.
שינוי המבנה הארגוני – יוצר פער: וגורם לארכיטקטורה הקיימת לא לתאום יפה למבנה הארגוני. את התיקון נעשה בצורת התאמות של הארכיטקטורה למבנה החדש.

התאמות כגון:

העברת אחריות של מודול m מאחריות של צוות a לאחריות צוות b.
ייתכן והמודול עדיין רלוונטי למשימות והמומחיות של צוות a – אבל צוות b יידרש לעבודה אינטנסיבית באזור הזו, ועדיף שחלק הקוד הזה יהיה מעתה באחריותו. העברת אחריות על פיסת קוד בין צוותים היא לכאורה שינוי ניהולי שאינו קשור לארכיטקטורה, מלבד עובדה אחת: העובדה שהצוות הקודם (צוות a) עדיין צריך חופש מסוים לבצע שינויים ותוספות במודול m.
כיצד מאפשרים לצוות a את היכולת לבצע שינויים בקוד שרוב העבודה עליו נעשית ע"י צוות b – ועבור מטרה אחרת?
האם צוות a יאפיין ויגיש דרישות – בכדי שצוות b יממש אותן בעבורו (סוג של פתרון ניהולי עם תקורה גבוהה)?
אפשרות אחרת היא מעבר הדרגתי ל Plug-In Architecture בו מבנה התוכנה מאפשר לצוות a להמשיך ולפתח חלק מכובד מהפונקציונליות שהוא נדרש לה – ללא תלות בצוות b, וכך לשפר את מרחב-הפעולה העצמאי שלו.
PlugIn Architecture

פיצול של מודול ל-2: חלק שיישאר בצוות a, וחלק שיעבור לצוות b

אנו מתחילים כרגע לבחון את המשמעות של פיצולים שכאלה. לעתים הם לא פשוטים: הם עשויים לגרום לשכתוב של קוד ולפגיעה מסוימת בביצועים (בעיקר בעיות Latency, בגלל שאנו עובדים בארכיטקטורה של מיקרו-שירותים. כלומר: פיצול מיקרו-שירות ל-2 שירותים, ע"פ הצרכים של הצוותים השונים).
בעיה שלישית שיכולה לצוץ היא סיבוך של flow מסוימים: שירות שלישי שצריך כעת לעבוד עם שני שירותים שונים במקום אחד, או פגיעה באטומיות: מה שעד היום התרחש בצורה אטומית בתוך שירות אחד מעכשיו ידרוש coordination בכדי לא לסיים במצב בו רק חצי עבודה נעשתה.
היכן נכון לפצל? מה יהיה ממשק העבודה ביניהם וכיצד הפיצול ישתלב ב Flow? – כולן שאלות טובות.עוד עניין שצץ מחלוקה שכזו הוא עניין של פלורליזם: האפשרות ששני השירותים יכירו תמונות עולם מעט שונות של המציאות, כל אחד ע"פ צרכיו. למשל: אובייקטים המתארים אותו הדבר ("לקוח") יכילו שדות שונים בכל שירות. זה אפשרי כל עוד במידה ויש כפילות – ברור מהו המקור שאליו מתייחסים. כלומר: אם מספר הטלפון של איש הקשר של לקוח מסוים שונה בין שני השירותים – חשוב שיהיה ברור איזה שירות הוגדר כ"קובע העובדה" – והשירות השני יתיישר איתו.
עקרון זה הוא נפוץ ב Microservices, אבל פחות מקובל בארכיטקטורות ריכוזיות.

הגדרה של שירותים חדשים
זה החלק הקל יחסית. אנו מזהים פונקציונליות חדשה במערכת שעל הצוותים יהיה לפתח – ואנו מוצאים לה "בית חם". במידה ואין מקום מתאים-מספיק, אנו מגדירים שירותים (microservices) חדשים שיארחו את הפונקציונליות המיועדת, ומתכננים כיצד הם ישתלבו בכלל המערכת.

סיכום

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

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

שיהיה בהצלחה!

לינקים רלוונטיים

על השינוי של מייקרוסופט לחברת ענן