Reintentar funciones asincrónicas

Este documento describe cómo puede solicitar funciones en segundo plano asíncronas (no HTTPS) para reintentar en caso de error.

Semántica de reintento

Cloud Functions garantiza la ejecución al menos una vez de una función controlada por eventos para cada evento emitido por un origen de eventos. De forma predeterminada, si la invocación de una función termina con un error, la función no se vuelve a invocar y el evento se descarta. Cuando habilita los reintentos en una función controlada por eventos, Cloud Functions reintenta una invocación de función fallida hasta que se completa exitosamente o la ventana de reintento expira.

Para las funciones de segunda generación, esta ventana de reintento vence después de 24 horas. Para funciones de 1.ª generación, caduca a los 7 días. Cloud Functions reintenta funciones basadas en eventos recién creadas mediante una estrategia de retroceso exponencial, con un retroceso creciente de entre 10 y 600 segundos. Esta política se aplica a las nuevas funciones la primera vez que las implementa. No se aplica retroactivamente a las funciones existentes que se implementaron por primera vez antes de que los cambios descritos en esta nota de la versión entraran en vigencia, incluso si vuelve a implementar las funciones.

Cuando los reintentos no están habilitados para una función, que es la opción predeterminada, la función siempre informa que se ejecutó correctamente y pueden aparecer 200 OK en sus registros. Esto ocurre incluso si la función encontró un error. Para dejar claro cuando su función encuentra un error, asegúrese de informar los errores de manera adecuada.

Por qué las funciones controladas por eventos no se completan

En raras ocasiones, una función puede cerrarse prematuramente debido a un error interno y, de forma predeterminada, la función puede volver a intentarse automáticamente o no.

Lo más habitual es que una función controlada por eventos no se complete correctamente debido a errores generados en el propio código de la función. Las razones por las que esto podría suceder incluyen:

  • La función contiene un error y el tiempo de ejecución genera una excepción.
  • La función no puede llegar a un punto final de servicio o se agota el tiempo de espera al intentar hacerlo.
  • La función genera intencionalmente una excepción (por ejemplo, cuando un parámetro no supera la validación).
  • Una función de Node.js devuelve una promesa rechazada o pasa un valor no null a una devolución de llamada.

En cualquiera de los casos anteriores, la función deja de ejecutarse por defecto y el evento se descarta. Para volver a intentar la función cuando se produce un error, puede cambiar la política de reintento predeterminada configurando la propiedad "reintentar en caso de error" . Esto hace que el evento se reintente repetidamente hasta que la función se complete exitosamente o expire el tiempo de espera del reintento.

Activar o desactivar los reintentos

Configurar reintentos desde GCP Console

Si está creando una nueva función:

  1. En la pantalla Crear función , seleccione Agregar activador y elija el tipo de evento que actuará como activador de su función.
  2. En el panel de activación de Eventarc , seleccione la casilla Reintentar en caso de error para habilitar los reintentos.

Si está actualizando una función existente:

  1. Desde la página Descripción general de Cloud Functions , haga clic en el nombre de la función que está actualizando para abrir su pantalla de detalles de la función , luego elija Editar en la barra de menú para mostrar los paneles de activación HTTPS y Eventarc .
  2. En el panel del disparador de Eventarc , haga clic en el ícono de para editar la configuración del disparador.
  3. En el panel de activación de Eventarc , seleccione o desmarque la casilla Reintentar en caso de error para habilitar o deshabilitar los reintentos.

Configure los reintentos desde su código de función

Con Cloud Functions para Firebase, puedes habilitar los reintentos en el código de una función. Para hacer esto para una función en segundo plano como functions.foo.onBar(myHandler); , use runWith y configure una política de fallas:

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

Establecer true como se muestra configura una función para reintentar en caso de falla.

Mejores prácticas

Esta sección describe las mejores prácticas para utilizar reintentos.

Utilice el reintento para manejar errores transitorios

Debido a que su función se reintenta continuamente hasta que se ejecuta exitosamente, los errores permanentes, como los errores, deben eliminarse de su código mediante pruebas antes de habilitar los reintentos. Los reintentos se utilizan mejor para manejar fallas intermitentes/transitorias que tienen una alta probabilidad de resolución al reintentar, como un punto final de servicio inestable o un tiempo de espera.

Establezca una condición final para evitar bucles de reintento infinitos

Es una buena práctica proteger su función contra bucles continuos al utilizar reintentos. Puede hacer esto incluyendo una condición final bien definida, antes de que la función comience a procesarse. Tenga en cuenta que esta técnica solo funciona si su función se inicia correctamente y puede evaluar la condición final.

Un enfoque simple pero efectivo es descartar eventos con marcas de tiempo anteriores a un tiempo determinado. Esto ayuda a evitar 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 de más de 10 segundos:

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

Utilice catch con promesas

Si su función tiene los reintentos habilitados, cualquier error no controlado activará un reintento. Asegúrese de que su código capture cualquier error que no deba resultar en un reintento.

Aquí tienes un ejemplo de lo que debes hacer:

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

Hacer que las funciones controladas por eventos reintentables sean idempotentes

Las funciones controladas por eventos que se pueden reintentar deben ser idempotentes. Aquí hay algunas pautas generales para hacer que dicha función sea idempotente:

  • Muchas API externas (como Stripe) le permiten proporcionar una clave de idempotencia como parámetro. Si está utilizando una API de este tipo, debe utilizar el ID del evento como clave de idempotencia.
  • La idempotencia funciona bien con la entrega al menos una vez, porque hace que sea seguro volver a intentarlo. Por lo tanto, una mejor práctica general para escribir código confiable es combinar idempotencia con reintentos.
  • Asegúrese de que su código sea internamente idempotente. Por ejemplo:
    • Asegúrese de que las mutaciones puedan ocurrir más de una vez sin cambiar el resultado.
    • Consultar el estado de la base de datos en una transacción antes de mutar el estado.
    • Asegúrese de que todos los efectos secundarios sean en sí mismos idempotentes.
  • Imponer una verificación transaccional fuera de la función, independiente del código. Por ejemplo, persista el estado en algún lugar registrando que un ID de evento determinado ya se ha procesado.
  • Manejar llamadas de funciones duplicadas fuera de banda. Por ejemplo, tenga un proceso de limpieza independiente que limpie después de llamadas a funciones duplicadas.

Configurar la política de reintento

Dependiendo de las necesidades de su función en la nube, es posible que desee configurar la política de reintento directamente. Esto le permitiría configurar cualquier combinación de lo siguiente:

  • Acorte el período de reintento de 7 días a tan solo 10 minutos.
  • Cambie el tiempo de retroceso mínimo y máximo para la estrategia de reintento de retroceso exponencial.
  • Cambie la estrategia de reintento para reintentar inmediatamente.
  • Configurar un tema de mensajes fallidos .
  • Establece un número máximo y mínimo de intentos de entrega.

Para configurar la política de reintento:

  1. Escribe una función HTTP.
  2. Utilice la API de Pub/Sub para crear una suscripción de Pub/Sub, especificando la URL de la función como destino.

Consulte la documentación de Pub/Sub sobre cómo manejar fallas para obtener más información sobre cómo configurar Pub/Sub directamente.