Lidar com os eventos do ciclo de vida da sua extensão

Sua extensão pode incluir funções do Cloud Tasks que são acionadas quando uma instância de extensão passa por qualquer um dos seguintes eventos de ciclo de vida:

  • Uma instância da extensão está instalada
  • Uma instância da extensão é atualizada para uma nova versão
  • A configuração de uma instância de extensão foi alterada

Um dos casos de uso mais importantes desse recurso é o preenchimento de dados . Por exemplo, suponha que você esteja criando uma extensão que gera visualizações em miniatura de imagens enviadas para um bucket do Cloud Storage. O trabalho principal da sua extensão seria feito em uma função acionada pelo evento onFinalize Cloud Storage. No entanto, apenas as imagens carregadas após a instalação da extensão serão processadas. Ao incluir em sua extensão uma função acionada pelo evento de ciclo de vida onInstall , você também poderá gerar visualizações em miniatura de quaisquer imagens existentes quando a extensão for instalada.

Alguns outros casos de uso de acionadores de eventos de ciclo de vida incluem:

  • Automatize a configuração pós-instalação (criação de registros de banco de dados, indexação, etc.)
  • Se você precisar publicar alterações incompatíveis com versões anteriores, migre automaticamente os dados na atualização

Manipuladores de eventos de ciclo de vida de curta duração

Se sua tarefa puder ser executada completamente dentro da duração máxima do Cloud Functions (9 minutos usando a API de primeira geração), você poderá escrever seu manipulador de eventos de ciclo de vida como uma função única que é acionada no evento onDispatch da fila de tarefas:

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

Em seguida, no arquivo extension.yaml da sua extensão, faça o seguinte:

  1. Registre sua função como um recurso de extensão com o conjunto de propriedades taskQueueTrigger . Se você definir taskQueueTrigger como o mapa vazio ( {} ), sua extensão provisionará uma fila do Cloud Tasks usando as configurações padrão; você pode opcionalmente ajustar essas configurações .

    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 sua função como manipulador para um ou mais eventos de 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
    
    

    Você pode registrar funções para qualquer um dos seguintes eventos: onInstall , onUpdate e onConfigure . Todos esses eventos são opcionais.

  3. Recomendado : se a tarefa de processamento não for necessária para que sua extensão funcione, adicione um parâmetro configurado pelo usuário que permita aos usuários escolher se desejam ativá-la.

    Por exemplo, adicione um parâmetro como o seguinte:

    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
    

    E na sua função, se o parâmetro estiver definido como false , saia mais cedo:

    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.
        // ...
      });
    

Executando tarefas de longa duração

Se sua tarefa não puder ser concluída dentro da duração máxima do Cloud Functions, divida-a em subtarefas e execute cada subtarefa em sequência enfileirando jobs com o método TaskQueue.enqueue() do Admin SDK.

Por exemplo, suponha que você queira preencher os dados do Cloud Firestore. Você pode dividir a coleção de documentos em partes usando cursores de consulta . Depois de processar um pedaço, avance o deslocamento inicial e enfileire outra invocação de função conforme mostrado abaixo:

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

Adicione a função ao seu extension.yaml conforme descrito na seção anterior .

Status do relatório

Quando todas as suas funções de processamento forem concluídas, com êxito ou com erro, informe o status da tarefa usando os métodos de tempo de execução de extensão do SDK Admin. Os usuários podem ver esse status na página de detalhes da extensão no console do Firebase.

Conclusão bem-sucedida e erros não fatais

Para relatar a conclusão bem-sucedida e erros não fatais (erros que não colocam a extensão em um estado não funcional), use o método de tempo de execução da extensão setProcessingState() do SDK Admin:

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

// ...

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

Você pode definir os seguintes estados:

Estados não fatais
PROCESSING_COMPLETE

Use para relatar a conclusão bem-sucedida da tarefa. Exemplo:

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

Use para relatar sucesso parcial. Exemplo:

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

Use para relatar erros que impedem a conclusão da tarefa, mas não deixam a extensão inutilizável. Exemplo:

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

Para relatar erros que deixam a extensão inutilizável, chame setFatalError() .

NONE

Use para limpar o status da tarefa. Opcionalmente, você pode usar isso para limpar a mensagem de status do console (por exemplo, após algum tempo ter passado desde a configuração de PROCESSING_COMPLETE ). Exemplo:

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

Erros fatais

Se ocorrer um erro que impeça o funcionamento da extensão (por exemplo, falha em uma tarefa de configuração necessária), relate o erro fatal com setFatalError() :

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

// ...

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

Ajustando a fila de tarefas

Se você definir a propriedade taskQueueTrigger como {} , sua extensão provisionará uma fila do Cloud Tasks com as configurações padrão quando uma instância de extensão for instalada. Como alternativa, você pode ajustar os limites de simultaneidade da fila de tarefas e tentar novamente o comportamento fornecendo 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 filas do Cloud Tasks na documentação do Google Cloud para obter detalhes sobre esses parâmetros.

Não tente especificar parâmetros de fila de tarefas passando-os para taskQueue() . Essas configurações são ignoradas em favor da configuração em extension.yaml e dos padrões de configuração.

Por exemplo, isso não 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(
    // ...
  );

A propriedade taskQueueTrigger em extension.yaml é a única maneira de configurar as filas de tarefas de uma extensão.

Exemplos

As extensões oficiais storage-resize-images , firestore-bigquery-export e firestore-translate-text usam manipuladores de eventos de ciclo de vida para preencher dados.