ניסיון חוזר של פונקציות אסינכרוניות

במסמך הזה מוסבר איך לבקש מפונקציות אסינכרוניות (לא HTTP) ברקע לבצע ניסיון חוזר במקרה של כשל.

למה פונקציות מבוססות-אירועים לא מצליחות להסתיים

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

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

  • הפונקציה מכילה באג וזמן הריצה יוצר חריגה.
  • הפונקציה לא יכולה להגיע לנקודת קצה של שירות, או שהזמן הקצוב לתפוגה שלה מסתיים בזמן הניסיון לעשות זאת.
  • הפונקציה יוצרת חריגה בכוונה (לדוגמה, כשפרמטר לא עובר אימות).
  • פונקציית Node.js מחזירה הבטחה שנדחתה, או מעבירה ערך שאינו null לפונקציית קריאה חוזרת.

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

סמנטיקה של ניסיון חוזר

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

  • אם יוצרים פונקציות ב-Google Cloud Console או באמצעות Cloud Run Admin API, צריך ליצור ולנהל בנפרד את טריגרים לאירועים. לפעולות שמפעילות פונקציות יש התנהגויות ברירת מחדל של ניסיון חוזר, שאפשר להתאים לצרכים של הפונקציה.
  • פונקציות שנוצרו באמצעות Cloud Functions v2 API יוצרות באופן מרומז את הטריגרים הדרושים לאירועים, לדוגמה נושאי Pub/Sub או טריגרים של Eventarc. כברירת מחדל, הניסיונות החוזרים מושבתים עבור הטריגרים האלה, ואפשר להפעיל אותם מחדש באמצעות Cloud Functions v2 API.

פונקציות מבוססות-אירועים שנוצרו באמצעות Cloud Run

אם יוצרים פונקציות במסוף Google Cloud או באמצעות Cloud Run Admin API, צריך ליצור ולנהל בנפרד את טריגרים האירועים. מומלץ מאוד לבדוק את התנהגות ברירת המחדל של כל סוג טריגר:

פונקציות מבוססות-אירועים שנוצרו באמצעות Cloud Functions v2 API

פונקציות שנוצרו באמצעות Cloud Functions v2 API, למשל באמצעות Cloud Functions gcloud CLI,‏ REST API או Terraform, ייצרו וינהלו טריגרים לאירועים בשמכם. כברירת מחדל, אם הפעלה של פונקציה מסתיימת בשגיאה, הפונקציה לא מופעלת שוב והאירוע מושמט. כשמפעילים ניסיונות חוזרים בפונקציה מבוססת-אירועים, Cloud Functions מנסים שוב להפעיל פונקציה שנכשלה עד שהיא מסתיימת בהצלחה או עד שחלון הניסיונות החוזרים מסתיים.

כשניסיונות חוזרים לא מופעלים בפונקציה (זו ברירת המחדל), הפונקציה תמיד מדווחת שהיא בוצעה בהצלחה, ויכול להיות שיופיעו קודי תגובה 200 OK ביומנים שלה. הפעולה הזו מתרחשת גם אם הפונקציה נתקלה בשגיאה. כדי להבהיר מתי הפונקציה נתקלת בשגיאה, חשוב לדווח על שגיאות בצורה מתאימה.

הפעלה או השבתה של ניסיונות חוזרים

הגדרת ניסיונות חוזרים מקוד הפונקציה

באמצעות Cloud Functions for Firebase, אפשר להפעיל ניסיונות חוזרים בקוד של פונקציה. כדי לעשות זאת עבור פונקציית רקע כמו functions.foo.onBar(myHandler);, משתמשים ב-runWith ומגדירים מדיניות לגבי כשלים:

functions.runWith({failurePolicy: true}).foo.onBar(myHandler);

ההגדרה 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 דקות בלבד.
  • שינוי הזמן המינימלי והמקסימלי להשהיה של אסטרטגיית הניסיון החוזר עם השהיה אקספוננציאלית.
  • משנים את אסטרטגיית הניסיון החוזר לניסיון חוזר מיידי.
  • מגדירים נושא להודעות ללא מוצא.
  • מגדירים מספר מקסימלי ומינימלי של ניסיונות מסירה.

כדי להגדיר את מדיניות הניסיון החוזר:

  1. כתיבת פונקציית HTTP.
  2. משתמשים ב-Pub/Sub API כדי ליצור מינוי Pub/Sub, ומציינים את כתובת ה-URL של הפונקציה כיעד.

מידע נוסף על הגדרה ישירה של Pub/Sub זמין בPub/Subמאמרים בנושא טיפול בכשלים.