Google Cloud Firestore 트리거(1세대)

Cloud Run Functions는 함수와 동일한 Google Cloud 프로젝트의 Firestore에서 이벤트를 처리할 수 있습니다. Firestore API 및 클라이언트 라이브러리를 사용하여 이러한 이벤트에 대한 응답으로 Firestore를 읽거나 업데이트 할 수 있습니다.

일반적인 수명 주기에서 Firestore 함수는 다음을 수행합니다.

  1. 특정 문서가 변경되기를 기다립니다.

  2. 이벤트가 발생하면 트리거되어 작업을 수행합니다.

  3. 영향을 받는 문서의 스냅샷이 있는 데이터 객체를 수신합니다. write 또는 update 이벤트의 경우 데이터 객체에는 이벤트 트리거 전후의 문서 상태를 나타내는 스냅샷이 포함됩니다.

이벤트 유형

Firestore는 create, update, delete, write 이벤트를 지원합니다. write 이벤트는 문서에 대한 모든 수정사항을 포함합니다.

이벤트 유형 트리거
providers/cloud.firestore/eventTypes/document.create(기본) 문서를 처음으로 기록할 때 트리거됩니다.
providers/cloud.firestore/eventTypes/document.update 이미 존재하는 문서에서 값이 변경되었을 때 트리거됩니다.
providers/cloud.firestore/eventTypes/document.delete 데이터가 있는 문서가 삭제되면 트리거됩니다.
providers/cloud.firestore/eventTypes/document.write 문서가 생성, 업데이트 또는 삭제되면 트리거됩니다.

와일드 카드는 다음과 같이 중괄호를 사용하여 트리거에 작성됩니다. "projects/YOUR_PROJECT_ID/databases/(default)/documents/collection/{document_wildcard}"

문서 경로 지정

함수를 트리거하려면 리슨할 문서 경로를 지정합니다. 함수는 문서 변경에만 응답하며 특정 필드나 컬렉션을 모니터링할 수 없습니다. 다음은 유효한 문서 경로의 몇 가지 예시입니다.

  • users/marie: 유효한 트리거입니다. 단일 문서 /users/marie를 모니터링합니다.

  • users/{username}: 유효한 트리거입니다. 모든 사용자 문서를 모니터링합니다. 와일드 카드를 사용하면 컬렉션의 모든 문서를 모니터링할 수 있습니다.

  • users/{username}/addresses: 트리거가 잘못되었습니다. 문서가 아닌 하위 컬렉션 addresses를 나타냅니다.

  • users/{username}/addresses/home: 유효한 트리거입니다. 모든 사용자에 대한 집 주소 문서를 모니터링합니다.

  • users/{username}/addresses/{addressId}: 유효한 트리거입니다. 모든 주소 문서를 모니터링합니다.

와일드 카드 및 매개변수 사용

모니터링할 특정 문서를 알 수 없는 경우에는 문서 ID 대신 {wildcard}를 사용합니다.

  • users/{username}은 모든 사용자 문서에 대한 변경을 리슨합니다.

이 예시에서 users의 문서에서 필드가 변경되면 필드는 {username}라는 와일드 카드와 일치합니다.

users의 문서에 하위 컬렉션이 있고, 이 하위 컬렉션 문서 중 하나에서 필드가 변경되면 {username} 와일드 카드가 트리거되지 않습니다.

와일드 카드 일치는 문서 경로에서 추출됩니다. 원하는 만큼 와일드 카드를 정의하여 명시적 컬렉션 또는 문서 ID를 대체할 수 있습니다.

이벤트 구조

이 트리거는 아래와 유사한 이벤트로 함수를 호출합니다.

{
    "oldValue": { // Update and Delete operations only
        A Document object containing a pre-operation document snapshot
    },
    "updateMask": { // Update operations only
        A DocumentMask object that lists changed fields.
    },
    "value": {
        // A Document object containing a post-operation document snapshot
    }
}

Document 객체에는 Value 객체가 하나 이상 포함됩니다. 유형 참조는 Value 문서를 참조하세요. 특히 Go와 같은 입력 언어를 사용하여 함수를 작성하는 경우에 유용합니다.

코드 샘플

아래의 Cloud 함수 샘플은 트리거하는 Cloud Firestore 이벤트의 필드를 출력합니다.

Node.js

