פיד הדלתא שלך מפספס קומיטים מאוחרים: בנה סמן אמין
שורה הגיעה באיחור מפני שסימן המים לא היה גבול קומיט. עקוב אחרי הכשל, ואז הגדר כיסוי, גבול, שימור וחידוש בטוח.
הנה כשל שראיתי קורה פעמיים, בשני מנועים שונים. ייצוא רץ ב-10:00 ורושם את חותם הזמן הגבוה ביותר של שינוי אחרון שראה. טרנזקציה שהחלה ב-09:58 עדיין פתוחה, מבצעת קומיט ב-10:03 ונושאת חותם זמן מרגע תחילתה. הייצוא הבא מבקש רק שורות חדשות מ-10:00 ולעולם אינו רואה אותה. גודל ההעברה היה מצוין והנתונים היו שגויים.
עקוב אחרי הייצוא שאיבד שורה
עקוב אחרי סדר האירועים ולא אחרי ה-SQL. האפליקציה כותבת את זמן השינוי של השורה בתחילת הטרנזקציה, לא בקומיט. הייצוא קורא סימן מים משעון קיר בזמן שהטרנזקציה עדיין פתוחה. אחר כך הטרנזקציה מבצעת קומיט עם זמן שינוי ישן יותר מסימן המים השמור. האצווה הבאה של הצרכן מתחילה אחרי סימן המים, כך שהשורה נשארת מאחורי הסמן ולעולם לא תיבחר שוב.
שום דבר לא רושם שגיאה, כי שום דבר לא נכשל. זה מה שהופך את סוג התקלה הזה ליקר: פיד הדלתא מהיר, ספירות השורות נראות סבירות, והשורה החסרה צפה שבועות אחר כך ככרטיס התאמה שאיש לא יכול לשחזר.
למספרי סדר יש אותה צורת פגם מסיבה אחרת. מזהה מונוטוני אומר לך את סדר הקצאת הערכים, לא את סדר ביצוע הקומיטים. טרנזקציה שלקחה מספר נמוך יכולה לבצע קומיט אחרי זו שלקחה מספר גבוה, ולכן סמן המבוסס על סדר ההקצאה עלול גם הוא להשאיר חור.
חמש הבטחות שהפיד חייב לתת
לפני בחירת תעבורה, בקש ממערכת המקור חוזה עם חמישה סעיפים. אם חסר סעיף, יש לך היוריסטיקה ולא פיד שינויים.
- כיסוי: הוספות, עדכונים ומחיקות, כל אחד עם המפתח היציב הדרוש להחלתו.
- גבול: מה משמעות ערך סמן ומה מכילה אצווה עקבית.
- עקביות: כיצד נקראות טבלאות קשורות בלי לערבב מצבי מקור לא תואמים.
- שימור: כמה זמן צרכן מנותק יכול לחדש מעמדתו השמורה.
- אתחול מחדש: תמונת מצב מלאה נתמכת לכשהעמדה ההיא אינה תקפה עוד.
מנגנון נתמך יכול לספק את כל החמש. התיעוד של SQL Server על מעקב שינויים הוא דוגמה מפורסמת קונקרטית: הוא מגדיר גרסה מינימלית תקפה, רושם מחיקות כמו גם שינויים, ומתאר טרנזקציית תמונת מצב שמכסה אימות, לכידת גרסה ושאילתות השינויים הנלוות. זו דוגמה למה לדרוש ממקור, לא תחביר להדביק במנוע אחר.
תכנן את נקודת הביקורת כחלק מהצינור
העמדה השמורה של הצרכן היא מכונת המצבים האמיתית, אז כתוב אותה ככזו. הסמן רשאי להתקדם רק דרך זרימת העבודה שכבר החילה את האצווה וביצעה קומיט לטרנזקציית היעד.
cursor = durable_checkpoint() # last fully applied position
batch = extract(cursor, upper_bound) # one consistent batch
apply(batch) # idempotent at the target
commit()
checkpoint(batch.upper_bound) # only now
אם האספקה נכשלת באמצע, התאם את אותה אצווה במקום להמציא עמדת מקור חדשה. חילוץ מחדש עם גבול עליון טרי יותר הוא הדרך שבה החלה חלקית הופכת לפער קבוע. כלל המראה תקף גם בצד השני: החלה אידמפוטנטית ביעד מונעת כפילויות ואינה עושה דבר לשינוי שהחילוץ מעולם לא ראה.
מחיקות זקוקות לרישומים, לא למסקנות
השוואת שורות שעדיין קיימות אינה יכולה לומר לצרכן אילו מפתחות נעלמו. מקור שלעולם אינו רושם מחיקות משאיר לך שתי אפשרויות לא מושלמות: להחזיק היסטוריית מפתחות זמן רב מספיק כדי להשוות, או להריץ התאמה מלאה מפורשת לפי לוח זמנים. שתיהן החלטות תכנון עם מחיר, ואף אחת אינה קיימת אלא אם מישהו אחראי לה.
שימור הוא אותו סוג של החלטה. לכל פיד שינויים נתמך יש חלון; הצהר עליו בחוזה והתרע כאשר נקודת הביקורת של צרכן מתקרבת אליו. צרכן שמתעורר אחרי הפסקה של שבועיים צריך לקרוא שגיאה ברורה "נדרש אתחול מחדש" במקום לקבל בשקט אצווה חלקית.
ואז מדוד אם הדלתא הייתה שווה את זה
חזרה להשוואת כל מפתח ו-hash היא לפעמים נכונה, ושווה לדעת מה מחירה. אתה עדיין מעביר מידע על כל שורה, ה-hash לא נותן תמונת מצב עקבית בין טבלאות, וסכימת hash מביאה את השאלות שלה על ייצוג והתנגשויות. פחות בתים עם גבול שניתן לאימות הוא היעד; פחות בתים שמפילים בשקט שינוי שקיבל קומיט אינם דלתא בכלל.
עמודת השינוי האחרון היא רמז. סמן ששורד קומיט מאוחר, מחיקה והפסקה ארוכה הוא חוזה.