Manejar los eventos del ciclo de vida de su extensión

Su extensión puede incluir funciones de Cloud Tasks que se activan cuando una instancia de extensión pasa por cualquiera de los siguientes eventos del ciclo de vida:

  • Se instala una instancia de la extensión.
  • Una instancia de la extensión se actualiza a una nueva versión.
  • Se cambia la configuración de una instancia de extensión.

Uno de los casos de uso más importantes de esta función es el reabastecimiento de datos . Por ejemplo, suponga que está creando una extensión que genera vistas previas en miniatura de las imágenes cargadas en un depósito de Cloud Storage. El trabajo principal de su extensión se realizaría en una función activada por el evento onFinalize Cloud Storage. Sin embargo, solo se procesarán las imágenes cargadas después de instalar la extensión. Al incluir en su extensión una función activada por el evento del ciclo de vida onInstall , también puede generar vistas previas en miniatura de cualquier imagen existente cuando se instala la extensión.

Algunos otros casos de uso de desencadenadores de eventos del ciclo de vida incluyen:

  • Automatizar la configuración posterior a la instalación (creación de registros de bases de datos, indexación, etc.)
  • Si tiene que publicar cambios incompatibles con versiones anteriores, migre automáticamente los datos al actualizar

Controladores de eventos de ciclo de vida de corta duración

Si su tarea puede ejecutarse completamente dentro de la duración máxima de Cloud Functions (9 minutos usando la API de primera generación), puede escribir su controlador de eventos de ciclo de vida como una función única que se activa en el evento onDispatch de la cola de tareas:

export const myTaskFunction = functions.tasks.taskQueue()
  .onDispatch(async () => {
    // Complete your lifecycle event handling task.
    // ...

    // When processing is complete, report status to the user (see below).
  });

Luego, en el archivo extension.yaml de su extensión, haga lo siguiente:

  1. Registre su función como un recurso de extensión con la propiedad taskQueueTrigger establecida. Si configura taskQueueTrigger en el mapa vacío ( {} ), su extensión aprovisionará una cola de Cloud Tasks usando la configuración predeterminada; Opcionalmente, puede ajustar estas configuraciones .

    resources:
      - name: myTaskFunction
        type: firebaseextensions.v1beta.function
        description: >-
          Describe the task performed when the function is triggered by a lifecycle
          event
        properties:
          location: ${LOCATION}
          taskQueueTrigger: {}
    
  2. Registre su función como controlador de uno o más eventos del ciclo de vida:

    resources:
      - ...
    lifecycleEvents:
      onInstall:
        function: myTaskFunction
        processingMessage: Resizing your existing images
      onUpdate:
        function: myOtherTaskFunction
        processingMessage: Setting up your extension
      onConfigure:
        function: myOtherTaskFunction
        processingMessage: Setting up your extension
    
    

    Puede registrar funciones para cualquiera de los siguientes eventos: onInstall , onUpdate y onConfigure . Todos estos eventos son opcionales.

  3. Recomendado : si la tarea de procesamiento no es necesaria para que su extensión funcione, agregue un parámetro configurado por el usuario que les permita elegir si habilitarla.

    Por ejemplo, agregue un parámetro como el siguiente:

    params:
      - param: DO_BACKFILL
        label: Backfill existing images
        description: >
          Should existing, unresized images in the Storage bucket be resized as well?
        type: select
        options:
          - label: Yes
            value: true
          - label: No
            value: false
    

    Y en su función, si el parámetro está configurado en false , salga temprano:

    export const myTaskFunction = functions.tasks.taskQueue()
      .onDispatch(async () => {
        if (!process.env.DO_BACKFILL) {
          await runtime.setProcessingState(
            "PROCESSING_COMPLETE",
            "Existing images were not resized."
          );
          return;
        }
        // Complete your lifecycle event handling task.
        // ...
      });
    

Realizar tareas de larga duración

Si su tarea no puede completarse dentro de la duración máxima de Cloud Functions, divida la tarea en subtareas y realice cada subtarea en secuencia poniendo en cola los trabajos con el método TaskQueue.enqueue() del Admin SDK.

Por ejemplo, supongamos que desea reponer los datos de Cloud Firestore. Puede dividir la colección de documentos en fragmentos utilizando cursores de consulta . Después de procesar un fragmento, avance el desplazamiento inicial y ponga en cola otra invocación de función como se muestra a continuación:

import { getFirestore } from "firebase-admin/firestore";
import { getFunctions } from "firebase-admin/functions";

exports.backfilldata = functions.tasks.taskQueue().onDispatch(async (data) => {
  // When a lifecycle event triggers this function, it doesn't pass any data,
  // so an undefined offset indicates we're on our first invocation and should
  // start at offset 0. On subsequent invocations, we'll pass an explicit
  // offset.
  const offset = data["offset"] ?? 0;

  // Get a batch of documents, beginning at the offset.
  const snapshot = await getFirestore()
    .collection(process.env.COLLECTION_PATH)
    .startAt(offset)
    .limit(DOCS_PER_BACKFILL)
    .get();
  // Process each document in the batch.
  const processed = await Promise.allSettled(
    snapshot.docs.map(async (documentSnapshot) => {
      // Perform the processing.
    })
  );

  // If we processed a full batch, there are probably more documents to
  // process, so enqueue another invocation of this function, specifying
  // the offset to start with.
  //
  // If we processed less than a full batch, we're done.
  if (processed.length == DOCS_PER_BACKFILL) {
    const queue = getFunctions().taskQueue(
      "backfilldata",
      process.env.EXT_INSTANCE_ID
    );
    await queue.enqueue({
      offset: offset + DOCS_PER_BACKFILL,
    });
  } else {
      // Processing is complete. Report status to the user (see below).
  }
});

