2015-06-27

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

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

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

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

אם תחפשו באמזון (אני משוטט בין הספרים הטכניים שם בתור תחביב קבוע) - אין הרבה ספרות על מערכות מבוזרות. זהו תחום מצד אחד "נחשב" (נראה לי) ומצד שני שכיסוי הידע בו נמצא בחסר - מול הצורך האמיתי בשוק. יש כמה ספרים שעוסקים באלגוריתמים מבוזרים (צד אחד נקודתי של העניין) ו 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 - הוא לא יכול להתבצע על חלק מהמחשבים במערכת ולהשיג את התוצאה.


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



סיכום


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

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


----

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

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


-----

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



2015-06-22

משרת ארכיטקט ב 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". אפשר גם לשלוח מייל רק בכדי להתייעץ. עבורכם או עבור חברים.


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


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

ליאור




2015-06-07

על 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, בשעות העומס שלכם, ולשרוד עם שירות סביר - אז אתם בליגה העולמית!


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