איך משפרים מהירות של טרנזאקציות (הפתרון המסוכן)

בפוסט הקודם הסברתי על התהליך שקורה בזמן שאנחנו מבצעים טרנזאקציות. דיברנו על מנגנון ה-isolation של אורקל ועל מנגנון ה-durability. בנוסף, תיארנו בעיה: במערכות שבהן יש הרבה מאוד טרנזאקציות קצרות שרצות במקביל נוצר עומס על קובץ ה-redo log.

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

מבחינת ה-wait events נוכל לראות לפעמים המתנה לכתיבה עצמה (לפני ה-commit) אבל זו יכולה להיות גם המתנה ל-commit עצמו. ההמתנה הנפוצה ביותר שנראה שקשורה לנושא זה log file sync – כלומר המתנה לסנכרון קובץ ה-redo log וה-redo log buffer.

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

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

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

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

COMMIT_WRITE

אז במה מדובר?

במידה ואנחנו נמצאים במערכת שבה אובדן של טרנזאקציות בודדות זה לא ביג דיל (ואני מדבר על טרנזאקציה מלאה, כן?  הכול או כלום, זוכרים?) אז יש לנו את האפשרות לבוא ולהגיד לאורקל שאנחנו מוותרים על ההגנה המלאה של מנגנון ה-redo. במצב הזה אנחנו נותנים לו לתזמן את הכתיבה ל-redo ממש כמו שהוא מתזמן כתיבה ל-database files. מה שיקרה בפועל זה יכול להיווצר מצב שבו בקריסה (כולל shutdown abort) יהיה לנו אובדן מלא של טרנזאקציות בודדות למרות שבוצע להן commit.

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

הפרמטר יודע לקבל 4 פרמטרים משתי קבוצות (וקומבינציות ביניהן):

  • Immediate– כאשר פקודת commit, הפקודה תירשם מידית לקובץ ה-redo log. זו ההתנהגות בברירת המחדל.
  • Batch– כאשר מבצעים פקודת commit, הפקודה תיצבר בזיכרון עד שתהיה מסה לכתיבה רציפה. פתרון זה טוב כאשר יש לנו הרבה פקודות בודדות שרצות אחת אחרי השנייה – לדוגמה אלפי insert-ים בודדים.
  • Wait– כאשר מבצעים פקודת Commit הפקודה תמתין עד שהמידע ירשם פיזית לדיסק (לקבצי ה-redo log) ולא תחזיר את השליטה למשתמש עד שזה יסיים. זוהי ההתנהגות בברירת המחדל.
  • NoWait– כאשר מבצעים פקודת Commit הפקודה תחזור מידית לשליטת המשתמש ללא קשר לכתיבת המידע.

כמובן שיש קומבינציות:

  • Immediate wait– ברירת המחדל, המידע נרשם מיד ב-commit והמשתמש ממתין לכתיבה
  • Batch wait– הרשומות נצברות בזיכרון אבל אנחנו ממתינים לכתיבה בפועל בזמן ה-commit.
  • Immediate nowait– המידע נרשם מיידית אבל המשתמש לא ממתין לכתיבה (עלול ליצור עומס על ה-redo logs אם יש הרבה כתיבות במקביל).
  • Batch nowait– האופציה המהירה ביותר והמסוכנת ביותר – המידע ירשם במועד מאוחר יותר והמשתמש לא ימתין לכתיבה.

את הפרמטר ניתן לשנות בכמה רמות: זה יכול להיות ברמת ה-session או ברמת ה-system אבל אני ממליץ שלא להשתמש בזה מעבר לרמת ה-session. מעבר לזה נוכל לבצע שינוי של ההתנהגות לטרנזאקציה בודדת אם נכתוב את הפקודה commit write batch nowait (לדוגמה).

השלכות

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

במידה ואנחנו עובדים עם batch nowait (האופציה המהירה ביותר) אז מה שאנחנו נרגיש מבחינת המשתמשים זה שני דברים. האפקט הראשון יהיה ירידה בזמן הפקודות של ה-commit. פעולת ה-commit תתבצע כמעט ללא המתנה ונוכל לבצע מספר גדול יותר של פקודות בפרק זמן נתון. האפקט השני שנקבל יהיה שמכוון שהניהול של הכתיבה ל-redo יתבצע בצורה א-סינכרונית לא נראה יותר המתנה ל-log file sync. ההמתנה הזו לא תתקיים כ-foreground wait event ונוכל להריץ יותר תהליכים שכותבים מידע במקביל – אין לנו יותר צוואר בקבוק באיזור הזה.

עוד דבר שכדאי לזכור זה שהפיצ’ר הזה משפיע אך ורק על המנגנון של ה-durability. אין למנגנון הזה שום השפעה על ה-undo tablespace או על הזמן שלוקח ל-dbwr לכתוב את הבלוקים לדיסק.

 

מי יכול להשתמש

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

התחלנו את הבדיקה בסביבת הבדיקות ואני מקווה להביא תוצאות בעתיד.

סיכום

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

 

2 תגובות

Trackbacks & Pingbacks

  1. […] הזה הוא הכנה לפוסט נוסף על הפרמטר commit_write. מכוון שהמאמר יצא ארוך במיוחד, פיצלתי אותם לשניים. […]

  2. […] הזה הוא הכנה לפוסט נוסף על הפרמטר commit_write. מכוון שהמאמר יצא ארוך במיוחד, פיצלתי אותם לשניים. […]

השאירו תגובה

Want to join the discussion?
Feel free to contribute!

השאר תגובה

This site uses Akismet to reduce spam. Learn how your comment data is processed.