Réessayer les fonctions asynchrones

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

Sémantique de la nouvelle tentative

Cloud Functions garantit l'exécution 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 pas invoquée à nouveau et l'événement est supprimé. Lorsque vous activez les tentatives sur une fonction basée sur des événements, Cloud Functions réessaye un appel de fonction ayant échoué jusqu'à ce qu'il se termine avec succès ou que la fenêtre de nouvelle tentative expire.

Pour les fonctions de 2e génération, cette fenêtre de nouvelle tentative expire après 24 heures. Pour les fonctions de 1ère génération, il expire au bout de 7 jours. Cloud Functions réessaye les fonctions basées sur les événements nouvellement créées à l'aide d'une stratégie d'attente exponentielle, avec une attente croissante comprise 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 pour la première fois avant que les modifications décrites dans cette note de version n'entrent en vigueur, même si vous redéployez les fonctions.

Lorsque les tentatives ne sont pas activées pour une fonction, ce qui est la valeur par défaut, la fonction signale toujours qu'elle s'est exécutée avec succès et 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, assurez-vous de signaler les erreurs de manière appropriée.

Pourquoi les fonctions basées sur les événements ne parviennent pas à se terminer

Dans de rares occasions, une fonction peut se terminer prématurément en raison d'une erreur interne et, par défaut, la fonction peut ou non être automatiquement réessayée.

Plus généralement, une fonction événementielle peut échouer en raison d'erreurs générées dans le code de la fonction lui-même. Les raisons pour lesquelles cela peut se produire incluent :

  • La fonction contient un bug et le runtime lève une exception.
  • La fonction ne peut pas atteindre un point de terminaison de service ou expire en essayant de le faire.
  • La fonction lève intentionnellement une exception (par exemple, lorsqu'un paramètre échoue à la validation).
  • Une fonction Node.js renvoie une promesse rejetée ou transmet une valeur non null à un rappel.

Dans tous les cas ci-dessus, la fonction cesse de s'exécuter par défaut et l'événement est ignoré. Pour réessayer la fonction lorsqu'une erreur se produit, vous pouvez modifier la stratégie de nouvelle tentative par défaut en définissant la propriété « réessayer en cas d'échec » . Cela entraîne une nouvelle tentative de l'événement jusqu'à ce que la fonction se termine avec succès ou que le délai de nouvelle tentative expire.

Activer ou désactiver les tentatives

Configurer les tentatives à partir de la console GCP

Si vous créez une nouvelle fonction :

  1. Dans l'écran Créer une fonction , sélectionnez Ajouter un déclencheur et choisissez le type d'événement qui servira de déclencheur pour votre fonction.
  2. Dans le volet du déclencheur Eventarc , cochez la case Réessayer en cas d'échec pour activer les nouvelles tentatives.

Si vous mettez à jour une fonction existante :

  1. Sur la page Présentation des fonctions Cloud , cliquez sur le nom de la fonction que vous mettez à jour pour ouvrir son écran Détails de la fonction , puis choisissez Modifier dans la barre de menus pour afficher les volets de déclenchement HTTPS et Eventarc .
  2. Dans le volet du déclencheur Eventarc , cliquez sur l'icône pour modifier les paramètres de votre déclencheur.
  3. Dans le volet de déclenchement Eventarc , cochez ou décochez la case Réessayer en cas d'échec pour activer ou désactiver les tentatives.

Configurer les tentatives à partir de votre code de fonction

Avec Cloud Functions pour Firebase, vous pouvez activer les nouvelles tentatives dans le code d'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 d'échec :

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

La définition true comme indiqué configure une fonction pour réessayer en cas d'échec.

Les meilleures pratiques

Cette section décrit les meilleures pratiques d'utilisation des tentatives.

Utiliser la nouvelle tentative pour gérer les erreurs passagères

Étant donné que votre fonction est réessayée en continu jusqu'à son exécution réussie, les erreurs permanentes telles que les bogues doivent être éliminées de votre code grâce à des tests avant d'activer les nouvelles tentatives. Les tentatives sont mieux utilisées pour gérer les échecs intermittents/transitoires qui ont une forte probabilité d'être résolus lors d'une nouvelle tentative, comme un point de terminaison de service irrégulier ou un délai d'attente.

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

Il est recommandé de protéger votre fonction contre les boucles continues lors de l'utilisation de nouvelles tentatives. Vous pouvez le faire en incluant une condition de fin bien définie, avant que la fonction ne commence le traitement. Notez que cette technique ne fonctionne que si votre fonction démarre avec succès et est capable d'évaluer la condition de fin.

Une approche simple mais efficace consiste à supprimer les événements dont l’horodatage est antérieur à une certaine heure. Cela permet d'éviter des exécutions excessives lorsque les échecs sont persistants ou durent plus longtemps que prévu.

Par exemple, cet extrait de code supprime tous les événements datant de plus de 10 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 Promises

Si les tentatives sont activées pour votre fonction, toute erreur non gérée déclenchera une nouvelle tentative. Assurez-vous que votre code capture toutes les erreurs qui ne devraient pas entraîner une nouvelle tentative.

Voici un exemple de ce que vous devriez faire :

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

Rendre idempotentes les fonctions basées sur les événements réessayables

Les fonctions événementielles qui peuvent être réessayées doivent être idempotentes. Voici quelques directives générales pour rendre une telle fonction idempotente :

  • De nombreuses API externes (telles que Stripe) vous permettent de fournir une clé d'idempotence en 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 au moins une fois, car elle permet de réessayer en toute sécurité. Ainsi, une bonne pratique générale pour écrire du code fiable consiste à combiner l’idempotence avec les tentatives.
  • Assurez-vous que votre code est idempotent en interne. Par exemple:
    • Assurez-vous que les mutations peuvent se produire plus d’une fois sans 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.
  • Imposer un contrôle transactionnel en dehors de la fonction, indépendant du code. Par exemple, persistez l'état quelque part en enregistrant qu'un ID d'événement donné a déjà été traité.
  • Gérez les appels de fonction en double hors bande. Par exemple, disposez d'un processus de nettoyage distinct qui nettoie après des appels de fonction en double.

Configurer la politique de nouvelle tentative

En fonction des besoins de votre fonction Cloud, vous souhaiterez peut-être configurer directement la stratégie de nouvelle tentative. Cela vous permettrait de configurer n'importe quelle combinaison des éléments suivants :

  • Réduisez la fenêtre de nouvelle tentative de 7 jours à 10 minutes seulement.
  • Modifiez le temps d'attente minimum et maximum pour la stratégie de nouvelle tentative d'attente exponentielle.
  • Modifiez la stratégie de nouvelle tentative pour réessayer immédiatement.
  • Configurez un sujet lettre morte .
  • Définissez un nombre maximum et minimum de tentatives de livraison.

Pour configurer la stratégie de nouvelle tentative :

  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 comme cible.

Consultez la documentation Pub/Sub sur la gestion des échecs pour plus d'informations sur la configuration directe de Pub/Sub.