SQL Server: גיבויים, שחזורים–ומה עושים כשה-DB נעשה גדול

18 בMay 2017

תגיות: , , ,
אין תגובות

אחד הנושאים הכי חשובים שיש זה גיבוי ושחזור הנתונים. אני משוכנע שלא צריך להסביר לאף אחד , בין אם הוא DBA ביום-יום ובין אם לא – את החשיבות שבתוכנית גיבוי ושחזור סדורה, שבמסגרתה ברור איך משתחזרים, מה מאבדים (אם בכלל) בזמן שחזור ובין היתר – מה עושים אם זה לא עובד.  יש לא מעט מסמכי Best Practice של איך להגדיר תוכנית גיבוי ושחזור טובה, איך להאיץ את מהירות הגיבוי (ומהירות השחזור) – ובאופן כללי, ממש לא חסר חומר בנושא הזה. 
בפוסט הזה אני רוצה להתמקד על תת-בעייה – איך מגבים DB גדול, כאשר  אני בעיקר אתמקד בשיטות שהמטרה שלהם היא לאפשר שחזור מהיר.

Back to Basics – גיבויים ושחזורים

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

אני מניח שדי ברור לכולם מה התרחישים שבגינם אנחנו זקוקים לגיבוי ושחזור. זה יכול לנוע על כל הרצף של בין “אוי, שיט – עשיתי טעות קריטית” שגורר צורך בשחזור, זה יכול להיות מענה משני לתרחישי DR (למרות שיש מענים הרבה יותר טובים, בגלל זה הגדרתי את זה בתור מענה משני), זה יכול להיות מענה לפיתרון בעיית corruption שנוצרה ב-DB – בקיצור, יש לא מעט תרחישים שבהם זה עוזר.

את תוכנית הגיבוי והשחזור שלנו אנחנו מודדים בד”כ לפי הפרמטרים הבאים:

  • כמה מידע אנחנו מאבדים בעת השחזור (ידוע בשם RPO)– כלומר אם קרתה תקלה בשעה 10:00 בבוקר ואנחנו מתחילים לבצע שחזור, איזה מרווח של נתונים נאבד. שעה? שעתיים? עשר? מין הסתם, אנחנו רוצים למזער את הזמן הזה כמה שיותר ולחשוב מראש על מה אנחנו עושים עם המידע שאובד.
  • כמה זמן לוקח השחזור (ידוע בשם RTO)– נניח שקרתה תקלה והתחלנו להשתחזר. כמה זמן ייקח עד שהכל יעבוד שוב? שעה? עשר? יממה? גם פה, נרצה למזער את הזמן הזה כמה שיותר
  • כמה זמן לוקח הגיבוי – אמנם זה פרמטר שבד”כ הוא פחות חשוב (משמעותית) מהפרמטרים האחרים, אבל הוא עלול להיעשות חשוב יותר ככל שה-DB גדול יותר ועמוס יותר. דמיינו לעצמכם DB גדול ועמוס (שמתבצעות אליו גם הרבה פעולות, כולל כתיבות ושינויים, על ביס קבוע). בזמן הגיבוי יש איזושהי השלכה מסויימת על ביצועי המערכת (בעיקר משאבי IO שנדרשים, וקצת CPU – בעיקר בשביל ה- compression) ובנוסף בזמן הגיבוי לא מתנקים ה- transaction logs. ההצטברות שלהם עלולה להיות משמעותית אם זה מגיע לנפחים גדולים.

כשאנחנו עובדים עם SQL Server יש לנו כל מיני אמצעים שאנחנו יכולים להשתמש בהם כשעושים גיבוי. אם אתם לא מכירים איך עושים גיבויים ב- SQL Server אזי מומלץ לכם (בחום!) לקרוא את המאמר של פול רנדל בנושא וכמובן גם תמיד כדאי לקרוא את מה שיש להגיד לתיעוד הרשמי להגיד בנושא.

דטאבייסים בינוניים וגדולים והאתגרים שהם מציבים

כשמדברים על דטאבייס קטן (נניח, גודל כולל של מתחת ל- 500GB) – בד”כ אין בעייה, ואלה שיש – פתירות באמצעות טוויקים שגרתיים יחסית.
ה- Best Practice הסטנדרטי של “תעשה full backup פעם ביום, differential backup פעם במס’ שעות [נגיד 6-7] ו- transaction log backup כל 15-30 דק’” עובד מעולה. השחזור יתבצע ע”י restore של ה- full backup, ולאחריו ה- diffrenetial backup העדכני ביותר ואח”כ לנגן את ה- transaction logs כל הדרך עד הסוף. 
כשעובדים ב-VM, אז גם קל מאד לשלב גם גיבוי system מלא, application consistent, שמתבצע תוך שילוב היכולות של snapshots ל-VM, עם snapshot של ה- storage (כאשר לכל ה- vendors העיקריים יש אינטגרציה מעולה שמאפשרת לבצע את זה בצורה קלה ויעילה) ועם VSS שרץ על המכונה לשמירה על הקונסיסטנטיות. זה מאפשר גם שחזור מלא של המכונה על כל הנתונים שבה (על כל הדטאבייסים שרצים בה, ה- state המדוייק שבו היו דברים מותקנים כך שזה נותן מענה גם לתקלות שדרוג וכו’).

בקיצור, כשעובדים עם DB-ים קטנים יחסית, אפשר לאכול את העוגה ולהשאיר אותה שלמה ולהנות למעשה מיתרונות בכל הסעיפים שהזכרתי קודם שבוחנים למעשה יעילות של תוכנית גיבוי ושחזור.
לעיתים, כמובן, נדרשים כל מיני טוויקים ושיפורים כדי לייעל את מהירות הגיבוי והשחזור. בין היתר אפשר למנות שיפורים כמו כתיבת הגיבויים ל- storage “טוב” יותר (אם קיים) או ל-storage שונה מהנוכחי (כדי לפצל את ה-IO), שימוש ב- compression (אם לא משתמשים, וסתם חבל…) ,שימוש במספר קבצי backups במקביל (מוסיף backup threads, מאפשר לפצל IO), הגדלת כל מיני באפרים וכו’. באמצעות שיפורים כאלה אפשר להגיע למהירויות גיבוי ושחזור סבירות לחלוטין שבד”כ יענו על הדרישה.

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

הפתרונות האפשריים

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

  • שיטת גיבוי “סטנדרטית”
  • פיצול דטאבייסים
  • פיצול ל- Filegroup-ים וגיבוי Filegroup-ים ספיציפיים
  • גיבויים מבוסס application-consistent snapshot בסביבות וירטואליות ומערכי אכסון תומכים
  • גיבוי crash-consistent מבוסס snapshot ב- storage

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

המטרה בתיאור הפתרונות הללו היא להציג לכם אוסף של שיטות אפשריות לגיבוי דטאבייסים גדולים, היתרונות והחסרונות שלהן. חשוב לי להדגיש מראש שאין אלמנט של “שיטה טובה” ו-“שיטה לא טובה”. שיטה היא טובה אם היא מתאימה לצרכים שלכם כמה שיותר – שיטה שמתאימה ל-DB אחד לא בהכרח תתאים ל-DB אחר.

השיטה “הסטנדרטית”

