Conseils et astuces des trucs

Ce document décrit les bonnes pratiques pour la conception, la mise en œuvre, les tests et le déploiement de Cloud Functions.

Exactitude

Cette section décrit les bonnes pratiques générales pour la conception et la mise en œuvre de Cloud Functions.

Écrire des fonctions idempotentes

Vos fonctions devraient produire le même résultat même si elles sont appelées plusieurs fois. Cela vous permet de réessayer un appel si l'appel précédent échoue à mi-chemin de votre code. Pour plus d’informations, consultez nouvelle tentative de fonctions basées sur des événements .

Ne démarrez pas les activités en arrière-plan

L'activité en arrière-plan est tout ce qui se produit après la fin de votre fonction. Un appel de fonction se termine une fois que la fonction renvoie ou signale l'achèvement, par exemple en appelant l'argument callback dans les fonctions basées sur les événements Node.js. Tout code exécuté après une terminaison gracieuse ne peut pas accéder au processeur et ne progressera pas.

De plus, lorsqu'un appel ultérieur est exécuté dans le même environnement, votre activité en arrière-plan reprend, interférant avec le nouvel appel. Cela peut entraîner un comportement inattendu et des erreurs difficiles à diagnostiquer. L'accès au réseau après la fin d'une fonction entraîne généralement la réinitialisation des connexions (code d'erreur ECONNRESET ).

L'activité en arrière-plan peut souvent être détectée dans les journaux d'appels individuels, en trouvant tout ce qui est enregistré après la ligne indiquant que l'appel est terminé. L'activité en arrière-plan peut parfois être enfouie plus profondément dans le code, en particulier lorsque des opérations asynchrones telles que des rappels ou des minuteries sont présentes. Vérifiez votre code pour vous assurer que toutes les opérations asynchrones se terminent avant de terminer la fonction.

Supprimez toujours les fichiers temporaires

Le stockage sur disque local dans le répertoire temporaire est un système de fichiers en mémoire. Les fichiers que vous écrivez consomment de la mémoire disponible pour votre fonction et persistent parfois entre les appels. Ne pas supprimer explicitement ces fichiers peut éventuellement entraîner une erreur de mémoire insuffisante et un démarrage à froid ultérieur.

Vous pouvez voir la mémoire utilisée par une fonction individuelle en la sélectionnant dans la liste des fonctions de la console GCP et en choisissant le tracé d'utilisation de la mémoire .

N'essayez pas d'écrire en dehors du répertoire temporaire et veillez à utiliser des méthodes indépendantes de la plate-forme/du système d'exploitation pour construire les chemins de fichiers.

Vous pouvez réduire les besoins en mémoire lors du traitement de fichiers plus volumineux à l'aide du pipeline. Par exemple, vous pouvez traiter un fichier sur Cloud Storage en créant un flux de lecture, en le faisant passer par un processus basé sur le flux et en écrivant le flux de sortie directement dans Cloud Storage.

Cadre de fonctions

Lorsque vous déployez une fonction, Functions Framework est automatiquement ajouté en tant que dépendance, en utilisant sa version actuelle. Pour garantir que les mêmes dépendances sont installées de manière cohérente dans différents environnements, nous vous recommandons d’épingler votre fonction à une version spécifique de Functions Framework.

Pour ce faire, incluez votre version préférée dans le fichier de verrouillage approprié (par exemple, package-lock.json pour Node.js ou requirements.txt pour Python).

Outils

Cette section fournit des directives sur la façon d'utiliser les outils pour implémenter, tester et interagir avec Cloud Functions.

Développement local

Le déploiement d'une fonction prend un peu de temps, il est donc souvent plus rapide de tester le code de votre fonction localement.

Les développeurs Firebase peuvent utiliser l' émulateur Firebase CLI Cloud Functions .

Utilisez Sendgrid pour envoyer des e-mails

