Two-Phase Commit: איך שומרים על אטומיות בין כמה מסדי נתונים

מאת צוות מדיה דיל · 29.06.2026 · טכנולוגיה · 7 דק׳ קריאה

שלב Prepare, שלב Commit, Coordinator יחיד כנקודת כשל, נעילת משאבים, Saga Pattern, ו-Compensating Transactions.

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

שלב Prepare: הצבעה בלי מחויבות

בשלב הראשון, ה-coordinator שולח לכל משתתף (participant) בקשת Prepare: האם אתה מסוגל לבצע את הפעולה הזו. כל משתתף נועל את המשאבים הרלוונטיים, כותב את השינוי ל-log מקומי, ומשיב Yes או No, בלי לבצע commit בפועל עדיין. אם כל המשתתפים משיבים Yes, הטרנזקציה עברה את שלב ההסכמה. אם ולו משתתף אחד משיב No, או לא עונה בזמן, כל התהליך מבוטל כבר בשלב הזה, לפני שנעשה כל שינוי בלתי הפיך במערכת כלשהי.

שלב Commit: ביצוע סופי ובלתי הפיך

רק אחרי שכל המשתתפים הצביעו Yes, ה-coordinator שולח פקודת Commit סופית, וכל משתתף מבצע את השינוי בפועל ומשחרר את הנעילות. אם מישהו הצביע No, נשלחת פקודת Abort במקום, וכל משתתף מבטל את מה שהכין בשלב ה-Prepare. הנקודה הקריטית היא שברגע שמשתתף הצביע Yes, הוא מחויב לבצע commit אם תגיע הפקודה, הוא לא יכול לחזור בו, גם אם קרס וקם מחדש, מה שדורש כתיבת מצב ה-Prepare לדיסק לפני התשובה.

נקודת התורפה: Coordinator יחיד

הבעיה המרכזית של 2PC היא ש-coordinator שקורס בין שליחת ה-Prepare לשליחת ה-Commit משאיר את כל המשתתפים תקועים במצב blocked, הם נעלו משאבים והצביעו Yes, אבל אין להם דרך לדעת אם להשלים או לבטל בלי לשמוע מה-coordinator. משתתף לא יכול סתם להחליט לבד, כי משתתפים אחרים אולי כבר קיבלו Commit. הפתרון הנפוץ הוא coordinator עמיד יותר באמצעות Raft Consensus, אבל זה מוסיף מורכבות משמעותית למערכת.

מחיר הביצועים: נעילות לאורך כל התהליך

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

האלטרנטיבה המודרנית: Saga Pattern

רוב המערכות המבוזרות המודרניות נמנעות מ-2PC לטובת Saga Pattern: רצף של טרנזקציות מקומיות עצמאיות, כשכל אחת מפרסמת event שמפעיל את הצעד הבא, ואם צעד נכשל מריצים compensating transactions שמבטלות את מה שכבר בוצע. Saga לא מבטיחה אטומיות אמיתית ברגע נתון, אלא eventual consistency, אבל היא לא נועלת משאבים לאורך כל התהליך ולא תלויה ב-coordinator יחיד חי. תבנית קרובה שמסייעת ל-Saga לשמור אמינות היא Outbox Pattern, שמבטיח שכל שלב אכן יפרסם את ה-event שלו.

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

Presumed Abort - איך מקצרים את התקשורת

אחת האופטימיזציות הנפוצות ל-2PC נקראת Presumed Abort: אם coordinator לא מוצא רשומה בלוג עבור טרנזקציה מסוימת, הוא מניח כברירת מחדל שהיא בוטלה, ולא צריך לשמור רשומת log נפרדת עבור כל abort. זה מקטין את כמות הכתיבה לדיסק בתרחישים שבהם רוב הטרנזקציות מסתיימות בהצלחה, כי רק commit דורש רישום מפורש, בעוד abort הוא ברירת המחדל המשתמעת כשאין מידע אחר.

Three-Phase Commit - ניסיון לפתור את בעיית ה-Blocking

