למה batch transactions לא מגיעות לרשת
Batch transactions נראות כמו קריאה אחת, אבל הרשת רואה בהן עשרות פעולות עצמאיות. כל אחת מקבלת מספר סידורי משלה, ומספיק שאחת נתקעת — התור נעצר: הקריאות הבאות של החבילה ממתינות לשחרור המספר ותלויות ב-mempool כ"ממתינות" ולא כ"בוצעו".
שליחת חבילת קריאות עם מספר אחד או עם דילוג נשברת עוד קודם — הצומת דוחה סט כזה כולו, בלי לנתח את הסיבות.
צוואר הבקבוק השני — מגבלת gas ל-block והתחרות על מקום בו. חבילה שמחושבת ל-block אחד כמעט תמיד לא נכנסת בו: transactions זרות מעלות את מחיר ה-gas, וחלק מהקריאות שלנו נדחק ל-block הבא.
אם הלוגיקה של ה-contract לא מותאמת לביצוע ממושך בזמן, בין הצעדים מספיק משתנה ה-state שעליו נשענה החבילה.
כאן מופיע ביצוע חלקי. שלוש קריאות מתוך שבע עברו — ה-balance חויב, ה-limits עודכנו, אבל העברת הנכסים לא התקיימה. חוסר סנכרון כזה של ה-state יקר יותר משגיאה בקריאה אחת: האפליקציה מחשיבה את הפעולה כהושלמה, בעוד שמחצית מהצעדים שלה נותרו לא מבוצעים.
חבילה שנדחתה בלי atomicity משאירה את המערכת במצב ביניים, שאותו צריך לפרק ידנית — להצליב logs, להשלים צעדים חסרים ולהסביר למשתמש למה החיוב לא הוביל לתוצאה.
מספר סידורי, מגבלת gas והיעדר atomicity מכים יחד: אותה חבילה בדיוק עוברת ברשת הבדיקות ומתפרקת ברשת הראשית. נפרט בהמשך איך שליחת חבילת קריאות נסגרת ברמת ה-contract, ולא בתקווה ל-block מוצלח.
מה נותנת שליחה מקבצת של קריאות בפועל
שליחה מקבצת של קריאות, או batch transactions, הופכת כמה פניות נפרדות ל-contract ל-transaction אחת עם מערך של פעולות בתוכה. העלות הבסיסית של ה-transaction ובדיקת החתימה משולמות פעם אחת במקום N — בעשר פעולות מאותו סוג הצריכה של gas לפעולה יורדת פי 2-3, והרווח גדל עם גודל החבילה.
הצירוף "קודם אישור, אחר כך החלפה" מתקפל לקריאה אחת, וה-state הביניים של ה-contract לא תלוי בין שתי אישורים.
החלק השני של הרווח — הסרת המרוץ על nonce. כשעשרה משלוחים יוצאים במקביל, חלק נדחים כמיושנים, חלק נתקעים בתור ודורשים האצה ידנית. חבילה אחת — nonce אחד, חתימה אחת, אישור אחד: סוג הכשלים שקשור לתחרות על תור השליחה נעלם כולו.
Atomicity סוגרת סוג כשלים יקר יותר — ביצוע חלקי. אם לפחות פעולה אחת בתוך החבילה לא עוברת בדיקה, כל החבילה מתבטלת: אין מצב שבו הפעולה הראשונה הוחלה והשנייה לא, וב-contract נשארו נכסים חסומים עד לפרק ידני. עבור המשתמש זו המתנה אחת במקום שרשרת "אישר — לא עבד — אישר שוב".
העומס על הרשת באותו היקף עבודה מועילה יורד: פחות transactions בתור, פחות בקשות לאישור, פחות סיבות להיתפס ברגע של מחיר שיא של ה-block.
גם המגבלות הוגנות — מגבלת gas ל-block ולעצם ה-transaction, ולכן סטים גדולים נחתכים לחלקים, והרכב החבילה לפני השליחה מורץ בסימולציה לקריאה בלבד, כדי לא לשלם על ביטול מובטח.
פורמטים של batch transactions: השוואת גישות
פורמט החבילה קובע היכן נבדקות התנאים ומי אחראי להקשר המשותף של הקריאות. ברשתות מבוססות Ethereum ותואמות להן מיישמים שלוש סכמות: contract-מתווך, multi-call בתוך ה-contract וסדרה עוקבת. ההבדל ניכר בכשל חלקי — ממנו תלוי אם תאבדו את כל החבילה כולה או רק צעד אחד כושל.
| אפשרות | מתי מתאימה | מגבלות |
|---|---|---|
| מתווך-אגרגטור | הצעדים נוגעים ב-contracts שונים, נדרשות בדיקות הרשאה משותפות, השהיה ומעקב אחר limits | נוצר contract מהימן: את הלוגיקה שלו צריך לבדוק בנפרד, שגיאה בו חוסמת את כל החבילה |
| Multi-call בתוך ה-contract | כל הפעולות שייכות ל-contract אחד וחייבות להתבצע ברצף בלתי ניתן להפרדה | שינוי הלוגיקה דורש contract חדש, והיקף של קריאה אחת מוגבל על ידי ביצוע ה-block |
| סדרה עוקבת של קריאות | הצעדים עצמאיים, וחלק מהם מותר לדלג | אין atomicity: מצבי הביניים גלויים ברשת, וכשל של צעד מחייב לחזור על השאר בנפרד |
מה אנחנו לוקחים בחשבון בבחירה
מגבלת גודל החבילה נשענת לא על מספר הקריאות אלא על עלות הביצוע: ככל שהחבילה ארוכה יותר, כך גבוה הסיכון לא להסתדר בגבולות ה-block. אנחנו מחלקים אותה לחלקים לפי צריכה מתוכננת ומוסיפים מפתחות לשליחה חוזרת, כדי שכשל בתקשורת לא יכפיל פעולות.
עבור סדרות ארוכות אנחנו מאגדים בדיקות: הן נשארות חד-פעמיות, ומספר הצעדים גדל בלי לבנות מחדש את ה-contract. תאימות עם contracts שכבר פרוסים אנחנו בודקים לפני תחילת העבודה — אחרת הסכמה הנבחרת תדרוש מיגרציה.
איך אנחנו מיישמים שליחה מקבצת: שלבי העבודה
שליחה מקבצת מתחילה לא בקוד אלא במיפוי של האופן שבו transactions יוצאות כיום. כל עוד לא רואים אילו קריאות תלויות בתוצאה של הקודמת ואילו אפשר לאחד לחבילה אחת, כל האצה הופכת לפעולות תקועות ולפער ב-state.
- ביקורת תרחישי השליחה. מפרקים את הפעולות הנוכחיות לצעדים: היכן שרשרת הקריאות הכרחית, היכן היא נוצרה בגלל מאפייני הממשק, ומה אפשר לאחד לחבילה אחת בלי לשנות את התוצאה.
- תרשים הזרימות. מתעדים את המסלול של כל פעולה: מי מרכיב את החבילה, במה היא נחתמת, באיזה סדר יוצאות הקריאות ומה נחשב להשלמה מוצלחת.
- הרצה ברשת הבדיקות. מצליבים את החישוב עם המציאות: צריכת gas לחבילה מול סכום הקריאות הנפרדות, זמן האישור, ההתנהגות בעומס יתר.
- בדיקת מגבלות. משווים את גודל החבילה לגבול העליון של gas ל-block ולמגבלות הצומת: חבילה שלא נכנסת כולה לא מבוצעת בכלל.
- טיפול בכשלים. מתארים את ההתנהגות בביצוע חלקי — שליחה חוזרת של קריאות שלא התקבלו, יומן פעולות, התראה לאחראי.
- מסירת נהלים. מוסרים תיעוד לתחזוקה: איך לשנות את הרכב החבילה, אילו מדדים לבדוק ולפי אילו ספים להגיב.
בסוף העבודה נשאר לצוות לא תיקון חד-פעמי אלא תהליך מתועד: הרכב החבילה משתנה לפי נוהל, והחיסכון ב-gas נראה במדדים ולא בדוח חד-פעמי.
מה נשאר אצל הלקוח אחרי היישום
קוד ב-repository הוא לא התוצאה היחידה של העבודה. אחרי היישום נשאר לצוות הלקוח סט ארטיפקטים שלפיהם אפשר להפעיל ולפתח את המערכת בלעדינו: מפרמטרים של שליחת חבילות ועד סדר הפעולות בעת כשל. כל סעיף אנחנו בודקים — מוסרים ומפרקים יחד עם המהנדסים שיישארו בתמיכה.
- קונפיגורציות שליחה — אסטרטגיות gas, גודל החבילה, מגבלות ל-batch transaction אחת, ספי ניסיונות חוזרים וזמן ההמתנה לאישור. הערכים לא מקודדים בקוד: משתנים דרך קונפיגורציה, ולצדם הסבר — מה ולפי איזה גידול בעומס לסובב.
- תרשימי זרימות — המסלול של הבקשה מקריאה ועד אישור: היכן נאספת החבילה, היכן נחתמת, היכן ממתינה להיכלל ב-block. מסומנות נקודות כשל וההתנהגות בעת החלפה או ביטול של transaction.
- מדיניות גישה למפתחות — תפקידים, מי ובאמצעות איזה שירות חותם, איך נשמרים המפתחות, סדר הרוטציה ושלילת הגישה. בבלוק נפרד — הפעולות בעת חשיפה.
- תיעוד — תיאור של contracts ומתודות, פורמטים של נתונים, קודי שגיאה עם פירוש ופעולה לכל אחד. מספיק כדי שמהנדס חדש ייכנס לעבודה לפי הטקסט ולא לפי סיפור.
- נוהל תחזוקה — מה אנחנו מנטרים (שיעור החבילות הכושלות, זמן האישור, יתרת הנכסים בכתובות החותמות), אילו התראות וספים, למי הן מגיעות ובאיזה סדר מגיבים.
- תוכנית שחזור — איך לחזור למצב הקודם, איך לשלוח מחדש חבילות שנתקעו ואיך לבדוק זאת במתחם בדיקות לפני הרצה אמיתית.
קייס: תשלומים אטומיים לשירות גיימינג
הלקוח — שירות גיימינג עם זיכויים המוניים יומיים: פרסים על משחקים, בונוסים על ריטנשן, החזרים על תקלות. כל תשלום יצא כפעולה נפרדת, ועם מאות אלפי נמענים בימי שיא רוב הצריכה נבעה לא מהפרס עצמו אלא מהוצאות נלוות: העלות הבסיסית של הרשומה, החתימה והמקום בתור. ככל שהעומס על רשת Ethereum היה צפוף יותר, כך לעתים קרובות יותר חבילות התשלום התפרקו באמצע.
הדבר הכואב ביותר היה לא הצריכה אלא הפעולות שנתקעו. אם תשלום אחד לא עבר, ה-handler נעצר, וההצלבה — מי כבר קיבל את הפרס ומי לא — נעשתה ידנית לפי היומן. הרצה חוזרת של אותו סט הובילה לזיכויים כפולים, ולכן לפני כל הפעלה מחדש ייצאו את רשימת הנמענים והצליבו אותה עם ההיסטוריה.
העברנו את התשלומים לסכמה מקבצת: קריאה אחת נושאת עד כמה מאות מקבלים, סכומים ומזהה שירותי של המנה. המזהה נרשם לפני החלוקה, ולכן ביצוע חוזר של אותה מנה נחסם בכניסה — הפעלה מחדש בטוחה ולא דורשת הצלבה ידנית. כשל אצל כל מקבל שהוא מבטל את המנה כולה: ה-state נשאר שלם, ולא "מחצית שולמה".
לפי המדדים: הצריכה לזיכוי אחד ירדה בערך פי שמונה — החלק הבסיסי מתחלק על כל החבילה, ושיעור הצריכה על הוצאות נלוות ירד מ-~60% לאחוזים בודדים. הפעולות שנתקעו הצטמצמו מעשרות בשבוע לבודדות, וסגירת יום המשחקים הפסיקה להימתח לשעות — המנות מאושרות תוך דקות.
במה נבדלת שליחה מקבצת מסדרה של קריאות?
שליחה מקבצת היא transaction אחת, שבתוכה smart contract קורא לכמה מתודות ברצף במסגרת ביצוע אחד. כל הקריאות נכנסות ל-block אחד, וה-state או משתנה כולו או לא משתנה בכלל.
סדרה של קריאות בנויה אחרת: אלו transactions נפרדות, כל אחת עם החתימה שלה, המספר שלה בתור וההמתנה לאישור. בין הצעדים עובר זמן, והתוצאה הביניים כבר רשומה ב-block.
אם הקריאה השלישית תיפול, הראשונות יישארו מבוצעות — את ביטול ה-state יהיה צריך לעשות ב-transaction מפצה נפרדת, שאותה עוד צריך להרכיב נכון.
סדר הביצוע בחבילה קבוע בקוד באופן נוקשה: הקריאות הולכות אחת אחרי השנייה בלי הפסקות, ואי אפשר להידחף ביניהן. בסדרה הסדר נשען רק על תור השליחה, ובין האישורים יכולה להיכנס אליו transaction זרה ולשנות את הנתונים שעליהם הסתמכתם.
הצריכה של gas נבדלת מאותה סיבה: בחבילה האגרה הבסיסית והחתימה משולמות פעם אחת על כל הפעולות, ובסדרה — על כל transaction בנפרד. ככל שיש יותר צעדים בשרשרת, כך הפער בולט יותר.
הגבול עובר בפשטות. אם הצעדים בלתי ניתנים להפרדה לוגית וחייבים להתבצע או להתבטל יחד — אלו batch transactions. אם בין הצעדים נדרשות השהיות, נתונים חיצוניים, בדיקת התוצאה בידי אדם או רישום חלקי של מצב ביניים — נשארת סדרה של קריאות עם המתנה לאישור.
ספקות טכניים ותשובות לגבי קריאות מקבצות
Batch transaction היא transaction אחת, שבתוכה מבוצעות ברצף כמה פעולות: העברה, קריאה למתודה, שינוי state. עבור dApp זה מסיר אישורים מיותרים וצריכת gas על כל צעד, אבל מוליד ארבע שאלות הנדסיות שסוגרים לפני התכנון.
האם אפשר לאסוף לחבילה אחת קריאות ל-contracts שונים?
כן — החבילה נאספת דרך contract-אגרגטור, שקורא בתורו לכתובות זרות ומחזיר תוצאות. המגבלה היא ב-atomicity: נפילה של כל קריאה שהיא מבטלת את כל הרצף. לכן פעולות עצמאיות, שאחת מהן עשויה לא לעבור לפי תנאי, עדיף לפזר לחבילות נפרדות.
מה יקרה בשליחה חוזרת?
נדרש מזהה לחבילה — hash של התוכן או מונה מונוטוני לכתובת. אם הצומת לא השיב והשליחה חזרה, ה-contract רואה מזהה שכבר בוצע ודוחה את הכפילות. בלי תג, חזרה על הפעולה בעת timeout נותנת ביצוע כפול וחוסר סנכרון ב-state.
האם נשמר סדר נוקשה של הפעולות בתוך החבילה?
הקריאות מבוצעות בדיוק לפי סדר ההוספה, וזה קריטי לצעדים קשורים: אישור גישה ואחריו החלפה חייבים ללכת באותה חבילה, אחרת בין לבין ישתנה ה-state והצעד השני ייפול. הצד השני — מצב הביניים לא נראה כלפי חוץ.
מה מגביל את גודל החבילה?
שני גבולות: מגבלת gas ל-transaction וקיבולת ה-block. את החבילה מרכיבים עם מרווח ביטחון, כדי שתיכנס ל-block בעומס הרשת הנוכחי. לפני השליחה מריצים אותה על צומת מקומי, ואם הצריכה נתקלת במגבלה, חותכים לחלקים לפי גבולות של צעדים משמעותיים — והסדר לא נשבר בכך.
בואו נדבר על המשימה: קריאות מקבצות ו-atomicity
כדי שהערכת המשימה תהיה הנדסית ולא מקורבת, נדרש סט קלט לפי לוגיקת השליחה הנוכחית. ככל שתתארו במדויק יותר את התרחישים והיקפי הפעולות, כך נניח פחות הנחות בתכנית העבודה והתחזית ללוחות הזמנים תהיה ריאלית יותר.
- תרחישי שליחה נוכחיים: אילו קריאות ל-contracts יוצאות כיום כ-batch transactions נפרדות, אילו — אחת-אחת, ובאיזה צעד הצריכת gas מפסיקה להיות צפויה.
- היקפי הפעולות: כמה קריאות בחבילה אחת, באיזו תדירות נוצרות חבילות, מהו עומס השיא בשעה וביממה.
- דרישות לסדר: האם יש תלות בין הקריאות, האם סדר הביצוע חשוב ומה נשבר בשינוי הסדר.
- האם ביצוע חלקי קביל: האם הקבוצה חייבת לעבור כולה או שמותר שחלק מהפעולות יוחלו וחלק יתבטלו.
- רשתות וסביבות יעד: היכן הלוגיקה פועלת — ברשת הבדיקות או ברשת הראשית, והאם נדרשים כללים זהים לכמה רשתות.
- הרשאות ותפקידים: מי חותם על החבילות, האם יש multi-sig, אילו מפתחות משמשים באוטומציה.
- מגבלות: גודל החבילה המרבי והסף שמתחתיו איחוד קריאות מאבד את המשמעות.
לפי הרשימה הזו נחזור עם הערכה ותכנית עבודה: היכן לאחד קריאות, היכן נדרשת atomicity, איך לטפל בכשלים ומה לעשות עם חבילות שכבר נשלחו.