/**
 * Background Function triggered by a change to a Firestore document.
 *
 * @param {!Object} event The Cloud Functions event.
 * @param {!Object} context Cloud Functions event metadata.
 */
exports.helloFirestore = (event, context) => {
  const triggerResource = context.resource;

  console.log(`Function triggered by event on: ${triggerResource}`);
  console.log(`Event type: ${context.eventType}`);

  if (event.oldValue && Object.keys(event.oldValue).length) {
    console.log('\nOld value:');
    console.log(JSON.stringify(event.oldValue, null, 2));
  }

  if (event.value && Object.keys(event.value).length) {
    console.log('\nNew value:');
    console.log(JSON.stringify(event.value, null, 2));
  }
};

Python

import json

def hello_firestore(data, context):
    """Triggered by a change to a Firestore document.
    Args:
        data (dict): The event payload.
        context (google.cloud.functions.Context): Metadata for the event.
    """
    trigger_resource = context.resource

    print("Function triggered by change to: %s" % trigger_resource)

    print("\nOld value:")
    print(json.dumps(data["oldValue"]))

    print("\nNew value:")
    print(json.dumps(data["value"]))

Go


// Package hello contains a Cloud Function triggered by a Firestore event.
package hello

import (
	"context"
	"fmt"
	"log"
	"time"

	"cloud.google.com/go/functions/metadata"
)

// FirestoreEvent is the payload of a Firestore event.
type FirestoreEvent struct {
	OldValue   FirestoreValue `json:"oldValue"`
	Value      FirestoreValue `json:"value"`
	UpdateMask struct {
		FieldPaths []string `json:"fieldPaths"`
	} `json:"updateMask"`
}

// FirestoreValue holds Firestore fields.
type FirestoreValue struct {
	CreateTime time.Time `json:"createTime"`
	// Fields is the data for this value. The type depends on the format of your
	// database. Log the interface{} value and inspect the result to see a JSON
	// representation of your database fields.
	Fields     interface{} `json:"fields"`
	Name       string      `json:"name"`
	UpdateTime time.Time   `json:"updateTime"`
}

// HelloFirestore is triggered by a change to a Firestore document.
func HelloFirestore(ctx context.Context, e FirestoreEvent) error {
	meta, err := metadata.FromContext(ctx)
	if err != nil {
		return fmt.Errorf("metadata.FromContext: %w", err)
	}
	log.Printf("Function triggered by change to: %v", meta.Resource)
	log.Printf("Old value: %+v", e.OldValue)
	log.Printf("New value: %+v", e.Value)
	return nil
}

자바

import com.google.cloud.functions.Context;
import com.google.cloud.functions.RawBackgroundFunction;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.logging.Logger;

public class FirebaseFirestore implements RawBackgroundFunction {
  private static final Logger logger = Logger.getLogger(FirebaseFirestore.class.getName());

  // Use GSON (https://github.com/google/gson) to parse JSON content.
  private static final Gson gson = new Gson();

  @Override
  public void accept(String json, Context context) {
    JsonObject body = gson.fromJson(json, JsonObject.class);
    logger.info("Function triggered by event on: " + context.resource());
    logger.info("Event type: " + context.eventType());

    if (body != null && body.has("oldValue")) {
      logger.info("Old value:");
      logger.info(body.get("oldValue").getAsString());
    }

    if (body != null && body.has("value")) {
      logger.info("New value:");
      logger.info(body.get("value").getAsString());
    }
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Events.Protobuf.Cloud.Firestore.V1;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace FirebaseFirestore;

public class Function : ICloudEventFunction<DocumentEventData>
{
    private readonly ILogger _logger;

    public Function(ILogger<Function> logger) =>
        _logger = logger;

    public Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Function triggered by event on {subject}", cloudEvent.Subject);
        _logger.LogInformation("Event type: {type}", cloudEvent.Type);
        MaybeLogDocument("Old value", data.OldValue);
        MaybeLogDocument("New value", data.Value);

        // In this example, we don't need to perform any asynchronous operations, so the
        // method doesn't need to be declared async.
        return Task.CompletedTask;
    }

    /// <summary>
    /// Logs the names and values of the fields in a document in a very simplistic way.
    /// </summary>
    private void MaybeLogDocument(string message, Document document)
    {
        if (document is null)
        {
            return;
        }

        // ConvertFields converts the Firestore representation into a .NET-friendly
        // representation.
        IReadOnlyDictionary<string, object> fields = document.ConvertFields();
        var fieldNamesAndTypes = fields
            .OrderBy(pair => pair.Key)
            .Select(pair => $"{pair.Key}: {pair.Value}");
        _logger.LogInformation(message + ": {fields}", string.Join(", ", fieldNamesAndTypes));
    }
}

Ruby

require "functions_framework"

# Triggered by a change to a Firestore document.
FunctionsFramework.cloud_event "hello_firestore" do |event|
  # The event parameter is a CloudEvents::Event::V1 object.
  # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
  payload = event.data

