2019-01-05

דרכים להתמודד עם חוב טכני (Technical Debt)

אני מניח שכולכם מכירים את המונח "חוב טכני" (Technical Debt), מונח שקבע Ward Cunningham בכדי לעזור ולחשוב על פשרות טכניות במערכת - שיש להן השפעות ארוכות טווח.

משמעות "חוב טכני" היא שפיתחנו פיצ'ר מסוים, וידענו שהפתרון הנכון ארכיטקטונית הוא פתרון X.
משיקולי זמנים (time-to-market, בעיקר), בחרנו ליישם פתרון נחות-ארכיטקטונית שנקרא לו x`.

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

למשל: בפיצר מסוים, במקום ליצור טבלה חדשה בבסיס הנתונים המתארת בצורה מדויקת את אופי האובייקט o, החלטנו "להלביש" את קיום האובייקט o על טבלה קיימת. שחררו את הפיצ'ר מהר יותר - אבל יצרנו עיוות שעכשיו בפיצ'רים חדשים נצטרך לעשות עבודה נוספת / לפתור חוסרי-התאמה חדשים בתוך המערכת, כי לא עשינו את ה"פתרון הנכון".

למטאפורה של "חוב טכני" יש כמה צדדים מדויקים יותר, ומדויקים פחות:
  • מתאר יפה את המצב:
    • לחוב טכני יש "עלות" או "ריבית" שתכביד עלינו ונשלם עליה - עד אשר החוב יוחזר.
    • בחוב טכני יש משהו גם חיובי: פעמים רבות נכון לביזנס לעשות פשרה מידית בכדי להשיג תוצאה מידית. הדבר טבעי במיוחד לשוק האמריקאי שם צמיחה של חברות היא מבוססת חובות - וזה מודל שמצליח.
  • לא מתאר בצורה טובה:
    • חוב כספי הוא מספרי וקל לחשב אותו: בדולרים. חוב טכני הוא לא מדויק, לא ניתן ממש להעריך את העלות "להחזיר אותו" וגם לא ניתן למדוד במדויק את הנזק שהוא גורם לאורך זמן.
      פריון מפתחים - הוא נושא לא-מדויק מטבעו.
    • חוב כספי, גדול ככל שיהיה אפשר להחזיר ברגע אחד. חוב טכני משמעותי יוחזר על ידי עבודה ארוכה, של המשאבים הקיימים.
    • יש כלים (למשל: SonarQube) שמנסים לתת מספר (זמן / דולרים) שיתאר את החוב הטכני של המערכת. קצרה היריעה מלפרט עד כמה ההערכות הללו הן סתמיות...

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







קצת מעבר להגדרה היבשה


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

  • לא תמיד אנחנו צודקים בהערכה שארכיטקטורה X עדיפה על ארכיטקטורה x`. 
    • לעתים אנו מבצעים הערכות-יתר לחשיבות של אופן מימוש מסוים.
    • לפעמים הנסיבות משתנות באופן בלתי-צפוי: דרישה או צורך באובייקט מרכזי במערכת מתבטל או משתנה, ופתאום x` הופכת לארכיטקטורה עדיפה על X. זה לא קורה כל יום, אבל זה קורה. 
  • גם כאשר ארכיטקטורה X עדיפה - לא תמיד היתרון שלה הוא משמעותי או מוצדק.
    • יכול להיות ש"לתקן" את החוב הטכני יעלה יותר מכל מחיר שנשלם אי פעם בחיי המערכת, בשל העיוותים שיצר. דוגמה קלאסית: שמות של עמודת בבסיס הנתונים: השם הלא-נכון מציק, אבל שינוי עשוי להיות יקר מאוד לביצוע.
  • הצהרה על חוב טכני הוא לעתים "קלף מיקוח" בניסיון להשפיע על אחרים בכדי להסכים דרך פעולה שאני רוצה לקדם. מתוך רצון להשפיע - אני עלול להגזים (ויותר) בתיאור החוב הטכני.
  • "חוב טכני" הוא טיעון של אנשי-טכנולוגיה, לא אנשי ביזנס 
    • אחד מסוגי החובות הטכניים הנפוצים והמזיקים ביותר הוא הנדסת-יתר (over-engineering). הרבה יותר קשה להתקדם ולפתח פ'צרים עם 6 שכבות הפשטה - אם נדרשות רק 2.
      • במקרים של הנדסת-יתר לרוב לא נשמע את הקול שאומר "זהו חוב טכני. אנחנו חייבים להחזיר אותו (להסיר רמות הפשטה) בכדי להתקדם יותר מהר", אבל הרבה פעמים - זה בדיוק המצב.
    • וריאציה אחרת היא סיבוך המערכת ויצירת חוב-טכני, דווקא תחת ה ticket של "החזרת חוב טכני".
      לא תמיד מתן אשראי למי שמוכן "להחזיר חוב טכני" - היא פעולה נכונה. עולם התוכנה הוא אכן מורכב: על מנת לפעול נכון חשוב לצלול ולהבין את הדברים.
  • למרות ערימת ההסתייגויות למעלה, חוב טכני עמוק הוא דבר הרסני למוצר ולחברה
    • חובות טכניים עמוקים, בליבת המוצר והמודל שלו - עשויים לעשות את ההבדל בין מוצר מצליח לכושל.


חוב טכני - אבוי! מקור: וויקיפדיה


חובות טכניים, הם לא אחידים, ולא כדאי להתייחס אליהם ככאלו. אם ניזכר לרגע בכמה חובות טכניים מפורסמים:
  • באג 2000 (Y2K) - אבותינו חסכו בשטח האחסון ולכן מידלו שנה כ-2 ספרות ("95") ולא כ-4 ספרות ("1995"). מה קורה כאשר מגיעה שנת 2000? מיון? בדיקה איזה תאריך קדם לשני? - בלאגן.
    החוב הטכני קיבל פרופיל תקשורתי עולמי - והיה סיכון ברור ומידי. למרות נבואות-זעם על עולם לא-מתפקד, הוא תוקן בזמן (ובמחיר עצום), ועברנו לשנות ה-2000 בשלום.
  • Referer של HTTP - בתקן ה HTTP הוגדר header חשוב בשם Referrer. בתקן נפלה שגיאת כתיב (נכתב: "Referer") אך עד שגילו את הטעות כבר היו עשרות מפתחים ואולי מאות מפתחים כבר מימשו את שגיאת הכתיב. מלבד בדיחה על בקשה שהוגשה למילון אוקסופרד לתקן את הטעות במילון (שם יותר קל לתקן) - לא נעשה ניסיון אמיתי לתקן את שגיאת הכתיב. זו דוגמה טובה לחוב טכני שהוא טעות - אך לא משתלם לתקן. ככל שהזמן עובר - זה הופך למשתלם אפילו פחות.
    • דוגמה נוספת למשהו שאולי היה רצוי לתקן, אך בלתי-אפשרי בעליל הוא שיטת המדידה האימפריאלית הנהוגה בארה"ב. מדוע צריך לעבוד עם המרות לא נוחות כמו "מייל הוא 5280 רגל"? האם השיטה המטרית היא לא נוחה יותר? - כנראה שכן, אבל כבר מאוחר מדי.
  • להתחיל מערכת חדשה ללא בדיקות-יחידה / CI-CD אמיתי / תשתית לוגים סבירה - היא דוגמה לחוב טכני הרסני. אלו דברים שאם לא מתחילים איתם, הקושי להוסיף אותם מאוחר יותר הוא חסר תקדים. הייתי שותף להשקעה של שנות-אדם רבות בניסיון להחיל בדיקות יחידה, CI אמיתי, או תשתיות לוגים במספר מערכות שונות ש"גררו כמה שנים בלי". על אף מאמצים הרואיים, מגובי-הנהלה גבוהה - לא זכיתי לחזות במקרה שהייתי מגדיר כמוצלח.
    • רק להסיר ספק: קיום כל אחד מהשלושה הנ"ל הוא כלי productivity חשוב מאוד לארגון פיתוח / למערכת.


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



איך (לא) מחזירים חוב טכני?


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

זה לא מעניין מבחינתי.

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

הניסיון "להחזיר את החוב עד תומו" - הוא נאיבי וחסר-בסיס.

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

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


את החובות הטכניים המשמעותיים - לא יגלו לכם כלי בדיקה אוטומטיים (כגון Static Analysis tools): הם יגדלו את הקטנים עד הפצפונים - והמון מהם, אך גם לא את כולם.

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

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

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

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

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

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

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




סיווג אחד (מני רבים) של סוגים שונים של חובות טכניים ומקורותיהם. מקור: Martin Fowler
כדאי מאוד להימנע ככל האפשר מחובות טכניים משמעותיים ומיותרים. סיווג המקור - פחות חשוב.


איך (כן) מחזירים חוב טכני?


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

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

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


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

השיקוף של הבעיות - הוא חשוב מאוד גם כן. גם לצד ההנהלה, וגם לצד המפתחים:
  • אם ההנהלה לא יודעת שיש בעיות חמורות, זה לא הוגן ולא אחראי. היא לא תדע להקצות את המשאבים (שתמיד נמצאים במחסור) בכדי להתמודד עם הבעיה.
  • אם המפתחים לא מבינים שדברים מסוימים מהווים בעיה חמורה - הם לא ידעו לצמצם ולהגביל את הבעיה. מכירים את ה Anti-Pattern של "שכפול Anti-Patterns במערכת"?
  • אם אתם לא מבינים את הבעיה, איך המפתחים לא יתקנו אתכם ויסבירו לכם - אם לא תדברו איתם על זה. תנו להם הזדמנות לתקן אתכם, ולחסוך לכם חוסר-נעימות. 

מציאת המנגנון הארגוני לטיפול בבעיה:

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



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



מציאת המנגנון הארגוני לצמצום חובות טכניים


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


גישת הצוות:

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


הגישה הגלובאלית:

גם כאשר הזמן המוקצה לצמצום חובות טכניים הוא "גלובאלי" (משותף לכל ארגון הפיתוח) - חשוב מאוד כיצד הוא מתוקשר ומנוהל. 

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


יתירות בפיתוח:

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

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



לפעול במהירות ובנחישות:

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


צוות לצמצום חובות טכניים:

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


צמצום חוב טכני - כאירוע:

מודל אחר שיכול לעבוד הוא לרכז מאמץ של יחידה גדולה, או כל הפיתוח - על מנת לצמצם במרוכז חובות טכניים.
  • וריאציה אחת ששמעתי עליה, היא חברה שמקדישה 3 ימים כל תקופה מסוימת - על מנת לשפר ולהעשיר את המערכת בבדיקות אוטומטיות (בדיקות יחידה, בדיקות אינטגרציה, וכו' - בדיקות שמפתחים כותבים). ע"פ הסיפור - זה עבד דיי טוב.
  • וריאציה שניסינו לאחרונה בחברה הנוכחית שאני עובד בה (Next-Insurance) היא להפעיל את כל הפיתוח למשך שבועיים - על מנת להתמודד עם רשימת בעיות שהוכנה ותועדפה מראש.
    • הזמן הארוך יחסית - מאפשר לפתור בעיות שהן לא רק פצפונות / נקודתיות.
    • האמירה שיש השקעה משמעותית באיכות הטכנית של המערכת - משדרת מסר חשוב גם למתכנתים, וגם לשאר הארגון
    • אני אישית, מאוד נהנה מתקופות כאלו.

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



סיכום


שוב אמרתי לעצמי, שאבחר נושא קצרצר לפוסט, ואכתוב משהו באורך 4 tweets לכל היותר. לא הצלחתי - היה לי יותר מה לומר משחשבתי.

כמה דגשים שחשוב לי שלא תצאו מקריאת הפוסט בלעדיהם:
  • נקודת האופטימום הארגונית היא בהחלט לא "אפס חוב טכני", או לפחות לא בתפיסה המקובלת. "מקסימום הנדסה" - היא לא מקום טוב לביזנס או למערכת חיה להיות בו. חשוב לקחת כמה סיכונים, ולהתקדם בקצב טוב.
  • "חוב טכני" הוא מושג מאוד סובייקטיבי. בהגדרה מסוימת - רעיון החוב הטכני עשוי להתרגם לאיסוף עבודה חסרת חשיבות.
    • חשוב חשוב חשוב להתמקד בטיפול בחוב טכני שיש לו השפעה חיובית מורגשת (Impact).
    • לעתים יש חוב טכני (Design, שמות של משתנים) שעצם קיומו מציק לנו כמהנדסים, אבל אין לו חשיבות לפעילות המערכת. 
      • הייתי משקיע מעט עבודה (low hanging fruits) עבור ההרגשה הטובה, בנוסח "החלונות השבורים" (לשמר אווירה של אכפתיות במערכת)
      • הייתי מתאמץ לקבל גם אלמנטים לא-אלגנטיים במערכת, ואפילו צורמים - כל עוד העלות / תועלת לפתור אותם היא לא סבירה. למשל: עניין ה referer ב HTTP.
        • מערכות מתחדשות מטבען. אם תתעדו ("רשימה") ותשתפו ("שיקוף") בבעיה - יש סיכוי טוב שבשכתוב הבא המצב יהיה טוב יותר.
        • הכי מעצבן זה לראות קוד ששוכתב ושימר חובות טכניים מהותיים - מחוסר הבנה.
  • חוב טכני לא צריך להיפתר לחלוטין. הורדה של בעיה מרמת Critical לרמת Medium - עשויה להיות התקדמות חשובה ומספיקה. 
    • בפעם הבאה שנרצה לשפר משהו, כנראה שנבחר בעיה קריטית אחרת - על פני העלמה של בעיה בעלת חומרה בינונית.
  • למרות ש"חוב טכני" הוא עניין בד"כ עניין טכני וטכנולוגי - מציאת המנגנון לצמצום חובות טכניים הוא בעיקר ארגוני. עבדו עם ההנהלה ומי שיכול לקדם דברים בארגון - לא רק עם מקצועני התוכנה.

שיהיה בהצלחה!



2018-12-24

MySQL Transactions - חלק א'

פוסטים בסדרה:
"תסביר לי" - גרסת ה SQL
לקחת את הביצועים ברצינות, בעזרת MySQL Performance Schema
נושאים מתקדמים ב MySQL: חלק א' - עבודה עם JSON
נושאים מתקדמים ב MySQL: חלק ב' - json_each  ו Generated Columns



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

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

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

איך משתמשים בטרנזקציה? בבסיס, באופן הבא:


START TRANSACTION; 
-- do something  

COMMIT; 
-- or: ROLLBACK; 



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






למה להשתמש בטרנזקציות?



כיום, טרנזקציות הוא דבר "לא-קולי" ("not cool").

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

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

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

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

ובכן: 
  • טרנזקציות הן אויב ל Scalability (ברוב המקרים), וכאשר צריך הרבה Scalability - עלינו להימנע מהן.
    • גם במערכות המתמודדות עם Scalability, ישנם flows ותסריטים שעובדים ב volume נמוך יותר - ויש להם בעיות שטרנזקציות יכולות לפתור.
  • חטאנו (גם) בשנות האלפיים, ועשינו שימוש-יתר, ביכולות שונות של בסיסי-הנתונים הרלציוניים: כמו טרנזקציות, Foreign Keys, או Triggers. עדיין, בשימוש מידתי - אלו כלים שימושים שיכולים לפתור בעיות רבות.
    • הסיפור של Overselling של כלים וטכניקות, הוא לא מקרה יחידני. הוא קורה שוב ושוב, ויקרה שוב ושוב. תתרגלו.
  • טרנזקציות הן פעמים רבות, הכלי הנכון והטוב ביותר לפתור בעיות.
    • אם אתם מסוגלים להתגבר על הקושי שלא המציאו את הטרנזקציות בשנת 2018 בגוגל, והן לא מככבות במצעד ה"טכנולוגיות היפות והנכונות של 2019 [א]" - אז יש לכם סיכוי טוב להעשיר את סט הכלים שלכם בצורה מועילה.



נחזור לשאלה המקורית, שמסתבר שהיא לא טריוויאלית: "מדוע / מתי להשתמש בטרנזקציות"?


הנה שימושים מרכזיים לדוגמה:
  • אנו רוצים לעדכן שתיים (או יותר) טבלאות בבסיס הנתונים בצורה עקבית: שלא יוותר מצב שאחת מעודכנת, אבל של תקלה - השנייה לא עודכנה, ויש לנו אי-עקביות בנתונים / נתונים חסרים.
  • אנו רוצים לעדכן ערך בטבלה, בצורה עקבית ותחת racing condition אפשרי: שני threads (או מנגנון מקבילי אחר) רוצים לבצע שינוי שתלוי במצב הקיים - אבל ללא הגנה התוצאה יכולה להיות שגויה.
  • אנו רוצים להשתמש בבסיס-הנתונים כמגנון סנכרון פשוט בין כמה שרתים
    • אין לנו מנגנון אחר, ובסיס-הנתונים הוא מספיק טוב למשימה.
  • שיפור ביצועים - זה נשמע לא אינטואטיבי, אבל במקרים מסוימים טרנזקציות דווקא יכולות לעזור לשפר ביצועים.
  • ביצוע שינוי בנתונים בצורה זהירה ומפוקחת - (בסביבת פרודקשיין, למשל). נבצע את השינוי, נבחן את השלכותיו, ורק אז נעשה commit.

לכאורה אפשר לחשוב (בתמימות) שלכל המקרים יש פתרון זהה. שבשימוש בפקודות START TRANSACTION ... COMMIT - נפתרו בעיותינו. מובטח לנו שמה שציפינו, אכן יקרה, וללא תופעות לוואי מיותרות - כמובן.

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

ישנו Trade-off מאוד בסיסי פה:
יותר בטיחות כנגד יותר מקביליות / ביצועים.

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

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

את ה tradeoff בין בטיחות למקביליות - בסיס הנתונים לא יודע לקחת עבורנו. הוא מספק לנו כמה נקודות בסיסיות על ציר ה tradeoff (להלן Isolation Levels), ועוד כמה כלים נוספים בכדי לדייק את המקום בו אנו רוצים להיות.

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



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


זו הזדמנות טובה להזכיר את Amdahl's law המראה את הקשר בין החלק בפעולה שאינו מקבילי - לחסמים על מקביליות, לא משנה בכמה threads נשמש....
כדי להשלים את התמונה, שווה להכיר גם את ה Universal Scalability Law (בקיצור USL) שמפרמל מהי מקביליות, וממנו ניתן לראות שניסיון לדחוף יותר עבודה מקיבולת מסויימת - דווקא תאט את המערכת עוד יותר.


מודל המקביליות של InnoDB


לב מודל המקביליות של InnoDB, בדומה לבסיסי-נתונים רבים אחרים, מבוסס על שני כלים עיקריים:
  • נעילה פסימיסטית - לקיחת מנעול על משאב (טבלה, רשומה, וכו') בצורה שתגביל פעולות מקביליות אחרות - אך תמנע בעיות של עקביות-נתונים.
    • מחלקים את הנעילות ל-2 סוגים:
      • נעילה משותפת (Shared lock) עבור פעולות קריאה. אני קורא ערך ואוסר על שינוי הערך - אבל לא יפריע לי שטרנזקציות נוספות יקראו את הערך גם.
      • נעילה בלעדית (Exclusion lock) - לצורך שינוי הערך. אני תופס את המנעול, ולא אתן לשום טרנזקציה אחרת אפילו לקרוא את הערך (כי הוא עומד להשתנות)
  • נעילה אופטימיסטית - מאפשרת לתת לפעולות לרוץ במקביל, לגלות "התנגשויות" ואז להתמודד איתן.
    • לפעמים נרצה להכשיל את אחת הטרנזקציות (או יותר - אם יש כמה). לפעמים נסכים לקבל חוסר אחידות בקוד.
    • למרות שיש עבודה משמעותית נוספת בגילוי וטיפול ב"התנגשויות", אנו מאפשרים מקביליות בין הפעולות - שזה יתרון שלא יסולא בפז (הציצו שוב בתרשים שלמעלה - כמה צמצום החלקים ה"בלעדיים" הוא חשוב).

מבחינת אלגוריתמים MySQL, בדומה לרוב בסיסי-הנתונים הרלציוניים משתמשים בשני אלגוריתמים עיקריים:
  • 2PL (קיצור של Two-Phase Locking) עבור נעילה פסימיסטית. הרעיון הוא שנחלק את הפעולה לשני שלבים:
    • שלב ראשון - בו ניתן רק לתפוס מנעולים.
    • שלב שני - בו ניתן רק לשחרר מנעולים.
    • לרוב נרצה לתפוס מנעולים ע"פ סדר מסוים ("קודם אובייקט מסוג X ורק אז אובייקט מסוג Y") - על מנת להימנע מ deadlocks.
      במקרים אחרים, אנו יכולים להחליט לתפוס בסדר לא-קבוע על מנת לצמצם זמני-נעילה ולהגביר מקביליות, במחיר שטרנזקציות יבוטלו לנו. המשמעות: נצטרך לפעמים לנסות להריץ אותן כמה ניסיונות - עד שנצליח לתפוס את המנעולים הרצויים.
  • MVCC (קיצור של Multi-version concurrency control) הרעיון שבו אני "מעתיק" הנתונים שטרנקזציה צריכה הצידה, ואז היא חיה ב״סביבה וירטואלית משלה״, ללא התעסקות ב racing conditions או צורך בנעילות.
    • במימוש של InnoDB, לא באמת מעתיקים נתונים, אלא משתמשים בעמודות טכנית של המערכת לכל טבלה, המנהלת איזה עותק של הנתונים שייך לכל טרנזציה, ואלו ערכים נמחקו.
    • כמובן שיש מחיר שנוסף בניהול "העותק הוירטואלי". למשל: כאשר טרנזקציה מבקשת ערך שבטיפול של טרנזקציה אחרת - על בסיס הנתונים לבצע הדמיה של rollback של הטרנזקציה האחרת על מנת לדעת אלו ערכים צריכים להיות לטרנזקציה הנוכחית.
      • למרות המחיר הנוסף בפעולות הללו - הוא אינו דורש בלעדיות ולכן scales well.


בעיות חוסר-עקביות


כל טרנזקציה ב MySQL היא, כברירת מחדל, ברמת הפרדה (Isolation Level) שנקראת Repeatable Read.
ישנן 4 רמות הפרדה שהוגדרו ע"י התקן ANSI-SQL 92 ומקובלות בכל בסיסי-הנתונים הרלציוניים המוכרים.


למרות שרמות ההפרדה והתופעות האפשריות[ב] הוגדרו בתקן ה ANSI-SQL - ההתנהגויות בין בסיסי-הנתונים עדיין מעט שונות.
למשל: SQL Server לא מגן בפני Phantom Reads ברמת הפרדה של Repeatable Read, אבל כן מגן בפני Write Skew או Lost Update. אורקל משתמש רק ב MVCC ולא ב 2PL, מה שתורם למקביליות - אבל גם אומר שרמת הפרדה של Serializable לא מגנה בפני Write Skew....

בקיצור: Tradeoffs. Tradeoffs everywhere.




רשימת התופעות הבעייתיות האפשריות:

  • Dirty Write - כאשר שתי טרנזקציות יכולות לשנות את אותו השדה, כך שטרנזקציה אחת משנה את הערך לשנייה. 
    • בגלל השימוש ב MVCC (או גישה יותר מחמירה: 2PL) - זה לא יקרה אף פעם בטרנזקציה של MySQL.
  • Dirty Read - הטרנזקציה יכולה לקרוא שינוי של טרנזקציה אחרת שהוא עדיין לא committed. הערך הזה יכול להתבטל (rollback) מאוחר יותר - בעוד הטרנזקציה שלנו משתמשת בו. התוצאה עשויה להיות שנשתמש בערך שאין לו ייצוג תקין בשאר המערכת (למשל: id לרשומה שלא קיימת). לא משהו...
  • Not Repeatable Read (בקיצור: NRR) - הטרנזקציה קוראת ערך משדה x בנקודת זמן t1, וכאשר היא קוראת שוב את השדה הזה בנקודת זמן מאוחרת יותר, t2 - ערך השדה הוא שונה. כלומר: טרנזקציה אחרת עשתה commit (אולי autocommit) - והערך שאנו רואים איננו עקבי. ההתנהגות ה NRR שוברת את תמונת "העולם המבודד" שרצינו ליצור לטרנזקציה שלנו - ובמקרים רבים היא יכולה להיות בעייתית.
  • Phantom Read - הטרנזקציה ביצעה קריאה של תנאי מסוים (נניח: year between 2016 and 2018) וקיבלה סדרה של רשומות, אך בינתיים טרנזקציה אחרת עשתה commit והוסיפה רשומות חדשות לטווח. כלומר: אם ניגש שוב לטווח - התוצאה תהיה שונה.
    • זו וריאציה מורכבת יותר של NRR. בעוד NRR ניתן לפתור בעזרת נעילה המאפשרת קריאות-בלבד מרשומה שאליה ניגשה הטרנזקציה, נעילה של טווח שנובע מפרדיקט היא דבר מסובך למדי - גם עבור בסיס נתונים משוכלל.
  • Read Skew - וריאציה נוספת של NRR: ישנן 2 טבלאות עם קשר לוגי ביניהן. שדה x באחת משפיע או מושפע על שדה y בטבלה השנייה. הטרנזקציה קוראת ערך x מטבלה אחת, אך בינתיים טרנזקציה אחרת משנה את x וגם את y (בצורה עקבית). בסיס הנתונים לא יודע בוודאות על הקשר, וכאשר אנו קוראים את y - עדיין עלולים לקבל את הערך הישן - לפני העדכון של הטרנזקציה השנייה. התסריט הזה מעט מבלבל - הנה דוגמה מפורטת.
  • Write Skew - וריאציה דומה, בה שתי טרנזקציות קוראות את שני הערכים x ו y, ואז אחת מעדכנת את x - בעוד השנייה מעדכנת את y. התוצאה - עקביות הנתונים נשברה. הלינק מלמעלה מספק גם דוגמה מפורטת ל Write skew.
  • Lost update - שתי טרנזקציות קוראות ערך ומעדכנות אותו. אחד העדכונים יאבד - מבלי שנדע שכך אכן קרה. במקרה של counter, למשל - הנזק מוחשי מאוד. גם בשדות המכילים ריבוי פריטים (כמו json) - הנזק הוא ברור. ישנם גם מקרים נוספים בהם התוצאה היא בעייתית.
למרות ההגנה הרבה שהן מספקות, חשוב לנסות ולהימנע מרמת הפרדה של Serializable - היכולה לפגוע משמעותית במקביליות, במיוחד כאשר הטרנזקציות אינן קצרות.



חזרה לתסריטים מתחילת הפוסט


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



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

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


אם מעוניינים ב Atomicity, היכולת של טרנזקציה להתבטל מבלי להשאיר "שאריות" אחריה בפעולת Rollback, אזי רמת ההפרדה של Read Committed עשויה להספיק - ולאפשר מקביליות רבה.


כאשר אנו רוצים להתגונן בפני Racing condition אפשרי (למשל: מתעסקים בדברים רגישים, כמו כסף או פעולות שיש לדייק בהן) - אזי לרוב נרצה להשתמש ברמת הפרדה של Repeatable Read.


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


בשתי הדוגמאות האחרונות, אם אנו יודעים בוודאות שהגישה הקריטית היא רק לרשומה בודדת (למשל: קריאת ערך - ואז עדכון) - אזי Read committed היא רמה מספיק טובה. כל הרמות הגבוהות יותר מגינות מפני תסריטים של ריבוי רשומות / טבלאות.
עם אופטימיזציות כאלו כדאי להיות זהירים: משהו עשוי להשתנות בתוכן הטרנזצקיה, מבלי שמבצע השינוי יזכור / יבין שעליו להעלות את רמת ההפרדה.
מצד שני, יש כמה יתרונות משמעותיים לשימוש ב Read Committed (בקיצור RC) על פני Repeatable Read (בקיצור RR) מבחינת ביצועים:
  • ב RR, מנוע InnoDB ינעל כל רשומת אינדקס ששימשה את הטרנזקציה. אם חיפוש הרשימה מתבצע באינדקס לא יעיל (סריקה של הרבה רשומות) - אזי חסמנו הרבה מקביליות.
  • ב RR, המנוע מחזיק את כל הנעילות עד סוף הטרנזקציה. ככל שהטרנזקציה ארוכה יותר - כך זה בעייתי יותר.
  • ב RR, המנוע יוצר gap lock, על רשומות באינדקס שעלו בטווח של השאילתה (גם אם לא נסרקו). שוב - נעילה שעשויה להיות משמעותית למקביליות. נדבר על gap lock בחלק השני של הפוסט.
הנעילות על האינדקסים הן דוגמה למצב בו טרנזקציה אחת מאיטה פעולות אחרות בבסיס הנתונים, שלא ניגשים לאותן הרשומות. הנה דוגמה נוספת (history length).


עוד שני תסריטים שציינתי ולא כיסינו הם אלו:

שיפור ביצועים - זה מקרה ייחודי - אך שימושי.
InnoDB מחזיק binary log, על כל שינוי שבוצע בינתיים עבור התאוששות מהתרסקות ועבור replications. כדי לשמור על הלוג ככשיר להתאוששות, עליו לבצע flush ללוג (כלומר: לדיסק) על כל טרנזקציה שמתבצעת.
אם אנו מבצעים עשרות או מאות שינויים (למשל: הכנסה של רשומות חדשות), בשל ה autocommit - אנו נחייב את המנוע לבצע עשרות או מאות flushes לדיסק (פעולה יקרה).
שימוש בטרנזקציה (הכי פשוטה) - יאפשר לבסיס הנתונים לבצע flush יחיד.

ביצוע שינוי בנתונים בצורה זהירה ומפוקחת - למשל: אנחנו מבצעים תיקון של נתונים היסטוריים בבסיס-הנתונים על פרודקשיין. אנחנו רוצים להימנע מטעויות בשאילתה (למשל: UPDATE ללא WHERE יעדכן את כל הרשומות - מבלי שהתכוונו) שיכולות לגרום לאסונות קטנים וגדולים.

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

בד"כ נשתמש ברמת Repeatable Read לכאלו שינויים. כבני-אדם, אנו נחזיק את הטרנזקציה פתוחה לנצח (דקות? יותר?) - ולכן כדאי להימנע מ Serialization - במידת האפשר.


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

כיצד עושים "התערבות ידנית"?
אלו מנגנוני-נעילה נוספים, הרצים מ"אחורי-הקלעים", קיימים ב InnoDB? (למשל: הזכרנו את ה gap lock)
כיצד מאתרים בעיות של נעילות בעייתיות / עודפות?

על כל זה - נדבר בפוסט ההמשך.



שיהיה בהצלחה!






------


[א] החל מחודש ספטמבר, נהוג כבר להתמקד רק בטכנולוגיות השנה הבאה. #שנה_נוכחית_זה_פח

[ב] התקן המקורי של ANSI-SQL 92 זיהה רק 3 תופעות אפשריות של חוסר עקביות בנתונים, אך מאמר שהגיע שלוש שנים אחריו, הציף עוד 4 מקרים בעייתיים נוספים.

[ג] בעצם, בסיסי-נתונים לא רלציוניים היו תמיד: היררכיים (קדמו, והתחרו עם בסיסי-נתונים רלציוניים - תקופה מסוימת), Time series, מבוססי-אובייקטים, מבוססי-XML, גיאוגרפיים, ועוד.
הם פשוט לרוב נשארו נישה, ולא הצליחו לייצר רעש, כמו הגל האחרון.