תעתועי זמן במערכות תוכנה גדולות
במרחבי האינטרנט, כמו במרחבי היקום, הזמן אינו מוחלט. לכל מחשב ציר זמן ייחודי
"אני מסתכל במשקפת על אדם המסתכל עליי במשקפת: זהו אויבי", כתב יהודה עמיחי בשירו 'זה גורלי'. אבל אם נבנה טלסקופ חזק במיוחד שיכול לראות את הנעשה על-פני פלנטה המרוחקת מיליוני שנות-אור מאתנו, לא נראה חיים תבוניים המסתכלים עלינו בטלסקופ 'ברגע זה ממש'. אנו נראה את הפלנטה כפי שהייתה לפני מיליוני שנים. גם אם התפתחו בה חיים תבוניים שבנו טלסקופ 'ועכשיו' צופים בנו, הם אינם רואים אותנו אלא את כדור הארץ כפי שהיה לפני עידן ועידנים.
תופעה דומה קיימת במערכות תוכנה גדולות. אני מסתכל בפייסבוק על אדם המסתכל עליי בפייסבוק: זהו חברי, אבל אם אני מעדכן סטטוס חדש ומבקש מחברי להסתכל, ייתכן שרק כעבור דקות ארוכות (נצח, במונחי אינטרנט) יתעדכן הסטטוס גם אצלו. חוויה זו מעט מפתיעה, כי כאשר מעדכנים מסמך במחשב, שומרים את הקובץ ופותחים אותו שוב, המסמך תמיד מעודכן. אם כך, כיצד ייתכן שאצל צופה א' מופיע סטטוס מסוים בפייסבוק ואילו אצל צופה ב' הוא טרם הופיע?
הסיבה לכך נעוצה באופן פעולתן של מערכות תוכנה גדולות מאוד. במערכות כאלה שום מחשב אינו חזק או גדול דיו כדי לשמור את מלוא המידע. לכן פייסבוק, טוויטר וגוגל משתמשים בעשרות או מאות אלפי מחשבים הפזורים ברחבי העולם. כל אחד מהמחשבים מחזיק רק בשבריר של המידע, וכל פריט מידע משוכפל כמה פעמים על מחשבים שונים כדי למנוע מצב שבו כשל במחשב אחד או ברשת אחת יגרום לאבדן מידע או לפגיעה בזמינות. לכל מחשב כזה יש 'ציר זמן' משלו, והוא 'רואה' את פריט המידע מתעדכן בזמן שונה מאשר המחשבים האחרים, ה'רואים' את אותו פריט מידע. אי לכך, כשברגע מסוים אנו מבקשים מידע מסוים, אנו עשויים לקבל תשובות שונות ממחשבים שונים.
ניתן להשלים עם מידע לא עדכני כשמדובר ברשתות חברתיות, אבל במערכות רבות אחרות יש לעדכניוּת המידע חשיבות ניכרת. נמחיש זאת במערכת גלובלית להזמנת חדרי מלון, כגון, booking.com. תארו לכם שני ברנשים במקומות שונים בעולם, דוד ובעז, שגולשים באינטרנט פחות או יותר באותו הזמן וכל אחד מהם מבצע הזמנה לחדר האחרון שנותר פנוי בתאריך מסוים במלון מבוקש. באופן מפתיע, בהחלט ייתכן ששתי ההזמנות תאושרנה: במערכות תוכנה כאלה קורה שהחדר הוא 'בו-זמנית' גם פנוי וגם תפוס, תלוי בשרת שאליו מתחברים, היות שמקצת השרתים המטפלים בהזמנות 'יודעים' שהחדר כבר נתפס, בעוד יתר השרתים עדיין אינם ערים לשינוי במצב החדר. אלה ממשיכים לטפל בהזמנה כאילו החדר פנוי, אולם מובן שהמלון יוכל לכבד רק אחת משתי ההזמנות.
בעיה זמנית
שורש הבעיה הוא בשינוי שחל במושג הזמן במערכות תוכנה גדולות. בדרך כלל אנו מדמים שיש ציר זמן אחד, גלובלי. לפיכך אנו מצפים שאחד המשתמשים יקדים את השני בציר הזמן ויתפוס את החדר, והלה יבחין שהגיע מאוחר מדי. אולם במרחבי האינטרנט, כמו במרחבי היקום, הזמן אינו מוחלט. לכל מחשב ציר זמן ייחודי – משמע, צירי זמן נפרדים עבור דוד ועבור בעז. בציר הזמן של דוד, האירוע שבו הוא הזמין את החדר התרחש לפני האירוע שבו בעז ניסה להזמין את החדר; אך בציר הזמן של בעז, סדר האירועים הפוך ולכן גם דוד וגם בעז סבורים שהחדר מובטח להם. לפיכך אף שכל מחשב כשלעצמו מתנהג באופן צפוי, התנהגותה של מערכת התוכנה כולה כלפי המשתמש נעשתה פתאום בלתי צפויה.
נוסף על בעיה עקרונית זאת, נוצרה כאן בעיה מעשית. הזמנת חדר במלון עשויה להיות חלק מחבילת תיור שכוללת טיסות, השכרת רכב ולינה – והלוא לקוח כזה ירצה להתחייב לכל החבילה כיחידה אחת: אם אחד הרכיבים אינו זמין, הוא לא ירצה להיות מחויב לשום רכיב מהחבילה. במערכות התוכנה הגדולות של היום קשה לטפל בכמה הזמנות כמקשה אחת.
פתרון מזדמן
במחקרנו שמנו לב לכך ששיטות פיתוח תוכנה בצוותים גדולים מתמודדות בהצלחה עם בעיה דומה. את התוכנה מפתחת קבוצת מתכנתים, והיא משתנה עם הזמן. כל מתכנת למעשה מתפצל מציר הזמן הראשי, מעדכן על ציר הזמן שלו את התוכנה, וכלי ניהול גרסאות הם שדואגים לאחד מחדש את השינויים של כולם לכדי גרסה קוהרנטית. החידוש של המחקר הוא באימוץ השיטות לשליטה בגרסאות של תוכנה, ובהתאמתן לשליטה בציר הזמן של ריצת מערכות תוכנה גדולות.
בפיתוח תוכנה המושג המקובל עבור ציר זמן הוא ‘ענף’ (Branch). ענפים מעין אלה מתפצלים מענפים אחרים. בדרך כלל יש ענף ראשי, ובו שמורה הגרסה הרשמית של התוכנה, וכל מתכנת יוצר ענף שמתפצל מאותו ענף ראשי או מענף אחר שהתפצל ממנו. שלא כמו ענפי עץ, שרק הולכים ומתפצלים, ענפי תוכנה יכולים גם להתאחד. באופן זה שינויי הקוד שביצע מפתח א' ואלה שביצע מפתח ב' מוצאים את דרכם יחד לאותו קובץ בענף הראשי ולבסוף הפיצ'רים ששניהם פיתחו מוצאים את דרכם למוצר.
כלי התוכנה שמשמשים לביצוע פעולות הפיצול והאיחוד הללו נקראים 'כלי בקרת גרסאות' (Version Control). השימוש באלה החל כבר בשנות ה-70 של המאה ה-20, אך תחום בקרת הגרסאות קיבל תנופה אדירה בשנים האחרונות עם הפריצה של כלי ניהול גרסאות מבוזרים, ובראשם Git. כלים אלה מנהלים ענף נפרד בכל מחשב המחזיק קוד, שלא כקודמיהם שניהלו את כל הענפים במרוכז בשרת מרכזי. מדובר בהבדל זניח, לכאורה, אך למעשה זהו הבדל מהותי: כאשר כל הרכיבים שמורים בשרת מרכזי, כל תקלה באותו שרת משבשת את העבודה. לעומת זאת, כאשר כל מחשב מנהל רק את הענפים שלו אבל מאפשר לפצל ולאחד ענפים מול מחשבים אחרים, אין מחשב מרכזי שיכול 'ליפול' ולשבש לכולם את העבודה. יתרה מזו, מתכנת יכול להיות מנותק זמנית מהאינטרנט ועדיין להמשיך לעבוד. הדבר היחיד שנבצר ממנו לעשות עד שהתקשורת 'תעלה' מחדש הוא איחוד של עבודתו עם זו של האחרים.
הבעיה שפותרים כלי ניהול גרסאות דומה מאוד לבעיית העדכניות של מידע במערכות תוכנה גדולות: יש לבצע את השינויים על-פי העיקרון של 'הכול או לא כלום'. לדוגמה, תהליך ההזמנה של חבילת תיור שבו עסקנו לעיל דומה להפליא לתהליך המתרחש בעת שמפתח תוכנה נדרש להוסיף פרמטר להגדרה של פונקציה. שינוי כזה דורש תיקונים בכמה קבצים (שבהם הוגדרה, מומשה, ונקראה הפונקציה). הקוד חוקי לפני השינוי ואחריו, אך יש הכרח לבצע בד-בבד את תיקוני הקוד בכל אותם קבצים כמקשה אחת. אם נשנה רק חלק מהקבצים, יתקבל קוד לא חוקי. אם מכלול השינויים שמבצע מפתח א' אינם עולים בקנה אחד עם מכלול השינויים שמבצע מפתח ב' – לדוגמה, מפתח א' הוסיף פרמטר לפונקציה בזמן שמפתח ב' השמיט פרמטר לאותה פונקציה – המערכת תדחה את השינוי שהתבצע מאוחר יותר, ולמפתח שביצע את אותו שינוי תינתן ההזדמנות לערוך את הקוד המעודכן.
אפשר לאמץ את הנחות היסוד שעליהן מתבססת בקרת הגרסאות כדי להגביר את השליטה של מפתחי מערכות תוכנה גדולות בצירי הזמן השונים – ובפרט במועד שבו נרצה להתפצל, במועד שבו נרצה לאחד את השינויים, ובענף שמולו נרצה לבצע את השינויים. במערכת להזמנת חדרי מלון, למשל, ניתן לנהל ענף נפרד עבור כל מלון. כדי להבטיח הזמנת חדר על השרת שמנהל את הזמנת הלקוח לבצע את העדכון ולאחד אותו עם הענף של המלון המבוקש. מכיוון שזהו הענף היחידי שמוסמך לאשר חדרים במלון, לא ייתכן ששני לקוחות יצליחו להזמין את החדר האחרון.
אם מחשב יחיד שולט על הענף שמחזיק את בית המלון, ייתכן שהדבר יגרום לאי-זמינות של השירות. עם זאת, בעת הזמנת חדר ניתן לקבל אחת משלוש תשובות אפשריות: 'אין חדר', 'יש חדר', או 'לא הצלחתי לוודא'. אם תינתן התשובה השלישית, השרת המטפל בהזמנה יכול לנסות להזמין את החדר שוב ושוב עד שיתפנה הענף המטפל במלון. ברגע שיתפנה החדר, ניתן לשלוח למזמין תשובה ודאית – חיובית או שלילית. הדבר עדיף בהרבה על המצב הנוכחי במערכות כאלה, שבו יש רק שתי תשובות אפשריות: 'אין חדר' ו'יש חדר )אבל לא הצלחתי לוודא)'.
פתרון נוסף שבקרת גרסאות יכולה לספק הוא לאחד חבילת הזמנות לכדי פעולה אחת. בעת איחוד, פעולה כזאת יכולה להיכנס בשלמותה לענף המאוחד או לא להיכנס כלל. באופן זה, לקוחות אינם נדרשים להתמודד עם מצב שבו אין להם חדר באחד המלונות, אבל הם מחויבים לשלם עבור כרטיסי טיסה, שכירת רכב, או מלונות נוספים.
מאמר זה מתבסס על מאמר מדעי של הכותבים שפורסם ב- Proceedings of the 2014 ACM International Symposium on New Ideas, New Paradigms, and Reflections on Programming & Software. ACM, New York, NY, USA, 29-42. doi.org/10.1145/2661136.2661151.