איך לבצע replay לאירועים בלי לחזור על פעולות עסקיות שכבר בוצעו
אינטגרציות · אוקטובר 2026
מדריך ל-replay בטוח של אירועים: בחירת טווח, הפרדת side effects, זהות פעולה, dry run, checkpoints, מניעת כפילויות ו-reconciliation.
קטגוריה: אינטגרציות | תאריך: אוקטובר 2026
כשמתגלה תקלה באינטגרציה מבוססת אירועים, אחת הדרכים לתקן את הפער היא לעבד מחדש אירועים שכבר התקבלו. הפעולה הזאת נקראת replay. היא יכולה להשלים נתונים חסרים, לבנות מחדש תצוגה, להפעיל לוגיקה שתוקנה או לשחזר צרכן שלא פעל — אבל היא גם עלולה לשלוח שוב הודעה ללקוח, ליצור משימה כפולה, לבצע חיוב נוסף או לעדכן מערכת ליבה במידע שכבר נכתב.
לכן replay אינו פשוט "להחזיר הודעות לתור". צריך להגדיר מה רוצים לשחזר, אילו תוצאות מותר לחשב מחדש, אילו פעולות חיצוניות אסור להפעיל שוב, איך מזהים אירוע שכבר טופל ואיך מוכיחים שההרצה תיקנה את הפער בלי ליצור פער חדש.
להתחיל מתוצאת התיקון המבוקשת
לפני שבוחרים טווח זמנים או מפעילים כלי תשתית, מגדירים את המטרה העסקית. למשל:
- להשלים רשומות שחסרות בדוח BI.
- לבנות מחדש projection או אינדקס חיפוש לאחר תיקון לוגיקה.
- לעדכן שדה שחושב באופן שגוי בתקופה מסוימת.
- להעביר לצרכן חדש היסטוריה שהוא לא קיבל בזמן אמת.
- לבדוק מה הייתה התוצאה אילו גרסה חדשה של הכללים הייתה פעילה.
המטרה קובעת אילו אירועים נדרשים, מאיזו נקודה, באיזו גרסת schema ואילו תוצאות אמורות להשתנות. replay רחב של כל ההיסטוריה כאשר הבעיה נוגעת לסוג אירוע אחד או לקבוצה קטנה של ישויות מגדיל את הסיכון ואת זמן הבדיקה ללא צורך.
כדאי לנסח מראש תנאי סיום מדיד: אילו רשומות צריכות להופיע, איזה פער צריך להיסגר, אילו מונים לא אמורים להשתנות ואילו פעולות חיצוניות חייבות להישאר ללא שינוי.
להפריד בין חישוב מצב לבין side effects
הסיכון המרכזי ב-replay הוא שהקוד שמעבד אירוע מערבב שתי אחריויות:
- חישוב או שמירת מצב פנימי.
- ביצוע side effect — שליחת מייל, פתיחת משימה, קריאה ל-API, חיוב, יצירת מסמך או עדכון מערכת אחרת.
בזרימה רגילה ייתכן ששתי הפעולות צריכות להתרחש יחד מבחינה עסקית. בזמן replay, לעיתים רוצים לחשב מחדש את המצב בלי להפעיל שוב את התוצאה החיצונית. לכן כדאי להפריד את שלב החישוב משלב הפקודה החיצונית ולתת לכל אחד מדיניות replay מפורשת.
לדוגמה, אירוע "בקשה אושרה" יכול לעדכן projection פנימי של סטטוס הבקשה. אותו אירוע עשוי גם לגרום לשליחת הודעה ולפתיחת משימת המשך. בהרצה היסטורית ניתן לבנות מחדש את ה-projection, אך לדכא את ההודעה ואת המשימה אם קיימת הוכחה שהן כבר בוצעו.
הפרדה כזאת אינה אומרת להתעלם מפעולות חיצוניות. צריך לתעד אילו side effects דוכאו, אילו חסרים באמת ואילו דורשים החלטה אנושית לפני השלמה.
להגדיר זהות יציבה לאירוע ולפעולה העסקית
מזהה הודעה טכני לבדו לא תמיד מספיק. אותו אירוע עסקי יכול להישלח שוב עם message ID חדש בעקבות retry, ייצוא היסטורי או מעבר בין תורים. מצד שני, אירועים שונים יכולים להשתייך לאותה ישות אך לייצג שינויים חוקיים ונפרדים.
לכן כדאי לשמור כמה רמות זהות:
- Event ID: מזהה יציב של האירוע המקורי.
- Entity ID: הלקוח, ההזמנה, הבקשה או המסמך שאליהם האירוע שייך.
- Event type and version: סוג האירוע וגרסת החוזה שלו.
- Business operation ID: זהות הפעולה שאסור לבצע פעמיים.
- Causation ID: האירוע או הפקודה שגרמו לאירוע הנוכחי.
- Correlation ID: הקשר למסלול העסקי הרחב בין מערכות.
כאשר פעולה חיצונית רגישה, יש לה מפתח idempotency משלה ורישום תוצאה אטומי. המדריך על מניעת עיבוד כפול באינטגרציה מפרט איך לקשור את המפתח לפעולה העסקית ולא לניסיון הביצוע.
לבחור מקור replay שאפשר לסמוך עליו
אפשר לבצע replay מלוג אירועים, מתור שומר היסטוריה, מארכיון, מטבלת outbox או מייצוא ממערכת מקור. בכל מקרה צריך לבדוק שהמקור כולל את המידע הדרוש ושאפשר להסביר את שלמותו.
לפני ההרצה בודקים:
- מהו טווח השמירה והאם חסרים בו חלונות זמן.
- האם נשמר payload מקורי או ייצוג שעבר שינוי.
- האם סדר האירועים נשמר ברמת הישות.
- אילו גרסאות schema קיימות בתקופה הנבחרת.
- האם אירועים שנמחקו, תוקנו או בוטלו מסומנים בהתאם.
- האם אפשר לשחזר checkpoint מדויק ולא רק תאריך משוער.
עותק של מסד הנתונים הנוכחי אינו בהכרח תחליף להיסטוריית אירועים. הוא מציג מצב סופי, אך לא תמיד את המעברים שהובילו אליו. מנגד, גם לוג אירועים אינו בהכרח מקור אמת לכל שדה. צריך להגדיר מה משחזרים מהאירועים ומה מאמתים מול מערכת הליבה.
לקבע גרסאות schema וכללי המרה
אירוע ישן עשוי לא להתאים למבנה שהצרכן הנוכחי מצפה לו. שדה יכול לשנות שם, משמעות או פורמט; ערך שהיה אופציונלי עשוי להפוך לחובה; וקוד עסקי שהיה תקף בעבר עשוי לא להיות תקף היום.
יש שלוש גישות נפוצות:
- להריץ את האירוע דרך קוד הצרכן ההיסטורי.
- להמיר את האירוע לגרסה קנונית לפני העיבוד.
- לתמוך בכמה גרסאות בתוך הצרכן.
הבחירה תלויה במשך ההיסטוריה, מספר הגרסאות והיכולת לשחזר build ישן. בכל גישה, ההמרה צריכה להיות דטרמיניסטית, ניתנת לבדיקה ומתועדת. אין להשלים שדה חסר בערך "סביר" בלי כלל עסקי מאושר.
אם אירוע אינו ניתן להמרה, מעבירים אותו למסלול חריג עם הסיבה והנתונים הנדרשים להחלטה. לא מדלגים עליו בשקט ולא ממשיכים כאילו הטווח הושלם.
לבצע dry run לפני כתיבה
dry run מעביר את האירועים דרך אותה בחירה ולוגיקה ככל האפשר, אך אינו כותב למערכות ייצור ואינו מפעיל side effects. מטרתו להראות מה עומד להשתנות לפני שהשינוי מתרחש.
תוצאת dry run צריכה לכלול:
- מספר האירועים שנמצאו, סוננו, הומרו ונכשלו.
- הישויות והשדות שצפויים להשתנות.
- פעולות שהיו נוצרות אך דוכאו.
- אירועים כפולים או מחוץ לסדר.
- התנגשויות בין המצב המחושב למצב הנוכחי.
- רשומות שלא ניתן לעבד והסיבה לכך.
רצוי להשוות את התוצאה לסביבת צל או לטבלאות זמניות, ולא רק להדפיס לוג. כך אפשר לבצע diff בין המצב הקיים למצב המחושב, לדגום מקרים עסקיים ולבקש אישור מבעלי התהליך לפני כתיבה.
להריץ בטווח קטן עם checkpoint מפורש
גם לאחר dry run, מתחילים בקבוצה מצומצמת: סוג אירוע אחד, מספר ישויות מוגדר או חלון זמן קצר. לכל הרצה שומרים מזהה replay נפרד, פרמטרי בחירה, גרסת קוד, זמן התחלה, checkpoint אחרון ותוצאות.
checkpoint צריך להיות חד-משמעי, למשל offset, sequence או צירוף של partition ומיקום. תאריך בלבד עלול לכלול שוב אירועים בעלי אותה חותמת זמן או לדלג עליהם. אם עוצרים וממשיכים, משתמשים באותו מזהה replay ובאותם כללי בחירה, אלא אם נפתחה הרצה חדשה ומתועדת.
יש להגביל קצב כדי שה-replay לא יתחרה בתעבורה החיה על מסד הנתונים, התור או ה-API. לעיתים עדיף להקצות תור, consumer group או מכסת משאבים נפרדת. אם האירועים החדשים ממשיכים להגיע, מגדירים כיצד נשמר סדר ביחס להיסטוריה עבור אותה ישות.
להחליט מה עושים כשהמצב כבר התקדם
אירוע ישן עשוי להגיע לישות שמצבה הנוכחי מתקדם יותר. כתיבה עיוורת של התוצאה ההיסטורית יכולה להחזיר הזמנה לסטטוס קודם או למחוק תיקון שבוצע מאז.
אפשר להשתמש בכמה הגנות:
- optimistic concurrency על גרסת הרשומה.
- השוואת sequence או זמן עסקי אחרון לכל ישות.
- כתיבה לשדה נגזר בלבד במקום החלפת הרשומה כולה.
- חישוב מחדש מכל ההיסטוריה הרלוונטית ולא מאירוע בודד.
- העברת קונפליקט לבדיקה במקום הכרעה אוטומטית.
הכלל החשוב הוא שה-replay אינו מקבל עדיפות רק משום שהוא יזום. הוא חייב לכבד את אותם כללי בעלות, סדר ומקור אמת שחלים על הזרימה הרגילה. המדריך על אירועים שמגיעים בסדר שונה מסביר כיצד להשתמש בגרסאות, מכונת מצבים ו-reconciliation כדי למנוע נסיגה של מצב.
לנהל פעולות שכבר בוצעו ולא רק הודעות שכבר נקראו
טבלת processed events יכולה למנוע עיבוד חוזר של Event ID מוכר, אך replay יזום לעיתים אמור לחשב מחדש דווקא אירועים שכבר סומנו כמעובדים. לכן צריך להפריד בין שני רישומים:
- האם הצרכן ראה ועיבד את האירוע בעבר.
- האם הפעולה העסקית או ה-side effect כבר הושלמו.
במצב replay אפשר לאפשר חישוב חוזר תחת מזהה ההרצה, ובמקביל לחסום פעולה חיצונית לפי מזהה הפעולה העסקית. אם מתברר שהפעולה החיצונית חסרה, לא מפעילים אותה אוטומטית רק משום שהחישוב הצליח. מסווגים אותה כפעולת השלמה, בודקים את המצב במערכת היעד ומחליטים לפי הרשאה וכלל עסקי.
במיוחד לאחר timeout, ייתכן שהפעולה בוצעה אך התשובה לא נשמרה. במקרה כזה יש לבדוק לפי מזהה חיצוני או מפתח idempotency לפני שליחה נוספת.
לבצע reconciliation אחרי ההרצה
סיום טכני של כל ההודעות אינו הוכחה שהתיקון הצליח. לאחר replay משווים בין התוצאה הצפויה למצב בפועל:
- האם כל הישויות שבטווח נבדקו.
- האם הפער שבגללו התחיל התיקון נסגר.
- האם נוצרו רשומות, משימות או הודעות כפולות.
- האם נשארו אירועים שלא הומרו או קונפליקטים שלא הוכרעו.
- האם מונים, סכומים וסטטוסים מתיישבים עם מערכת המקור.
- האם התעבורה החיה המשיכה ללא עיכוב או שינוי לא צפוי.
את הפערים שומרים כרשימת עבודה עם בעלים, סיבה ופעולה הבאה. אם נדרש replay נוסף, הוא מקבל טווח ומזהה חדשים ולא משנה בדיעבד את רישום ההרצה הקודמת. עבור תיקון פערים בין מערכות אפשר להיעזר במסגרת של reconciliation אחרי כשל חלקי.
הרשאות, אבטחה ו-audit trail
יכולת replay מעניקה גישה להיסטוריה ולעיתים גם יכולת לשנות מצב בהיקף גדול. לכן היא צריכה להיות מוגנת כמו כלי תפעולי רגיש:
- הפרדת הרשאה בין צפייה, dry run ואישור כתיבה.
- הגבלת טווח, סוגי אירועים וצרכנים מורשים.
- הסתרת סודות ומידע רגיש מפלטי בדיקה ולוגים.
- אישור נוסף כאשר ההרצה יכולה להפעיל פעולה חיצונית.
- שמירת מי יזם, מי אישר, איזו גרסת קוד רצה ומה השתנה.
- אפשרות לעצור את ההרצה בלי לאבד את ה-checkpoint.
אין לערוך ידנית את לוג המקור כדי "לתקן" replay. תיקון צריך להופיע ככלל המרה, אירוע מתקן או פעולה מתועדת, כך שאפשר יהיה להסביר ולשחזר את התוצאה.
בדיקות שכדאי לבצע לפני ייצור
- אותו טווח מופעל פעמיים ואינו יוצר side effects כפולים.
- אירוע ישן מגיע אחרי שהישות התקדמה ואינו מחזיר אותה לאחור.
- שתי גרסאות schema מומרות לאותה משמעות עסקית מאושרת.
- אירוע פגום עובר למסלול חריג ולא נעלם מהספירה.
- dry run וכתיבה משתמשים באותם מסננים ואותה גרסת לוגיקה.
- עצירה באמצע וחידוש מ-checkpoint אינם מדלגים ואינם מכפילים.
- replay איטי אינו חוסם את הצרכן של התעבורה החיה.
- timeout מול מערכת יעד אינו גורם לשליחה חוזרת עיוורת.
- פעולת השלמה חיצונית דורשת הרשאה ונבדקת מול מצב היעד.
- reconciliation מזהה רשומה חסרה, כפולה וקונפליקט מצב.
שאלות נפוצות
האם idempotency מספיק כדי להפוך replay לבטוח?
לא. idempotency מגינה על פעולה מוגדרת מפני ביצוע כפול, אך replay דורש גם בחירת טווח, טיפול בגרסאות schema, שמירת סדר, הפרדת side effects, הגבלת עומס ו-reconciliation. בנוסף, צריך לוודא שמפתח ה-idempotency מתייחס לפעולה העסקית ולא רק להודעה הטכנית.
האם כדאי למחוק את טבלת האירועים שעובדו לפני replay?
בדרך כלל לא. מחיקה גורפת מאבדת היסטוריה ועלולה לפתוח את כל ה-side effects לביצוע חוזר. עדיף להפעיל מצב replay מפורש עם מזהה הרצה, תוך שמירה נפרדת של תוצאות החישוב ושל פעולות שכבר הושלמו.
איך בוחרים בין replay מלא ל-replay חלקי?
מתחילים מהפער העסקי ומהתלות בין האירועים. אם המצב נגזר מכל ההיסטוריה, ייתכן שנדרש replay מנקודת התחלה יציבה. אם התקלה מוגבלת לסוג אירוע, גרסה או קבוצת ישויות, replay ממוקד מפחית סיכון. בכל מקרה מאמתים ב-dry run שהטווח כולל את כל התלויות.
האם אפשר לבצע replay בזמן שהתהליך הרגיל ממשיך לעבוד?
כן, אם קיימים בידוד משאבים, כללי סדר ברמת הישות, כתיבות בטוחות ויכולת להבדיל בין תעבורה חיה להיסטורית. כאשר ההפרדה אינה ברורה, עדיף חלון מבוקר או כתיבה לסביבת צל לפני מיזוג התוצאה.
להתחיל מאירוע אחד ומפעולה אחת
בחרו סוג אירוע אחד שגרם לפער תפעולי. הגדירו את מקור ההיסטוריה, טווח האירועים, גרסאות ה-schema, המצב שאמור להיבנות מחדש והפעולות שאסור לבצע שוב. הריצו dry run, בדקו diff על מספר ישויות, הפעילו כתיבה בקצב מוגבל וסיימו ב-reconciliation מתועד.
Adaptico מסייעת בתכנון אינטגרציות, APIs ותהליכי אוטומציה ארגוניים, כולל idempotency, תורים, replay, מסלולי חריגים ובקרה. אפשר [ליצור קשר](/#contact) כדי למפות תרחיש replay אחד ולתכנן דרך בטוחה להשלים פערים בלי לחזור על פעולות עסקיות שכבר בוצעו.