확장 프로그램의 수명 주기 이벤트 처리

확장 프로그램에는 확장 프로그램 인스턴스에 다음과 같은 수명 주기 이벤트가 진행될 때 트리거되는 Cloud Tasks 함수가 포함될 수 있습니다.

  • 확장 프로그램 인스턴스가 설치됨
  • 확장 프로그램 인스턴스가 새 버전으로 업데이트됨
  • 확장 프로그램 인스턴스의 구성이 변경됨

이 기능의 가장 중요한 사용 사례 중 하나는 데이터 백필입니다. 예를 들어 Cloud Storage 버킷에 업로드된 이미지의 썸네일 미리보기를 생성하는 확장 프로그램을 빌드한다고 가정하겠습니다. 확장 프로그램의 기본 작업은 onFinalize Cloud Storage 이벤트에 의해 트리거되는 함수에서 수행됩니다. 그러나 확장 프로그램이 설치된 에 업로드된 이미지만 처리됩니다. 확장 프로그램에 onInstall 수명 주기 이벤트에 의해 트리거되는 함수를 포함하면 확장 프로그램이 설치될 때 기존 이미지의 썸네일 미리보기를 생성할 수도 있습니다.

수명 주기 이벤트 트리거의 다른 사용 사례에는 다음이 포함됩니다.

  • 설치 후 설정 자동화(데이터베이스 레코드 생성, 색인 생성 등)
  • 이전 버전과 호환되지 않는 변경사항을 게시해야 하는 경우 업데이트 시 자동으로 데이터 마이그레이션

단기 실행 수명 주기 이벤트 핸들러

태스크를 최대 Cloud Functions 기간(1세대 API 사용 시 9분) 내에 완전히 실행할 수 있는 경우 수명 주기 이벤트 핸들러를 태스크 큐 onDispatch 이벤트에서 트리거되는 단일 함수로 작성할 수 있습니다.

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

그런 다음 확장 프로그램의 extension.yaml 파일에서 다음을 수행합니다.

  1. 함수를 taskQueueTrigger 속성이 설정된 확장 프로그램 리소스로 등록합니다. taskQueueTrigger를 빈 맵({})으로 설정하는 경우 확장 프로그램이 기본 설정을 사용하여 Cloud Tasks 큐를 프로비저닝합니다. 원하는 경우 이러한 설정을 미세 조정할 수 있습니다.

    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. 함수를 하나 이상의 수명 주기 이벤트에 대한 핸들러로 등록합니다.

    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
    
    

    onInstall, onUpdate, onConfigure 이벤트에 대한 함수를 등록할 수 있습니다. 이러한 이벤트는 모두 선택사항입니다.

  3. 권장: 확장 프로그램이 작동하는 데 처리 태스크가 필요하지 않은 경우 사용자가 사용 설정 여부를 선택할 수 있도록 사용자 구성 매개변수를 추가합니다.

    예를 들어 다음과 같은 매개변수를 추가합니다.

    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
    

    함수에서 매개변수가 false로 설정된 경우 조기에 종료합니다.

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

장기 실행 태스크 수행

최대 Cloud Functions 기간 내에 태스크를 완료할 수 없는 경우 태스크를 하위 태스크로 분할한 후 Admin SDK의 TaskQueue.enqueue() 메서드를 사용해 작업을 큐에 추가하여 각 하위 태스크를 순서대로 수행합니다.

예를 들어 Cloud Firestore 데이터를 백필한다고 가정하겠습니다. 쿼리 커서를 사용하여 문서 컬렉션을 청크로 분할할 수 있습니다. 청크를 처리한 후 아래와 같이 시작 오프셋을 진행하여 다른 함수 호출을 큐에 추가합니다.

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

이전 섹션에 설명된 대로 extension.yaml에 함수를 추가합니다.

보고 상태

모든 처리 함수가 성공적으로 완료되거나 오류와 함께 완료되면 Admin SDK의 확장 프로그램 런타임 메서드를 사용하여 태스크 상태를 보고합니다. 사용자는 Firebase Console의 확장 프로그램 세부정보 페이지에서 이 상태를 확인할 수 있습니다.

성공적인 완료 및 심각하지 않은 오류

성공적인 완료 및 심각하지 않은 오류(확장 프로그램이 비작동 상태로 전환되지 않는 오류)를 보고하려면 Admin SDK의 setProcessingState() 확장 프로그램 런타임 메서드를 사용합니다.

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

// ...

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

다음과 같은 상태를 설정할 수 있습니다.

심각하지 않은 상태
PROCESSING_COMPLETE

성공적인 태스크 완료를 보고하는 데 사용합니다. 예:

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

부분적인 성공을 보고하는 데 사용합니다. 예:

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

태스크 완료를 방해하지만 확장 프로그램을 사용할 수 없는 상태로 두지 않는 오류를 보고하는 데 사용됩니다. 예:

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

확장 프로그램을 사용할 수 없는 상태로 두는 오류를 보고하려면 setFatalError()를 호출합니다.

NONE

태스크 상태를 삭제하는 데 사용합니다. 필요한 경우 이를 사용하여 콘솔에서 상태 메시지를 삭제할 수 있습니다(예: PROCESSING_COMPLETE 설정 후 일정 시간이 경과한 후). 예:

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

심각한 오류

확장 프로그램의 작동을 방해하는 오류(예: 필수 설정 태스크 실패)가 발생하는 경우 setFatalError()와 함께 심각한 오류를 보고합니다.

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

// ...

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

태스크 큐 미세 조정

taskQueueTrigger 속성을 {}로 설정하는 경우 확장 프로그램 인스턴스가 설치되면 확장 프로그램이 기본 설정으로 Cloud Tasks 큐를 프로비저닝합니다. 또는 특정 값을 제공하여 태스크 큐의 동시 실행 제한과 재시도 동작을 미세 조정할 수 있습니다.

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

이러한 매개변수에 대한 자세한 내용은 Google Cloud 문서의 Cloud Tasks 큐 구성을 참조하세요.

예시

공식 storage-resize-images, firestore-bigquery-export, firestore-translate-text 확장 프로그램은 모두 수명 주기 이벤트 핸들러를 사용하여 데이터를 백필합니다.