במשפט אחד: גיבוי מלא בתדירות מסויימת, גיבוי דיפרנציאלי בתדירות גבוהה קצת יותר וגיבוי transaction logs בתדירות גבוהה מאד. ה- comfort zone של כל DBA. יאפשר לכם להשתחזר אפילו עד לדקה האחרונה (כתלות בתדירות הגיבויי transaction logs), אבל זה יכול להיות עסק די ארוך ב-DB גדול.

בואו נסתכל על השיטה הסטנדרטית: לקחת Full Backup פעם בפרק זמן מסויים (יום, יומיים, שבוע… נדבר על ההשלכות עוד מעט), לקחת Differential Backup בתדירות קטנה יותר ולקחת transaction log backups בתדירות גבוהה מאד (15-30 דק’).
כאשר, אני מזכיר – אנחנו מדברים על DB גדול מצד אחד ודינמי מצד שני (כלומר, יש מולו לא מעט פעילות של כתיבה). בואו נניח שהגודל שאנחנו מדברים עליו הוא סדר גודל של מס’ טרות (5-10TB).

היתרונות ברורים. זה ה- best practice. אנחנו עובדים בדיוק כפי שרצוי, לא לוקחים הנחות מקלות. אנחנו גם מקבלים זמן שחזור שמאפשר לנו לחזור ל-15-30 דקות עד לרגע שבו קרתה התקלה (ואפילו 2-3 דקות, כי אפשר להוריד את זמן גיבויי ה- transaction log כמה שרוצים. אבל מה החסרונות?

שחזור

שחזור של DB מורכב למעשה מהרבה מאד IO. ברגע שנחליט לשחזר נצטרך לשנע את קבצי הגיבוי (ה- full, ה- differential וכל ה- transaction logs מאז אותו גיבוי דיפרנציאלי) מהמקום שבו הם שמורים למכונה שלנו (פעולת שמורכבת מ-IO רשתי אל המכונה שלנו ו-IO דיסק מהמקום שבו הם מאוכסנים). חוץ מזה, נידרש גם לבצע מלא IO של כתיבות: לכתוב את כל ה- data וה- transaction log לדיסק. לפעמים אפילו יידרש עוד יותר IO, כי נצטרך לאכסן את הקבצים אולי במקום “זמני” במכונה שלנו (לפעמים זה משפר), או לפתוח איזשהו archive שבו נמצא המידע.
וכל ה- IO הזה לוקח זמן. אם ניקח DB במשקל של 10 טרה, ונניח שאנחנו אפילו מזיניחים את ה- IO הרשתי – השחזור הראשוני של ה- full backup ידרוש כתיבה של 10 טרה לדיסק. אפילו בקצב כתיבה מכובד של 1GB/sec זה עומד לקחת קרוב ל-3 שעות. בקצב כתיבה  נמוך יותר – אתם יכולים כבר לעשות את החשבון לבד.
אחרי ה- full backup אנחנו עוד צריכים להוסיף את העלות של הגיבוי הדיפרנציאלי, ואז את עלות ניגון ה-transaction logs בחזרה. בקיצור, זה לוקח לא מעט זמן (וגם הדברים שהזנחתי בחישוב יכולים להפוך למשמעותיים).

גיבוי

גיבוי של DB דורש קריאה של כל הנתונים שלו וכתיבה (של פחות נתונים, בזכות הדחיסה) למקום שאליו אנחנו מגבים. מכיוון שבד”כ גם אם ה- storage שעליו אנחנו מריצים את השרת production שלנו הוא חזק ומטורף, ה- storage שאליו אנחנו מגבים נוטה להיות משיקולי מחיר פחות טוב. אם יש לנו מזל זה פיתרון שמשלב tiering דינמי כלשהו, אבל גם אז אנחנו צפויים לקבל אפקט של כתיבה למערך storage משמעותית פחות טוב (בד”כ). כלומר ה- full backup עומד לקחת הרבה זמן.
בזמן הזה יש ירידה מסויימת בביצועים של ה-DB (בשל ה- overhead של הגיבוי) אבל בד”כ מה שהכי חשוב זה שה- transaction logs לא מתנקים בזמן הזה (אין בעייה לעשות גיבויי transaction logs במקביל, אבל ה- transaction log לא יתנקה) – מה שיכול להפוך בשלב כלשהו למגבלה משמעותית (שהולכת וגדלה ככל שזמן הגיבוי מתארך…).

הפיתרון הסטנדרטי שאפשר לחשוב עליו יכול להיות על בסיס fine-tuning לפיתרון הזה. כלומר, במקום לקחת full backup פעם ביום ניקח פעם ב-3 ימים או פעם בשבוע ואז נפחית את ה- impact של זמן הגיבוי. כדי לא להזדקק לנגן 3-7 ימים של transaction logs, ניקח גם differential backups בתדירות של 6-7 שעות ונאפשר לצמצם את זמן הניגון של הטרנזקציות.
אבל, צריך לזכור ש- differential backups הם לא אינקרמנטליים, אלא הם כוללים למעשה את כל האיזורים ב- data files שנעשה בהם שינוי מאז הגיבוי המלא האחרון. כלומר, הם גדלים ככל שעושים יותר שינויים עד שבשלב מסויים הם יהפכו ללא אפקטיביים (כי כבר יהיה עדיף לקחת full backup).
* החל מ- SQL Server 2017 יש עמודה של modified_extent_page_count  ב- sys.dm_db_file_space_usage שיכול לאפשר לנו לדעת אם משתלם לבצע גיבוי differential או לא.

 

כלומר, בשיטה הסטנדרטית אנחנו מרוויחים באופן מובהק את היתרון של היכולת שלנו להשתחזר עד לרוזולוציה שנרצה (אפשר אפילו דקה-שתיים) ואנחנו מרוויחים גם את היתרון שאנחנו יכולים לנגן את הגיבויי transaction log ולהשתחזר ל- point-in-time (ב- Full recovery model). זאת גם שיטה די סטנדרטית, שמאפשרת לנו לא מתבססת על שום דבר שהוא “מחוץ” ל- SQL Server.

כהערת אגב, אני אציין שאם אתם עובדים עם איזושהי תוכנת גיבוי/שחזור חיצונית (NetBackup למשל, או אחת המתחרות שלה) שבאה כחלק ממערך הגיבוי שבו הארגון משתמש, מאד מומלץ כשעושים בדיקות והערכות זמנים גם לבדוק מה ה- overhead של התוכנה ביחס לביצוע הפעולות הללו רק מתוך SQL Server עצמו.
לעיתים גיבוי שלא דרך תוכנת הגיבוי יהיה מהיר יותר,  בין אם בגלל ההתנהגות של תוכנת הגיבוי (למשל, המימוש של ה-VDI שהיא מפעילה כדי לאפשר ל-SQL SERVER “לכתוב” את הגיבויים לתוך ה- stream שלה שאותו היא מעבירה למקומות אחרים), או הביצועים של ה- storage שמוקצה לגיבויים בארגון (storage ייעודי, שרתי המדיה של תוכנת הגיבוי וכו’).
לעיתים גיבוי ל- storage כלשהו שיש בארגון והוא בכל זאת זול (NAS כלשהו) יהיה טוב יותר מה-storage שמוקצה באופן רגיל לגיבויים (שרתי המדיה של תוכנות הגיבוי השונות, או מערכי אכסון הגיבויים השונים שיש בשוק) – בצורה כזאת ששימוש בתוכנת הגיבוי ופיתרון הגיבוי “הסטנדרטי” בארגון לא יהיה טוב, אבל שימוש בפתרונות הגיבוי המובנים מול storage-ים אחרים שיש בארגון יעשה את העבודה, ואז ניתן לפתור את הסוגייה עם קצת סקריפטים שמחליפים למעשה את תוכנת הגיבוי (וזה ישתלם, על אף שזה חורג מה-“תקן” שיש בארגונים מסויימים, בהיבטים אחרים).

פיצול לדטאבייסים שונים

במשפט אחד: לפעמים במקום להסתבך בפתרונות מתוחכמים יותר (ואולי גם מרוכבים יותר) עדיף קצת לעגל פינות, לבחור פיתרון שהוא אולי קצת פחות יפה, כדי להקטין את הגודל של המידע. כי יכול להיות שעדיף 2 DB-ים קטנים יותר, על פני אחד גדול.

לא מדובר בשיטה בפני עצמה, אלא בדרך לאפשר לנו לשמר עבודה גם עם שיטת הגיבוי הסטנדרטית (ולשמור על היכולת לעבוד מולה לא רק “על הנייר” אלא גם בסיטואציה אמיתית) ע”י משחק בחלוקה של הנתונים שלנו לדטאבייסים שונים.
כפי שאפשר להבין מההקדמה שלי, השיטה הקלאסית עובדת מעולה ומומלץ להיצמיד אליה כל עוד היא מתאימה, ולא מונעת מאיתנו לעמוד ביעדי הגיבוי והשחזור שלנו. על דטאבייסים גדולים, כמו שראינו כשתארתי קודם– השיטה הזאת מתחילה כבר פחות לענות על הדרישות (של זמן הגיבוי וזמן השחזור).  אז רגע לפני שאנחנו עוברים לשיטות נוספות ויצירתיות יותר (שיש להן את החסרונות שלהן), שווה לבדוק אם אנחנו יכולים “להתאים את עצמנו מחדש” לשיטה הקלאסית. ואיך נעשה את זה? ע”י זה שנראה אם אפשר לפצל את ה-DB שלנו לכמה DB-ים, כל אחד מהם בגודל קטן יחסית.

לא מעט פעמים ה-DB גדל ועל הדרך מצטרפים אליו “נספחים” שונים שאולי לא בהכרח חייבים להיות בו, ויכולים להיות ב-DB נפרד משלהם ואולי להוריד את הנפח. זאת יכולה להיות טבלת לוגים ענקית של האפליקציה שנשמרת ב-DB, טבלאות גדולות של נתונים בינאריים ששומרים כי “נוח לנו שזה נשמר ב-DB ולא בקבצים ואין לנו כוח לעבוד עם File Tables”, טבלאות ארכיוניות של סכימה ישנה שפעם השתמשם ומאז כבר הסבתם את כל הנתונים בהם לסכימה החדשה אבל אתם עדיין שומרים את הסכימה שתואמת לאפליקציה הישנה “למקרה שיעלה צורך”.

בפעמים אחרות, יכול להיות שאין דברים שאפשר “לחתוך” ולהעביר בטבעיות ל-DB אחר, אבל אפשר לסמן קבוצה מסויימת של דברים שהיא מספיק בלתי תלוייה בדברים אחרים כדי להעביר אותם כיחידה ל-DB אחר, ובכך להוריד את הנפח שבשימוש ב-DB המקורי (שימו לב, מה שמעניין אותנו פה זה בעיקר הנפח שבשימוש, ולא הנפח שאתם מקצים ל-DB – אם משתמשים ב- sparse files בשחזור, אז כל השטח “הריק” לא עולה לנו בזמן שחזור, וגם בגיבוי הוא לא מגדיל את נפח קובץ הגיבוי). אפשר לעשות שימוש ב- views שעושים cross-database query כדי לשמר את השמות המקוריים ב-DB, רק שעכשיו אלה יהיו views שיפנו לאובייקט שנמצא ב-DB אחר (עדיין אפשר יהיה לעשות אליהם INSERT-ים, BULK INSERT-ים וכו’, כי זה סה”כ SELECT * FROM OtherDB.schema.Table).

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

מה אנחנו מרוויחים מזה אם סכום המידע הוא עדיין אותו סכום? אנחנו מרוויחים את ההפרדה. אם מצאתם דברים שהם מספיק בלתי תלויים כדי להפריד אותם אחד מהשני כך שהקונסיסטנטיות של הנתונים נשמרת גם אם משחזרים רק אחד מהם,  אז במקרה של תקלה נצטרך לשחזר רק אחד מהם (בד”כ, כי הסבירות לכך שהנזק שהביא אותנו לשחזור השפיע על שניהם קטנה יותר). גם בגיבויים, אנחנו מסתכלים על הזמן שלוקח לגבות רק אחד מהם (כי אח”כ בזמן שהשני מתגבה, ה- transaction log של הראשון כבר יכול להתנקות והוא מבחינתו חופשי לעבוד כרגיל). בקיצור, למרות שסכום הגודל של הנתונים הוא אותו גודל – לנהל אותם כשהם מפוצלים, וקטנים יותר, זה הרבה פעמים קל יותר.

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

פיצול ל- Filegroups וגיבוי ושחזור Filegroup-ים מתאימים בלבד

במשפט אחד:  פיצול מידע (ברמת הטבלאות או ה- partitions) ל- FILEGROUP-ים שונים (מתאים במיוחד כשאופי הנתונים מאפשר FILEGROUP-ים שהם READ ONLY) ובמידת הצורך שחזור ה- FILEGROUP שבו נשמר המידע “הפעיל” בלבד. אפשר לעשות את כל זה גם בזמן שה-DB הוא אונליין. פיצ’ר מעולה, אבל זמין רק למשתמשי SQL Server 2016 ומעלה, בגרסת האנטרפרייז בלבד.

[החלק הזה מתבסס על ההנחה שעובדים עם SQL Server 2016 ומעלה בגרסאת Enterprise. חלק מהפרוצדורות שמופיעות פה לא אפשריות ב-SKU שונה]

ידוע לכולם ש- SQL Server מאפשר לנו לייצר מספר Data Files שהמידע מתפצל בינהם. ה- Data Files הללו מחולקים ל- Filegroups, כאשר ב- default קיים FILEGROUP יחיד. ניתן לפצל טבלאות ל- Filegroups שונים, כך שטבלה אחת תישמר ב- Filegroup א’ והשנייה ב- Filegroup ב’. יותר מכך – ניתן לעשות את הפיצול גם ברמת האינדקס (אינדקסים שונים של אותה טבלה ב- filegroups שונים) ואף ברמת ה- partition (כלומר, לחלק partitions שונים של אותה טבלה או אינדקס ל- filegroups שונים).

Filegroup יכול להיות בשני מצבים: פעיל ו- Read Only. כאשר Filegroup הוא Read Only, לא ניתן לשנות את המידע ששמור בו בשום צורה.

בתרחיש שבו למשל יש לנו DB גדול שמתווסף אליו כל הזמן מידע, שנשמר בצורה מפורטשת לפי חודשים. אנחנו יכולים להעביר את החודשים שהסתיימו כבר ל- FILEGROUP של מידע לקריאה בלבד. את הגיבויים אנחנו יכולים לעשות בין ה- FILEGROUP-ים באופן בלתי תלוי (כלומר, לגבות את אלה שהם read only פעם אחת, ולגבות באופן תדיר את ה- read-write) ולשחזר אותם גם באופן בלתי תלוי, וכך למעשה להפחית את זמן הגיבוי (כי מגבים רק מידע “חם”) ואת זמן השחזור (כי בד”כ לא נדרשים לשחזר הכל, רק את מה שצריך [מה שנדפק]).

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

דוגמא בסיסית

לשם הדוגמא, נייצר DB בשם test3 שכולל שני קבצים: test3, test3_sec כאשר test3 הוא ב- Filegroup שנקרא PRIMARY (הדיפולטי) ו- test3_sec ב-FILEGROUP בשם SECONDARY. לאחר מכן, נמלא מידע בטבלאות A ו-B שנמצאות ב- PRIMARY ו-SECONDARY בהתאמה ונפוך את SECONDARY ל- Read-Only.

CREATE DATABASE [test3]

 CONTAINMENT = NONE

 ON  PRIMARY 

( NAME = N'test3', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\test3.mdf' , SIZE = 8192KB , FILEGROWTH = 65536KB )

 LOG ON 

( NAME = N'test3_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\test3_log.ldf' , SIZE = 8192KB , FILEGROWTH = 65536KB )

GO

ALTER DATABASE [test3] SET COMPATIBILITY_LEVEL = 130

IF NOT EXISTS (SELECT name FROM sys.filegroups WHERE is_default=1 AND name = N'PRIMARY') ALTER DATABASE [test3] MODIFY FILEGROUP [PRIMARY] DEFAULT

GO

ALTER DATABASE [test3] ADD FILEGROUP [SECONDARY]

GO

ALTER DATABASE [test3] ADD FILE ( NAME = N'test_sec', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\test_sec.ndf' , SIZE = 8192KB , FILEGROWTH = 65536KB ) TO FILEGROUP [SECONDARY]

GO

USE [test3]

CREATE TABLE [dbo].[A](

    [ID] [int] NOT NULL

) ON [PRIMARY]

GO

CREATE TABLE [dbo].[B](

    [ID] [int] NOT NULL

) ON [SECONDARY]

GO

USE [test3]

INSERT INTO A(ID) VALUES (1), (2), (3), (4), (5), (6)

INSERT INTO B(ID) VALUES (1), (2), (3), (4), (5), (6)

GO

USE [test3]

GO

declare @readonly bit

SELECT @readonly=convert(bit, (status & 0x08)) FROM sysfilegroups WHERE groupname=N'SECONDARY'

if(@readonly=0)

    ALTER DATABASE [test3] MODIFY FILEGROUP [SECONDARY] READONLY

GO

USE [master]

GO

declare @readonly bit

SELECT @readonly=convert(bit, (status & 0x08)) FROM sysfilegroups WHERE groupname=N'SECONDARY'

if(@readonly=0)

    ALTER DATABASE [test3] MODIFY FILEGROUP [SECONDARY] READONLY

GO

עכשיו, אחרי שיצרנו את המידע בשביל הבדיקה, ניקח גיבוי של PRIMARY בלבד:

BACKUP DATABASE [test3] FILEGROUP = N'PRIMARY' TO  DISK = N'c:\tmp\bak\primary.bak' WITH NOFORMAT, NOINIT,  NAME = N'test3-Full Database Backup', SKIP, NOREWIND, NOUNLOAD,  STATS = 10

ועכשיו נרוקן את טבלת A שעל ה-primary:

truncate table [test3].[dbo].[A]

לאחר שרוקנו (אוי, שיט!) נראה שאפשר לשחזר אותה רק מהגיבוי של ה- FILEGROUP של ה- primary שעשינו קודם. קודם כל נצטרך לקחת גיבוי של ה- transaction log ולהגדיר שהוא יתבצע עם NORECOVERY.  זה יכניס את כל ה-FILEGROUPS ל-state של RECOVERY. בנקודה הזאת, נשחזר את ה-FILEGROUP שלנו מהגיבוי:

USE [master]

BACKUP LOG [test3] TO  DISK = N'c:\tmp\bak\l1.trn' WITH NORECOVERY

GO

USE [master]

RESTORE DATABASE [test3] FILEGROUP = N'PRIMARY' FROM  DISK = N'C:\tmp\bak\primary.bak' WITH RECOVERY, REPLACE

GO

וזהו – שחזרנו את הטבלה A, בלי שהיינו צריכים לגבות בכלל (!) ובטח שלא לשחזר את ה- FILEGROUP שבו נמצאת B.

כמובן שיש עוד הרבה תרחישים שאפשר לעשות (עם יותר מ- filegroup אחד, לשחזר קובץ בודד,  שילובים שונים בין Read-Only ל- Read-Write file groups, דברים שאפשר לעשות רק כשה- recovery model הוא FULL ודברים שאפשר לעשות גם ב- Simple. למידע נוסף, אני ממליץ בחום לקרוא את התיעוד בנושא, במיוחד אם יש לכם מחשבות ליישם את זה אצלכם:

היתרונות של השיטה ברורים – במידה שעולם הנתונים שבו מתעסקים מאפשר להגדיר נתונים חמים וקרים (שיכולים להיות אפילו באותה טבלה, אבל ב- partitions נפרדים), ולהפריד אותם ל-FILEGROUPS שונים כאשר הנתונים הקרים הם לקריאה בלבד (ואז גם מקטינים את הסיכוי שייעשו מולן טעויות) – אנחנו מאפשרים לחסוך בכמות החומר שאנחנו מגבים, ולהקטין שממעותית את זמן השחזור (כי נצטרך לכתוב פחות DATA. כלומר אנחנו מקבלים פה עוד צורה של פיצול, כאשר בניגוד להצעה הקודמת – אנחנו לא נדרשים להפרדה בין DB-ים אלא יכולים לעשות את ההפרדה לפי אובייקטים באותו ה-DB (כאשר הנתונים מסודרים בצורה כזאת שזה אפשרי).

מה הקאצ’?

  • הנתונים צריכים להיות במבנה מתאים כדי שזה יהיה רלוונטי, והטבלאות צריכות להיות מפורטשות לפי המבנה הזה (אם יש DB עצום שכל הנתונים בו מתעדכנים כל הזמן, כל הטבלאות קשורות זה לזה, ואין משמעות לשחזור “חלקי” [כי זה יפגום בנכונות המידע] – אז זה לא רלוונטי)
  • רלוונטי ונוח בעיקר ב- Full Recovery Model (על אף שאפשר לעשות בזה שימוש גם ב- Simple Recovery Model – זה דורש Partial Backup שכולל אולי filegroups שאנחנו גם לא צריכים, מה שהופך אותו לכבד יותר)
  • דורש ניהול מתאים של ה- backup-ים, כאשר הניהול הוא קצת יותר מורכב מניהול של BACKUP-ים בתרחיש הרגיל. במיוחד כשב-DB יש מעבר של טבלאות ו- partitions בין FILEGROUP-ים, ואז כשרוצים להשתחזר לגרסא שהיא אולי לא הכי עדכנית, צריכים לוודא שמחזיקים ביד את הגיבויים המתאימים, ויודעים מה בדיוק רוצים לשחזר – איזה גיבוי מלא של ה-FILEGROUP ואיזה transaction logs אחריו.
  • בתצורה שהראיתי (עם יכולות אונליין וכו’) – רלוונטי ל- SQL Server  עדכני בגרסת אנטרפרייז

Application-Consistent Snapshot שמתבסס על יכולות snapshot של מערכת האכסון (וסביבת וירטואליזציה)

במשפט אחד: להתבסס על יכולות ה- snapshot של מערך האכסון, שמאפשרות לשמור את ה- state בדיסקים ברגע נתון ללא ביצוע פעולות IO (בד”כ metadata only), תוך שילוב עם יכולות snapshot של הסביבה הוירטואלית ורכיב שמותקן על השרת עצמו – שמאפשר לוודא שה- snapshot נלקח בצורה שהיא application consistent. נשמע כמו הפיתרון האידיאלי, אבל ב-DB-ים עמוסים הוא כנראה פשוט לא יעבוד.

Snapshot-ים וחברים

אם אתם עובדים בסביבת אנטרפרייז, סביר מאד להניח שה-DB הגדול שבאחריותכם לא רץ על דיסקים מקומיים. בסבירות גבוהה אתם רצים על שרת פיזי שמחובר לאיזשהו מערך אכסון מבוסס SAN, או לחלופין, רצים על מכונה וירטואלית שהדיסקים שלה מאוכסנים על מערך אכסון כלשהו (SAN או NFS).
מרבית פתרונות האכסון (אם לא כולם) תומכים באופן מובנה ב- snapshot-ים ברמת מערך האכסון. 
ברמה הפשטנית ביותר, זה אומר שאם אנחנו לוקחים snapshot של שטח מסויים, התוכנה של מערך האכסון זוכרת שה- snap שהשם שלו הוא XYZ קיים. בד”כ היא לא צריכה לעשות שום פעולת IO משמעותית בעת לקיחת ה- snapshot, חוץ מלרשום את עצם קיומו. בכל שינוי (כתיבה מכל סוג) שמתבצעת אח”כ על אותם בלוקים ששייכים לאותו ה- snapshot, מתבצעת ההפרדה – הבלוקים המקוריים נשארים as is, השינוי מתבצע על בלוקים אחרים, ו- metadata שמשייך כל בלוק ל-snapshots הרלוונטיים מתוחזק.

ככלל, הנקודות הבאות מאפיינות snapshot:

  • הלקיחה שלו זולה מאד במשאבים ובזמן
  • השחזור ל- snapshot זול מאד במשאבים ובזמן
  • הגודל של snapshot הוא למעשה גודל השינויים שבוצעו מאז שהוא נלקח (כלומר בזמן הלקיחה הגודל שלו הוא בערך 0, וככל שהוא ישן יותר, אז נצברו יותר שינויים מאז, ואז יותר מידע נשמר למעשה רק כדי להחזיק את ה- snapshot- כך שהגודל שלו משמעותי יותר).

מעבר ל-snapshots שבהם תומך מערך האכסון, לא מעט שרתי DB בעולם מתבססים ורצים מעל תשתית וירטואלית.
בד”כ במצב כזה יש כמה שחקנים עיקריים: המכונה הוירטואלית (במקרה שלנו, כזאת שמריצה Windows Server 2012 R2 ומעלה ככה”נ, אם עובדים עם גרסאות עדכניות של SQL Server), תשתית הוירטואליזציה (HyperV/ESXi) ומערך האכסון (EMC/NetApp/Infinidat וכו’).
אמרנו כבר שהתשתית אכסון כנראה תומכת ב- snapshots, אבל גם תשתית הוירטואליזציה (vSphere / HyperV) תומכת גם היא ב- snapshots שיכולים לעבוד ללא תלות במערך האכסון. עם זאת, לכל היצרנים בתחום האכסון יש אינטראקציה טובה בד”כ עם יצרניות הוירטואליזציה (כי אלה דברים שהרבה פעמים הולכים ביחד) באמצעות API-ים שמאפשרים לתשתית הוירטואליזציה להיות מודעת למערך האכסון, ולהיפך.
הפתרונות הללו בד”כ מאפשרים לשלב את העולמות – לקחת snapshot באמצעות תשתית הוירטואליזציה, שעושה שימוש גם ביכולות ה- snapshot של ה- storage כדי לייעל את התהליך, ולהקטין את ה- overhead המתמשך (כי ברמת ה-storage המימוש של הסנאפשוטים טוב יותר מהמימוש של תשתית הוירטואליזציה, ובד”כ משתלב עם יכולות אחרות שהפיתרון storage מציע).

עד עכשיו כל מה שאמרנו למעשה היה מנותק לחלוטין מהמכונה הוירטואלית עצמה, וה- SQL Server שרץ עליה, ולכאורה הם לא מודעים לתהליך שקורה. עם זאת, כדי לאפשר את היתרון של גיבוי קונסיסטנטי (כי בזמן לקיחת ה- snapshot יש נתונים בזיכרון, דברים שמתבצעים בזיכרון, ולהסתכל רק על ה- state של הדיסק עלול להיות בעייתי עבור אפליקציות מסויימות), ל- Windows קיימת תשתית שנקראת Volume Shadow Copy Service, שמאפשרת לו להיות מודע לכך שנלקח snapshot כלשהו ולרכיבים שונים שמותקנים במכונה להשתתף בתהליך כדי לספק snapshot קונסיסטנטי ברמת האפליקציה.

אז בואו נסכם את הרכיבים השונים שדיברנו עליהם, שהשילוב שלהם הוא הבסיס למרבית מערכות הגיבוי לשרתים וירטואליים:

  • יכולות ה- snapshot של מערך האכסון
  • יכולת ה- snapshot-ים של הסביבה הוירטואלית
  • התממשקות של הוירטואליזציה לאכסון באמצעות API משותף
    למרות שתאורטית אין הכרח שמערך ה-storage ומערך הוירטואליזציה יהיו מודעים זה לזה, בפועל, קיימים ממשקים שמאפשרים להם כן להיות מודעים זה לזה ולחשוף API-ים משותפים, מה שמאפשר שיפור ביצועים והוספה של לא מעט יכולות חשובות. הנקודה שמעניינת אותנו בהקשר הזה, היא יכולות ה- snapshot של מערך האכסון שנחשפות החוצה ומשתלבות עם יכולות ה- snapshot של סביבת הוירטואליזציה.
  • התממשקות של הסביבה הוירטואלית, ותהליך לקיחת ה- snapshot שלה עם VSS (שזה ה- Volume Shadow Copy Service שרץ ב- Windows ותפקידו לאפשר את התהליך של “יצירת snapshot” על מכונת windows
    • SQL Writer Service – הוא הרכיב שחושף את יכולות הגיבוי של SQL Server בממשק שתואם ל-VSS ומאפשר למעשה ליצרני תוכנות הגיבוי השונות להביא גיבוי שהוא SQL Server-aware. ה- service הזה למעשה חושף ליצרני תוכנות הגיבוי שעובדים עם VSS את היכולת לעשות גיבוי מלא ודיפרנציאלי (אך לא גיבוי transaction log) דרך ה-API וצורת העבודה של VSS (אם מישהו רוצה לקרוא יותר לעומק איך יצרני תוכנות הגיבוי משתמשים ב- VSS וב- SQL Writer Service, ממליץ לקרוא את המאמר המעמיק יותר הזה).
  • שימוש ב-VDI (Virtual Device Interface) שזה למעשה “כוננים וירטואליים” ש- SQL Server רואה וכותב אליהם, כאשר המשמעות של מה זה אומר למעשה כתיבה אליהם מסופקת באמצעות רכיב צד שלישי (agent כלשהו) שמותקן במכונה

שימוש לטובת גיבוי DB

השילוב של כל הנקודות שכתבתי קודם הוליד שורה של פתרונות של מספר חברות, שהקונספט שלהן פשוט: הן מאפשרות לקחת snapshot מלא של המכונה, שהוא application-awere (כלומר, התהליך של הלקיחה לא “מנותק” מה- SQL Server שרץ על המכונה, אלא נעשה תוך כדי שהוא מודע לתהליך ולמעשה מאפשר יצירת גיבוי סדור) ולאחר מכן שחזור שלו. אני רוצה לשמור על ההסבר כללי, ולא להיכנס למוצרים ספיציפיים, אבל אני אזכיר בכל זאת כמה דוגמאות: מוצרים של Veam, ה- applience של vSphere Data Protection, ה- SMVI של NetApp ו- SMSQL וכו’.

למשל, בסביבת וירטואליזציה מבוססת NetApp, אז ניתן להשתמש ב- SMVI (SnapManager for Virtual Infrastructure ) כדי לקחת snap מלא של המכונה או להשתמש ב- SMSQL (Snap Manager for Microsoft SQL Server) כדי לקחת גיבויים של DB-ים ספיציפיים שמתבססים למעשה על לקיחת snapshot ברמת ה- NetApp.

למשפחת הפתרונות הזאת יש על הנייר מספר יתרונות משמעותיים בהיבט לקיחת הגיבוי ופיצ’רים נלווים:

  • זמן לקיחת הגיבוי קצר משמעותית בד”כ, בנוסף, גם הגודל של הגיבוי שנשמר בפועל הוא קטן יותר ומתבסס רק על גודל השינויים שהתבצעו ב-DB.
  • הרבה פעמים משולבים לא מעט יכולות חמודות שמתבססות על פיצ’רים שונים של מערך האכסון – למשל היכולת של באופן שקוף לחבר את ה-DB-ים לאחר הגיבוי לשרת אחר לטובת הרצת DBCC CHECKDB (לא באמצעות שחזור שלהם שם, אלא חיבור למעשה לבלוקים ממש ששמורים בדיסק), יכולות DR וכו’
  • בסביבת וירטואליזציה הרבה פעמים הדברים הללו משתלבים באופן טבעי עם הגיבוי הכללי של הסביבה הוירטואלית
  • מרוויחים RPO (כמות המידע שמאבדים) טוב, כי אפשר לשלב את זה עם גיבויי transaction log סדירים.

בהקשרי השחזור, הדבר כבר תלוי בפיתרון הספיציפי.  בד”כ ה- snapshot  מתבצע ביחידות של volume/LUN – בעוד שאנחנו רוצים בד”כ לשחזר דטאבייס ספיציפי. הרבה פעמים זה ממומש באמצעות “mount and copy”, כלומר טעינה של ה- volume בגרסא כפי שהיא שמורה של ה- snapshot “בצד” ואז ביצוע הרבה data movements והעתקות ממנו. וזה כבר יכול להיות גם מאד יקר כשמדובר בכמויות נתונים גדולות. אולי יותר מהיר משחזור גיבוי שמגיע ממקום אחר (יתרון הלוקאליות, cache, tiering , ים אופטימיזציות שיצרני ה- storage עושים וכו’) – אבל עדיין מתייקר עם הזמן.

אבל זה השחזור הוא לא הבעייה האמיתית שיכולה להפיל את הפיתרון הזה.
הבעייה המהותית ביותר שאפשר להיתקל בה, ומשפיעה על ההיתכנות של הפיתרון, בהקשר הזה נוגעת דווקא לביצוע התהליך על שרתי high-end עמוסים מאד.  אם התרחיש הוא רק תרחיש של DB גדול, שלא מתבצעת מולו הרבה פעילות אונליינית (טעינת מידע, תשאול  מידע, עדכונים בדיסק וכו’) אז פיתרון כזה יכול להתאים. אולם, תהליך לקיחת snapshot על מכונת high-end שעושה גם הרבה פעולות ועובדים מולה הרבה משתמשים – עלול פשוט לא לעבוד.
תופעות כמו תהליך לקיחת snapshot ארוך (שפשוט לא מסתיים), איטיות קשה ובלתי נסבלת בשימוש במכונה תוך כדי עד כדי תקיעה מוחלטת שלה בזמן לקיחת ה-snapshot (שנובעת מכך שכדי לאפשר את לקיחת ה- snapshot,  מתבצעת האטה מכוונת ואף הקפאה מלאה של גישה ל-RAM ופעולות CPU) עלולות בהחלט לקרות, וכמובן להשפיע על המשתמשים לטובת תהליך שבסוף לא ממש מצליח. מה שהופך את כל מה שכתבנו קודם ללא רלוונטי ופוסל את הפיתרון הזה לחלוטין במצבים מסויימים.

מילה לגבי SQL Snapshots

גם SQL Server בעצמו כולל יכולת לייצר snapshots וגם לשחזר ל-snapshot שנוצר.  עם זאת, זה לא פיתרון מתאים לגיבויים.  לא רק בגלל ה-overhead שזה יוצר באופן קבוע על הביצועים של ה-DB בכתיבות – אלא בעיקר בגלל המגבלות בשחזור: אפשר לשחזר ל-snapshot רק כאשר יש snapshot בודד. אין אפשרות להתחרט, אין אפשרות לשמור כמה ועתקים ולבחור בינהם למה לשחזר, אין אפשרות להוציא את ה- snapshot “מחוץ לגבולות המכונה” למקרה של תקלה במכונה עצמה  (ובטח שלא מחוץ לגבולות הארגון, שלא באמצעות גיבוי בצורה אחרת ממה שמתואר) ויש עוד כמה וכמה מגבלות.
כלומר, מדובר באפשרות שקיימת, יש לה גם מס’ תרחישי שימוש (לא רב במיוחד), אבל זה בטח ובטח לא פיתרון גיבוי.

Crash-Consistent Snapshot ברמת מערך האכסון (למכונה וירטואלית ולשרתים פיזיים)

במשפט אחד: לגבות בדקה ולהשתחזר בדקה. זה במחיר של איבוד פרק זמן ארוך יותר של מידע בזמן השחזור ותהליך קצת יותר טריקי

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

  • הוא לא בהכרח עובד (במיוחד לא על מכונה עמוסה)
  • השחזור אולי יותר מהיר, אבל עדיין גדל באיזשהו יחס עם הגידול במידע

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

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

התשובה פשוטה – במקום לקחת את ה- snapshot ברמת הסביבת הוירטואליזציה, תוך כדי המעורבות של מערכת ההפעלה וה-VSS – ניקח רק snapshot של מערך האכסון.  כאמור, זאת פעולת metadata בלבד ובד”כ מסתיימת תוך שניות. בצורה דומה, השחזור שלנו יורכב גם הוא משחזור ה- snapshot של מערך האכסון, in-place (כלומר, להחזיר את המצב למצב שהיה ב- snapshot, ולא להרים את המידע מה- snapshot בצד ולהעתיק רק את מה שאנחנו צריכים).

איך זה עובד – מבט מלמעלה

  • נגדיר תהליך מחזורי ברמת מערך האכסון שלוקח פעם במס’ שעות snapshot של כל ה-volumes/LUN’s הרלוונטיים
  • נגדיר retention ל- snapshot-ים. חשוב לזכור שגודל ה- snapshot מתחיל מ-0 וגדל בהתמדה ככל שעובד הזמן ויותר שינויים נעשו למידע, וצריך לקחת את זה בחשבון
  • בעת שחזור – נעשה restore ל-snapshot. נשים לב שאנחנו לא מחזירים פה DB, אלא מחזירים פה את כל מה שכלול ב- snapshot למצבו הקודם.  לאחר מכן, נריץ תהליכים שהמטרה שלהם זה לשחזר (ממקורות אחרים) את המידע שנוצר מאז ה- snapshot הקודם.

מה מגבים?

חשוב להבין שה-storage שלנו לא יודע מה זה DB. הוא לא מכיר Data Files, הוא לא מכיר Transaction Logs.
הוא מכיר יחידה הרבה יותר כללית מזה. מה בדיוק היחידה הזאת ומה היא כוללת, זה בהתאם לתצורה שבה אנחנו עובדים:

  • זה יכול להיות  LUN שבו כתוב data מסוגים שונים שהוא לא יודע מה הוא ומה בדיוק הוא מכיל. ה- data הזה יכול להיות ה- transaction logs שלנו, קבצים שלנו, הגדרות שלנו אם אנחנו מדברים על תרחיש שבו יש לנו שרת פיזי שמדובר ל-SAN ומשתמש ב-LUN מסויים.
  • זה יכול להיות LUN שבו מאוכסנים vmdk-ים/vhd-ים של מכונות וירטואליות שמרוצות ממנו
  • זה יכול להיות volume שחשוף ב- NFS עם כל מיני קבצים, שבהם יש vmdk-ים של המכונה הוירטואלית שלנו (בתרחיש של וירטואליזציה מעל NFS)

בכל אופן, כשאנחנו לוקחים snapshot אנחנו לוקחים snapshot של כל ה- data שנמצא על אותו Volume/LUN. וכשאנחנו “נשתחזר” אנחנו למעשה נעשה restore ל-snapshot הזה ונדרוס את כל ה- data, בגרסא ישנה יותר שלו (של ה- snap הקודם).

כלומר, אם אנחנו רוצים להשיג הפרדה, נצטרך לעשות אותה על בסיס היחידות הללו. לכל הפחות, נרצה להבטיח שאם אנחנו משחזרים, אנחנו נמצאים ומוכלים רק ב- scope של המכונה שלנו. למשל, בסביבת וירטואליזציה מבוססת NFS עם volume גדול שחשוף בתור datastore ושבו נמצאים vmdk-ים של הרבה מכונות, אם נעשה restore ל-snapshot נחזיר את כל ה- vmdk-ים הללו למצב הקודם שלהם – שזה ממש לא מה שאנחנו רוצים. אנחנו גם לא רוצים לעשות data movement בשעת השחזור, ולכן הדרך שלנו תהיה להפריד מראש את המכונה שלנו ל- volume נפרד.

אם נרצה להשיג רמה גבוהה יותר של ההפרדה, נוכל אולי להפריד גם דטאבייסים שונים ל-volumes שונים (ל-VMDK-ים שונים), או ל-LUN-ים שונים ולייצר policy של לקיחת snapshots שמופרד בהתאם לחלוקה שאנחנו מכירים של דטאבייסים ל- LUN-ים.

חשוב לזכור שבמידה שה- data files וה- transaction log מפוצלים על פני volumes שונים או LUN-ים שונים, חשוב לוודא שה-snap שלהם נלקח ב- storage ביחד בצורה אטומית,  אחרת ה- DBעלול להפוך ל- corrupted בזמן השחזור.

באיזה תדירות מגבים ולכמה זמן שומרים אחורה?

במרבית מפתרונות ה- storage נהיה מושפעים מהמגבלות הבאות:

  • מגבלת האכסון והגודל של ה- snapshots(שהוא לא אכסון זול שמיועד לגיבויים, אלא בד”כ אכסון high-end יקר יותר, שהוא חלק ממערך האכסון שמשמש אותנו ל-data עצמו) כאשר הגודל של ה- snapshot הוותיק ביותר ייקבע מכמה רחוק אחורה נשמור, וכמה שינויים אנחנו עושים
  • מגבלה על מספר ה- snapshot-ים שקיימת בחלק ממערכי האכסון

שיטת הגיבוי הזאת מותאמת יותר להתאוששות מהירה מאד מאסונות שמגלים מהר (טעויות אנוש, למשל, כמו truncate table לטבלה הלא נכונה…) ופחות לשמירה היסטורית ארוכה של מידע. לכן, בד”כ הכיוון שאליו אני ממליץ ללכת הוא לקחת snapshot בתדירות כמה שיותר גבוהה שאפשר, ולשמור אחורה לפרק זמן לא ארוך מדי. למשל, בתור מספרים לדוגמא – snapshot כל 4 שעות (אם יודעים שאפשר להשלים באמצעים אחרים את המידע שהיה ב-4 שעות הללו) ושמירה אחורה של שבוע.
צריך לזכור גם שאחרי שעושים את ה- restore, אין לנו אפשרות לנגן transaction logs קדימה – ולכן התדירות של לקיחת ה- snapshots היא אחד הגורמים העיקריים שקובע כמה מידע נאבד בעת שחזור (גם לגרסא הכי אחרונה).

שימו לב שהגיבויים במקרה הזה הם snapshots, שנשמרים בתוך אותו מערך אכסון. כלומר אם ה- storage עצמו נפל – הגיבויים הללו לא יצילו אתכם. עם זאת, מרבית ה- storage-ים כיום תומכים בתצורות שונות של יציבות – כולל הרחקה לאתרי DR וסנכרון מולם, פיצול למספר “ראשים” שעובדים במקביל וכו’, כך שזאת לא תקלה כזאת שכיחה. אם אתם נדרשים לגיבוי off-site, כמובן שצריך לקחת את זה בחשבון כשקובעים את תוכנית הגיבוי.

איך הולך שחזור?

השחזור שלנו יילך בד”כ בצורה הבאה:

  1. ניכנס ל-downtime (נוריד אפליקציות)
  2. נתכונן לשחזור: נעצור AlwaysOn, נוריד את השרת DB (אם הוא למשל ב-VMDK שאנחנו עומדים להחזיר אחורה) או לחלופין נעשה detach ל-DB-ים הרלוונטיים
  3. נעשה restore ל-snapshot של ה- volume הרלוונטי, בד”כ ל-snapshot האחרון
  4. נעלה את השרת, או נעשה attach לקבצים
  5. נעשה בדיקות sanity, נעלה מערכות ונחזיר דברים שצריך להחזיר (AlwaysOn ידרוש הוספה מחדש של ה-DB)

אחד היתרונות שיכולים להיות לנו הוא שאנחנו יכולים לעשות בד”כ שימוש בפיצ’רים חמודים שונים שמציע המערך אכסון, למשל לעשות mount של ה- snapshot הספיציפי “בצד” ולבדוק שבאמת עובד לנו ואנחנו באמת מצליחים לעבוד מולו (למשל בתרחיש וירטואליזציה, לעשות mount של ה- snapshot, לחשוף אותו בתור datastore ולהוסיף ממנו את ה-VM ל- inventory, להפעיל ולראות מה קורה. או לחלופין, בתרחיש של SAN לשרת פיזי, לעשות mount לכוננים שמכילים את הקבצים תחת אות כונן אחרת ולראות שמצליחים לעשות להם attach).

האם מובטח שזה יעבוד?

התהליך הזה הוא לא תהליך שגרתי ולכן יכולה לעלות השאלה – למה שזה יעבוד?

התשובה היא “באותה צורה שזה היה עובד אם הייתה הפסקת חשמל וה-UPS-ים לא היו עובדים. לא אידיאלי, אבל המוצרים הללו [רק SQL Server בתרחיש של שרת פיזי, ESXi ו- SQL Server בתרחיש של שרת וירטואלי] נבדקים היטב כדי לתמוך גם בתרחישים הללו, כי אלה דברים שיכולים לקרות.

ברמת ה- SQL Server, אם ה-snapshot כולל גם את ה- transaction log וגם את ה- data files שמייצגים את אותה נקודה אטומית בזמן שבה הוא נלקח, בזכות ה- Write ahead logging שזה אחד הדברים הבסיסיים בכל DB שצריך להבטיח את עקרונות ה-ACID, הכל אמור לעבוד. ה-DB יעלה, ויעשה recovery כמו שקורה בכל עלייה מאפס. הוא יעשה Analysys, ינגן קדימה (REDO) את כל המידע ויעשה UNDO לטרנזקציות שלא הושלמו.

גם אם זה נראה לכם אולי לא כ”כ בטוח – אחד היתרונות הוא שכמעט תמיד אתם יכולים לדעת מראש האם זה עומד לעבוד או לא עוד טרם התחלתם את התהליך (עוד לפני שהתחלתם את התהליך, כמו שכתבתי קודם, בזכות יכולות שיש בהרבה מערכי אכסון ל- Mount ל-snap במקביל) והעלות מבחינת זמן של לעשות restore, ולזוז קדימה ואחורה היא מינורית.

מה החסרונות ואיך מתמודדים איתם?

  • שחזור בגרנולריות גבוהה ממה שרוצים: כאמור, כשמשחזרים אנחנו לא משחזרים DB, אלא קבוצה של דברים שהיו באותו ה- volume. אנחנו כן יכולים לנסות לפצל את זה, אבל יש עוד כל מיני שיקולים שיכולים למנוע מאיתנו (מסיבות טובות) להגיע לרמת הגרנולריות שאנחנו רוצים (שחזור של DB) וצריך להיות מודעים לזה.
    עם זאת, בד”כ כשיש מספר דטאבייסים על אותו שרת DB, אחד מהם הוא בד”כ הגדול והמשמעותי שבגינו אתם קוראים את הפוסט הזה ומחפשים פתרונות יצירתיים – והשאר קטנים יותר. לא פעם החיסכון זמן בשחזור של ה-DB הגדול מצדיק את ה-extra עבודה שיש אח”כ – לשחזר את ה-DB-ים האחרים שהשתחזרו ללא סיבה ביחד איתו מפתרונות גיבוי אחרים שמגבים אותם (סטנדרטיים יותר).
    כמובן, שחשוב לוודא שבסביבה וירטואלית נשמרת לפחות גרנולריות של VM – שלא “נשחזר” גם VM-ים שבכלל לא קשורים אלנו ל-state קודם.
  • איבוד מידע: בניגוד לשיטות קודמות שאנחנו יכולים לשחזר ממש עד לנקודה העדכנית ביותר שלקחנו בה גיבוי transaction log, פה אין לנו את האפשרות הזאת. צריך להיות מודעים לזה, כאשר השיטה הזאת מתאימה מין הסתם באחד מהתרחישים הבאים: או שלא אכפת לאבד מידע מטווח מסויים, או שיודעים “לנגן” את המידע ממקור אחר. למשל, מערכת שמתבססת על טעינת נתונים מקבצים – ואז אפשר להריץ טעינה חוזרת של אותם הקבצים ל-DB הגדול (כאשר ההנחה היא שה-DB התפעולי שמשמש כדי לעקוב אחרי מה נטען ומה טופל מגובה בנפרד באמצעי שמאפשר להגיע באמת לרמה העדכנית ביותר).
  • פיתרון לא סטנדרטי: הפיתרון הזה הוא לא סטנדרטי. הוא לא חלק מתוכנת גיבויי ולא פעם זה דברים שאנשי ה- storage וה-system יידרשו להיות מעורבים בו בזמן התקלה (כי נדרשת עבודה מול ה- storage עצמו,  ולפעמים גם טריקים ברמת ה- system). זה אומר שבד”כ זה פיתרון שייעשה בו שימוש לדברים שבאמת הפתרונות הסטנדרטיים פשוט לא מספקים עבורם מענה טוב.
    בכל אופן, כמו כל דבר לא סטנדרטי, חשוב לוודא ששומרים על כשירות. שיודעים שה-snapshots אכן קורים, ושיודעים במידת הצורך שלב אחרי שלב איך משתחזרים (והכי טוב – אם הכל מובנה גם בסקריפטים מוכנים מראש, כי תקלה זה לא הזמן האידיאלי לאילתורים).
  • ה-snap נשמר למעשה בתוך המכונת storage עצמה ולא במדיה חיצונית (אלא אם כן מערך ה-storage הוא streched על פני אתר מרוחק, או עושה שימוש בפתרונות שונים כדי לשנע את ה- snap-ים החוצה).
  • לא ניתן (או לכל הפחות, משמעותית יותר יקר) לשמור גיבויים לטווח ארוך

בכל אופן, אף פעם לא הייתי בוחר בשיטת הגיבוי הזאת בלבד. אם בוחרים בה, תמיד הייתי מלווה אותה גם בשיטת גיבוי סטנדרטית יותר (כמו הראשונה שתארתי בפוסט הזה) כדי לענות על החסרונות, ולתת fallback במקרה של תקלה אמיתית.

 

סיכום

לשם הנוחות, הנה סיכום קצר של השיטות שראינו בטבלת השוואה:

image

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

בכל אופן, לא משנה באיזה שיטה אתם משתמשים, תוודאו שאתם מקפידים על כללי הזהב הבאים:

  • תוודאו שיש לכם תוכנית גיבוי ושחזור
  • תוודאו שכל האנשים שעלולים להידרש לבצע את השחזור יודעים איך לעשות את זה, בהתאם לתוכנית שבחרתם
  • הקפידו לבדוק באופן תקופתי את התוכנית שלכם, ובפרט את הפרמטרים הבאים:
    • האם היא עובדת? האם אתם מצליחים לגבות ולהשתחזר? אולי זה נשמע טריוויאלי, אבל הקפידו לעשות את זה. העולם מלא בסיפורים מפורסמים יותר ופחות של אנשים שגילו ברגע האמת שאין להם גיבוי. אם אתם זקוקים לסיפור כזה למוטיבציה, תלמדו את הלקח של GitLab שגילו ברגע האמת שהגיבויים שלהם לא באמת עבדו.
    • האם היא עונה על הדרישות מבחינת זמני ריצה, וכמות איבוד החומר (אין טעם בתוכנית מפוארת אם לוקח לכם 20 שעות להשתחזר, כשמצופה מכם להשתחזר תוך 4, או בתוכנית שאתם מאבדים במסגרתה מידע שנכנס בדקות האחרונות, אם הוא קריטי לארגון)?
    • האם התוכנית שלכם עונה לדרישות הכלליות של הארגון (שמירה במקום חיצוני, מענה לתקלות כאלה ואחרות)?
  • תוודאו שאתם יודעים מה לעשות אחרי שחזור (טיפול בשאריות מידע שלא השתחזרו, הוספה מחדש ל-AG של ה- AlwaysOn שלכם)?

 

 

הרבה הצלחה, ושלא נדע מצרות!

הוסף תגובה
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *