Firebase の課金で爆死しないための設定方法

# まえがき

個人アプリで Firebase を使用していると、課金が怖くなることがあります。
無料プランなら容量を超えたらサービスが利用できなくなるだけですが、Braze プラン(従量課金制)だと放っておくと無限に料金が増えることになるわけです。
それを防ぐための知恵をここにまとめます。

そもそも、Functions さえ無料だったら Braze プランに移行しなくて済んだんですけどね・・・。
同じような方はきっと多いはず。

# 読者の前提

* 個人での Firebase 利用者
  * 商用アプリケーション等で停止できないサービスを運営している方は、この資料は参考にはなると思いますがやや趣旨がズレます。例えばオートスケールの最大インスタンス数などは余力を持った設定が望ましいですが、ここではその方法は案内しません。
* Brazeプラン(従量課金制)を利用している方

# Functions

Functions は関数の実行時間、関数の呼び出し回数、関数にプロビジョニングしたリソースの数に基づいて課金されます。
関数に対しDos攻撃されると困るので、以下の最大インスタンス数の設定をしておくことでコストを抑えられます。

### 最大インスタンス数の確認と設定方法

Functions はアクセスが増えるとオートスケールされるので、最大インスタンス数を決めておくことで予期せぬコスト増を避けることができます。
個人アプリでアクセスも全然なく、最悪死んでも良いのなら最大インスタンス数は「1」で十分でしょう。

現在の設定の確認方法は以下です。

1. Google Cloud の CloudFunctions に行く
https://console.cloud.google.com/functions/list
1. 対象の api をクリックし「詳細」を押下する

するとこのように現在の設定が表示されます。

![スクリーンショット 2023-01-05 16.50.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27159/81a124fb-0aeb-be99-21c4-94a9fe1b87e4.png)

設定の変更方法は以下の2パターンあります。

* この Google Cloud コンソールで「編集」ボタンを押して編集する
* アプリケーションから値を指定する

前者は説明の必要がないと思うので、後者のやり方を説明します。
functions の API を export する以下のコードがあったとすると

```typescript
export const api = functions.https.onRequest(app);
```

これを以下のように書き換えます。 `maxInstances` は最大インスタンス数ですが、他にも `timeoutSeconds` タイムアウト秒数、 `memory` メモリ、などの値を設定できます。

```typescript
export const api = functions.runWith({
  maxInstances: 1,
  timeoutSeconds: 30,
  memory: "128MB",
}).https.onRequest(app);
```

# Hosting

Hosting はストレージが10GBまで無料なのと、データ転送は1ヶ月あたり 10GB まで無料です。それを超えると課金が発生します。

### 保持するリリース数を制限しストレージを確保する

Hosting はリリースするたびにそのリリースしたバージョンを保持します。
これがデフォルトでは無制限に保持する設定になっているため、ストレージを圧迫します。
Firebase コンソールの Hostring から設定を変更しましょう。「{プロジェクト名} のリリース履歴」という項目の三点メニューを押すと「ストレージのリリースに関する設定」というのが表示されるのでそこから設定を変えます。

![スクリーンショット 2023-01-05 17.13.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27159/33e07d8e-e841-91fb-24aa-a068d7e38ac5.png)

個人アプリなら10もあれば十分でしょう。数値を入れると使用するストレージの目安も表示されるので、それを確認しながら設定します。

### プレビューチャネルの有効期限を設定しストレージを確保する

プレビューチャネルを利用している場合は有効期限の設定を確認しておきましょう。プレビューチャネルはデフォルトで作成日から7日で期限切れになりますが、ライブチャネルは期限切れになりません。

プレビューチャネルの行で、3点メニューをクリックし、「チャネル設定」を選択します。そこで有効期限の日時を入力します。

### 転送量は制限できないのでアプリケーションの作りで頑張る

転送量のほうは何かを設定して塞ぐことはできません。
そのためFirebaseのドキュメントによると以下を頑張って転送量を削減しましょう、と言っています。