  logger.info "Function triggered by change to: #{event.source}"
  logger.info "Old value: #{payload['oldValue']}"
  logger.info "New value: #{payload['value']}"
end

PHP


use Google\CloudFunctions\CloudEvent;

function firebaseFirestore(CloudEvent $cloudevent)
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');

    fwrite($log, 'Event: ' . $cloudevent->getId() . PHP_EOL);
    fwrite($log, 'Event Type: ' . $cloudevent->getType() . PHP_EOL);

    $data = $cloudevent->getData();

    $resource = $data['resource'];
    fwrite($log, 'Function triggered by event on: ' . $resource . PHP_EOL);

    if (isset($data['oldValue'])) {
        fwrite($log, 'Old value: ' . json_encode($data['oldValue']) . PHP_EOL);
    }

    if (isset($data['value'])) {
        fwrite($log, 'New value: ' . json_encode($data['value']) . PHP_EOL);
    }
}

아래 예시에서는 사용자가 추가한 값을 검색하고 해당 위치의 문자열을 대문자로 변환한 후 해당 값을 대문자 문자열로 바꿉니다.

Node.js

const Firestore = require('@google-cloud/firestore');

const firestore = new Firestore({
  projectId: process.env.GOOGLE_CLOUD_PROJECT,
});

// Converts strings added to /messages/{pushId}/original to uppercase
exports.makeUpperCase = event => {
  const resource = event.value.name;
  const affectedDoc = firestore.doc(resource.split('/documents/')[1]);

  const curValue = event.value.fields.original.stringValue;
  const newValue = curValue.toUpperCase();

  if (curValue !== newValue) {
    console.log(`Replacing value: ${curValue} --> ${newValue}`);

    return affectedDoc.set({
      original: newValue,
    });
  } else {
    // Value is already upper-case
    // Don't perform a(nother) write to avoid infinite loops
    console.log('Value is already upper-case.');
  }
};

Python

from google.cloud import firestore

client = firestore.Client()


# Converts strings added to /messages/{pushId}/original to uppercase
def make_upper_case(data, context):
    path_parts = context.resource.split("/documents/")[1].split("/")
    collection_path = path_parts[0]
    document_path = "/".join(path_parts[1:])

    affected_doc = client.collection(collection_path).document(document_path)

    cur_value = data["value"]["fields"]["original"]["stringValue"]
    new_value = cur_value.upper()

    if cur_value != new_value:
        print(f"Replacing value: {cur_value} --> {new_value}")
        affected_doc.set({"original": new_value})
    else:
        # Value is already upper-case
        # Don't perform a second write (which can trigger an infinite loop)
        print("Value is already upper-case.")

Go


// Package upper contains a Firestore Cloud Function.
package upper

import (
	"context"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	"cloud.google.com/go/firestore"
	firebase "firebase.google.com/go/v4"
)

// FirestoreEvent is the payload of a Firestore event.
type FirestoreEvent struct {
	OldValue   FirestoreValue `json:"oldValue"`
	Value      FirestoreValue `json:"value"`
	UpdateMask struct {
		FieldPaths []string `json:"fieldPaths"`
	} `json:"updateMask"`
}

// FirestoreValue holds Firestore fields.
type FirestoreValue struct {
	CreateTime time.Time `json:"createTime"`
	// Fields is the data for this value. The type depends on the format of your
	// database. Log an interface{} value and inspect the result to see a JSON
	// representation of your database fields.
	Fields     MyData    `json:"fields"`
	Name       string    `json:"name"`
	UpdateTime time.Time `json:"updateTime"`
}

// MyData represents a value from Firestore. The type definition depends on the
// format of your database.
type MyData struct {
	Original struct {
		StringValue string `json:"stringValue"`
	} `json:"original"`
}

// GOOGLE_CLOUD_PROJECT is automatically set by the Cloud Functions runtime.
var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT")

