Napa Log

Blog

【firebase】ボタンを押してからの「1秒」の処理とひたすら戦い続けて「0.3秒」にした【firestore , cloud functions】

サムネイル

こんにちは、なぱです。
本業ではバックエンドエンジニアを1年経験したのちは、いつの間にかパワーポイントを作り続けるパワポ職人になっていて、開発欲がとても高まり、firebaseのcloud functionsを使ってみています。

cloud functionsとはなんぞや

AWSでいうlambdaになるんですかね。「サーバーレスコンピューティングサービス」という見たいです。有識者の方のブログを置いておきますので参考に。。。

awsのlambdaについて

自分でサーバーを用意することは不要で、関数を書いておいて、それをcloud functionsにデプロイしておくと呼び出すときに関数実行してくれるみたいな解釈です。

  1. 関数を呼び出し
  2. インスタンスが起動
  3. 必要なライブラリなどをインストール
  4. 関数が実行される

という動きになります。
知らない人でもなんだ、普通やないかと思うかもしれませんが、今回私が苦しめられたのは

「関数を呼び出すたびに、インスタンス起動&ライブラリインストールが始まる」というところです。
「いいねをする」という単純な処理でも、ものすごい時間がかかってしまうんです。

そもそものアプリの仕組み

ぺらぺら専門用語ばかりしゃべっちゃいました。私のブログは、ITについて詳しくない人でも楽しんでもらいたいと思ってるんです。(本当です)
なので、アプリの仕組みを少しお話しておきます

皆さんがSNSで使ってる「いいねボタン」とか「ブックマーク」とかって、画面のボタンを押すと、他の人の画面で見ても反映されてますよね?
SNSアプリの「いいね」とか「ブックマーク」とか「投稿内容」って、皆さんのスマホに保存されるんじゃなくて、サーバーに保存されているんです。

簡単には下記のような感じで、サーバーに「napaさんがいいねしましたよ!反映させといてね!」お願いして、サーバーさんがなんやかんや処理して投稿が保存される。
それで、ほかの皆さんの画面でも反映されてるんですよね。皆さんのスマホ自体にすべてが保存されるわけじゃないんです!

それで、firebaseのcloud functionsはユーザーのいいねしたいとかを受け取ると、「いいね処理を行います」とはならず、「サーバーのここの部分を確保して、必要なライブラリをインストールして、、、よし、いいね処理を行います!」
みたいな感じなんです。ずっと動いているサーバー(EC2とか)と比べると、これは遅くなりそうですね、APIには向いてないかも、、、

それでもcloud functionsを使いたかったのでどうにかするで

頑張って調査していく

調査方法

今実際どれぐらい時間がかかっているのかを知るのは大事です。
今回の処理の関数とセットで書いちゃってますが、下記のように同じ文字列でconsole.timeを仕込んでおくと、時間を計ってくれるみたいなので入れ込んでみる。

    console.time("addLike Execution Time"); // 処理時間の測定開始
      console.timeEnd("addLike Execution Time"); // 処理時間の測定完了

下記のような処理になってます(コードめちゃくちゃ省略してるのでつじつま合わないですが、、、)

import * as functions from "firebase-functions";
import {admin, apiLimitSetting} from "../firebaseConfig";
import {Timestamp} from "firebase-admin/firestore";

const db = admin.firestore();

export const addLike = functions
  .region("asia-northeast1")
  .runWith(apiLimitSetting)
  .https.onCall(async (data, context) => {
    console.time("addLike Execution Time"); // 処理時間の測定開始

    const targetRecordRef = db.doc(targetRecordPath);

    try {
      const targetDoc = await targetRecordRef.get();
      const timestamp = Timestamp.now();
      
      /// とても省略してます

      const likesSnapshot = await db
        .collection("Like")
        .where("picture_id", "==", imageId)
        .get();
      /// とても省略してます

      console.timeEnd("addLike Execution Time"); // 処理時間の測定完了
      return likes;
    } catch (error) {
      console.error("Error adding or removing like:", error);

    }
  });

調査結果

