איך מסד נתונים לא מאבד מידע כשהוא קורס באמצע כתיבה - Write-Ahead Log¶
בפוסט על טרנזקציות הסברנו שעקרון העמידות (Durability) מבטיח שברגע ש-COMMIT הצליח, הנתונים לא ייעלמו, גם אם השרת קורס בשנייה שאחרי. זו הבטחה חזקה - אבל איך מסד נתונים בכלל מקיים אותה, כשכתיבה בפועל לדיסק, לקבצים המסודרים של הטבלאות והאינדקסים, יכולה לקחת זמן ולכלול כמה שלבים? התשובה נקראת Write-Ahead Log, ובעברית פשוטה - יומן שכותבים אליו קודם.
הבעיה - כתיבה לדיסק היא לא פעולה אטומית¶
כשמסד נתונים מעדכן שורה, בפועל צריך לעדכן כמה מקומות שונים על הדיסק - את הטבלה עצמה, ואת כל האינדקסים שמצביעים על השורה הזאת. אם השרת קורס בדיוק באמצע, אחרי שעודכן מקום אחד אבל לפני שעודכן השני, מסד הנתונים נשאר במצב לא עקבי - הטבלה אומרת דבר אחד, האינדקס אומר אחר. תארו לעצמכם קריסת חשמל בדיוק בין שני עדכוני דיסק כאלה - בלי מנגנון הגנה, אין דרך פשוטה לדעת מה בדיוק הספיק להיכתב ומה לא.
הרעיון - קודם כותבים כוונה, אחר כך מבצעים אותה¶
Write-Ahead Log, בקיצור WAL, פותר את זה בעזרת כלל פשוט - לפני שמסד הנתונים משנה בפועל את קבצי הטבלה או האינדקס, הוא כותב תחילה רשומה קטנה ליומן ייעודי, שמתארת בדיוק מה הוא עומד לעשות. רק אחרי שהרשומה הזאת נכתבה בהצלחה לדיסק, בביטחון מוחלט, מסד הנתונים ממשיך לבצע את השינוי בפועל בקבצי הנתונים עצמם.
הרעיון המרכזי - כתיבה ליומן היא פעולה פשוטה, רציפה, ומהירה יחסית, כי היא רק מוסיפה שורה לסוף קובץ בלי לחפש איפה לשים אותה. השינוי המורכב עצמו, שעשוי לגעת בכמה קבצים בסדר לא רציף, קורה רק אחרי שהכוונה כבר מתועדת בבטחה.
למה זה מבטיח שחזור אחרי קריסה¶
עכשיו הקסם - אם השרת קורס בכל שלב, אפשר תמיד לדעת בדיוק מה קרה, כי היומן מכיל תיעוד מלא ומסודר של כל כוונה. כשמסד הנתונים עולה מחדש אחרי קריסה, הוא עובר תהליך שנקרא שחזור (recovery) - הוא קורא את היומן מהנקודה האחרונה שהוא בטוח שהושלמה, ולכל רשומה שמתועדת ביומן אך לא ברור אם הושלמה בפועל בקבצי הנתונים, הוא פשוט מבצע אותה שוב (redo). מכיוון שהיומן נכתב לפני הפעולה עצמה, אין מצב שבו פעולה בוצעה בפועל אבל לא מתועדת ביומן - הכי גרוע שיכול לקרות הוא שפעולה מתועדת ביומן אבל לא הושלמה, ואז פשוט מריצים אותה שוב, מה שבטוח ולא גורם נזק.
checkpoint - למה לא צריך לקרוא את כל היומן מההתחלה¶
אם מסד נתונים רץ שבועות, היומן יכול להיות ענק, ושחזור מלא ממנו בכל קריסה היה לוקח נצח. הפתרון הוא checkpoint - מדי פעם, מסד הנתונים מוודא שכל השינויים עד לנקודה מסוימת אכן נכתבו בפועל לקבצי הנתונים על הדיסק, ומסמן את הנקודה הזאת כ"בטוחה לגמרי". אחרי checkpoint, אפשר להתעלם מכל מה שביומן לפני אותה נקודה, כי כבר בטוח שהוא הושלם. שחזור אחרי קריסה צריך לקרוא רק מה שנכתב מאז ה-checkpoint האחרון, לא מתחילת הזמן.
למה זה נושא שכל מתכנת שכותב לוגיקה קריטית צריך להכיר¶
WAL הוא הדוגמה הטובה ביותר לעיקרון רחב יותר בהנדסת תוכנה - כשפעולה מורכבת יכולה להיכשל באמצע, לרוב הפתרון הוא לא "לנסות למנוע כשלים", אלא לתעד כוונה לפני ביצוע, כדי שאפשר יהיה לדעת בוודאות מה קרה גם אחרי הכי גרוע. אותו רעיון בדיוק חוזר במערכות תור הודעות, בקבצי לוג של מערכות קבצים, ובעוד הרבה מקומות שבהם אמינות קריטית.
בקורס צד-שרת עוברים על עקרונות אמינות במסדי נתונים בהקשר מעשי, כולל למה בחירת מסד נתונים עם WAL מהימן היא לא פרט טכני שולי.
יש לכם שאלה על אמינות ושחזור נתונים במערכת שלכם? שאלו בדיסקורד.
לסיכום¶
מסד נתונים לא מאבד מידע בזכות כלל פשוט אך חזק - לתעד כל כוונת שינוי ביומן לפני שמבצעים אותה בפועל. אם השרת קורס, השחזור פשוט קורא את היומן ומריץ מחדש כל מה שלא הושלם בוודאות. checkpoint תקופתי דואג שהיומן לא יגדל לנצח. זו בדיוק הסיבה שאתם יכולים לסמוך על מסד נתונים גם ברגע הכי גרוע.