// client is a Firestore client, reused between function invocations.
var client *firestore.Client

func init() {
	// Use the application default credentials.
	conf := &firebase.Config{ProjectID: projectID}

	// Use context.Background() because the app/client should persist across
	// invocations.
	ctx := context.Background()

	app, err := firebase.NewApp(ctx, conf)
	if err != nil {
		log.Fatalf("firebase.NewApp: %v", err)
	}

	client, err = app.Firestore(ctx)
	if err != nil {
		log.Fatalf("app.Firestore: %v", err)
	}
}

// MakeUpperCase is triggered by a change to a Firestore document. It updates
// the `original` value of the document to upper case.
func MakeUpperCase(ctx context.Context, e FirestoreEvent) error {
	fullPath := strings.Split(e.Value.Name, "/documents/")[1]
	pathParts := strings.Split(fullPath, "/")
	collection := pathParts[0]
	doc := strings.Join(pathParts[1:], "/")

	curValue := e.Value.Fields.Original.StringValue
	newValue := strings.ToUpper(curValue)
	if curValue == newValue {
		log.Printf("%q is already upper case: skipping", curValue)
		return nil
	}
	log.Printf("Replacing value: %q -> %q", curValue, newValue)

	data := map[string]string{"original": newValue}
	_, err := client.Collection(collection).Doc(doc).Set(ctx, data)
	if err != nil {
		return fmt.Errorf("Set: %w", err)
	}
	return nil
}

자바


import com.google.cloud.firestore.Firestore;
import com.google.cloud.firestore.FirestoreOptions;
import com.google.cloud.firestore.SetOptions;
import com.google.cloud.functions.Context;
import com.google.cloud.functions.RawBackgroundFunction;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class FirebaseFirestoreReactive implements RawBackgroundFunction {

  // Use GSON (https://github.com/google/gson) to parse JSON content.
  private static final Gson gson = new Gson();

  private static final Logger logger = Logger.getLogger(FirebaseFirestoreReactive.class.getName());
  private static final Firestore FIRESTORE = FirestoreOptions.getDefaultInstance().getService();

  private final Firestore firestore;

  public FirebaseFirestoreReactive() {
    this(FIRESTORE);
  }

  FirebaseFirestoreReactive(Firestore firestore) {
    this.firestore = firestore;
  }

  @Override
  public void accept(String json, Context context) {
    // Get the recently-written value
    JsonObject body = gson.fromJson(json, JsonObject.class);
    JsonObject tempJson = body.getAsJsonObject("value");

    // Verify that value.fields.original.stringValue exists
    String currentValue = null;
    if (tempJson != null) {
      tempJson = tempJson.getAsJsonObject("fields");
    }
    if (tempJson != null) {
      tempJson = tempJson.getAsJsonObject("original");
    }
    if (tempJson != null && tempJson.has("stringValue")) {
      currentValue = tempJson.get("stringValue").getAsString();
    }
    if (currentValue == null) {
      throw new IllegalArgumentException("Malformed JSON: " + json);
    }

    // Convert recently-written value to ALL CAPS
    String newValue = currentValue.toUpperCase(Locale.getDefault());

    // Update Firestore DB with ALL CAPS value
    Map<String, String> newFields = Map.of("original", newValue);

    String affectedDoc = context.resource().split("/documents/")[1].replace("\"", "");

    if (!currentValue.equals(newValue)) {
      // The stored value needs to be updated
      // Write the upper-cased value to Firestore
      logger.info(String.format("Replacing value: %s --> %s", currentValue, newValue));
      try {
        FIRESTORE.document(affectedDoc).set(newFields, SetOptions.merge()).get();
      } catch (ExecutionException | InterruptedException e) {
        logger.log(Level.SEVERE, "Error updating Firestore document: " + e.getMessage(), e);
      }
    } else {
      // The stored value is already upper-case, and doesn't need updating.
      // (Don't perform a "second" write, since that could trigger an infinite loop.)
      logger.info(String.format("Value is already upper-case."));
    }
  }
}

C#

using CloudNative.CloudEvents;
using Google.Cloud.Firestore;
using Google.Cloud.Functions.Framework;
using Google.Cloud.Functions.Hosting;
using Google.Events.Protobuf.Cloud.Firestore.V1;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace FirestoreReactive;

public class Startup : FunctionsStartup
{
    public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) =>
        services.AddSingleton(FirestoreDb.Create());
}

