Habilita los reintentos de funciones basadas en eventos (1ª gen.)
En este documento, se describe cómo habilitar los reintentos para las funciones basadas en eventos. Los reintentos automáticos no están disponibles para las funciones de HTTP.
Semántica de los reintentos
Cloud Run Functions garantiza, al menos, una ejecución de una función basada en eventos para cada evento emitido por una fuente de eventos. De forma predeterminada, si la invocación de una función finaliza con un error, esta no se vuelve a invocar y se descarta el evento. Cuando habilitas los reintentos en una función basada en eventos, Cloud Run Functions volverá a intentar la invocación de una función que falló hasta que se complete con éxito o el período de reintentos venza.
Este período de reintentos vence luego de 7 días. Cloud Run Functions vuelve a intentar las funciones basadas en eventos recién creadas con una estrategia de retirada exponencial, con una retirada creciente de entre 10 y 600 segundos. Esta política se aplica a las funciones nuevas la primera vez que se implementan. No se aplica de forma retroactiva a las funciones existentes que se implementaron por primera vez antes de que se aplicaran los cambios descritos en esta nota de la versión, incluso si vuelves a implementar las funciones.
Cuando los reintentos no están habilitados para una función, que es la configuración predeterminada, la función
siempre informa que se ejecutó con éxito y pueden
aparecer códigos de respuesta 200 OK en sus registros. Esto ocurre incluso si se produjo un error en la función. Para
indicar con claridad cuándo se produce un error en la función, asegúrate de
informar esos errores
de forma adecuada.
Motivos por los que las funciones basadas en eventos no se completan
En ocasiones poco comunes, es posible que una función se cierre con anticipación debido a un error interno y, según la configuración predeterminada, podría volver a ejecutarse o no automáticamente.
Con mayor frecuencia, puede que una función basada en eventos no se complete con éxito debido a errores generados en su propio código. A continuación, se presentan algunos de los motivos por los que esto puede ocurrir:
- La función contiene un error, y el entorno de ejecución muestra una excepción.
- La función no puede alcanzar el extremo del servicio, o el tiempo de espera se agota mientras intenta hacerlo.
- La función muestra una excepción de forma intencional (por ejemplo, cuando falla la validación de un parámetro).
- Una función de Node.js muestra una promesa rechazada o pasa un valor que no es
nulla una devolución de llamada.
En cualquiera de estos casos, la función deja de ejecutarse de forma predeterminada y el evento se descarta. Para intentar ejecutar de nuevo una función que falla, puedes configurar la propiedad "Volver a intentar en caso de error" con el objetivo de cambiar la política predeterminada de reintentos. Esto hace que se vuelva a intentar ejecutar de nuevo el evento varias veces hasta que la función se complete con éxito o venza el tiempo de espera de reintentos.
Habilita o inhabilita los reintentos
Para habilitar o inhabilitar los reintentos, puedes usar la herramienta de línea de comandos de gcloud
o la consola de Google Cloud . De forma predeterminada, los reintentos están inhabilitados.
Configura los reintentos desde la herramienta de línea de comandos de gcloud
Para habilitar los reintentos con la herramienta de línea de comandos de gcloud, incluye la marca --retry
cuando implementes la función:
gcloud functions deploy FUNCTION_NAME --retry FLAGS...
Para inhabilitar los reintentos, vuelve a implementar la función sin la marca --retry:
gcloud functions deploy FUNCTION_NAME FLAGS...
Configura los reintentos desde la consola
Si vas a crear una función nueva, sigue los pasos que se indican a continuación:
- En la pantalla Crear función, en Activador, elige el tipo de evento que se usará como activador la función.
- Selecciona la casilla de verificación Volver a intentar en caso de error para habilitar los reintentos.
Si vas a actualizar una función existente, sigue los pasos que se indican a continuación:
- En la página Descripción general de Cloud Run Functions, haz clic en el nombre de la función que vas a actualizar para abrir la pantalla Detalles de la función y, luego, elige Editar en la barra de menú para abrir el panel Activador.
- Selecciona o borra la casilla de verificación Volver a intentar en caso de error para habilitar o inhabilitar los reintentos.
Prácticas recomendadas
En esta sección se describen las prácticas recomendadas para usar los intentos reiterados.
Usa los intentos reiterados para administrar errores temporales
Dado que se vuelve a intentar ejecutar la función varias veces hasta que se ejecuta con éxito, las fallas permanentes, como los errores, se deben eliminar del código a través de pruebas antes de habilitar los reintentos. Los reintentos son útiles para administrar fallas intermitentes o temporales con alta probabilidad de resolverse con un reintento, como un extremo de servicio o un tiempo de espera inestables.
Configura una condición de finalización para evitar los bucles infinitos de reintentos
Se recomienda que protejas la función contra bucles continuos cuando uses los reintentos. Para esto, puedes incluir una condición de finalización bien definida antes de que la función comience a procesarse. Ten en cuenta que esta técnica solo tiene éxito si la función se inicia de manera correcta y es capaz de evaluar la condición final.
Un enfoque simple y eficaz consiste en descartar eventos con marcas de tiempo anteriores a una hora determinada. Esto ayuda a evitar las ejecuciones excesivas cuando las fallas son persistentes o duran más de lo esperado.
Por ejemplo, este fragmento de código descarta todos los eventos que superen los 10 segundos:
Node.js
Python
Go
Java
C#
using CloudNative.CloudEvents; using Google.Cloud.Functions.Framework; using Google.Events.Protobuf.Cloud.PubSub.V1; using Microsoft.Extensions.Logging; using System; using System.Threading; using System.Threading.Tasks; namespace TimeBoundedRetries; public class Function : ICloudEventFunction<MessagePublishedData> { private static readonly TimeSpan MaxEventAge = TimeSpan.FromSeconds(10); private readonly ILogger _logger; // Note: for additional testability, use an injectable clock abstraction. public Function(ILogger<Function> logger) => _logger = logger; public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken) { string textData = data.Message.TextData; DateTimeOffset utcNow = DateTimeOffset.UtcNow; // Every PubSub CloudEvent will contain a timestamp. DateTimeOffset timestamp = cloudEvent.Time.Value; DateTimeOffset expiry = timestamp + MaxEventAge; // Ignore events that are too old. if (utcNow > expiry) { _logger.LogInformation("Dropping PubSub message '{text}'", textData); return Task.CompletedTask; } // Process events that are recent enough. // If this processing throws an exception, the message will be retried until either // processing succeeds or the event becomes too old and is dropped by the code above. _logger.LogInformation("Processing PubSub message '{text}'", textData); return Task.CompletedTask; } }
Ruby
PHP
/** * This function shows an example method for avoiding infinite retries in * Google Cloud Functions. By default, functions configured to automatically * retry execution on failure will be retried indefinitely - causing an * infinite loop. To avoid this, we stop retrying executions (by not throwing * exceptions) for any events that are older than a predefined threshold. */ use Google\CloudFunctions\CloudEvent; function avoidInfiniteRetries(CloudEvent $event): void { $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb'); $eventId = $event->getId(); // The maximum age of events to process. $maxAge = 10; // 10 seconds // The age of the event being processed. $eventAge = time() - strtotime($event->getTime()); // Ignore events that are too old if ($eventAge > $maxAge) { fwrite($log, 'Dropping event ' . $eventId . ' with age ' . $eventAge . ' seconds' . PHP_EOL); return; } // Do what the function is supposed to do fwrite($log, 'Processing event: ' . $eventId . ' with age ' . $eventAge . ' seconds' . PHP_EOL); // infinite_retries failed function executions $failed = true; if ($failed) { throw new Exception('Event ' . $eventId . ' failed; retrying...'); } }
Diferencia las funciones que se pueden reintentar de los errores no recuperables
Si tu función tiene habilitados los reintentos, cualquier error no administrado activará un reintento. Asegúrate de que tu código capture cualquier error que no deba generar un reintento.
Node.js
Python
Go
Java
C#
using CloudNative.CloudEvents; using Google.Cloud.Functions.Framework; using Google.Events.Protobuf.Cloud.PubSub.V1; using Microsoft.Extensions.Logging; using System; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace Retry; public class Function : ICloudEventFunction<MessagePublishedData> { private readonly ILogger _logger; public Function(ILogger<Function> logger) => _logger = logger; public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken) { bool retry = false; string text = data.Message?.TextData; // Get the value of the "retry" JSON parameter, if one exists. if (!string.IsNullOrEmpty(text)) { JsonElement element = JsonSerializer.Deserialize<JsonElement>(data.Message.TextData); retry = element.TryGetProperty("retry", out var property) && property.ValueKind == JsonValueKind.True; } // Throwing an exception causes the execution to be retried. if (retry) { throw new InvalidOperationException("Retrying..."); } else { _logger.LogInformation("Not retrying..."); } return Task.CompletedTask; } }
Ruby
PHP
use Google\CloudFunctions\CloudEvent; function tipsRetry(CloudEvent $event): void { $cloudEventData = $event->getData(); $pubSubData = $cloudEventData['message']['data']; $json = json_decode(base64_decode($pubSubData), true); // Determine whether to retry the invocation based on a parameter $tryAgain = $json['some_parameter']; if ($tryAgain) { /** * Functions with automatic retries enabled should throw exceptions to * indicate intermittent failures that a retry might fix. In this * case, a thrown exception will cause the original function * invocation to be re-sent. */ throw new Exception('Intermittent failure occurred; retrying...'); } /** * If a function with retries enabled encounters a non-retriable * failure, it should return *without* throwing an exception. */ $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb'); fwrite($log, 'Not retrying' . PHP_EOL); }
Convierte en idempotentes las funciones basadas en eventos que se pueden reintentar
Las funciones controladas por eventos cuya ejecución se puede reintentar deben ser idempotentes. A continuación, te mostramos algunos lineamientos generales para convertir estas funciones en idempotentes:
- Muchas API externas (como Stripe) te permiten proporcionar una clave de idempotencia como parámetro. Si usas una API de este tipo, debes usar el ID de evento como la clave de idempotencia.
- La idempotencia funciona bien con la entrega "al menos una vez", ya que permite que los intentos reiterados sean seguros. Por lo tanto, una recomendación general para escribir un código confiable es combinar la idempotencia con los intentos reiterados.
- Asegúrate de que tu código sea idempotente de forma interna. Por ejemplo:
- Asegúrate de que puedan ocurrir mutaciones más de una vez sin que cambie el resultado.
- Consulta el estado de la base de datos en una transacción antes de mutar el estado.
- Asegúrate de que todos los efectos secundarios sean idempotentes en sí.
- Debes imponer una verificación transaccional fuera de la función y que sea independiente del código. Por ejemplo, conserva el estado en algún lugar que registre si ya se procesó un ID de evento determinado.
- Administra las llamadas de función duplicadas fuera de banda. Por ejemplo, implementa un proceso de limpieza independiente que borre las llamadas duplicadas a funciones.
Configura la política de reintentos
Según las necesidades de tu función, puedes configurar directamente la política de reintentos. Esto te permitirá configurar cualquier combinación de las acciones siguientes:
- Acortar el período de reintento de 7 días a tan solo 10 minutos.
- Cambiar el tiempo de retirada mínimo y máximo para la estrategia de reintento de retirada exponencial.
- Cambiar la estrategia de reintento para volver a intentar de inmediato.
- Configurar un tema de mensajes no entregados.
- Establecer una cantidad máxima y mínima de intentos de entrega.
Para configurar la política de reintentos, haz lo siguiente:
- Escribe una función de HTTP
- Usa la API de Pub/Sub para crear una suscripción de Pub/Sub y especificar la URL de la función como objetivo.
Consulta la documentación de Pub/Sub sobre el manejo de fallas para obtener más información sobre cómo configurar Pub/Sub directamente.
Pasos siguientes
- Implementa Cloud Run Functions.
- Llama a funciones del activador de Pub/Sub.
- Llama a funciones del activador de Cloud Storage.
- Consulta el instructivo de Cloud Run Functions con Pub/Sub.
- Consulta el instructivo de Cloud Run Functions con Cloud Storage.