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

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

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

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

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

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

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

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

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

  • כדי ליצור פונקציות במסוף Google Cloud או באמצעות 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. לדוגמה, באמצעות gcloud CLI של Cloud Functions, ה-API ל-REST או Terraform, תוכלו ליצור ולנהל טריגרים של אירועים בשמכם. כברירת מחדל, אם קריאה לפונקציה מסתיימת בשגיאה, הפונקציה לא מופעלת שוב והאירוע נמחק. כשמפעילים ניסיונות חוזרים בפונקציה מבוססת-אירועים, Cloud Functions מנסה שוב להפעיל את הפונקציה שנכשלה עד שהיא מסתיימת בהצלחה או עד שחלון הניסיונות החוזרים פג.

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

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

הגדרת ניסיונות חוזרים מהמסוף

אם יוצרים פונקציה חדשה:

  1. במסך Create Function, בקטע Trigger בוחרים את סוג האירוע שישמש כטריגר לפונקציה.
  2. מסמנים את התיבה Retry on failure כדי להפעיל ניסיונות חוזרים.

אם מעדכנים פונקציה קיימת:

  1. בדף Cloud Functions Overview, לוחצים על שם הפונקציה שרוצים לעדכן כדי לפתוח את המסך Function details. לאחר מכן, בוחרים באפשרות Edit בסרגל התפריטים כדי להציג את החלונית Trigger.
  2. מסמנים או מבטלים את הסימון של התיבה Retry on failure כדי להפעיל או להשבית ניסיונות חוזרים.

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

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

שיטות מומלצות

בקטע הזה מתוארות שיטות מומלצות לשימוש בניסיונות חוזרים.

שימוש בניסיון חוזר כדי לטפל בשגיאות זמניות

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

הגדרת תנאי סיום כדי למנוע לולאות של ניסיונות חוזרים אינסופיים

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

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

לדוגמה, קטע הקוד הזה מבטל את כל האירועים שנוצרו לפני יותר מ-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);
});

איך הופכים פונקציות מבוססות-אירועים שניתן לנסות שוב לפונקציות חד-פעמיות (idempotent)

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

  • הרבה ממשקי API חיצוניים (כמו Stripe) מאפשרים לספק מפתח אימידנסיה כפרמטר. אם אתם משתמשים ב-API כזה, צריך להשתמש במזהה האירוע בתור מפתח האימות החד-פעמי.
  • אידמפוטנטיות פועלת היטב עם העברה אחת לפחות, כי היא מאפשרת לבצע ניסיון חוזר בבטחה. לכן, שיטה מומלצת לכתיבה של קוד מהימן היא לשלב בין אי-תלותיות ברצף לבין ניסיונות חוזרים.
  • מוודאים שהקוד הוא אידמפוטנטי מבפנים. לדוגמה:
    • חשוב לוודא שאפשר לבצע מוטציות יותר מפעם אחת בלי לשנות את התוצאה.
    • שאילתת המצב של מסד הנתונים בטרנזקציה לפני שינוי המצב.
    • חשוב לוודא שכל ההשפעות הנוספות הן גם הן חד-פעמיות.
  • להחיל בדיקה עסקית מחוץ לפונקציה, ללא קשר לקוד. לדוגמה, לשמור מצב במקום כלשהו שבו מתועד שמזהה אירוע מסוים כבר עבר עיבוד.
  • טיפול בקריאות כפולות לפונקציות מחוץ לתחום. לדוגמה, יש תהליך ניקוי נפרד שמנקה אחרי קריאות כפולות לפונקציות.

הגדרת מדיניות הניסיון החוזר

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

  • לקצר את חלון הניסיון החוזר מ-7 ימים ל-10 דקות לכל היותר.
  • שינוי זמן ההשהיה המינימלי והמקסימלי של שיטת הניסיון החוזר עם השהיה מעריכית.
  • משנים את אסטרטגיית הניסיון החוזר לניסיון חוזר מיידי.
  • מגדירים נושא להודעות ללא מוצא.
  • מגדירים מספר ניסיונות מקסימלי ומינימלי לשליחה.

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

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

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