第 1 世代の Node.js 関数を第 2 世代にアップグレードする

現在第 1 世代の関数を使用しているアプリは、このガイドの手順に沿って第 2 世代に移行することを検討してください。第 2 世代の関数は Cloud Run を使用し、パフォーマンス、構成、モニタリングなどを改善しています。

このページの例では、JavaScript で CommonJS モジュールを使用していること(require スタイルのインポート)を前提としていますが、JavaScript での ESM の使用(import … from スタイルのインポート)や TypeScript にも同じ原則が適用されます。

移行プロセス

第 1 世代と第 2 世代の関数は同じファイルに共存できます。これにより、移行の準備を少しずつ整えることができます。一度に 1 つの関数を移行し、テストと検証を行ってから次に進むことをおすすめします。

Firebase CLI と firebase-function のバージョンを確認する

Firebase CLI バージョン 12.00firebase-functions バージョン 4.3.0 を使用していることを確認します。新しいバージョンは第 1 世代だけでなく第 2 世代もサポートします。

インポートを更新する

第 2 世代の関数は、firebase-functions SDK の v2 サブパッケージからインポートされます。このインポートパスの違いから、Firebase CLI は関数コードを第 1 世代の関数としてデプロイするか、第 2 世代の関数としてデプロイするかを決めます。

v2 サブパッケージはモジュール式であるため、必要な特定のモジュールのみをインポートすることをおすすめします。

変更前: 第 1 世代

const functions = require("firebase-functions");

変更後: 第 2 世代

// explicitly import each trigger
const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");

トリガーの定義を更新する

第 2 世代の SDK ではモジュラー インポートが優先されるため、トリガーの定義を更新して、前の手順で変更したインポートを反映させます。

一部のトリガーでコールバックに渡される引数が変更されました。この例では、onDocumentCreated コールバックの引数は単一の event オブジェクトに統合されています。さらに、onRequest トリガーの cors オプションなど、一部のトリガーには便利な新しい構成機能があります。

変更前: 第 1 世代

const functions = require("firebase-functions");

exports.date = functions.https.onRequest((req, res) => {
  // ...
});

exports.uppercase = functions.firestore
  .document("my-collection/{docId}")
  .onCreate((change, context) => {
    // ...
  });

変更後: 第 2 世代

const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");

exports.date = onRequest({cors: true}, (req, res) => {
  // ...
});

exports.uppercase = onDocumentCreated("my-collection/{docId}", (event) => {
  /* ... */
});

パラメータ化された構成を使用する

第 2 世代の関数では functions.config のサポートが終了しています。代わりに、安全なインターフェースを使用して、コードベースの内部で構成パラメータを宣言的に定義できます。新しい params モジュールでは、すべてのパラメータに有効な値が指定されていなければ、CLI でデプロイがブロックされます。構成が欠落している関数もデプロイされなくなります。

params サブパッケージに移行する

functions.config で環境構成を使用している場合は、既存の構成をパラメータ化された構成に移行できます。

変更前: 第 1 世代

const functions = require("firebase-functions");

exports.date = functions.https.onRequest((req, res) => {
  const date = new Date();
  const formattedDate =
date.toLocaleDateString(functions.config().dateformat);

  // ...
});

変更後: 第 2 世代

const {onRequest} = require("firebase-functions/v2/https");
const {defineString} = require("firebase-functions/params");

const dateFormat = defineString("DATE_FORMAT");

exports.date = onRequest((req, res) => {
  const date = new Date();
  const formattedDate = date.toLocaleDateString(dateFormat.value());

  // ...
});

パラメータ値を設定する

初めてデプロイする場合、Firebase CLI はすべてのパラメータの値を求めるプロンプトを表示します。これらの値は dotenv ファイルに保存します。functions.config 値をエクスポートするには、firebase functions:config:export を実行します。

安全性を高めるため、パラメータのタイプ検証ルールを指定することもできます。

特殊なケース: API キー

params モジュールは Cloud Secret Manager と統合されています。API キーなどの機密性の高い値に対して、きめ細かいアクセス制御を行うことができます。詳細については、シークレット パラメータをご覧ください。

変更前: 第 1 世代

const functions = require("firebase-functions");

exports.getQuote = functions.https.onRequest(async (req, res) => {
  const quote = await fetchMotivationalQuote(functions.config().apiKey);
  // ...
});

変更後: 第 2 世代

const {onRequest} = require("firebase-functions/v2/https");
const {defineSecret} = require("firebase-functions/params");

// Define the secret parameter
const apiKey = defineSecret("API_KEY");

exports.getQuote = onRequest(
  // make the secret available to this function
  { secrets: [apiKey] },
  async (req, res) => {
    // retrieve the value of the secret
    const quote = await fetchMotivationalQuote(apiKey.value());
    // ...
  }
);

