-->

יום שלישי, 1 בנובמבר 2011

על הדרך הנכונה לחלק מערכת לשירותים מבוזרים

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

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

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

חלוקה של מערכת (process או service) למספר תהליכים או שירותים - הוא גם צעד שלא טומן בחובו Tradeoffs משמעותיים. לרוב - ה tradeoff בין צריכת זיכרון ל isolation - שבמקרים רבים עושה שכל.

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


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


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

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

יתרונות:
  • זמינות (high availability) גבוהה יותר, מכיוון ששני השרתים זהים. שרת A נפל - שרת B יוכל לספק את כל השירותים. בפרדיגמה ההטרוגנית כל שרת הוא single point of failure.
  • ניהול עומס (load balancing) מיטבי - כל שרת יכול לטפל בכל בקשה וכך ניתן לחלק את העומס בצורה מיטבית בין השרתים. בגישה ההטרוגנית ייתכן ששרת A עמוס לחלוטין, ולשרת B יש משאבים פנויים. קרי - לא ניצלתם את החומרה לתומה. חישבו על פיזור העומס לא רק ב load מרבי, אלא גם על השעות מהם יש עומס קטן יותר על המערכת.
  • יעילות תקשורת - אם שירות X ו Y זקוקים אחד לשני - הם יכולים לבצע קריאות לוקאליות בזכרון, שהן יעילות ובעלת latency קטן משמעותית מקריאה ברשת. אפילו אם בוחרים לתקשר על גבי http - הביצועים יהיו עדיפים במחשב מקומי.
  • תחזוקה קלה יותר (Maintainability). שיקול Operations. אנו יכולים להחזיק אותה חומרה ולנהל מלאי אחד של חלפים. אנו יכולים לנהל image יחיד, להתמודד עם בעיות של updates ו upgrade בצורה אחידה וכו'.
איך ממשים שני שירותים על אותו שרת? לרוב הפתרון פשוט כמו שהוא נשמע: אם מדובר על Servlet Engine - פשוט מתקינים את שני השירותים על אותו Container כשני קבצי war או ear נפרדים (שכל צוות בארגון יכול לפתח עצמאית).
אם השירותים הם בשפות תכנות שונות (נאמר Ruby ו Scala כמו ב [1]Twitter או C ו Java כפי שנפוץ במוצרים רבים) - פשוט מתקינים שני containers או אפליקציות על אותו השרת הפיסי. הרי ב Windows או Linux יש עשרות שירותים מותקנים, אז מונע מאיתנו להריץ עוד כמה?

עד כמה שנתקלתי המחסום העיקרי לאימוץ גישה זו - הוא פסיכולוגי. אולי התבנית מזכירה תבנית של שכפול קוד "The Mother of All Evil", אשר מעבירה בנו צמרמורת רק עם הזכרת שמה.