Agregue la función a su extension.yaml como se describe en la sección anterior .

Estado del informe

Cuando finalicen todas sus funciones de procesamiento, ya sea exitosamente o con un error, informe el estado de la tarea utilizando los métodos de tiempo de ejecución de la extensión Admin SDK. Los usuarios pueden ver este estado en la página de detalles de la extensión en Firebase console.

Finalización exitosa y errores no fatales.

Para informar la finalización exitosa y errores no fatales (errores que no ponen la extensión en un estado no funcional), use el método de tiempo de ejecución de la extensión setProcessingState() del SDK de administración:

import { getExtensions } from "firebase-admin/extensions";

// ...

getExtensions().runtime().setProcessingState(processingState, message);

Puede configurar los siguientes estados:

Estados no fatales
PROCESSING_COMPLETE

Úselo para informar la finalización exitosa de la tarea. Ejemplo:

getExtensions().runtime().setProcessingState(
  "PROCESSING_COMPLETE",
  `Backfill complete. Successfully processed ${numSuccess} documents.`
);
PROCESSING_WARNING

Úselo para informar un éxito parcial. Ejemplo:

getExtensions().runtime().setProcessingState(
  "PROCESSING_WARNING",
  `Backfill complete. ${numSuccess} documents processed successfully.`
    + ` ${numFailed} documents failed to process. ${listOfErrors}.`
    + ` ${instructionsToFixTheProblem}`
);
PROCESSING_FAILED

Úselo para informar errores que impiden que se complete la tarea, pero no deje la extensión inutilizable. Ejemplo:

getExtensions().runtime().setProcessingState(
  "PROCESSING_FAILED",
  `Backfill failed. ${errorMsg} ${optionalInstructionsToFixTheProblem}.`
);

Para informar errores que dejan la extensión inutilizable, llame a setFatalError() .

NONE

Úselo para borrar el estado de la tarea. Opcionalmente, puede usar esto para borrar el mensaje de estado de la consola (por ejemplo, después de que haya pasado un tiempo desde que se configuró PROCESSING_COMPLETE ). Ejemplo:

getExtensions().runtime().setProcessingState("NONE");

Errores fatales

Si se produce un error que impide que la extensión funcione (por ejemplo, si falla una tarea de configuración requerida), informe el error fatal con setFatalError() :

import { getExtensions } from "firebase-admin/extensions";

// ...

getExtensions().runtime().setFatalError(`Post-installation setup failed. ${errorMessage}`);

Ajustando la cola de tareas

Si configura la propiedad taskQueueTrigger en {} , su extensión aprovisionará una cola de Cloud Tasks con la configuración predeterminada cuando se instale una instancia de extensión. Como alternativa, puede ajustar los límites de simultaneidad de la cola de tareas y reintentar el comportamiento proporcionando valores específicos:

resources:
  - name: myTaskFunction
    type: firebaseextensions.v1beta.function
    description: >-
      Perform a task when triggered by a lifecycle event
    properties:
      location: ${LOCATION}
      taskQueueTrigger:
        rateLimits:
          maxConcurrentDispatches: 1000
          maxDispatchesPerSecond: 500
        retryConfig:
          maxAttempts: 100  # Warning: setting this too low can prevent the function from running
          minBackoffSeconds: 0.1
          maxBackoffSeconds: 3600
          maxDoublings: 16
lifecycleEvents:
  onInstall: 
    function: myTaskFunction
    processingMessage: Resizing your existing images
  onUpdate:
    function: myTaskFunction
    processingMessage: Setting up your extension
  onConfigure:
    function: myOtherTaskFunction
    processingMessage: Setting up your extension

Consulte Configurar colas de Cloud Tasks en los documentos de Google Cloud para obtener detalles sobre estos parámetros.

No intente especificar parámetros de la cola de tareas pasándolos a taskQueue() . Estas configuraciones se ignoran en favor de la configuración en extension.yaml y los valores predeterminados de configuración.

Por ejemplo, esto no funcionará:

export const myBrokenTaskFunction = functions.tasks
  // DON'T DO THIS IN AN EXTENSION! THESE SETTINGS ARE IGNORED.
  .taskQueue({
    retryConfig: {
      maxAttempts: 5,
      minBackoffSeconds: 60,
    },
    rateLimits: {
      maxConcurrentDispatches: 1000,
      maxDispatchesPerSecond: 10,
    },
  })
  .onDispatch(
    // ...
  );

La propiedad taskQueueTrigger en extension.yaml es la única forma de configurar las colas de tareas de una extensión.

Ejemplos

Las extensiones oficiales storage-resize-images , firestore-bigquery-export y firestore-translate-text utilizan controladores de eventos de ciclo de vida para reponer los datos.