Réessayer les fonctions asynchrones

Ce document explique comment demander aux fonctions d'arrière-plan asynchrones (non HTTPS) de réessayer en cas d'échec.

Sémantique d'une répétition de tentative

Cloud Functions permet l'exécution de type "au moins une fois" d'une fonction basée sur des événements pour chaque événement émis par une source d'événement. Par défaut, si un appel de fonction se termine par une erreur, la fonction n'est plus appelée et l'événement est supprimé. Lorsque vous activez la répétition des tentatives sur une fonction basée sur des événements, Cloud Functions relance un appel de fonction ayant échoué jusqu'à ce qu'il se termine correctement ou que la fenêtre de nouvelle tentative expire.

Pour les fonctions de 2e génération, cette fenêtre de nouvelle tentative expire au bout de 24 heures. Pour les fonctions de 1re génération, elle expire au bout de 7 jours. Cloud Functions relance les fonctions basées sur des événements nouvellement créées à l'aide d'une stratégie d'intervalle exponentiel entre les tentatives, avec un intervalle croissant entre 10 et 600 secondes. Cette stratégie est appliquée aux nouvelles fonctions la première fois que vous les déployez. Elle n'est pas appliquée rétroactivement aux fonctions existantes qui ont été déployées avant l'entrée en vigueur des modifications décrites dans cette note de version, même si vous redéployez les fonctions.

Lorsque la répétition des tentatives n'est pas activée pour une fonction, ce qui est la valeur par défaut, la fonction indique toujours qu'elle a bien été exécutée, et les codes de réponse 200 OK peuvent apparaître dans ses journaux. Cela se produit même si la fonction a rencontré une erreur. Pour indiquer clairement lorsque votre fonction rencontre une erreur, veillez à signaler les erreurs de manière appropriée.

Pourquoi les fonctions basées sur des événements échouent-elles ?

Il peut arriver qu'une fonction se ferme prématurément en raison d'une erreur interne. Par défaut, cette fonction peut être relancée automatiquement ou non.

Le plus souvent, une fonction basée sur des événements peut échouer en raison d'erreurs générées dans le code même de la fonction. Les raisons peuvent être les suivantes :

  • La fonction contient un bug et l’environnement d'exécution renvoie une exception.
  • La fonction ne peut pas atteindre de point de terminaison de service, ou bien elle dépasse le délai en essayant d'y parvenir.
  • La fonction renvoie intentionnellement une exception (par exemple, lorsqu'un paramètre échoue à la validation).
  • Une fonction Node.js renvoie une promesse refusée ou transmet à un rappel une valeur qui n'est pas null.

Dans tous les cas, la fonction cesse d'être exécutée par défaut et l'événement est supprimé. Pour relancer la fonction en cas d'erreur, vous pouvez modifier la stratégie de nouvelles tentatives par défaut en définissant la propriété "Réessayer après échec". L'événement est alors relancé de façon répétée jusqu'à ce que la fonction se termine ou que le délai avant expiration des nouvelles tentatives soit écoulé.

Activer ou désactiver les nouvelles tentatives

Configurer les nouvelles tentatives à partir de la console

Si vous créez une fonction :

  1. Dans l'écran Créer une fonction, sous Déclencheur, choisissez le type d'événement devant servir de déclencheur pour votre fonction.
  2. Cochez la case Réessayer après échec pour activer les nouvelles tentatives.

Si vous mettez à jour une fonction existante :

  1. Sur la page Présentation de Cloud Functions, cliquez sur le nom de la fonction que vous mettez à jour pour ouvrir l'écran Informations sur la fonction, puis sélectionnez Modifier dans la barre de menu pour afficher le volet Déclencheur.
  2. Cochez ou décochez la case Réessayer après échec pour activer ou désactiver les nouvelles tentatives.

Configurer les nouvelles tentatives à partir du code de votre fonction

Avec Cloud Functions for Firebase, vous pouvez activer les nouvelles tentatives dans le code pour une fonction. Pour ce faire pour une fonction d'arrière-plan telle que functions.foo.onBar(myHandler);, utilisez runWith et configurez une stratégie de défaillance :

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

Définir true comme indiqué configure une fonction pour qu'elle soit réessayée en cas d'échec.

Bonnes pratiques

Cette section décrit les bonnes pratiques relatives à l'utilisation de la répétition des tentatives.

Utiliser la répétition pour faire face aux erreurs temporaires

Votre fonction est relancée en continu tant que son exécution n'est pas réussie. Vous devez donc éliminer de votre code les erreurs permanentes telles que les bugs par le biais de tests. Ce n'est qu'après cette étape que vous pourrez activer la répétition des tentatives. Les tentatives sont particulièrement utiles pour gérer les échecs intermittents/temporaires qui présentent une probabilité élevée de résolution à mesure des nouvelles tentatives, par exemple lorsqu'un point de terminaison de service ou un délai d’inactivité est instable.

Définir une condition de fin pour éviter les boucles infinies de répétition de tentatives

Il est recommandé de protéger votre fonction contre les boucles continues lors de l'utilisation de la répétition des tentatives. Pour ce faire, incluez une condition de fin bien définie avant le début du traitement de la fonction. Notez que cette technique ne fonctionne que si votre fonction démarre correctement et qu'elle est en mesure d'évaluer la condition de fin.

Une approche simple, mais efficace, consiste à ignorer les événements dont l'horodatage est antérieur à une certaine période. Cela permet d'éviter des exécutions excessives lorsque les échecs sont persistants ou plus longs que prévu.

Par exemple, l'extrait de code suivant supprime tous les événements de plus de dix secondes :

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;
}

