コンソールへ移動

ヒントとアドバイス

このドキュメントでは、Cloud Functions を設計、実装、テスト、デプロイする際のおすすめの方法について説明します。

正確性

このセクションでは、Cloud Functions を設計および実装するための一般的なベスト プラクティスについて説明します。

べき等ファンクションを作成する

ファンクションは、何回呼び出されても結果が同じになることが必要です。これにより、前の呼び出しがコードの途中で失敗した場合は、呼び出しを再試行できます。詳しくは、バックグラウンド ファンクションの再試行についての記事をご覧ください。

バックグラウンド アクティビティを開始しない

バックグラウンド アクティビティはファンクションの終了後に発生します。ファンクションの呼び出しの終了とは、ファンクションが結果を返したときです。または、Node.js バックグラウンド ファンクションで callback 引数を呼び出すなどして完了のシグナルを発生させることもあります。正常に終了した後は、あらゆるコードは CPU にアクセスできず、処理を続行できません。

加えて、同じ環境で次の呼び出しが実行されると、バックグラウンド アクティビティが再開され、新しい呼び出しが中断されます。これが、予期せぬ動作や診断が難しいエラーにつながる可能性があります。ファンクションの終了後にネットワークにアクセスすると、通常は接続がリセットされます(ECONNRESET エラーコード)。

個々の呼び出しのログで呼び出し完了を示す行の後を見ると、バックグラウンド アクティビティが記録されていることがよくあります。特に、コールバックやタイマーなどの非同期処理が存在する場合は、バックグラウンド アクティビティがコードの中に埋もれている可能性があります。コードを確認し、ファンクションの終了前にすべての非同期処理が完了するようにしてください。

一時ファイルを常に削除する

一時ディレクトリ内のローカル ディスク ストレージは、メモリ内ファイル システムです。書き込んだファイルはファンクションで使用できるメモリを消費し、呼び出し後も維持される場合があります。これらのファイルを明示的に削除しないと、最終的にメモリ不足エラーにつながり、その結果コールド スタートが発生する可能性があります。

個々の関数で使用されるメモリの量を確認するには、GCP Console の関数リストで関数を選択してから、[メモリ使用量] プロットを選択します。

一時ディレクトリ以外への書き込みはしないでください。また、プラットフォームや OS に依存しない方法でファイルパスを構築してください。

一時ファイルのサイズ制限は、パイプラインを使用して回避できます。たとえば、Cloud Storage でファイルを処理するために、読み取りストリームを作成し、これをストリームベースのプロセスに渡してから、出力ストリームを Cloud Storage に直接書き込むことができます。

ツール

このセクションでは、ツールを使用して Cloud Functions を実装、テスト、操作する方法を説明します。

ローカルでの開発

関数のデプロイには時間がかかるため、多くの場合、関数のコードをローカルでテストするほうが時間を短縮できます。

Firebase デベロッパーは Firebase CLI Cloud Functions エミュレータを使用できます。

SendGrid を使ってメールを送信する

Cloud Functions はポート 25 での送信接続を許可しないため、保護されていない接続では SMTP サーバーに接続できません。メールを送信するには、SendGrid を使用することをおすすめします。完全な例は SendGrid のチュートリアルで確認できます。また、メールの送信に関するその他のオプションについては、Google Compute Engine ドキュメントのインスタンスからのメールの送信をご覧ください。

パフォーマンス

このセクションでは、パフォーマンスを最適化するためのベスト プラクティスについて説明します。

依存関係を適切に使用する

ファンクションはステートレスであるため、多くの場合、実行環境はゼロから初期化されます(これをコールド スタートといいます)。コールド スタートが発生すると、ファンクションのグローバル コンテキストが評価されます。

ファンクションによってモジュールがインポートされる場合、それらのモジュールの読み込み時間は、コールド スタート時の呼び出しレイテンシに加算されます。依存関係を正しく読み込み、ファンクションが使用しない依存関係を読み込まないようにすることで、このレイテンシとファンクションのデプロイに必要な時間を短縮できます。

グローバル変数を使用して将来の呼び出しでオブジェクトを再利用する

Cloud Function の状態は、将来の呼び出しのために必ずしも保持されるわけではありません。しかし、Cloud Functions が以前の呼び出しの実行環境をリサイクルすることはよくあります。変数をグローバル スコープで宣言すると、その値は再計算せずに後続の呼び出しで再利用できるようになります。

この方法では、ファンクションの呼び出しごとに再作成するためコストが高くなりがちなオブジェクトをキャッシュに保存できます。このようなオブジェクトをファンクションの本文からグローバル スコープに移動して、パフォーマンスを大幅に向上することができます。次の例では、heavy オブジェクトをファンクションのインスタンスにつき 1 回だけ作成し、指定されたインスタンスに到達するまですべてのファンクション呼び出しで共有します。

console.log('Global scope');
const perInstance = heavyComputation();
const functions = require('firebase-functions');

exports.function = functions.https.onRequest((req, res) => {
    console.log('Function invocation');
    const perFunction = lightweightComputation();

    res.send(`Per instance: ${perInstance}, per function: ${perFunction}`);
});

ネットワーク接続、ライブラリ参照、および API クライアント オブジェクトをグローバル スコープでキャッシュに保存することが非常に重要です。例については、ネットワークの最適化の記事をご覧ください。

グローバル変数の遅延初期化を行う

グローバル スコープで変数を初期化する場合、初期化コードは常にコールド スタート呼び出しによって実行され、ファンクションのレイテンシが長くなります。一部のコードパスでのみ使用されるオブジェクトについては、必要に応じて遅延させて初期化することを検討してください。

const functions = require('firebase-functions');
let myCostlyVariable;

exports.function = functions.https.onRequest((req, res) => {
    doUsualWork();
    if(unlikelyCondition()){
        myCostlyVariable = myCostlyVariable || buildCostlyVariable();
    }
    res.status(200).send('OK');
});

これは特に、複数のファンクションを 1 つのファイルに定義し、ファンクションごとに異なる変数を使用する場合に重要です。遅延初期化を使用しないと、初期化されたが使用されない変数にリソースを浪費することがあります。

参考リンク

パフォーマンスの改善について詳しくは、「Google Cloud Performance Atlas」の動画 Cloud Functions Cold Boot Time をご覧ください。