ランタイム オプションを設定する

第 1 世代と第 2 世代でランタイム オプションの構成が変更されました。第 2 世代は、すべての関数のオプションを設定するための新機能が追加されています。

変更前: 第 1 世代

const functions = require("firebase-functions");

exports.date = functions
  .runWith({
    // Keep 5 instances warm for this latency-critical function
    minInstances: 5,
  })
  // locate function closest to users
  .region("asia-northeast1")
  .https.onRequest((req, res) => {
    // ...
  });

exports.uppercase = functions
  // locate function closest to users and database
  .region("asia-northeast1")
  .firestore.document("my-collection/{docId}")
  .onCreate((change, context) => {
    // ...
  });

変更後: 第 2 世代

const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");
const {setGlobalOptions} = require("firebase-functions/v2");

// locate all functions closest to users
setGlobalOptions({ region: "asia-northeast1" });

exports.date = onRequest({
    // Keep 5 instances warm for this latency-critical function
    minInstances: 5,
  }, (req, res) => {
  // ...
});

exports.uppercase = onDocumentCreated("my-collection/{docId}", (event) => {
  /* ... */
});

同時実行を使用する

第 2 世代の関数の大きな利点は、1 つの関数インスタンスで同時に複数のリクエストを処理できることです。これにより、エンドユーザーが経験するコールド スタートの数を大幅に削減できます。デフォルトでは同時実行数は 80 に設定されていますが、1~1,000 の任意の値に設定できます。

const {onRequest} = require("firebase-functions/v2/https");

exports.date = onRequest({
    // set concurrency value
    concurrency: 500
  },
  (req, res) => {
    // ...
});

同時実行を調整すると、パフォーマンスが向上し、関数の費用を抑えることができます。同時実行の詳細については、同時リクエストを許可するをご覧ください。

グローバル変数の使用状況を監査する

同時実行を考慮せずに記述された第 1 世代の関数は、リクエストごとに設定と読み取りが実行されるグローバル変数を使用する場合があります。同時実行が有効になっているときに、1 つのインスタンスで複数のリクエストの処理が開始すると、同時実行リクエストがグローバル変数の設定と読み取りを同時に行うため、関数にバグが生じる可能性があります。

アップグレード中に、関数の CPU を gcf_gen1 に設定し、concurrency を 1 に設定すると、第 1 世代の動作に戻すことができます。

const {onRequest} = require("firebase-functions/v2/https");

exports.date = onRequest({
    // TEMPORARY FIX: remove concurrency
    cpu: "gcf_gen1",
    concurrency: 1
  },
  (req, res) => {
    // ...
});

ただし、第 2 世代関数のパフォーマンス上のメリットは失われるため、長期的な解決策としてはおすすめしません。代わりに、関数内のグローバル変数の使用状況を監査し、準備ができたらこれらの一時的な設定を削除します。

新しい第 2 世代の関数にトラフィックを移行する

関数のリージョンやトリガータイプを変更する場合と同様に、第 2 世代の関数に新しい名前を指定して、トラフィックを徐々に移行する必要があります。

同じ名前で第 1 世代から第 2 世代に関数をアップグレードして firebase deploy を実行することはできません。そのようにすると、次のエラーが発生します。

Upgrading from GCFv1 to GCFv2 is not yet supported. Please delete your old function or wait for this feature to be ready.

以下の手順を行う前に、まず関数がべき等であることを確認します。これは、関数の新旧両方のバージョンが変更中に同時に実行されるためです。たとえば、Firestore の書き込みイベントに応答する第 1 世代の関数がある場合、書き込みイベントに対して 2 回応答(第 1 世代の関数が 1 回、第 2 世代の関数が 1 回)したとしても、アプリの一貫性が維持された状態になるようにしてください。

  1. 関数のコード内で関数の名前を変更します。たとえば、resizeImage の名前を resizeImageSecondGen に変更します。
  2. 関数をデプロイし、元の第 1 世代の関数と第 2 世代の関数の両方が実行されるようにします。
    1. 呼び出し可能、タスクキュー、HTTP トリガーの場合は、第 2 世代の関数の名前または URL を使用するようにクライアント コードを更新して、すべてのクライアントが第 2 世代の関数を指すようにします。
    2. バックグラウンド トリガーを使用すると、第 1 世代と第 2 世代の両方の関数がデプロイ後すぐにすべてのイベントに応答します。
  3. すべてのトラフィックが移行されたら、firebase CLI の firebase functions:delete コマンドを使用して第 1 世代の関数を削除します。
    1. 必要に応じて、第 2 世代の関数の名前を変更し、第 1 世代の関数と同じ名前になるようにします。