Three-Phase Commit (3PC) הוא הרחבה של 2PC שמנסה לפתור את בעיית ה-coordinator התקוע, על ידי הוספת שלב ביניים בין Prepare ל-Commit שמוודא שרוב המשתתפים מודעים להחלטה לפני שהיא הופכת סופית, כך שמשתתפים יכולים במקרים מסוימים להחליט בעצמם מה לעשות גם בלי לשמוע מה-coordinator. בפועל 3PC נחשב מוגבל בשימוש בעולם האמיתי, כי הוא מניח שאין network partition בו-זמנית עם קריסת coordinator, הנחה שלא תמיד מחזיקה במערכות מבוזרות אמיתיות, ורוב המערכות המודרניות מעדיפות לפתור את הבעיה בגישות אחרות לגמרי, כמו Saga או קונצנזוס מבוסס Raft.

מתי עדיין הגיוני להשתמש ב-2PC היום

למרות המגבלות, 2PC לא נעלם: הוא עדיין הבחירה הסבירה כשכל המשתתפים נמצאים בתוך אותו data center או רשת מהירה ואמינה יחסית, מה שמקטין את הסיכון לקריסת coordinator ממושכת, וכשהאפליקציה דורשת אטומיות אמיתית ברגע נתון ולא יכולה לחיות עם eventual consistency של Saga. מסדי נתונים מבוזרים מסוימים ומערכות תשלומים פנים-ארגוניות עדיין משתמשים בגרסאות של 2PC בדיוק בגלל הדרישה הזו לאטומיות מוחלטת, כשהיקף המשתתפים מוגבל ומוכר מראש.

2PC מול פרוטוקול קונצנזוס - שתי בעיות שנראות דומות

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

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

אידמפוטנטיות: הכרח כשהודעות עלולות להישלח פעמיים

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

מעקב ודיאגנוסטיקה: מה מנטרים במערכת שמריצה 2PC

ניטור בריא של מערכת שמריצה 2PC עוקב אחרי כמה מדדים ספציפיים: כמה טרנזקציות נמצאות במצב Prepare באופן ממושך, סימן לבעיה אצל coordinator או משתתף איטי, משך הזמן הממוצע מרגע Prepare ועד Commit, ותדירות מקרי ה-Abort. עלייה חדה במספר הטרנזקציות התקועות במצב ביניים היא לרוב הסימן המוקדם ביותר לבעיה, עוד לפני שהיא מתבטאת בתלונות משתמשים, ולכן דשבורד שמציג את המדדים האלה בזמן אמת שווה יותר מבדיקה ידנית מדי פעם.

Heuristic Decisions - כשמשתתף מחליט בעצמו

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

שאלות נפוצות

מה קורה למשתתף שהצביע Yes ולא מקבל תשובה מה-coordinator?

הוא נכנס למצב blocked - נשאר עם המשאבים נעולים ולא יכול להחליט לבד אם לבצע commit או abort, כי משתתפים אחרים אולי כבר קיבלו החלטה שונה. זו בדיוק החולשה המרכזית של 2PC, והפתרון הנפוץ הוא coordinator עמיד יותר או timeout מתוזמן שמפעיל תהליך שחזור.

האם 2PC מתאים למערכות עם הרבה משתתפים גיאוגרפית מרוחקים?

פחות מומלץ. ככל שיש יותר משתתפים ומרחק גיאוגרפי גדול יותר, זמן ה-round-trip גדל וכך גם משך הזמן שבו משאבים נשארים נעולים, מה שהופך את הפגיעה בביצועים למשמעותית יותר. במקרים כאלה Saga Pattern לרוב מתאים יותר.

מה ההבדל המעשי בין 2PC ל-Saga מבחינת חוויית המשתמש?

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

האם אפשר להשתמש ב-2PC רק לחלק מהטרנזקציה ובשאר להסתמך על eventual consistency?

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

איך יודעים אם מערכת קיימת סובלת מבעיית ה-blocking של 2PC?

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

תגיות: Two-Phase Commit · 2PC · Coordinator · Saga Pattern · Compensating Transactions · טרנזקציה מבוזרת

← חזרה לבלוג · צור קשר