Cloud Functions n'autorise pas les connexions sortantes sur le port 25, vous ne pouvez donc pas établir de connexions non sécurisées à un serveur SMTP. La méthode recommandée pour envoyer des e-mails est d'utiliser SendGrid . Vous pouvez trouver d'autres options d'envoi d'e-mails dans le didacticiel Envoi d'e-mails à partir d'une instance pour Google Compute Engine.

Performance

Cette section décrit les meilleures pratiques pour optimiser les performances.

Utiliser judicieusement les dépendances

Les fonctions étant sans état, l'environnement d'exécution est souvent initialisé à partir de zéro (au cours de ce que l'on appelle un démarrage à froid ). Lorsqu'un démarrage à froid se produit, le contexte global de la fonction est évalué.

Si vos fonctions importent des modules, le temps de chargement de ces modules peut augmenter la latence d'appel lors d'un démarrage à froid. Vous pouvez réduire cette latence, ainsi que le temps nécessaire au déploiement de votre fonction, en chargeant correctement les dépendances et en ne chargeant pas les dépendances que votre fonction n'utilise pas.

Utiliser des variables globales pour réutiliser des objets lors d'appels futurs

Il n'y a aucune garantie que l'état d'une fonction Cloud sera préservé pour les appels futurs. Cependant, Cloud Functions recycle souvent l'environnement d'exécution d'un appel précédent. Si vous déclarez une variable dans une portée globale, sa valeur peut être réutilisée lors d'appels ultérieurs sans avoir à être recalculée.

De cette façon, vous pouvez mettre en cache des objets dont la recréation peut être coûteuse à chaque appel de fonction. Le déplacement de ces objets du corps de la fonction vers la portée globale peut entraîner des améliorations significatives des performances. L'exemple suivant crée un objet lourd une seule fois par instance de fonction et le partage entre tous les appels de fonction atteignant l'instance donnée :

Noeud.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}")
  

Cette fonction HTTP prend un objet de requête ( flask.Request ) et renvoie le texte de réponse, ou tout ensemble de valeurs pouvant être transformé en objet Response à l'aide de make_response .

Il est particulièrement important de mettre en cache les connexions réseau, les références de bibliothèque et les objets client API dans une portée globale. Voir Optimisation du réseau pour des exemples.

Faire une initialisation paresseuse des variables globales

Si vous initialisez des variables dans une portée globale, le code d'initialisation sera toujours exécuté via un appel de démarrage à froid, augmentant ainsi la latence de votre fonction. Dans certains cas, cela provoque des délais d'attente intermittents pour les services appelés s'ils ne sont pas traités correctement dans un bloc try / catch . Si certains objets ne sont pas utilisés dans tous les chemins de code, pensez à les initialiser paresseusement à la demande :

Noeud.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}.")
  

Cette fonction HTTP utilise des variables globales initialisées paresseusement. Il prend un objet de requête ( flask.Request ) et renvoie le texte de réponse, ou tout ensemble de valeurs pouvant être transformé en objet Response à l'aide make_response .

Ceci est particulièrement important si vous définissez plusieurs fonctions dans un seul fichier et que différentes fonctions utilisent différentes variables. À moins que vous n'utilisiez une initialisation paresseuse, vous risquez de gaspiller des ressources sur des variables initialisées mais jamais utilisées.

Réduisez les démarrages à froid en définissant un nombre minimum d'instances

Par défaut, Cloud Functions adapte le nombre d'instances en fonction du nombre de requêtes entrantes. Vous pouvez modifier ce comportement par défaut en définissant un nombre minimum d'instances que Cloud Functions doit conserver prêtes à répondre aux demandes. La définition d'un nombre minimum d'instances réduit les démarrages à froid de votre application. Nous vous recommandons de définir un nombre minimum d'instances si votre application est sensible à la latence.

Consultez Comportement de mise à l’échelle du contrôle pour plus d’informations sur ces options d’exécution.

Ressources additionnelles

Pour en savoir plus sur l'optimisation des performances, consultez la vidéo « Google Cloud Performance Atlas » Cloud Functions Cold Boot Time .