במסמך הזה מוסבר איך לבקש מפונקציות ברקע אסינכרוניות (לא HTTPS) לנסות שוב במקרה של כשל.
למה פונקציות מבוססות-אירועים לא מצליחות להסתיים
במקרים נדירים, יכול להיות שפונקציה תצא מוקדם מדי בגלל שגיאה פנימית, וכברירת מחדל יכול להיות שיהיה ניסיון חוזר אוטומטי להפעלה של הפונקציה, או שלא יהיה ניסיון כזה.
בדרך כלל, פונקציה מבוססת-אירועים עלולה להיכשל בהשלמה בגלל שגיאות שמוחזרות בקוד הפונקציה עצמה. יכולות להיות לכך כמה סיבות:
- הפונקציה מכילה באג וזמן הריצה זורק חריגה.
- הפונקציה לא מצליחה להגיע לנקודת קצה של שירות, או שהזמן הקצוב לתפוגה שלה מסתיים במהלך הניסיון להגיע לנקודת הקצה.
- הפונקציה יוצרת חריגה בכוונה (לדוגמה, כשפרמטר לא עובר אימות).
- פונקציית Node.js מחזירה אובייקט promise שנדחה, או מעבירה ערך שאינו
nullלקריאה חוזרת.
בכל אחד מהמקרים שלמעלה, הפונקציה תפסיק לפעול ותחזיר שגיאה. לגורמים שמפעילים אירועים שיוצרים את ההודעות יש מדיניות ניסיון חוזר שאפשר להתאים אישית כדי לענות על הצרכים של הפונקציה.
סמנטיקה של ניסיון חוזר
Cloud Functions מספקת הפעלה של פונקציה מבוססת-אירועים לפחות פעם אחת לכל אירוע שמופק ממקור אירועים. כברירת מחדל, אם הפעלה של פונקציה מסתיימת בשגיאה, הפונקציה לא מופעלת שוב והאירוע מושמט. כשמפעילים ניסיונות חוזרים בפונקציה מבוססת-אירועים, Cloud Functions מנסים להפעיל שוב פונקציה שנכשלה עד שהיא מסתיימת בהצלחה או עד שחלון הניסיונות החוזרים מסתיים.
אם לא מפעילים ניסיונות חוזרים לפונקציה (ברירת המחדל), הפונקציה תמיד מדווחת שהיא בוצעה בהצלחה, וקוד התגובה 200 OK עשוי להופיע ביומנים שלה. זה קורה גם אם הפונקציה נתקלה בשגיאה. כדי להבהיר מתי הפונקציה נתקלת בשגיאה, חשוב לדווח על שגיאות בצורה מתאימה.
הגדרת ניסיונות חוזרים מקוד הפונקציה
באמצעות Cloud Functions for Firebase, אפשר להפעיל ניסיונות חוזרים בקוד של פונקציה. כדי לעשות את זה לאירוע ברקע, כמו יצירה של מסמך חדש ב-Firestore, מגדירים את האפשרות failurePolicy (דור ראשון) או retry (דור שני) של המדיניות ל-true:
דור ראשון
exports.docCreated = functions
.runWith({
// retry on failure
failurePolicy: true,
})
.firestore.document("my-collection/{docId}")
.onCreate((change, context) => {
/* ... */
});
דור שני
const { onDocumentCreated } = require("firebase-functions/firestore");
exports.docCreated = onDocumentCreated(
{
// retry on failure
retry: true,
},
"my-collection/{docId}",
(event) => {
/* ... */
},
);
הגדרת true כמו שמוצג מגדירה פונקציה לניסיון חוזר במקרה של כשל.
חלון ניסיון חוזר
בפונקציות מהדור השני, חלון הניסיון החוזר הזה מסתיים אחרי 24 שעות. התוקף של פונקציות מהדור הראשון יפוג אחרי 7 ימים. Cloud Functions מבצע ניסיונות חוזרים להפעלת פונקציות חדשות מבוססות-אירועים באמצעות אסטרטגיה של השהיה מעריכית לפני ניסיון חוזר (exponential backoff), עם השהיה הולכת וגדלה של בין 10 ל-600 שניות. המדיניות הזו מופעלת על פונקציות חדשות בפעם הראשונה שמפעילים אותן. השינוי לא יחול באופן רטרואקטיבי על פונקציות קיימות שפרסמתם לפני שהשינויים שמתוארים בנתוני הגרסה האלה נכנסו לתוקף, גם אם תפרסמו מחדש את הפונקציות.שיטות מומלצות
בקטע הזה מתוארות שיטות מומלצות לשימוש בניסיונות חוזרים.
שימוש בניסיון חוזר לתיקון שגיאות חולפות
הפונקציה מופעלת מחדש באופן רציף עד להפעלה מוצלחת, ולכן צריך לבצע בדיקות כדי לוודא שאין בקוד שגיאות קבועות כמו באגים, לפני שמפעילים את האפשרות להפעלה מחדש. השימוש בנסיונות חוזרים מומלץ לטיפול בכשלים לסירוגין או זמניים, שיש סיכוי גבוה שייפתרו בניסיון חוזר, כמו נקודת קצה של שירות לא יציב או פסק זמן.
הגדרת תנאי סיום כדי למנוע לולאות אינסופיות של ניסיונות חוזרים
מומלץ להגן על הפונקציה מפני לולאה אינסופית כשמשתמשים בניסיונות חוזרים. כדי לעשות את זה, צריך לכלול תנאי סיום מוגדר היטב, לפני שהפונקציה מתחילה לעבד. שימו לב שהטכניקה הזו פועלת רק אם הפונקציה מתחילה לפעול בהצלחה ויכולה להעריך את תנאי הסיום.
גישה פשוטה ויעילה היא להשליך אירועים עם חותמות זמן מלפני זמן מסוים. כך אפשר למנוע ביצועים מוגזמים אם הכשלים נמשכים או ארוכים מהצפוי.
לדוגמה, קטע הקוד הזה מבטל את כל האירועים שהתרחשו לפני יותר מ-10 שניות:
const eventAgeMs = Date.now() - Date.parse(event.timestamp);
const eventMaxAgeMs = 10000;
if (eventAgeMs > eventMaxAgeMs) {
console.log(`Dropping event ${event} with age[ms]: ${eventAgeMs}`);
callback();
return;
}
שימוש ב-catch עם Promises
אם הפעלתם ניסיונות חוזרים בפונקציה, כל שגיאה שלא טופלה תפעיל ניסיון חוזר. מוודאים שהקוד מתעד שגיאות שלא אמורות לגרום לניסיון חוזר.
לדוגמה:
return doFooAsync().catch((err) => {
if (isFatal(err)) {
console.error(`Fatal error ${err}`);
}
return Promise.reject(err);
});
איך הופכים פונקציות מבוססות-אירועים שניתן לבצע בהן ניסיון חוזר לאידמפוטנטיות
פונקציות מבוססות-אירועים שאפשר לבצע ניסיון חוזר שלהן חייבות להיות אידמפוטנטיות. ריכזנו כאן כמה הנחיות כלליות ליצירת פונקציה אידמפוטנטית:
- הרבה ממשקי API חיצוניים (כמו Stripe) מאפשרים לספק מפתח אידמפוטנטיות כפרמטר. אם אתם משתמשים בממשק API כזה, עליכם להשתמש במזהה האירוע כמפתח האידמפוטנטיות.
- אידמפוטנטיות פועלת היטב עם מסירה של לפחות פעם אחת, כי היא מאפשרת ניסיון חוזר בבטחה. לכן, שיטה מומלצת כללית לכתיבת קוד אמין היא לשלב בין אידמפוטנטיות לבין ניסיונות חוזרים.
- חשוב לוודא שהקוד הוא אידמפוטנטי באופן פנימי. לדוגמה:
- מוודאים שהמוטציות יכולות לקרות יותר מפעם אחת בלי לשנות את התוצאה.
- שליחת שאילתה למצב מסד הנתונים בעסקה לפני שינוי המצב.
- חשוב לוודא שכל תופעות הלוואי הן אידמפוטנטיות.
- הטלת בדיקה טרנזקציונלית מחוץ לפונקציה, ללא תלות בקוד. לדוגמה, אפשר לשמור את הסטטוס במקום כלשהו שבו מתועד שמזהה אירוע מסוים כבר עבר עיבוד.
- טיפול בקריאות כפולות לפונקציות מחוץ לפס. לדוגמה, אפשר להגדיר תהליך ניקוי נפרד שמנקה את הנתונים אחרי קריאות כפולות לפונקציות.
הגדרת מדיניות הניסיון החוזר
בהתאם לצרכים של הפונקציה, יכול להיות שתרצו להגדיר את מדיניות הניסיון החוזר ישירות. כך תוכלו להגדיר כל שילוב של האפשרויות הבאות:
- לקצר את חלון הניסיון החוזר מ-7 ימים ל-10 דקות בלבד.
- שינוי הזמן המינימלי והמקסימלי להשהיה באסטרטגיית הניסיון החוזר של ההשהיה האקספוננציאלית.
- משנים את אסטרטגיית הניסיון החוזר לניסיון חוזר מיידי.
- מגדירים נושא להודעות ללא מוצא.
- מגדירים מספר מקסימלי ומספר מינימלי של ניסיונות מסירה.
כדי להגדיר את מדיניות הניסיון החוזר:
- כותבים פונקציית HTTP.
- משתמשים ב-Pub/Sub API כדי ליצור מינוי Pub/Sub, ומציינים את כתובת ה-URL של הפונקציה כיעד.
מידע נוסף על הגדרה ישירה של Pub/Sub זמין במאמר Pub/Sub בנושא טיפול בכשלים.