Utiliser catch avec des promesses

Si la répétition de tentatives est activée pour votre fonction, toute erreur non gérée déclenche une nouvelle tentative. Assurez-vous que votre code capture toutes les erreurs qui ne doivent pas entraîner de nouvelle tentative.

Voici un exemple de ce que vous devez faire:

return doFooAsync().catch((err) => {
    if (isFatal(err)) {
        console.error(`Fatal error ${err}`);
    }
    return Promise.reject(err);
});

Rendre les fonctions déclenchées par des événements idempotentes

Les fonctions basées sur des événements qui peuvent être relancées doivent être idempotentes. Voici quelques consignes générales pour créer une telle fonction :

  • De nombreuses API externes (telles que Stripe) vous permettent de fournir une clé d'idempotence en tant que paramètre. Si vous utilisez une telle API, vous devez utiliser l'ID d'événement comme clé d'idempotence.
  • L'idempotence fonctionne bien avec une livraison de type "au moins une fois", car elle permet de répéter la tentative en toute sécurité. Une bonne pratique pour écrire du code fiable consiste donc à combiner l'idempotence à la répétition des tentatives.
  • Assurez-vous que votre code est idempotent en interne. Exemple :
    • Assurez-vous que les mutations peuvent se produire plus d'une fois sans en changer le résultat.
    • Interrogez l'état de la base de données dans une transaction avant de muter l'état.
    • Assurez-vous que tous les effets secondaires sont eux-mêmes idempotents.
  • Imposez un contrôle transactionnel en dehors de la fonction, indépendamment du code. Par exemple, conservez l'état quelque part en notant qu'un ID d'événement donné a déjà été traité.
  • Gérez les appels de fonction doubles hors bande. Par exemple, mettez en place un processus de nettoyage distinct qui se lance après les appels de fonction doubles.

Configurer la stratégie de nouvelle tentative

En fonction des besoins de votre fonction, vous pouvez configurer directement la stratégie de nouvelle tentative. Cela vous permet de configurer n'importe quelle combinaison des éléments suivants :

  • Réduire la fenêtre de nouvelle tentative de sept jours une durée qui peut aller jusqu'à 10 minutes seulement.
  • Modifier le temps d'intervalle minimal et maximal pour la stratégie de nouvelle tentative avec intervalle exponentiel entre les tentatives.
  • Modifier la stratégie de nouvelle tentative pour réessayer immédiatement.
  • Configurer une file d'attente de lettres mortes.
  • Définir un nombre maximal et minimal de tentatives de livraison.

Pour configurer la stratégie de nouvelle tentative, procédez comme suit :

  1. Écrivez une fonction HTTP.
  2. Utilisez l'API Pub/Sub pour créer un abonnement Pub/Sub, en spécifiant l'URL de la fonction en tant que cible.

Pour en savoir plus sur la configuration directe de Pub/Sub, consultez la documentation de Pub/Sub sur la gestion des échecs.