// Register the startup class to provide the Firestore dependency.
[FunctionsStartup(typeof(Startup))]
public class Function : ICloudEventFunction<DocumentEventData>
{
    private readonly ILogger _logger;
    private readonly FirestoreDb _firestoreDb;

    public Function(ILogger<Function> logger, FirestoreDb firestoreDb) =>
        (_logger, _firestoreDb) = (logger, firestoreDb);

    public async Task HandleAsync(CloudEvent cloudEvent, DocumentEventData data, CancellationToken cancellationToken)
    {
        // Get the recently-written value. This expression will result in a null value
        // if any of the following is true:
        // - The event doesn't contain a "new" document
        // - The value doesn't contain a field called "original"
        // - The "original" field isn't a string
        string currentValue = data.Value?.ConvertFields().GetValueOrDefault("original") as string;
        if (currentValue is null)
        {
            _logger.LogWarning($"Event did not contain a suitable document");
            return;
        }

        string newValue = currentValue.ToUpperInvariant();
        if (newValue == currentValue)
        {
            _logger.LogInformation("Value is already upper-cased; no replacement necessary");
            return;
        }

        // The CloudEvent subject is "documents/x/y/...".
        // The Firestore SDK FirestoreDb.Document method expects a reference relative to
        // "documents" (so just the "x/y/..." part). This may be simplified over time.
        if (cloudEvent.Subject is null || !cloudEvent.Subject.StartsWith("documents/"))
        {
            _logger.LogWarning("CloudEvent subject is not a document reference.");
            return;
        }
        string documentPath = cloudEvent.Subject.Substring("documents/".Length);

        _logger.LogInformation("Replacing '{current}' with '{new}' in '{path}'", currentValue, newValue, documentPath);
        await _firestoreDb.Document(documentPath).UpdateAsync("original", newValue, cancellationToken: cancellationToken);
    }
}

Ruby

require "functions_framework"

FunctionsFramework.on_startup do
  # Lazily construct a Firestore client when needed, and reuse it on
  # subsequent calls.
  set_global :firestore_client do
    require "google/cloud/firestore"
    Google::Cloud::Firestore.new project_id: ENV["GOOGLE_CLOUD_PROJECT"]
  end
end

# Converts strings added to /messages/{pushId}/original to uppercase
FunctionsFramework.cloud_event "make_upper_case" do |event|
  # Event-triggered Ruby functions receive a CloudEvents::Event::V1 object.
  # See https://cloudevents.github.io/sdk-ruby/latest/CloudEvents/Event/V1.html
  # The Firebase event payload can be obtained from the event data.
  cur_value = event.data["value"]["fields"]["original"]["stringValue"]

  # Compute new value and determine whether it needs to be modified.
  # If the value is already upper-case, don't perform another write,
  # to avoid infinite loops.
  new_value = cur_value.upcase
  if cur_value == new_value
    logger.info "Value is already upper-case"
    return
  end

  # Use the Firestore client library to update the value.
  # The document name can be obtained from the event subject.
  logger.info "Replacing value: #{cur_value} --> #{new_value}"
  doc_name = event.subject.split("documents/").last
  affected_doc = global(:firestore_client).doc doc_name
  new_doc_data = { original: new_value }
  affected_doc.set new_doc_data, merge: false
end

PHP


use Google\Cloud\Firestore\FirestoreClient;
use Google\CloudFunctions\CloudEvent;

function firebaseReactive(CloudEvent $cloudevent)
{
    $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');
    $data = $cloudevent->getData();

    $resource = $data['value']['name'];

    $db = new FirestoreClient();

    $docPath = explode('/documents/', $resource)[1];

    $affectedDoc = $db->document($docPath);

    $curValue = $data['value']['fields']['original']['stringValue'];
    $newValue = strtoupper($curValue);

    if ($curValue !== $newValue) {
        fwrite($log, 'Replacing value: ' . $curValue . ' --> ' . $newValue . PHP_EOL);

        $affectedDoc->set(['original' => $newValue]);
    } else {
        // Value is already upper-case
        // Don't perform another write (it might cause an infinite loop)
        fwrite($log, 'Value is already upper-case.' . PHP_EOL);
    }
}

함수 배포

다음 gcloud 명령어는 /messages/{pushId} 문서의 쓰기 이벤트에 의해 트리거되는 함수를 배포합니다.