256MB
[1] >  addLike Execution Time: 1.156s
[1] >  addLike Execution Time: 1.166s
[1] >  addLike Execution Time: 1.170s
[1] >  addLike Execution Time: 1.169s
[1] >  addLike Execution Time: 1.242s
[1] >  addLike Execution Time: 1.146s
[1] >  addLike Execution Time: 1.304s
[1] >  addLike Execution Time: 1.147s
[1] >  addLike Execution Time: 1.136s
[1] >  addLike Execution Time: 1.154s
[1] >  addLike Execution Time: 2.239s
[1] >  addLike Execution Time: 1.144s
//省略
128MB
addLike Execution Time: 1.144s
addLike Execution Time: 1.212s
addLike Execution Time: 1.131s
addLike Execution Time: 1.128s
addLike Execution Time: 1.141s
addLike Execution Time: 1.131s
addLike Execution Time: 1.150s
addLike Execution Time: 1.618s
addLike Execution Time: 1.142s
addLike Execution Time: 1.134s
addLike Execution Time: 1.144s
addLike Execution Time: 1.133s
addLike Execution Time: 1.641s
addLike Execution Time: 1.277s
addLike Execution Time: 1.151s
//省略

表にまとめた。
メモリ設定量は、firebase cloud functionsに割り当てるメモリ容量を設定できますので、その値です。

メモリ設定量

回数

128MB

256MB

1

1.144

1.156

2

1.212

1.166

3

1.131

1.17

4

1.128

1.169

5

1.141

1.242

6

1.131

1.146

7

1.15

1.304

8

1.618

1.147

9

1.142

1.136

10

1.134

1.154

11

1.144

2.239

12

1.133

1.144

13

1.641

1.137

14

1.277

1.158

15

1.151

1.233

16

1.631

17

1.637

18

1.153

19

1.137

20

1.43

21

1.45

22

1.629

23

1.43

24

1.186

25

1.129

平均

1.22

1.30

256MBの11回目が特段遅いのは、コールドスタートのタイミングを引いたのでしょうかね。
呼び出されるときにインスタンスを作成するといいましたが、一度作成されるとしばらくはインスタンスが残ったままになるようですね。

それも含めての平均値ですが、あまり変わらずですね。コールドスタートと思しきところ除いてもメモリ設定量ではあまり変わらない模様。。。

ただ、私もうっかりしていましたが、この時間計測の関数は、関数内の頭から、関数内の値を返却するreturn文の間の計測ですので、厳密にコールドスタートがカウントされているとは限りません。
しかも、特異値を除くと1秒がアベレージですが、つまり、コールドスタートをのけても1秒近くかかってしまっているということです。これは遅い。遅いし、金額がかかる。

考察

どうやら、コールドスタートがすべて悪だと思っていたがそういうわけではなさそうであることがわかりました。
そこでふと、リージョンを思い返して設定を見直してみました。

cloud functions

firestore database

asia-northeast1

nam5

nam5は米国のようです。もしかしてこれが原因かな。

firebaseのリージョンについて

リージョン設定してみる

どうやら作成するときしか設定できないみたいなので、一度DB消して再設定になります。

再実施結果

メモリ設定量

回数

128MB

256MB

128MB
リージョン変更

1

1.144

1.156

0.448

2

1.212

1.166

0.311

3

1.131

1.17

0.87

4

1.128

1.169

0.26

5

1.141

1.242

0.107

6

1.131

1.146

0.327

7

1.15

1.304

0.137

8

1.618

1.147

0.318

9

1.142

1.136

0.115

10

1.134

1.154

0.288

11

1.144

2.239

0.107

12

1.133

1.144

0.99

13

1.641

1.137

0.185

14

1.277

1.158

0.104

15

1.151

1.233

0.338

16

1.631

0.129

17

1.637

0.106

18

1.153

0.113

19

1.137

0.1

20

1.43

0.83

21

1.45

0.16

22

1.629

0.92

23

1.43

0.95

24

1.186

0.101

25

1.129

0.136

平均

1.22

1.30

0.34

結果はコールドスタート込みで0.34秒!!!!1秒近く短縮できた。。。。。!!!!!!

結論

コールドスタート抜きでも処理が遅くなっていた理由は、functionsのリージョンと、firestoreのリージョンが異なっていたから、ということがわかりました。

クラウド初心者で、知識が乏しいことに加えて、アプリ作りたいのせっかちな心でドキュメントもまともに読んでいませんでした。正直、firestoreのリージョンどこにしてたっけも今回思い出しましたからね。

ただ、リージョンは金額に影響するだけではなく、パフォーマンスにもかなり影響してくることに気づけたので、本当によかったです。

困っている方がいらっしゃいしたら、是非参考にしてみてください。