Cloud Functions for Firebase のコールドスタート含む時間計測したら思ったより高速化されていた
投稿日: 2024/05/23
更新日: 2024/05/28
Firebase Cloud Functions を使ってAPIを開発したり、動的Webサイトのホスティングを行おうとしたときに気になるのはコールドスタートにかかる時間だと思います。
個人的に動的Webサイトのホスティング先にコールドスタートのないCloudflare Workersを検討していたのですが、Node環境でないので未対応のPackageが多く、苦戦していました。また、個人的にFirebaseやGCPを中心に活用しているので、Cloud Functionsについて改めて調査してみました。
事前情報
2022/02 ごろの記事ですが、メモリを増やすとFirestoreのコネクションを張る時間が軽減されるらしい。コールドスタート含めて1.3秒 ~ 4.5秒ほどかかる。
今回、Cloud Functionsの関数内で、Firestoreとのやりとりがあるが、Firestoreへの最初のリクエスト時には、新規にコネクションを貼る処理が発生する。Cloud Functionsの新しいインスタンスが立ち上がるたびに、Firestoreとの新規コネクション生成が発生するので、その分さらに時間がかかる。
https://zenn.dev/seisei/articles/7c02fdffa866a0
色々と調査した結果、コネクションの確立を早くするためには、メモリの割り当てを増やすといいとのこと。当初、メモリの割り当てはデフォルトの256MBで、コールドスタート時の実行時間は4.5秒ほどかかっていた。メモリ使用量を見ても、それほど問題に思えなかったので、それに紐づいているCPUの割り当てが原因だと思う。メモリの割り当て量を512MBに増やしたところ、コールドスタート時の実行時間は2.2秒となった。さらに、1024MBに増やすと、1.3秒にまで改善した。
https://zenn.dev/seisei/articles/7c02fdffa866a0
さらに上記記事が参考にしているのは2019年の記事であり、かなり時間がたっている。
また、2020/09ごろにCloud Run および Cloud Functions(第 2 世代)向けに起動時CPU ブースト機能が提供されている。
Firebase Admin SDK なし
Firebase Admin SDK を使わないコードでレスポンスが返ってくる時間を測ります。適当な計測なのであくまで参考程度にしてください。
コードは以下の感じです。
import * as functionsV1 from "firebase-functions/v1"; import { onRequest } from "firebase-functions/v2/https"; // import * as logger from "firebase-functions/logger"; // Start writing functions // https://firebase.google.com/docs/functions/typescript export const v1m128 = functionsV1 .region("asia-northeast1") .runWith({ maxInstances: 1, timeoutSeconds: 10, memory: "128MB", }) .https.onRequest((req, res) => { res.json({}); }); export const v1m256 = functionsV1 .region("asia-northeast1") .runWith({ maxInstances: 1, timeoutSeconds: 10, memory: "256MB", }) .https.onRequest((req, res) => { res.json({}); }); export const v1m512 = functionsV1 .region("asia-northeast1") .runWith({ maxInstances: 1, timeoutSeconds: 10, memory: "512MB", }) .https.onRequest((req, res) => { res.json({}); }); export const v2m128 = onRequest( { region: ["asia-northeast1"], maxInstances: 1, timeoutSeconds: 10, memory: "128MiB", }, (req, res) => { res.status(200).send({}); } ); export const v2m256 = onRequest( { region: ["asia-northeast1"], maxInstances: 1, timeoutSeconds: 10, memory: "256MiB", }, (req, res) => { res.status(200).send({}); } ); export const v2m512 = onRequest( { region: ["asia-northeast1"], maxInstances: 1, timeoutSeconds: 10, memory: "512MiB", }, (req, res) => { res.status(200).send({}); } );
結果です。
functions version / memory | 1回目 | 2回目 |
---|---|---|
v1 / 128MB | 1270ms | 1320ms |
v1 / 256MB | 1170ms | 1160ms |
v1 / 512MB | 1250ms | 960ms |
v2 / 128MB | 1600ms | 1330ms |
v2 / 256MB | 1280ms | 1500ms |
v2 / 512MB | 1430ms | 1390ms |
v1とv2共にメモリが128MBの時点で予想より速いです。この結果はコールドスタートの時間を含んでいます。また、メモリを増やしても高速化はしてないように見えます。
Firebase Admin SDK あり(データFetchなし)
コードは先ほどのFirebase Admin SDK なし
のコードとほぼ同じです。import { onRequest } from "firebase-functions/v2/https";
の下に以下のコードを追加してFirebase Admin SDKを初期化します。
import * as admin from "firebase-admin"; admin.initializeApp();
結果です。
functions version / memory | 1回目 | 2回目 |
---|---|---|
v1 / 128MB | 1830ms | 1730ms |
v1 / 256MB | 1730ms | 1720ms |
v1 / 512MB | 1420ms | 1800ms |
v2 / 128MB | 1640ms | 1770ms |
v2 / 256MB | 2120ms | 2140ms |
v2 / 512MB | 1380ms | 1620ms |
初期化分遅くなりましたが、それもわずかな差です。
参考にした記事のように4秒以上かかることもありませんでした。
Firebase Admin SDK あり(データFetchあり)
Firebase Admin SDKを初期化のあとに、FirestoreからデータをFetchしてみます。
import * as functionsV1 from "firebase-functions/v1"; import { onRequest } from "firebase-functions/v2/https"; // import * as logger from "firebase-functions/logger"; // Start writing functions // https://firebase.google.com/docs/functions/typescript import * as admin from "firebase-admin"; admin.initializeApp(); export const v1m128 = functionsV1 .region("asia-northeast1") .runWith({ maxInstances: 1, timeoutSeconds: 10, memory: "128MB", }) .https.onRequest(async (req, res) => { const adminDb = admin.firestore(); const ref = adminDb.collection("dummy").doc("dummy"); await ref.get(); res.status(200).send({}); }); export const v1m256 = functionsV1 .region("asia-northeast1") .runWith({ maxInstances: 1, timeoutSeconds: 10, memory: "256MB", }) .https.onRequest(async (req, res) => { const adminDb = admin.firestore(); const ref = adminDb.collection("dummy").doc("dummy"); await ref.get(); res.status(200).send({}); }); export const v1m512 = functionsV1 .region("asia-northeast1") .runWith({ maxInstances: 1, timeoutSeconds: 10, memory: "512MB", }) .https.onRequest(async (req, res) => { const adminDb = admin.firestore(); const ref = adminDb.collection("dummy").doc("dummy"); await ref.get(); res.status(200).send({}); }); export const v2m128 = onRequest( { region: ["asia-northeast1"], maxInstances: 1, timeoutSeconds: 10, memory: "128MiB", }, async (req, res) => { const adminDb = admin.firestore(); const ref = adminDb.collection("dummy").doc("dummy"); await ref.get(); res.status(200).send({}); } ); export const v2m256 = onRequest( { region: ["asia-northeast1"], maxInstances: 1, timeoutSeconds: 10, memory: "256MiB", }, async (req, res) => { const adminDb = admin.firestore(); const ref = adminDb.collection("dummy").doc("dummy"); await ref.get(); res.status(200).send({}); } ); export const v2m512 = onRequest( { region: ["asia-northeast1"], maxInstances: 1, timeoutSeconds: 10, memory: "512MiB", }, async (req, res) => { const adminDb = admin.firestore(); const ref = adminDb.collection("dummy").doc("dummy"); await ref.get(); res.status(200).send({}); } );
結果です。
functions version / memory | 1回目 | 2回目 |
---|---|---|
v1 / 128MB | 2470ms | 2070ms |
v1 / 256MB | 2120ms | 3970ms |
v1 / 512MB | 2800ms | 1900ms |
v2 / 128MB | 2390ms | 2490ms |
v2 / 256MB | 2310ms | 1150ms |
v2 / 512MB | 2500ms | 1850ms |
増えた時間は純粋にFetchにかかった時間という感じですね。実際のAPIとして使うときに高速ではないですが、実用上許容範囲だと思います。
まとめ
Cloud Functionsのコールドスタートは十分に速かったです。Firebase Admin SDKの初期化なしの場合1000 ~ 1500ms程度、Firebase Admin SDKの初期化ありの場合1500 ~ 2000ms程度でした。
ちなみにコールドスタートなしの場合、15 ~ 100ms程度になります。
計測方法が全く違うので単純に比較できませんが、2022年ごろの記事のように4秒以上かかるといったことはありませんでした。また、メモリを増やしてもFirebase Admin SDKの初期化にかかる時間は短くなりませんでした。メモリを1GBも計測しましたが大差ありませんでした。
v1とv2の性能差が出なかったのは意外でした。しかし、v2の方がコールドスタートしづらいように改善されてます。
2024年9月ごろにv2がデフォルトになるというメールが来ていたのですが、2024年5月時点でCloud Functions for Firebase(第 2 世代)はまだ現在は公開プレビューなんですよね。。。
Firebase Admin SDKの初期化には500ms程度かかっていますが、これはそこまで気にしなくてもいいです。なぜなら2回目以降のアクセス(コールドスタートなしの場合)で同じインスタンスを使う場合はFirebase Admin SDKの初期化はスキップされるからです。
コールドスタート含んでこの時間は個人的に想像より早かったですし、実用的にも許容範囲でした。GCPで運用しているサービスではCloud Functionsを一層活用しようと思ったのでした。めでたし、めでたし。