gcloud functions deploy FUNCTION_NAME \
  --no-gen2 \
  --entry-point ENTRY_POINT \
  --runtime RUNTIME \
  --set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID \
  --trigger-event "providers/cloud.firestore/eventTypes/document.write" \
  --trigger-resource "projects/YOUR_PROJECT_ID/databases/(default)/documents/messages/{pushId}"
인수 설명
FUNCTION_NAME 배포 중인 Cloud 함수의 등록된 이름입니다. 소스 코드의 함수 이름 또는 임의의 문자열일 수 있습니다. FUNCTION_NAME이 임의의 문자열이면 --entry-point 플래그를 포함해야 합니다.
--entry-point ENTRY_POINT 소스 코드의 함수 또는 클래스 이름입니다. FUNCTION_NAME을 사용하여 배포 중에 실행할 소스 코드에 함수를 지정하지 않은 경우 선택사항입니다. 이러한 경우 --entry-point를 사용하여 실행 가능한 함수의 이름을 제공해야 합니다.
--runtime RUNTIME 사용 중인 런타임 이름입니다. 전체 목록은 gcloud 참조에서 확인할 수 있습니다.
--set-env-vars GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID 프로젝트의 고유 식별자(런타임 환경 변수)
--trigger-event NAME 함수에서 모니터링할 이벤트 유형입니다(write, create, update 또는 delete 중 하나).
--trigger-resource NAME 함수가 리슨할 정규화된 데이터베이스 경로입니다. "projects/YOUR_PROJECT_ID/databases/(default)/documents/PATH" 형식을 따라야 합니다. {pushId} 텍스트는 위의 문서 경로 지정에서 설명한 와일드 카드 매개변수입니다.

제한사항

Cloud Run Functions용 Firestore 트리거의 다음 제한사항에 유의하세요.

  • Cloud Run Functions(1세대)는 Firestore 기본 모드의 기존 '(기본값)' 데이터베이스를 기본 요건으로 합니다. Firestore 이름이 지정된 데이터베이스 또는 Datastore 모드는 지원하지 않습니다. 이 경우 Cloud Run Functions(2세대)를 사용하여 이벤트를 구성하세요.
  • 순서는 보장되지 않습니다. 급격하게 변경하면 예기치 않은 순서로 함수 호출이 트리거될 수 있습니다.
  • 이벤트는 최소 1회 전송되지만 하나의 이벤트에 함수가 여러 번 호출될 수 있습니다. 정확히 한 번에 처리하는 메커니즘에 의존하지 말고 멱등 함수를 작성하세요.
  • Datastore 모드의 Cloud Firestore에는 Cloud Run Functions(2세대)가 필요합니다. Cloud Run Functions(1세대)는 Datastore 모드를 지원하지 않습니다.
  • 트리거는 단일 데이터베이스와 연결됩니다. 여러 데이터베이스와 일치하는 트리거를 만들 수 없습니다.
  • 데이터베이스를 삭제해도 해당 데이터베이스의 트리거가 자동으로 삭제되지 않습니다. 트리거가 이벤트 제공을 중지하지만 트리거를 삭제하기 전까지 계속 존재합니다.
  • 일치하는 이벤트가 최대 요청 크기를 초과하면 이벤트가 Cloud Run Functions(1세대)로 전달되지 않을 수 있습니다.
    • 요청 크기로 인해 전송되지 않은 이벤트는 플랫폼 로그에 로깅되고 프로젝트의 로그 사용량에 반영됩니다.
    • 이러한 로그는 로그 탐색기에서 '크기가 1세대... 한도를 초과하여 Cloud 함수에 이벤트를 전송할 수 없음'이라는 error 심각도의 메시지와 함께 확인할 수 있습니다. functionName 필드 아래에서 함수 이름을 찾을 수 있습니다. receiveTimestamp 필드가 지금부터 1시간 이내인 경우 타임스탬프 전후의 스냅샷과 함께 해당 문서를 읽어 실제 이벤트 콘텐츠를 추론할 수 있습니다.
    • 이러한 주기를 피하려면 다음을 수행하면 됩니다.
      • Cloud Run Functions(2세대)로 마이그레이션 및 업그레이드
      • 문서 크기 줄이기
      • 문제의 Cloud Run Functions 삭제
    • 제외를 사용하여 로깅 자체를 사용 중지할 수 있지만 그래도 문제가 되는 이벤트가 전송되지 않습니다.