Cloud Scheduler inclut l'identification des jobs et les en-têtes HTTP de planification dans les requêtes HTTP et App Engine. Cela vous permet de dédupliquer les exécutions relancées et de rendre vos gestionnaires cibles idempotents. Étant donné que Cloud Scheduler fournit une diffusion au moins une fois, la déduplication des requêtes empêche votre gestionnaire ou vos API Google Cloud en aval de traiter la même exécution planifiée plusieurs fois en cas de délai d'expiration ou d'interruption temporaire du réseau.
Fonctionnement de la déduplication des requêtes
Des demandes de job en double pour la même exécution planifiée peuvent se produire dans les situations suivantes :
- Cibles de longue durée : le service cible met plus de temps à répondre que le délai avant expiration de la tentative (
attemptDeadline) du job, ce qui entraîne l'expiration du délai de Cloud Scheduler et la nouvelle tentative du job alors que la première tentative est toujours en cours d'exécution. Pour les cibles HTTP,attemptDeadlineest défini par défaut sur trois minutes et peut être configuré jusqu'à 30 minutes. AlignezattemptDeadlinesur le délai avant expiration de votre cible ou traitez les tâches de longue durée de manière asynchrone si l'exécution peut dépasser 30 minutes. - Accusés de réception réseau perdus ou nouvelles tentatives de proxy temporaires : la cible reçoit et traite la requête, mais une interruption temporaire du réseau ou une déconnexion du proxy empêche Cloud Scheduler de recevoir la réponse HTTP
2xx. Même si vous définissezretryCountsur0, la distribution au moins une fois signifie qu'une cible peut toujours recevoir des requêtes en double lors d'échecs de transport temporaires.
Pour rendre votre gestionnaire de jobs idempotent et dédupliquer les requêtes relancées, utilisez les en-têtes HTTP que Cloud Scheduler inclut automatiquement dans les requêtes adressées aux cibles HTTP et App Engine :
| En-tête | Description |
|---|---|
X-CloudScheduler-JobName |
Identifiant de job court (JOB_ID) de projects/PROJECT_ID/locations/LOCATION_ID/jobs/JOB_ID. |
X-CloudScheduler-ScheduleTime |
Pour les jobs spécifiés au format Cet en-tête n'est pas garanti pour les jobs qui utilisent l'autre format de planification |
Exiger l'authentification sur votre point de terminaison cible
Les en-têtes X-CloudScheduler-* ne sont pas authentifiés et peuvent être définis par n'importe quel appelant. N'utilisez pas ces en-têtes pour l'authentification. Sécurisez plutôt votre point de terminaison en fonction du type de cible :
- Cibles HTTP : configurez le job pour qu'il envoie un jeton OIDC ou OAuth et exige une authentification sur votre point de terminaison. Pour en savoir plus, consultez Utiliser l'authentification avec des cibles HTTP.
- Cibles App Engine : limitez l'accès au gestionnaire en définissant
login: admindans votre fichierapp.yamlet en configurant des règles de pare-feu App Engine.
Utiliser des recherches d'en-têtes non sensibles à la casse
La casse des en-têtes peut varier selon les versions HTTP et l'infrastructure (par exemple, X-Cloudscheduler-Jobname en HTTP/1.1 ou x-cloudscheduler-jobname et x-cloudscheduler-scheduletime en minuscules dans les environnements HTTP/2 tels que Cloud Run). Utilisez toujours des recherches d'en-têtes non sensibles à la casse dans votre gestionnaire.
Désambiguïser les ID de job entre les projets ou les régions
Cloud Scheduler n'envoie pas automatiquement les en-têtes d'ID de projet ou de région. Un même point de terminaison de gestionnaire peut recevoir des requêtes de plusieurs projets ou régions qui partagent le même JOB_ID. Dans ce cas, transmettez l'ID du projet ou la région dans HttpTarget.headers, AppEngineHttpTarget.headers, ou le chemin d'accès ou les paramètres de requête de l'URL cible, et incluez ces valeurs dans votre clé de déduplication.
Dédupliquer les requêtes dans un gestionnaire HTTP
Avant d'implémenter la déduplication des requêtes dans votre gestionnaire, effectuez les étapes de configuration suivantes :
- Configurez la planification de votre job au format
unix-cron(par exemple,*/5 * * * *au lieu deevery 5 minutes) afin que Cloud Scheduler envoie l'en-têteX-CloudScheduler-ScheduleTime. - Configurez un datastore persistant compatible avec les transactions ou les écritures conditionnelles atomiques, tel que Firestore, Spanner ou Memorystore.
Configurez une règle de valeur TTL (Time To Live) sur la collection ou la table (par exemple, sur le champ
expire_at) pour supprimer les anciens enregistrements de déduplication après la fenêtre de nouvelle tentative du job (maxRetryDuration). La valeur TTL de Firestore supprime les enregistrements arrivés à expiration de manière asynchrone, généralement sous 24 à 72 heures. Pour permettre aux nouvelles tentatives de récupérer les exécutions des instances plantées avant le nettoyage TTL, stockez également un code temporel d'expiration du bail (tel quelease_expires, défini sur au moins le délai avant expiration de la requête de votre cible etattemptDeadline) et évaluez-le dans une transaction.
Pour ne traiter chaque exécution planifiée qu'une seule fois dans votre gestionnaire HTTP, procédez comme suit :
- Lisez
X-CloudScheduler-JobNameetX-CloudScheduler-ScheduleTimeà partir des en-têtes de requête entrants et hachez-les dans une clé de déduplication de longueur fixe (run_id). - Exécutez une transaction dans votre datastore pour vérifier et revendiquer l'enregistrement de la clé de déduplication :
- Si l'enregistrement existe et que
statusestcompleted, renvoyez immédiatement un code d'état HTTP200 OK(ou204 No Content) sans exécuter la logique du job afin que Cloud Scheduler arrête de réessayer. - Si l'enregistrement existe, que
statusestin_progresset que le bail (lease_expires) n'a pas expiré, renvoyez un code d'état d'erreur pouvant être relancé, tel que409 Conflict, afin que Cloud Scheduler maintienne sa règle de relance active en cas d'échec de la tentative en cours. Évitez de renvoyer503 Service Unavailableà partir des cibles App Engine, car App Engine traite503comme une erreur système et relance la requête sans la comptabiliser dansretryCount. - Si l'enregistrement n'existe pas ou si son bail a expiré, écrivez l'enregistrement avec
statusdéfini surin_progress, un ID de tentative unique (attempt_id), un code temporel d'expiration du bail (lease_expires) et un code temporel d'expiration de la valeur TTL (expire_at).
- Si l'enregistrement existe et que
Après avoir revendiqué l'enregistrement, exécutez la logique de votre job. Dans une transaction, vérifiez que le
attempt_idde l'enregistrement correspond toujours à la tentative actuelle avant de modifier l'enregistrement :- Si la logique du job réussit, remplacez
statusparcompletedet renvoyez un code d'état HTTP200 OK. - Si la logique du job échoue avec une erreur que vous pouvez réessayer, supprimez l'enregistrement de déduplication avant de renvoyer un code d'état
5xxafin que Cloud Scheduler puisse réessayer l'exécution sans attendre l'expiration du bail.
- Si la logique du job réussit, remplacez
Exemple de gestionnaire HTTP
L'exemple Python suivant (pour Cloud Run ou Cloud Run Functions) utilise une transaction Firestore avec un bail pour revendiquer des exécutions et dédupliquer les requêtes de job relancées.
Cet exemple n'est pas prêt pour la production. Examinez-le et ajustez-le à vos besoins avant de le déployer :
Empêcher les requêtes en double vers les cibles d'API Google Cloud
Lorsque Cloud Scheduler appelle directement un point de terminaison d'API Google Cloud (*.googleapis.com) via HTTP, l'API cible ignore les en-têtes X-CloudScheduler-JobName et X-CloudScheduler-ScheduleTime. Par exemple, appeler workflowexecutions.googleapis.com pour démarrer une exécution de Workflows, run.googleapis.com pour exécuter un job Cloud Run ou cloudbuild.googleapis.com pour exécuter un déclencheur de compilation Cloud Build démarre une nouvelle exécution à chaque nouvelle tentative.
Pour assurer la fiabilité de la production, appliquez l'idempotence dans le service le plus en aval qui modifie l'état. La déduplication des requêtes uniquement au niveau d'une couche en amont réduit la fenêtre de nouvelle tentative. Toutefois, si une requête en aval aboutit et que la réponse HTTP est perdue sur le réseau, l'appelant en amont ne peut pas déterminer si l'opération est terminée et peut toujours relancer la requête.
Pour éviter les exécutions en double lors de la planification de cibles d'API Google Cloud , utilisez les approches suivantes ensemble pour une idempotence de bout en bout :
Acheminez les requêtes via un proxy idempotent : au lieu d'appeler le point de terminaison
*.googleapis.comdirectement depuis Cloud Scheduler, déployez votre propre service Cloud Run ou proxy Cloud Run Functions, puis configurez Cloud Scheduler pour appeler ce proxy. Dans le proxy, implémentez le modèle de revendication transactionnelle atomique Python et Firestore décrit dans ce document, dans Dédupliquer les requêtes dans un gestionnaire HTTP.Le proxy lit
X-CloudScheduler-JobNameetX-CloudScheduler-ScheduleTime, déduplique les requêtes relancées et transmet la clé de déduplication en tant que jeton d'idempotence dans la charge utile de la requête (par exemple, dans le champargumentlors du démarrage d'une exécution Workflows).Exigez l'authentification sur le proxy (par exemple, déployez avec
--no-allow-unauthenticated) et configurez le job Cloud Scheduler pour qu'il s'authentifie avec un jeton OIDC. Exécutez le proxy sous un compte de service dédié ne disposant que des rôles IAM (Identity and Access Management) minimum requis (par exemple,roles/workflows.invokersur le workflow cible etroles/datastore.userpour les enregistrements de déduplication).Appliquer l'idempotence dans les services en aval : utilisez le jeton d'idempotence pour effectuer une vérification atomique ou transactionnelle dans un datastore tel que Firestore avant d'exécuter des étapes de changement d'état. Transmettez le jeton à tous les points de terminaison HTTP ou fonctions Cloud Run en aval que votre service appelle, afin que ces cibles puissent également dédupliquer les nouvelles tentatives. Pour obtenir un exemple de bout en bout utilisant Workflows, les transactions Firestore et Cloud Run Functions, consultez l'article de blog Google CloudEffectuer des appels d'API une seule fois lors de l'utilisation de Workflows.