Recovery Models
פוסט זה עוסק בדרך בה ניתן לגבות את הדטבייסים ולשחזר אותם בכפוף לאופן בו הם מוגדרים.
אנחנו אמורים לכלכל מעשינו כך שכשקורית תקלה במערכת המידע- נהיה ערוכים לקראתה במידת האפשר, ונהיה מסוגלים להחזיר את המערכת לפעולה תקינה במהירות סבירה. כמובן שלכל דבר יש מחיר במשאבים, ביצועים, עבודה וכו'; ומנגד הצרכים יכולים לנוע בין מערכות קריטיות שאסור בשום פנים ואופן שיושבתו לבין כאלו שחשיבותן שולית ואין צורך להקדיש להן תשומת לב מיוחדת. לצורך כך יש טכנולוגיות שונות שבהן מככבים מושגים כמו High Availability (מניעת השבתה של המערכת), Data Recovery (התאוששות מהירה במקרה של תקלה) ועוד.
Recovery Models עוסק בגיבויים ושיחזורים ברמת הדטבייס: זה אינו פתרון ברמת השרת, וגם לא ברמת הטבלה.
כשיוצרים דטבייס ניתן להגדיר לו באופן כללי אחד משניים:
Simple Recovery Model – במקרה זה יש לבצע לדטבייס גיבוי מלא או דיפרנציאלי אחת לזמן מה; וניתן לשחזר במקרה של תקלה לאחת מנקודות הגיבוי.
Full Recovery Model – במקרה זה יש לבצע לדטבייס גיבוי מלא או דיפרנציאלי, ובנוסף לגבות את הלוג; וניתן לשחזר במקרה של תקלה לכל נקודת זמן בין כל גיבוי מלא לגיבוי הלוג האחרון שלו (את היכולת האחרונה אינני מדגים בפוסט הזה).
לפרוטוקול, קיימת אופציה שלישית – Bulk Logged – שהיא גרסה של Full שנועדה להתמודד בקלות עם משימות כמו Bulk Insert, Insert Into, יצירת אינדקסים ועוד, במחיר של ויתור על אופציית השיחזור לכל נקודת זמן. כלומר- אופציה זו מאפשר לגבות את הלוג, אך לשחזר אותו במלואו ולא עד לנקודה מסויימת.
גיבוי דיפרנציאלי הוא גיבוי של השינוי מאז הגיבוי המלא האחרון, ולשיחזור יש צורך בגיבוי מלא אחד ובגיבוי דיפרנציאלי אחד.
קובץ הלוג שיש לו תפקיד נכבד ב-Full ממלא שני תפקידים במקביל:
1. כל טרנזקציה נכתבת אליו וקיימת בו כל עוד היא פתוחה.
2. במודל Full, הטרנזקציות שהסתיימו נשארות בלוג עד שמתבצע גיבוי.
מכל זה אפשר להבין שב-Simple הלוג גדל רק בגלל טרנזקציות פתוחות, אבל ב-Full גם בגלל טרנזקציות סגורות שטרם גובו (הלוג מתרוקן רק לאחר הגיבוי).
במקרה של שיחזור Full יש צורך בגיבוי מלא, בגיבוי דיפרנציאלי אחריו (אופציונאלי), ובכל גיבויי הלוג לאחר הגיבוי המלא/הדיפרנציאלי עד לנקודת הזמן אליה רוצים לשחזר.
סיכום ביניים: במקרה "רגיל" (עד כמה שתקלה המחייבת שיחזור יכולה להיות רגילה..) ב-Simple יש לשחזר את הגיבוי המלא האחרון והדיפרנציאלי האחרון שאחריו. כל מה שקורה לאחר הגיבוי הדיפרנציאלי האחרון- הולך לאיבוד.
ב-Full יש לשחזר את הגיבוי המלא האחרון, את הדיפרנציאלי האחרון, ואת כל גיבויי הלוג מאז הדיפרנציאלי האחרון. גם כאן כל מה שקורה לאחר הגיבוי האחרון הולך לאיבוד, אבל אם יש הפרדה בין הדיסקים של ה-Data (קבצי mdf) והלוג (קבצי ldf) – יש סיכוי שנוכל ליצור קובץ לוג חדש אם קרס הדיסק של הלוג (כאשר ב-Data נשמר כל המידע) או לגבות את הלוג ולשחזר בעזרתו ובעזרת שאר הגיבויים את המערכת עד לנקודת הזמן של התקלה אם הדיסק של ה-Data קרס. ב-Simple לא תצמח לנו כל תועלת מקובץ הלוג אם קובץ ה-Data אבד.
הדגמה: להלן נדגים מספר מצבי קטסטרופה לדטבייס ונראה כיצד להתמודד איתם. למותר לציין שלא מתרלגים זאת על שרת הייצור, אבל מי שכן- לא אחסום אותו בדישו..
ניצור דטבייס חדש ב-Simple Recovery, וניצור בו טבלה:
Use master;
Go
If DB_ID('MyDatabase') Is Not Null Drop Database MyDatabase;
Go
Create Database MyDatabase;
Go
Alter Database MyDatabase Set Recovery Simple;
Go
Create Table MyDatabase.dbo.MyTable(MyDate DateTime Default GetDate() Primary Key Clustered,
MyText Varchar(Max));
Go
נזין לטבלה שורה אחת, נגבה את הדטבייס, ונזין שורה נוספת (אינה נכללת בגיבוי):
Insert
Into MyDatabase.dbo.MyTable(MyText)
Select 'Simple Recovery Database was created';
Backup Database MyDatabase To Disk='C:\Tmp\MyBackup01_Simple.bak';
Go
Insert
Into MyDatabase.dbo.MyTable(MyText)
Select 'Backup 01 Simple has been done';
Select *
From MyDatabase.dbo.MyTable;
את מיקומם של הקבצים על פי טבלת המערכת sys.sysfiles כדאי להעתיק ולשמור בצד להמשך (לא בטוח שהוא זהה לזה שאצלי).
כעת ניזום תקלה בה קובץ הלוג הולך לאיבוד בגלל תקלה:
נעצור את ה-SQL Server על ידי קליק ימני ב-SSMS, בחירה ב-Stop ואישור:
ניגש למחיצה בה הקבצים נמצאים, ואת קובץ MyDatabase_Log.ldf נמחוק ברשעות (עדיף ליתר בטחון להעביר למחיצה צדדית), ונפעיל שוב את ה-Service של SQL Server, הפעם על ידי קליק ימני כנ"ל ו-Start.
כדאי לשים לב שלאחר שמבצעים Restart לשרת- יש להתחבר שוב בחלון ה-Query מכיוון שהוא התנתק כשה-Service ירד.
כעת אנחנו עלולים לגלות "לחרדתנו" שהדטבייס נדפק ולא ניתן לגשת אליו (יתכן והמערכת תיצור בעצמה קובץ לוג חדש ולא תהיה כל בעייה ויתכן שלא נוכל לגשת לדטבייס ולשלוף מהטבלה שיצרנו). במקרה הראשון נמהר לקנות כרטיס הגרלה של מפעל הפייס (Today is our lucky day נסביר לאראלה כשתתקשר אלינו), ובמקרה השני נריץ את הפקודות הבאות שיתקנו את הטעון תיקון:
Alter Database MyDatabase Set Emergency;
Alter Database MyDatabase Set Single_User;
DBCC CheckDB (MyDatabase,Repair_Allow_Data_Loss);
Alter Database MyDatabase Set Multi_User;
Alter Database MyDatabase Set Online;
מה קורה אם קובץ הנתונים MyDatabase.mdf אובד? במקרה זה נשחזר מהגיבוי את מה שניתן, וכל מה שקרה לאחר הגיבוי- ירד לטמיון:
If DB_ID('MyDatabase') Is Not Null Drop Database MyDatabase;
Go
Restore Database MyDatabase From Disk='C:\Tmp\MyBackup01_Simple.bak';
Go
Select *
From MyDatabase.dbo.MyTable;
ניתן לראות שהשורה השנייה שהזנו לטבלה- איננה; יהי זכרה ברוך!
נשנה כעת את הדטבייס ל-Full Recovery:
Alter Database MyDatabase Set Recovery Full;
Go
נוסיף שורה לטבלה ונבצע גיבוי מלא:
Insert
Into MyDatabase.dbo.MyTable(MyText)
Select 'The Database was altered to Full Recovery';
Backup Database MyDatabase To Disk='C:\Tmp\MyBackup02_Full.bak';
Go
נוסיף עוד שורה לטבלה ונבצע גיבוי דיפרנציאלי (של מה שהשתנה מאז הגיבוי האחרון):
Insert
Into MyDatabase.dbo.MyTable(MyText)
Select 'Backup 02 Full has been done';
Backup Database MyDatabase To Disk='C:\Tmp\MyBackup03_Differential.bak' With Differential;
Go
נוסיף עוד שורה לטבלה ונבצע גיבוי לוג (של מה שנכתב בלוג מאז הגיבוי הדיפרנציאלי/המלא האחרון):
Insert
Into MyDatabase.dbo.MyTable(MyText)
Select 'Backup 03 Differential has been done';
Backup Log MyDatabase To Disk='C:\Tmp\MyBackup04_Log.bak';
Go
ולבסוף נוסיף עוד שורה אחת שאינה מגובה בינתיים:
Insert
Into MyDatabase.dbo.MyTable(MyText)
Select 'Backup 04 Log has been done';
המצב כרגע:
Select *
From MyDatabase.dbo.MyTable;
בטבלה יש 5 שורות, כשכל אחת משלוש האחרונות נכתבה לאחר אחד הגיבויים (שוב- האחרונה אינה מגובה).
כעת נתחיל להתעלל מעט במערכת- קודם כל נמחק את קובץ הלוג כמו קודם: נבצע Stop למערכת, נמחק מהמחיצה את MyDatabase_Log.ldf (או נעתיק למחיצה צדדית), ונפעיל שוב את השרת על ידי Start.
הפעם מזלנו כבר לא יאיר לנו פנים- בקובץ הלוג יש מידע חשוב (שורה 5 בטבלה), ולא ניתן לגשת לדטבייס.
נריץ את הסקריפט שהוצע קודם ונקווה לטוב:
Alter Database MyDatabase Set Emergency;
Alter Database MyDatabase Set Single_User;
DBCC CheckDB (MyDatabase,Repair_Allow_Data_Loss);
Alter Database MyDatabase Set Multi_User;
Alter Database MyDatabase Set Online;
Select *
From MyDatabase.dbo.MyTable;
מכיוון שכל המידע היה בקובץ ה-Data והוא לא אבד- כל חמש השורות בטבלה.
במקרה אמת יש לבצע בהקדם גיבוי מלא!
מקרה חמור יותר- מה קורה אם קובץ ה-Data אובד?
נעצור את המערכת על ידי Stop, נעביר למחיצה צדדית את קובץ MyDatabase.mdf, ונפעיל על ידי Start.
כעת הבעייה קצת יותר חמורה- חלק מהמידע נמצא בקבצי הגיבוי וחלק בקובץ הלוג ששרד, ויש לפעול כך (כאשר הפעם מבצעים את כל הפעולות כחלק מתרגול של טיפול בתקלה ולא של ביום תקלה כמו קודם, אז לא להתבלבל):
1. לעצור את ה-Service על ידי Stop, להעביר את קובץ הלוג למחיצה צדדית (לא למחוק!) ולהפעיל שוב על ידי Start.
2. לבצע Drop לדטבייס שלא נותרו לו קבצים (ה-Data נמחק בשל תקלה והלוג הועבר הצידה כחלק מהטיפול בתקלה) וניצור מחדש:
If DB_ID('MyDatabase') Is Not Null Drop Database MyDatabase;
Go
Create Database MyDatabase;
Go
3. לעצור שוב את ה-Service על ידי Stop, למחוק את שני הקבצים החדשים שנוצרו MyDatabase.mdf, MyDatabase_Log.ldf, להחזיר את קובץ הלוג הקודם ששרד את התקלה (קובץ ה-Data "אבד" כזכור), ולהפעיל את המערכת על ידי Start (במציאות כדאי לשמור במקביל עותק שלו).
4. לגבות את קובץ הלוג (למרות שהדטבייס תקול כי יש רק קובץ לוג):
Backup Log MyDatabase To Disk='C:\Tmp\MyBackup05_Log.bak' With No_Truncate;
Go
5. לבצע Drop לדטבייס ולמחק את קובץ הלוג לאחר מכן, כדי שנוכל לשחזר בצורה מסודרת.
If DB_ID('MyDatabase') Is Not Null Drop Database MyDatabase;
Go –-למחוק כעת את קובץ הלוג
6. לשחזר את הדטבייס בעזרת ארבעת הגיבויים הרלוונטיים שיש כעת: הגיבוי המלא האחרון, הגיבוי הדיפרנציאלי האחרון, וכל גיבויי הלוג מאז הגיבוי המלא/הדיפרנציאלי האחרון (במקרה שלנו שניים- אחד לפני התקלה ואחד לאחר התקלה):
Restore Database MyDatabase From Disk = 'C:\Tmp\MyBackup02_Full.bak' With File = 1, NoRecovery;
Go
Restore Database MyDatabase From Disk ='C:\Tmp\MyBackup03_Differential.bak' With File = 1, NoRecovery;
Go
Restore Log MyDatabase From Disk = 'C:\Tmp\MyBackup04_Log.bak' With NoRecovery;
Go
Restore Log MyDatabase From Disk = 'C:\Tmp\MyBackup05_Log.bak' With Recovery;
Go
וכעת רק נותר לוודא שכל חמש השורות בטבלה:
Select *
From MyDatabase.dbo.MyTable;
הידד- הכל בסדר!
לסיום אני אדגים מה קורה אם הגרוע מכל קורה- נשארים ללא קבצי Data ולוג, ללא שרת, ללא גיבויים וללא DBA: אני אשים את הגיבויים בתיק הגב שלי, אקח את השרת ביד, ואקפוץ מהקומה העשירית:
אההההההה..... .... .. בום!
(עכשיו נראה אותם מסתדרים בלעדי)
סיכום: כשמשתמשים ב-Full Recovery יש סיכוי טוב יותר שנצליח לשחזר את הדטבייס במלואו אם אחד משני הקבצים (Data ולוג) שרד, ולכן כדאי במצב כזה להפריד בינהם פיזית. יש לכך גם יתרונות מבחינת הביצועים (בעת ביצוע Commit המערכת עובדת במקביל על שני הקבצים), אבל בראש ובראשונה מבחינת יכולת השחזור.
כשמשתמשים ב-Simple Recovery גם כדאי להפריד פיזית בין הקבצים, אבל רק משיקולי ביצועים: נוכל להתאושש באופן מלא מתקלה רק אם קובץ ה-Data שרד. בכל מקרה אחר- נצטרך לסמוך על מה שיש בגיבויים.
כמובן שלכל דבר יש מחיר והשימוש ב-Full Recovery מחייב נפח אחסון גדול יותר לקבצי הלוג.