מתי לבחור בפרדיגמה הטרוגנית?
השיקולים העיקריים לפרדיגמה הטרוגנית הם:
  • שימוש במשאבים מיוחדים: חומרה מיוחדת ויקרה שזקוקים לה עבור שירות Y ולא עבור X וכך אפשר לחסוך בעלויות.
    • מקרה נפוץ יותר - צורך בזיכרון רב. לדוגמא: שירות Y משתמש ב in-memory Database וזקוק לכמה עשרות GB של זכרון כדי לפעול בצורה מיטבית. דוגמא נפוצה היא חוות זיכרון נוסח memcached או [terracotta[2.
  • השירות רלווני במיקום גיאוגרפי מסוים / מיקום מסוים ברשת. למשל: השירות מבצע sniffing על הרשת או חייב להיות מותקן במיקום מסוים כדי לגשת לשירותים שהוא זקוק להם. 
במקרים אלו אנו סובלים מרשימת החסרונות שתוארו בפרדיגמה ההומוגנית - אך ייתכן וזו הברירה העדיפה.


כמה טיעונים נפוצים לשימוש בפרדיגמה ההטרוגנית 

"זיכרון, מכיוון ששירות Y דורש כ 2GB זכרון נוסף בכדי לרוץ".
מכיוון שזיכרון הוא זול כ"כ ועלות פיתוח של High Availability היא גבוהה כ"כ + ניצול מרבי של חומרה מחזיר מהר מאוד עלות של מעט זיכרון נוסף - טיעון זה לא נראה לי רציני.

הלקוחות שלנו רוצים לקנות שירות לראות את הקופסא שהם קנו.
שמעתי גם את זה. לפי דעתי זהו פידבק של אנשי מכירות או Professional Services ולא באמת לקוחות. לקוחות שאני מכיר מוטרדים מעלות התחזוקה הכוללת (TCO) שמושפעת יותר מ High Availability או אחידות החומרה. כשעבדתי בנייס, הפסדנו עסקאות כי השרת שלנו היה 3U (ר [3]) והשרת של המתחרה היה חלש קצת יותר אבל נכנס ב 2U (כלומר - קצת מקום איכסון היה מספיק משמעותי לעסקה). במידה וטיעון זה עלה - שווה לבקש לשוחח עם כמה לקוחות ולהבין במה מדובר.

"זיכרון, מכיוון ששירות Y דורש כ 20GB זכרון נוסף כדי לרוץ"
זהו אכן כלל-האצבע המצביע מתי הפרדיגמה ההטרוגנית עדיפה, אבל לפי דעתי כדאי לחשוב מעט ולא להחליט מייד. כמה עולה 20GB (ר [4]) זיכרון ומה מחיר חוסר-הזמינות?תזכורת: אלא אם מדובר בשרתי Unix עתיקים (שם הזיכרון יקר להחריד) - 20GB זיכרון הם לא דבר מאוד יקר. שווה לעשות את השיקול הכלכלי. לעיתים יהיה שווה לשים שרתים עם זכרון רב ולהשאר עם פרדיגמה הומוגנית (בעיקר ב cluster קטן). לעיתים באמת שווה לעשות את ההפרדה. במערכת עם מספר רב של שרתים (לרוב מערכת on-demand) ניתן לבחור בגישה מעורבת ולהנות מ-2 העולמות. לדוגמא: אם יש לי 12 שרתים אני יכול להקצות 8 לשירות X ו 4 לשירות Y (גישה הטרוגנית), אך כל קבוצה של שרתים תנוהל ב cluster שיספק לי high availability, load balancing וכו'.

ניהול משאבים הוגן או Resource Consumption Capping
זהו שיקול הגיוני: מספר שירותים במערכת שלנו עלולים לצרוך יותר משאבים ממה שאנו רוצים להקצות להם ולכן אנו רוצים להגביל את את השימוש בהם. גישה אחת לבצע את אכיפת שימוש המשאבים היא פריסה הטרוגנית של שירותים על מחשבים, למשל: יש לי 8 שרתים, 3 יוקצו לשירות X הבעייתי ו 5 לשירות Y. אכיפה כזו היא הקלה ביותר למימוש אך הגמישות לשנות את החלוקה ל 4:4 או 2:6 היא קטנה וייתכן שאותם 3 שרתים של שירות X לא יוקצו בצורה יעילה. פיתרון של Capping בתוכנה הוא פחות מדויק ודורש השקעה נוספת, אך יותר גמיש.
גישה מקובלת אחרת היא ליצור cluster הומוגני בו מספר קטן של שרתים להם רוצים לעשות Capping - מופרד ל cluster לוגי נפרד (כלומר: ה Load Balancer מתבקש לא להעביר להם תעבורה ע"פ כללים מסוימים). גישה מקובלת עבור ה crawler במערכות SharePoint או Batch Operations במערכות SAP.

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

לסיכום: למרות שהגישה ההומוגנית היא מעט Counter Intuitive - היא טומנת בחובה עוצמה רבה שכדאי לעשות מאמץ ולנצל.



[1] סיפור מעניין בפני עצמו על Scalability.

[2] החבר'ה מ Terracotta, חברה ישראלית, אוהבים לקרוא לחוות זיכרון אלו NAM - Network Attached Memory, על משקל NAS.

[3] חדרי שרתים בנויים מארונות הנקראים Rack מהם כל slot נספר כ U (אולי קיצור של Unit?). יחידות של 1U הן לרוב ציוד תקשורת כגון Router או Hub בעוד שרתים הם לרוב 2U. שרתים גדולים יותר יתפסו 3U או יותר.

[4] "איך לעזאזל מגיעים לצריכה של 20GB זיכרון?" אתם עשויים לתהות? לרוב כשיש Caches גדולים של מספר שירותים או In-Memory-Database.

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


2 comments:

  1. What if both services A and B are statefull and want to save their state locally, so that not to waste time on network waiting. The only solution in that case for homogenic approach is to duplicate the data, which is difficult in most cases

    השבמחק
  2. החסיכון בתעבורת רשת (או בעצם ב latency) הוא היתרון הקטן בד"כ, load balancing ו high availability הם היתרונות המשמעותיים. כמובן שמדובר בשכפול מידע - אך לרוב הוא לא אמור להיות גדול (בעיקר caches ו logs).

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

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

    השבמחק