TrueTime הוא שעון מבוזר עם זמינות גבוהה, שמועבר לאפליקציות בכל השרתים של Google1. TrueTime מאפשר לאפליקציות ליצור חותמות זמן שגדלות באופן מונוטוני: אפליקציה יכולה לחשב חותמת זמן T שמובטח שתהיה גדולה מכל חותמת זמן T', אם יצירת T' הסתיימה לפני שיצירת T התחילה. ההבטחה הזו תקפה בכל השרתים ובכל חותמות הזמן.
התכונה הזו של TrueTime משמשת את Spanner להקצאת חותמות זמן לעסקאות. באופן ספציפי, לכל עסקה מוקצית חותמת זמן שמשקפת את הרגע שבו מערכת Spanner מחשיבה את העסקה ככזו שהתרחשה. מכיוון ש-Spanner משתמש בבקרת בו-זמניות מרובת גרסאות (MVCC), ערבות הסדר על חותמות הזמן מאפשרת ללקוחות של Spanner לבצע קריאות עקביות במסד נתונים שלם (גם במספר אזורים ב-Cloud) בלי לחסום כתיבות.
עקביות חיצונית
אם לא מציינים את רמת הבידוד, או אם מגדירים את רמת הבידוד כבידוד ניתן לסידור, Spanner מספק ללקוחות את הערבויות המחמירות ביותר לבקרת מקביליות לעסקאות, שנקראות עקביות חיצונית2. במסגרת עקרון העקביות החיצונית, המערכת מתנהגת כאילו כל העסקאות מתבצעות ברצף, למרות שבפועל Spanner מבצע אותן בכמה שרתים (ואולי בכמה מרכזי נתונים) כדי לשפר את הביצועים והזמינות. בנוסף, אם עסקה אחת מסתיימת לפני שעסקה אחרת מתחילה להתחייב, המערכת מבטיחה שלקוחות אף פעם לא יראו מצב שכולל את ההשפעה של העסקה השנייה אבל לא של הראשונה. באופן אינטואיטיבי, Spanner לא נבדל מבחינה סמנטית ממסד נתונים במכונה אחת. למרות שהיא מספקת הבטחות חזקות כאלה, Spanner מאפשרת לאפליקציות להשיג ביצועים שדומים לאלה של מסדי נתונים שמספקים הבטחות חלשות יותר (בתמורה לביצועים טובים יותר). כברירת מחדל, Spanner מאפשר פעולות כתיבה בלי שהן ייחסמו על ידי טרנזקציות לקריאה בלבד, אבל בלי להציג את החריגות שבידוד קריאה חוזרת מאפשר.
לעומת זאת, בידוד של קריאה חוזרת מבטיח שכל פעולות הקריאה בתוך טרנזקציה יראו תמונה עקבית של מסד הנתונים כפי שהיה בתחילת הטרנזקציה. הגישה הזו מועילה בתרחישים של קריאה וכתיבה בו-זמנית, שבהם עסקאות רבות קוראות נתונים שעשויים להשתנות על ידי עסקאות אחרות. מידע נוסף זמין במאמר בנושא בידוד קריאה חוזרת.
עקביות חיצונית מפשטת מאוד את פיתוח האפליקציות. לדוגמה, נניח שיצרתם אפליקציה בנקאית ב-Spanner, ולאחד הלקוחות שלכם יש 50 $בחשבון העו"ב ו-50 $בחשבון החיסכון. האפליקציה מתחילה תהליך עבודה שבו היא קודם מבצעת טרנזקציה T1 להפקדת 200 $בחשבון החיסכון, ואז מבצעת טרנזקציה שנייה T2 לחיוב של 150 $מחשבון העו"ש. בנוסף, נניח שבסוף היום, יתרות שליליות בחשבון אחד מכוסות באופן אוטומטי מחשבונות אחרים, ושהלקוח סופג קנס אם היתרה הכוללת בכל החשבונות שלו שלילית בכל שלב במהלך אותו היום. עקביות חיצונית מבטיחה שמאחר שתהליך האישור של T2 מתחיל אחרי שתהליך האישור של T1 מסתיים, כל הקוראים של מסד הנתונים יראו שהפקדת T1 התרחשה לפני החיוב T2. במילים אחרות, עקביות חיצונית מבטיחה שאף אחד לא יראה מצב שבו T2 מתרחש לפני T1. כלומר, החיוב לא יגרור קנס בגלל יתרה לא מספקת.
מסד נתונים מסורתי שמשתמש באחסון של גרסה אחת ובנעילה קפדנית בשני שלבים מספק עקביות חיצונית. לצערנו, במערכת כזו, בכל פעם שהאפליקציה רוצה לקרוא את הנתונים העדכניים ביותר (מה שאנחנו מכנים 'קריאה חזקה'), המערכת מקבלת נעילת קריאה על הנתונים, שחוסמת כתיבה לנתונים שנקראים.
חותמות זמן ו-MVCC
כדי לקרוא בלי לחסום כתיבה, Spanner ומערכות רבות אחרות של מסדי נתונים שומרות כמה גרסאות של נתונים שלא ניתן לשנות (לרוב נקרא בקרת בו-זמניות מרובת גרסאות). פעולת כתיבה יוצרת גרסה חדשה שלא ניתן לשנות, וחותמת הזמן שלה היא של העסקה של פעולת הכתיבה. 'קריאת תמונת מצב' בחותמת זמן מחזירה את הערך של הגרסה האחרונה לפני חותמת הזמן הזו, ולא צריך לחסום כתיבות. לכן חשוב שחותמות הזמן שמוקצות לגרסאות יהיו עקביות עם הסדר שבו אפשר לראות את העסקאות מתבצעות. אנחנו קוראים למאפיין הזה 'חותמות זמן תקינות', ששווה ערך לעקביות חיצונית.
כדי להבין למה חשוב להוסיף חותמות זמן מתאימות, כדאי לעיין בדוגמה של הבנקאות מהקטע הקודם. ללא חותמות זמן מתאימות, יכול להיות של-T2 תוקצה חותמת זמן מוקדמת יותר מזו שהוקצתה ל-T1 (לדוגמה, אם מערכת היפותטית השתמשה בשעונים מקומיים במקום ב-TrueTime, והשעון של השרת שמעבד את T2 היה מעט מאחור). במקרה כזה, יכול להיות שקריאת התמונה תציג את החיוב מ-T2 אבל לא את ההפקדה ב-T1, למרות שהלקוח ראה שההפקדה הסתיימה לפני שהחיוב התחיל.
קל להשיג חותמות זמן תקינות במסד נתונים של מכונה אחת (לדוגמה, אפשר פשוט להקצות חותמות זמן ממונה גלובלי שעולה באופן מונוטוני). השגת התכונה הזו במערכת מבוזרת כמו Spanner, שבה שרתים בכל העולם צריכים להקצות חותמות זמן, היא הרבה יותר מסובכת.
Spanner מסתמך על TrueTime כדי ליצור חותמות זמן שהערכים שלהן גדלים באופן מונוטוני. מערכת Spanner משתמשת בחותמות הזמן האלה בשתי דרכים. קודם כול, המערכת משתמשת בהם כחותמות זמן מתאימות לעסקאות כתיבה, בלי צורך בתקשורת גלובלית. שנית, המערכת משתמשת בהם כחותמות זמן לקריאות חזקות, מה שמאפשר לקריאות חזקות להתבצע בסבב תקשורת אחד, גם לקריאות חזקות שמתבצעות בכמה שרתים.
שאלות נפוצות
אילו ערבויות עקביות מספק Spanner?
כברירת מחדל, Spanner מספק עקביות חיצונית, שהיא מאפיין העקביות המחמיר ביותר למערכות עיבוד עסקאות. כל העסקאות ב-Spanner שמשתמשות בבידוד של סדרות, עומדות בדרישות של מאפיין העקביות הזה, ולא רק העסקאות שבתוך מחיצה. מידע נוסף זמין במאמר סקירה כללית על רמות בידוד.
עקביות חיצונית קובעת ש-Spanner מבצע עסקאות באופן שלא ניתן להבחין בו ממערכת שבה העסקאות מבוצעות באופן סדרתי, ושהסדר הסדרתי עקבי עם הסדר שבו ניתן לראות את העסקאות מתבצעות. מכיוון שחותמות הזמן שנוצרות לעסקאות תואמות לסדר הסדרתי, אם לקוח כלשהו רואה שעסקה T2 מתחילה להתבצע אחרי שעסקה אחרת T1 מסתיימת, המערכת תקצה חותמת זמן ל-T2 שהיא גבוהה יותר מחותמת הזמן של T1.
האם Spanner מספק ליניאריות?
כן. כברירת מחדל, Spanner מספק עקביות חיצונית, שהיא מאפיין חזק יותר מליניאריות, כי ליניאריות לא אומרת דבר על התנהגות העסקאות. ליניאריות היא מאפיין של אובייקטים מקבילים שתומכים בפעולות קריאה וכתיבה אטומיות. במסד נתונים, 'אובייקט' הוא בדרך כלל שורה אחת או אפילו תא אחד. עקביות חיצונית היא מאפיין של מערכות לעיבוד עסקאות, שבהן לקוחות יוצרים באופן דינמי עסקאות שמכילות כמה פעולות קריאה וכתיבה על אובייקטים שרירותיים. אפשר לראות את התכונה 'ניתנות לליניאריזציה' כמקרה מיוחד של עקביות חיצונית, שבו טרנזקציה יכולה לכלול רק פעולת קריאה או כתיבה אחת באובייקט יחיד.
האם Spanner מספקת סריאליזציה?
כן. כברירת מחדל, Spanner מספק עקביות חיצונית, שהיא מאפיין מחמיר יותר מאשר אפשרות להרצה טורית. מערכת לעיבוד עסקאות היא סדרתית אם היא מבצעת עסקאות באופן שלא ניתן להבחין בינו לבין מערכת שבה העסקאות מבוצעות באופן סדרתי. בנוסף, מערכת Spanner מבטיחה שהסדר הסדרתי עקבי עם הסדר שבו אפשר לראות את העסקאות מתבצעות.
נחזור לדוגמה של הבנקאות שהשתמשנו בה קודם. במערכת שמספקת סריאליזציה אבל לא עקביות חיצונית, גם אם הלקוח ביצע את T1 ואז את T2 ברצף, המערכת יכולה לשנות את הסדר שלהם, מה שעלול לגרום לחיוב בעמלה בגלל יתרה לא מספקת.
האם Spanner מספק מודל עקביות חזק?
כן. Spanner מספק עקביות חיצונית, שהיא מאפיין חזק יותר מודל עקביות חזק. מצב ברירת המחדל של קריאות ב-Spanner הוא 'חזק', שמבטיח שהן יראו את ההשפעות של כל העסקאות שאושרו לפני תחילת הפעולה, בלי קשר לרפליקה שמקבלת את הקריאה.
מה ההבדל בין מודל עקביות חזק לעקביות חיצונית?
פרוטוקול שכפול מציג מודל עקביות חזק אם האובייקטים המשוכפלים ניתנים לליניאריזציה. בדומה לליניאריות, מודל עקביות חזק הוא חלש יותר מעקביות חיצונית, כי הוא לא אוכף שום דבר לגבי ההתנהגות של טרנזקציות.
האם Spanner מספק עקביות סופית (או עצלה)?
Spanner מספק עקביות חיצונית, שהיא מאפיין חזק הרבה יותר מודל עקביות הדרגתי. מודל עקביות הדרגתי מאפשר ביצועים טובים יותר על חשבון הבטחות חלשות יותר. מודל עקביות הדרגתי הוא בעייתי כי הוא אומר שקוראים יכולים לראות את מסד הנתונים במצב שלא היה קיים אף פעם (לדוגמה, קריאה יכולה לראות מצב שבו טרנזקציה B בוצעה אבל טרנזקציה A לא בוצעה, למרות ש-A התרחשה לפני B).
Spanner מספק קריאות לא עדכניות, שמציעות יתרונות דומים של ביצועים כמו עקביות הדרגתית, אבל עם הבטחות עקביות חזקות הרבה יותר. קריאה בעבר מחזירה נתונים מחותמת זמן קודמת, שלא יכולה לחסום כתיבות כי גרסאות קודמות של נתונים הן בלתי ניתנות לשינוי.