* コンテンツのクライアント側のキャッシュを微調整して、ブラウザーが CDN からリソースを要求する必要がないようにします。キャッシュの詳細については、 Web 開発者向けドキュメントを参照してください。
* 実際に表示する必要のない画像や動画を読み込まないようにします。
* 特定のリクエストを処理する Service Worker を作成します。

詳細はこちら
https://firebase.google.com/docs/hosting/usage-quotas-pricing#control-data-transfer-usage

# Firestore

Firestore は以下の内容で課金されます。

* **読んだり、書いたり、削除したりする文書の数**。
* **集計クエリで一致したインデックス エントリの数**。クエリに一致する最大 1000 のインデックス エントリのバッチごとに 1 つのドキュメントの読み取りが課金されます。
* メタデータとインデックスのオーバーヘッドを含む、**データベースが使用するストレージの量**。
* **使用するネットワーク帯域幅の量**。

Firestore は何か制限を設けることができません。
なので上記を参考に書き込みや読み取り方法をチューニングしたり、インデックスを作成してアプリケーション側をよくしていくしかありません。

# 全体：コストアラートを設定する

「これ以上課金されたら困る！」という閾値を設定しておくことで、「もうすぐ設定したコストに近づいちゃうよ！」というアラートを受け取ることができます。

Google Cloud コンソール上から「お支払い」>「予算とアラート」>「予算名」から設定できます。
https://console.cloud.google.com/billing/

私の個人アプリは自分がたまにアクセスする程度なので、月1,000円のアラートを設定しています。以下のように予算の 50%, 90%, 100% でアラートが来る設定をしています。

![スクリーンショット 2023-01-05 16.37.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27159/b34d5fd1-a90d-a729-52c2-041d8cec7268.png)

# 最悪、Firebaseを止める方法

最悪の状況を想定しておきましょう。
何者かにDDos攻撃を受け、コストアラートが鳴り響き、「やばい！なんとかしなきゃ！」となったとき、最悪 Firebase を止める方法を知っていれば落ち着いて対処できます。

### Hosting を止める方法

```bash
$ firebase hosting:disable
```

実行後、hostring にアクセスしてみるとちゃんと404になりました。

![スクリーンショット 2023-01-05 21.03.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27159/07721dcd-e065-99f4-1366-38a5be14e599.png)

### Functions を止める方法

Functions はGUIから簡単に関数を削除できます。
Firebase コンソールから Functions を選択し、対象の関数の3点メニューを押すと「関数を削除」というメニューがあるのでそこから削除できます。

![スクリーンショット 2023-01-08 21.19.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27159/a99bc3c8-7700-30f0-e334-e0360b05dc2c.png)

コマンドで関数名を指定して止める方法もあります。

```bash
$ firebase functions:delete {関数名}
```

関数を削除後、functions を叩いてみると以下のように謎のページが出て取得できなくなりました。

![スクリーンショット 2023-01-05 21.06.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27159/0fb06faf-33d0-c75c-38dd-b9ec766bbe1d.png)

### Firestore を止める方法

アプリケーション側が止まっていれば Firestore の読み書きがなくなるはずですが、キーが漏れたなど最悪のケースは Firestore 直で読み書きがくるかもしれません。念のため止める手を知っておきましょう。

Firestore はコレクションを delete したりすることはできますが、Firestore　自体を止めることはできません。というかコレクションを消してしまうとあとで復旧がきついです。
ではどうするかというと読み取り、書き込み全てをブロックします。
Firebase コンソールから以下のルールの設定を入れて公開してください。

```
rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if false;
      allow write: if false;
    }
  }
}

```

Firebase コンソール上で見ると以下のような画面です。

![スクリーンショット 2023-01-05 21.08.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27159/ef0f2d83-d017-cb8d-da19-6b938f358047.png)

これでデータの読み書きが制限されるので、これ以上の課金は発生しないはずです。

# まとめ

個人アプリで開発していると課金で爆死が怖いので、今回はそれを防ぐための方法を確認しました。

その他、Firebase全体のセキュリティが問題ないかチェックしたい場合は以下のドキュメントを参考にすると良いと思います。
https://firebase.google.com/support/guides/security-checklist