Outbox Pattern: איך משדרים אירועים בלי לאבד אותם אף פעם
מאת צוות מדיה דיל · 31.08.2026 · טכנולוגיה · 7 דק׳ קריאה
כשעדכון מסד נתונים ושידור אירוע לתור הודעות הם שתי פעולות נפרדות, כשל בין השתיים עלול לאבד מידע. Outbox Pattern פותר את זה בלי טרנזקציה מבוזרת.
שירות שמעדכן הזמנה במסד הנתונים וגם משדר אירוע "הזמנה עודכנה" לתור הודעות מבצע בפועל שתי פעולות נפרדות. אם העדכון במסד הנתונים מצליח אבל שידור האירוע נכשל (או להפך), המערכת נשארת במצב לא עקבי — בלי שאף אחד שם לב מיד.
הבעיה: שתי פעולות שצריכות להצליח יחד
כתיבה למסד נתונים ושליחה לתור הודעות הם שני מערכות נפרדות לגמרי, בלי טרנזקציה משותפת שמקיפה את שתיהן. קריסת השרת בדיוק בין שתי הפעולות — אחרי הכתיבה למסד, לפני השידור לתור — משאירה את השינוי בלי שאף אחד אחר במערכת יודע עליו.
הפתרון: טבלת Outbox באותו מסד נתונים
במקום לשדר לתור ישירות, השירות כותב את האירוע לטבלת "Outbox" באותו מסד נתונים בדיוק, באותה טרנזקציה של השינוי העסקי עצמו. מכיוון ששתי הכתיבות (השינוי העסקי והרשומה ב-Outbox) קורות בטרנזקציה אחת רגילה, הן תמיד מצליחות או נכשלות יחד — בלי שום צורך בתיאום בין שתי מערכות נפרדות.
תהליך נפרד ששולח מה-Outbox לתור
תהליך רקע נפרד (Relay) קורא באופן תדיר את הרשומות החדשות בטבלת ה-Outbox, משדר אותן לתור ההודעות בפועל, ומסמן אותן כנשלחו. אם השידור נכשל, הרשומה פשוט נשארת בטבלה וינסה שוב בסבב הבא — שום אירוע לא הולך לאיבוד.
Idempotency בצד הצרכן
מכיוון שתהליך ה-Relay עשוי לשדר את אותו אירוע פעמיים (אם הוא קרס אחרי השידור אבל לפני הסימון כנשלח), הצרכנים של האירוע צריכים להיות אידמפוטנטיים — בדיוק אותו עיקרון שהרחבנו עליו במאמר על Idempotency ב-API.
Change Data Capture: אלטרנטיבה מתקדמת יותר
במקום Relay שסורק את הטבלה בפולינג, כלי Change Data Capture (כמו Debezium) יכולים לקרוא ישירות את יומן השינויים (WAL) של מסד הנתונים ולשדר אירועים כמעט בזמן אמת, ברגע שהשורה נכתבת — ללא עומס פולינג חוזר ונשנה.
ניקוי הטבלה לאורך זמן
רשומות Outbox שכבר שודרו בהצלחה צריכות תהליך ניקוי תקופתי, אחרת הטבלה גדלה ללא הגבלה. ניקוי אגרסיבי מדי, לעומת זאת, עלול למחוק רשומות שעדיין נחוצות לצורך דיבוג או ביקורת — האיזון הנכון תלוי בדרישות השמירה של המערכת הספציפית.
מתי זה שווה את המורכבות
Outbox Pattern מוסיף טבלה, תהליך רקע, ולוגיקת ניקוי — מורכבות אמיתית. הוא מוצדק כשאיבוד אירוע עסקי (חיוב שלא דווח, הזמנה שלא הגיעה למחסן) עולה יותר מכאב הראש התפעולי, בדיוק סוג האיזון שדנו בו במאמר על Saga Pattern.
מוניטורינג על טבלת ה-Outbox
רשומות שנשארות ב-Outbox זמן רב מדי בלי להישלח הן איתות ברור לבעיה בתהליך ה-Relay עצמו — תור מלא, כשל תקשורת מתמשך. ניטור גודל התור וזמן ההמתנה הממוצע חושף בעיות כאלה הרבה לפני שהן הופכות לפער עסקי משמעותי.
סדר האירועים חשוב
ברוב המקרים חשוב לשמור על סדר השידור של האירועים כפי שהם נכתבו לטבלה — כדי שצרכן שמעבד אותם ברצף יראה את השינויים באותו סדר שבו הם קרו בפועל, ולא יגיע למצב שבו הוא רואה עדכון לפני היצירה שקדמה לו.
בונים מערכת שצריכה לשדר אירועים באמינות מלאה? וואטסאפ.
Outbox מול Two-Phase Commit: למה לא פשוט טרנזקציה מבוזרת
לכאורה, אפשר לפתור את אותה בעיה עם טרנזקציה מבוזרת שמקיפה גם את מסד הנתונים וגם את תור ההודעות (Two-Phase Commit). בפועל, 2PC דורש שכל המערכות המעורבות יתמכו בפרוטוקול הזה, מטיל נעילות שמשפיעות על ביצועים, ורוב תורי ההודעות המודרניים בכלל לא תומכים בו. Outbox Pattern פותר את אותה בעיה בלי לדרוש שום תמיכה מיוחדת מצד תור ההודעות — הוא נשען רק על היכולת הבסיסית והנתמכת בכל מסד נתונים רלציוני: טרנזקציה מקומית אחת.
תדירות הפולינג: האיזון בין השהיה לעומס
תהליך ה-Relay שסורק את טבלת ה-Outbox צריך תדירות מוגדרת: פולינג תכוף מדי מטיל עומס מיותר על מסד הנתונים גם כשאין אירועים חדשים לשידור; פולינג נדיר מדי מוסיף השהיה בין הרגע שהאירוע נכתב לרגע שהוא בפועל משודר לצרכנים. הבחירה בתדירות הנכונה תלויה בדרישות העסקיות — מערכת שדורשת שידור כמעט מיידי תיטה לפולינג תכוף יותר, ואילו מערכת שסובלת עיכוב של כמה שניות יכולה להסתפק בתדירות נמוכה יותר וחיסכון בעומס.
אבטחת טבלת ה-Outbox עצמה
טבלת ה-Outbox מכילה בפועל את כל האירועים העסקיים שהמערכת אי-פעם שידרה — כולל, לעיתים, פרטים רגישים. גישה אליה צריכה להיות מוגבלת בדיוק כמו לכל טבלה עסקית אחרת: רק תהליך ה-Relay עצמו זקוק לגישת קריאה שוטפת, ורק השירותים שכותבים אליה זקוקים לגישת כתיבה. הרשאות רחבות מדי לטבלת Outbox הופכות אותה לנקודת דליפה פוטנציאלית של כל היסטוריית האירועים העסקיים של המערכת.
בדיקות אמינות לתהליך ה-Relay
מכיוון שתהליך ה-Relay הוא רכיב קריטי שכל אירוע עסקי עובר דרכו, הוא ראוי לכיסוי בדיקות ייעודי: מה קורה כשתור ההודעות לא זמין זמנית, מה קורה כשהתהליך קורס באמצע סבב שידור, ומה קורה כשמגיע נפח גדול חריג של אירועים בבת אחת. בדיקות שמדמות תרחישי כשל אלה מראש, ולא רק את התרחיש התקין, הן ההבדל בין Relay שעומד בעומס אמיתי לבין רכיב שנשבר בדיוק כשהכי צריך אותו.
טיפול בעומסי שיא ובצבירת אירועים
בזמן עומס שיא — מבצע גדול, קמפיין שיווקי — כמות האירועים שנכתבים לטבלת ה-Outbox יכולה לגדול הרבה מעבר לקצב הרגיל. אם תהליך ה-Relay לא מתוכנן לקצב שידור גמיש שמתאים את עצמו לגודל התור, נוצרת צבירה (Backlog) שמעכבת אירועים חדשים גם אחרי שהעומס חלף. תכנון נכון כולל אפשרות להגדיל את קצב העיבוד באופן דינמי, ולנטר את גודל התור בזמן אמת כדי לזהות צבירה לפני שהיא הופכת לפער עסקי מורגש.
שימוש בטבלת ה-Outbox כתיעוד לביקורת
יתרון עקיף של Outbox Pattern הוא שהוא יוצר, כמעט בלי מאמץ נוסף, רישום כרונולוגי מלא של כל אירוע עסקי שהתרחש במערכת. רישום כזה שימושי מעבר לשידור לתור — הוא מספק תיעוד לביקורת (Audit Trail) שאפשר להיעזר בו כשצריך לשחזר מה בדיוק קרה בתקופה מסוימת, גם אם השאלה שנשאלת לא קשורה כלל לתקלה בתור ההודעות עצמו.
Outbox Pattern בסביבת מיקרו-שירותים מרובה
כשמערכת בנויה מהרבה מיקרו-שירותים, וכל אחד מהם מנהל מסד נתונים משלו, לכל שירות שצריך לשדר אירועים יש טבלת Outbox נפרדת משלו — אין טבלה גלובלית אחת משותפת לכל המערכת. זה מתיישב טבעי עם עיקרון הבעלות הבלעדית על נתונים במיקרו-שירותים: כל שירות אחראי על ה-Outbox שלו בדיוק כפי שהוא אחראי על שאר הנתונים העסקיים שהוא מנהל, בלי תלות בטבלה משותפת שדורשת תיאום בין צוותים שונים.
מתי לא כדאי Outbox Pattern: תקשורת בזמן אמת
Outbox Pattern בנוי סביב עיבוד אסינכרוני, ולכן פחות מתאים לתרחישים שדורשים אישור מיידי שהאירוע שודר בהצלחה לפני שהפעולה העסקית עצמה נחשבת גמורה. תקשורת שדורשת אישור סינכרוני קפדני כזה, לדוגמה תיאום ישיר בין שני שירותים שחייבים לדעת מיידית שהצד השני קיבל את המידע, מתאימה יותר לקריאת API ישירה ומחכה לתשובה, ולא לדפוס שמבוסס מיסודו על שידור מאוחר ולא מיידי.
כלים קיימים שמממשים את הדפוס מוכן
לא חובה לממש את כל מנגנון ה-Outbox ידנית מאפס. פריימוורקים וספריות רבים למסדי נתונים רלציוניים כבר כוללים תמיכה מובנית או תוספים מוכנים שמממשים את דפוס ה-Outbox, כולל תהליך Relay שמוכן לשימוש. שימוש בפתרון בדוק וקיים, במקום לבנות מנגנון בית מאפס, מפחית משמעותית את הסיכון לבאגים עדינים בלוגיקה קריטית כמו זו, שקל לפספס בה תרחיש קצה אחד שגורם לאובדן נתונים בדיוק במקרה שהמנגנון כולו נועד למנוע.
שאלות נפוצות
האם Outbox Pattern מתאים גם למסדי נתונים לא-רלציוניים?
העיקרון דורש טרנזקציה אטומית שמקיפה גם את השינוי העסקי וגם את כתיבת האירוע, ולכן מתאים בקלות רבה יותר למסדי נתונים רלציוניים עם תמיכה מלאה בטרנזקציות. חלק ממסדי הנתונים הלא-רלציוניים תומכים בטרנזקציות ברמה מוגבלת, ואז אפשר להחיל את אותו עיקרון בזהירות, תוך בדיקה מדויקת של גבולות התמיכה.
מה ההבדל בין Outbox Pattern ל-Two-Phase Commit?
שניהם פותרים את אותה בעיה — עקביות בין שתי פעולות בשתי מערכות שונות — אבל בדרכים שונות. 2PC דורש תיאום ישיר בין המערכות בזמן אמת ותמיכה מפורשת בפרוטוקול משני הצדדים. Outbox Pattern נשען רק על טרנזקציה מקומית רגילה במסד הנתונים, ומעביר את התיאום עם התור לתהליך אסינכרוני נפרד.
כמה השהיה (latency) Outbox Pattern מוסיף בפועל בין הכתיבה לשידור?
תלוי בתדירות הפולינג של תהליך ה-Relay, או בזמן התגובה של מנגנון ה-CDC אם נעשה בו שימוש. עם CDC ההשהיה יכולה להיות קרובה לזמן אמת; עם פולינג פשוט, ההשהיה תואמת בקירוב את מרווח הסריקה שהוגדר.
האם צריך Outbox Pattern גם במערכת קטנה עם עומס נמוך?
אם איבוד אירוע בודד, כמו חיוב שלא דווח או הזמנה שלא הגיעה, אינו קריטי עסקית, המורכבות הנוספת של Outbox Pattern עשויה שלא להיות מוצדקת במערכת קטנה. ברגע שיש השלכה עסקית ממשית לאובדן אירוע, גם מערכת קטנה נהנית מהעקביות שהדפוס מספק.
מה קורה אם תהליך ה-Relay עצמו קורס באמצע העבודה?
מכיוון שהאירועים שמורים בטבלה עצמה עד שהם מסומנים כנשלחו, קריסת ה-Relay לא גורמת לאובדן מידע — ברגע שהתהליך עולה מחדש, הוא פשוט ממשיך לסרוק את הרשומות שעדיין לא סומנו כנשלחו. זו בדיוק הסיבה שהדפוס עמיד לכשלים, לא רק לתקלות בתור ההודעות עצמו.
תגיות: Outbox Pattern · Event-Driven Architecture · Message Queue · עקביות נתונים