במסמך הזה מתוארות שיטות מומלצות לתכנון, להטמעה, לבדיקה ולפריסה של Cloud Functions.
תקינות
בקטע הזה מתוארות שיטות מומלצות כלליות לתכנון ולהטמעה של Cloud Functions.
כתיבת פונקציות אידמפוטנטיות
הפונקציות צריכות להניב את אותה תוצאה גם אם הן מופעלות כמה פעמים. כך תוכלו לנסות שוב להפעיל את הפונקציה אם ההפעלה הקודמת נכשלה באמצע הקוד. מידע נוסף זמין במאמר ניסיון חוזר בפונקציות מבוססות-אירועים.
לא להפעיל פעילויות ברקע
פעילות ברקע היא כל דבר שקורה אחרי שהפונקציה מסתיימת.
קריאה לפונקציה מסתיימת ברגע שהפונקציה מחזירה ערכים או מסמנת שהיא הושלמה בדרך אחרת, למשל על ידי קריאה לארגומנט callback
בפונקציות מבוססות-אירועים של Node.js. קוד שפועל אחרי סיום תקין לא יכול לגשת ל-CPU ולא יתקדם.
בנוסף, כשהפעלה חוזרת מתבצעת באותה סביבה, פעילות הרקע ממשיכה ופוגעת בהפעלה החדשה. כתוצאה מכך, יכולות להיות בעיות לא צפויות ושגיאות שקשה לאבחן אותן. בדרך כלל, גישה לרשת אחרי סיום הפונקציה מובילה לאיפוס החיבורים (קוד השגיאה ECONNRESET
).
לרוב אפשר לזהות פעילות ברקע ביומני קריאות נפרדות, על ידי חיפוש כל דבר שמופיע ביומן אחרי השורה שבה מצוין שהקריאה הסתיימה. לפעמים פעילות ברקע יכולה להיות מוסתרת עמוק יותר בקוד, במיוחד כשיש פעולות אסינכרוניות כמו קריאות חזרה או שעונים. בודקים את הקוד כדי לוודא שכל הפעולות האסינכרוניות מסתיימות לפני שמסיימים את הפונקציה.
תמיד למחוק קבצים זמניים
אחסון מקומי בדיסק בספרייה הזמנית הוא מערכת קבצים בזיכרון. קבצים שאתם כותבים צורכים זיכרון שזמין לפונקציה, ולפעמים הם נשמרים בין הקריאות. אם לא תמחקו את הקבצים האלה באופן מפורש, ייתכן שבסופו של דבר תופיע הודעת שגיאה של חוסר זיכרון ותצטרכו לבצע הפעלה מחדש.
כדי לראות את נפח הזיכרון שבו נעשה שימוש בפונקציה מסוימת, בוחרים אותה ברשימת הפונקציות במסוף Google Cloud ובוחרים בתרשים Memory usage.
אל תנסה לכתוב מחוץ לספרייה הזמנית, וחשוב להשתמש בשיטות שאינן תלויות בפלטפורמה או במערכת ההפעלה כדי ליצור נתיבים של קבצים.
אפשר להפחית את דרישות הזיכרון בזמן עיבוד קבצים גדולים יותר באמצעות צינור עיבוד נתונים. לדוגמה, אפשר לעבד קובץ ב-Cloud Storage על ידי יצירת מקור נתונים לקריאה, העברה שלו דרך תהליך שמבוסס על מקור נתונים וכתיבה של מקור הפלט ישירות ב-Cloud Storage.
Functions Framework
כשפורסים פונקציה, ה-Functions Framework מתווסף באופן אוטומטי כיחס תלות, באמצעות הגרסה הנוכחית שלו. כדי לוודא שאותן יחסי תלות מותקנים באופן עקבי בסביבות שונות, מומלץ להצמיד את הפונקציה לגרסה ספציפית של Functions Framework.
כדי לעשות זאת, צריך לכלול את הגרסה המועדפת בקובץ הנעילה הרלוונטי (לדוגמה, package-lock.json
עבור Node.js או requirements.txt
עבור Python).
כלים
בקטע הזה מפורטות הנחיות לשימוש בכלים להטמעה, לבדיקה וליצירת אינטראקציה עם Cloud Functions.
פיתוח מקומי
פריסה של פונקציה נמשכת קצת זמן, ולכן לרוב מהיר יותר לבדוק את הקוד של הפונקציה באופן מקומי.
מפתחי Firebase יכולים להשתמש באמולטור Cloud Functions של Firebase CLI.שימוש ב-Sendgrid לשליחת אימיילים
Cloud Functions לא מאפשר חיבורים יוצאים ביציאה 25, כך שלא תוכלו ליצור חיבורים לא מאובטחים לשרת SMTP. הדרך המומלצת לשליחת אימיילים היא באמצעות SendGrid. אפשר למצוא אפשרויות נוספות לשליחת אימייל במדריך שליחת אימייל ממכונה ב-Google Compute Engine.
ביצועים
בקטע הזה מתוארות שיטות מומלצות לאופטימיזציה של הביצועים.
שימוש חכם ביחסי תלות
מכיוון שהפונקציות הן ללא מצב, סביבת הביצוע מופעלת לרוב מאפס (במה שנקרא הפעלה קרה). כשמתרחש התחלה קרה, מתבצעת הערכה של ההקשר הגלובלי של הפונקציה.
אם הפונקציות מייבאות מודולים, זמן הטעינה של המודולים האלה יכול להוסיף לזמן האחזור של ההפעלה במהלך הפעלה קרה. כדי לצמצם את זמן האחזור הזה, וגם את הזמן הנדרש לפריסה של הפונקציה, אפשר לטעון את יחסי התלות בצורה נכונה ולא לטעון יחסי תלות שהפונקציה לא משתמשת בהם.
שימוש במשתנים גלובליים לשימוש חוזר באובייקטים בהפעלות עתידיות
אין ערובה לכך שמצב הפונקציה יישמר לקריאות עתידיות. עם זאת, לרוב Cloud Functions משתמש מחדש בסביבת הביצוע של קריאה קודמת. אם מגדירים משתנה ברמת ה-global, אפשר לעשות בו שימוש חוזר בקריאות הבאות בלי שצריך לחשב אותו מחדש.
כך אפשר לשמור אובייקטים במטמון, שיכול להיות שיהיה יקר ליצור מחדש בכל קריאה לפונקציה. העברת אובייקטים כאלה מגוף הפונקציה להיקף גלובלי עשויה להוביל לשיפורים משמעותיים בביצועים. בדוגמה הבאה נוצר אובייקט כבד רק פעם אחת לכל מופע פונקציה, והוא משותף לכל הקריאות לפונקציה שמגיעות למופע הנתון:
Node.js
console.log('Global scope'); const perInstance = heavyComputation(); const functions = require('firebase-functions'); exports.function = functions.https.onRequest((req, res) => { console.log('Function invocation'); const perFunction = lightweightComputation(); res.send(`Per instance: ${perInstance}, per function: ${perFunction}`); });
Python
import time from firebase_functions import https_fn # Placeholder def heavy_computation(): return time.time() # Placeholder def light_computation(): return time.time() # Global (instance-wide) scope # This computation runs at instance cold-start instance_var = heavy_computation() @https_fn.on_request() def scope_demo(request): # Per-function scope # This computation runs every time this function is called function_var = light_computation() return https_fn.Response(f"Instance: {instance_var}; function: {function_var}")
פונקציית ה-HTTP הזו מקבלת אובייקט בקשה (flask.Request
) ומחזירה את טקסט התגובה, או כל קבוצת ערכים שאפשר להפוך לאובייקט Response
באמצעות make_response
.
חשוב במיוחד לשמור במטמון חיבורי רשת, הפניות לספריות ואובייקטים של לקוחות API ברמת ה-global. דוגמאות מפורטות זמינות במאמר אופטימיזציה של רשתות.
ביצוע אתחול עצל של משתנים גלובליים
אם מאתחלים משתנים ברמת ה-global, קוד האינטלייזציה יבוצע תמיד באמצעות קריאה להתחלה קרה, וכך זמן האחזור של הפונקציה יגדל.
במקרים מסוימים, הדבר גורם לזמני תפוגה לסירוגין בשירותים שנקראים, אם הם לא מטופלים כראוי בבלוק try
/catch
. אם לא משתמשים באובייקטים מסוימים בכל נתיבי הקוד, כדאי להגדיר אותם באיטרציה לפי דרישה:
Node.js
const functions = require('firebase-functions'); let myCostlyVariable; exports.function = functions.https.onRequest((req, res) => { doUsualWork(); if(unlikelyCondition()){ myCostlyVariable = myCostlyVariable || buildCostlyVariable(); } res.status(200).send('OK'); });
Python
from firebase_functions import https_fn # Always initialized (at cold-start) non_lazy_global = file_wide_computation() # Declared at cold-start, but only initialized if/when the function executes lazy_global = None @https_fn.on_request() def lazy_globals(request): global lazy_global, non_lazy_global # This value is initialized only if (and when) the function is called if not lazy_global: lazy_global = function_specific_computation() return https_fn.Response(f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}.")
פונקציית ה-HTTP הזו משתמשת במשתנים גלובליים שמאותחלים באיטרציה. הפונקציה מקבלת אובייקט בקשה (flask.Request
) ומחזירה את טקסט התגובה, או כל קבוצת ערכים שאפשר להפוך לאובייקט Response
באמצעות make_response
.
חשוב במיוחד לעשות זאת אם מגדירים כמה פונקציות בקובץ אחד, ופונקציות שונות משתמשות במשתנים שונים. אם לא משתמשים בהפעלה איטית (lazy initialization), יכול להיות שתתבזבזו משאבים על משתנים שהוגדרו אבל אף פעם לא נעשה בהם שימוש.
צמצום ההפעלות הראשונות על ידי הגדרת מספר מינימלי של מכונות
כברירת מחדל, Cloud Functions משתנה בהתאם למספר הבקשות הנכנסות. אפשר לשנות את התנהגות ברירת המחדל הזו על ידי הגדרת מספר מינימלי של מכונות ש-Cloud Functions צריך לשמור מוכנות כדי לטפל בבקשות. הגדרת מספר מינימלי של מכונות מפחיתה את ההפעלות הראשונות של האפליקציה. אם האפליקציה שלכם רגישת לזמן אחזור, מומלץ להגדיר מספר מינימלי של מכונות.
מידע נוסף על האפשרויות האלה בסביבת זמן הריצה זמין במאמר שליטה בהתנהגות ההתאמה לעומס.משאבים נוספים
מידע נוסף על אופטימיזציה של הביצועים זמין בסרטון Cloud Functions זמן הפעלה מחדש (Cold Boot) של Google Cloud Performance Atlas.