Firebase の課金で爆死しないための設定方法
を参考に設定を調べる
Functions は関数の実行時間、関数の呼び出し回数、関数にプロビジョニングしたリソースの数に基づいて課金される
1 | export const api = functions.https.onRequest(app); |
の部分を
1 2 3 4 5 | export const api = functions.runWith({ maxInstances: 1, timeoutSeconds: 30, memory: "128MB" , }).https.onRequest(app); |
というように
最大インスタンス数1
timeoutSeconds30秒
メモリ128MB
というように設定することで回避できそう
次に
Hosting はストレージが10GBまで無料
データ転送は1ヶ月あたり 10GB まで無料
それを超えると課金が発生
デフォルトでは無制限に保持する設定になっているため
Firebase コンソールの Hostring から設定を変更
これらを元に
個人アプリなら10もあれば十分
転送量は制限できないのでアプリケーションの作りで頑張るしかない
あと
Firestore は以下の内容で課金されます。
* 読んだり、書いたり、削除したりする文書の数。
* 集計クエリで一致したインデックス エントリの数。
クエリに一致する最大 1000 のインデックス エントリのバッチごとに
1 つのドキュメントの読み取りが課金されます。
* メタデータとインデックスのオーバーヘッドを含む、データベースが使用するストレージの量。
* 使用するネットワーク帯域幅の量。
Firestore は何か制限を設けることができません。
なので上記を参考に書き込みや読み取り方法をチューニングしたり、
インデックスを作成してアプリケーション側をよくしていくしかない
そして
コストアラートを設定する
予算の 50%, 90%, 100% でアラートが来る設定をできるらしい
そして
最悪、Firebaseを止める方法
1 | firebase hosting:disable |
を実行すれば
Hosting は止まる
Functions を止める方法は
1 | firebase functions:delete {関数名} |
Firestore を止める方法は
Firebase コンソールから以下のルールの設定を入れて公開
1 2 3 4 5 6 7 8 9 10 | rules_version = '2' ; service cloud.firestore { match /databases/ {database} /documents { match /{document=**} { allow read : if false ; allow write: if false ; } } } |
これで読み取り、書き込み全てをブロック
これらを元にまずはアラートを予算1000円で動作するよう設定
考えた方法としては
予算を超えメールが来たら
1 2 | firebase functions:delete {関数名} firebase hosting:disable |
を実行するようにすれば回避できそう
メールから実行するにはgmail API を使えばできるはず
あとはこれを cron で毎分実行し監視しておけば心配は減る
監視スクリプトの実行だけなので
ラズパイ3ぐらいのスペックでいけるはず
あとはどんなメールが来るのか
アドレスとか件名などが分かれば対処ができそう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | from __future__ import print_function import os.path import subprocess import base64 import email from email.header import decode_header import re from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request from googleapiclient.discovery import build # Gmail APIのスコープ # 特定の送信元メールアドレスと件名の条件 FROM_EMAIL = 'cloud-billing-noreply@google.com' SUBJECT_PATTERN = r '.*の予算アラート: 予算の (\d+)% を使用しました' # Firebaseの関数名 FUNCTION_NAME = 'yourFunctionName' # 実際の関数名に置き換えてください def main(): creds = None # token.jsonが存在する場合、既存の認証情報を読み込む if os.path.exists( 'token.json' ): creds = Credentials.from_authorized_user_file( 'token.json' , SCOPES) # 有効な認証情報がない場合、新しく取得 if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else : # credentials.jsonを使用して認証 flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json' , SCOPES) creds = flow.run_local_server(port=0) # 認証情報を保存 with open ( 'token.json' , 'w' ) as token: token.write(creds.to_json()) # Gmail APIクライアントを構築 service = build( 'gmail' , 'v1' , credentials=creds) # 送信元と件名でメールを検索 query = f 'from:{FROM_EMAIL} subject:"予算アラート"' results = service. users ().messages().list(userId= 'me' , q=query).execute() messages = results.get( 'messages' , []) if not messages: print( '該当するメールはありません。' ) return for message in messages: msg = service. users ().messages().get(userId= 'me' , id =message[ 'id' ], format = 'full' ).execute() headers = msg[ 'payload' ][ 'headers' ] # 件名を取得 subject = '' for header in headers: if header[ 'name' ] == 'Subject' : subject = header[ 'value' ] break # 件名のエンコーディングをデコード decoded_subject = decode_header(subject)[0][0] if isinstance(decoded_subject, bytes): decoded_subject = decoded_subject.decode() # 件名から使用率を抽出 match = re.match(SUBJECT_PATTERN, decoded_subject) if match: usage_percentage = int(match.group(1)) print(f '予算の使用率: {usage_percentage}%' ) # 使用率が特定の閾値を超えた場合にコマンドを実行 if usage_percentage >= 90: print( 'Firebase FunctionsとHostingを無効化します。' ) # Firebase Functionsを削除 subprocess.run([ 'firebase' , 'functions:delete' , FUNCTION_NAME, '--force' ], check=True) # Firebase Hostingを無効化 subprocess.run([ 'firebase' , 'hosting:disable' , '--force' ], check=True) print( 'Firebase FunctionsとHostingを無効化しました。' ) return else : print( '件名の形式が一致しません。' ) print( '条件に合致するメールは処理済みです。' ) if __name__ == '__main__' : main() |
が生成されたコード
なおGmailAPIを使うにあたり制限があるので注意
以下はGPTのレポート
Gmail APIには、サービスの安定性と公平な利用を確保するために、
いくつかの**使用制限(クォータ)**が設けられています。
以下に、Gmail APIの使用制限の詳細と、それがあなたのスクリプトに与える影響についてご説明いたします。
## **Gmail APIの使用制限について**
Gmail APIの使用制限は、大きく分けて以下の2種類があります。
1. **ユーザー単位のレート制限**
2. **プロジェクト単位のレート制限**
これらの制限は、リクエストの頻度や送信可能なメールの数を制限するものです。
### **1. ユーザー単位のレート制限**
– **メールの読み取り、検索、ラベル付けなどの操作に対する制限**があります。
– **1秒あたりのリクエスト数**や**1日あたりのリクエスト数**が制限されます。
### **2. プロジェクト単位のレート制限**
– **APIキーやOAuthクライアントIDを共有する全ユーザーの合計リクエスト数**に対する制限です。
– **1日あたりのリクエスト数**や**1秒あたりのユーザー数**などが制限されます。
—
## **具体的な使用制限の数値**
2023年10月時点でのGmail APIの主な使用制限は以下のとおりです。
ただし、正確な数値や最新の情報については、[Googleの公式ドキュメント](https://developers.google.com/gmail/api/guides/quota)を参照してください。
### **ユーザー単位の制限**
– **1日あたりのメール送信数**:
通常のGmailアカウントで500通、Google Workspace(旧G Suite)の場合は最大2,000通
– **1秒あたりのリクエスト数**: ユーザーあたり約10リクエスト/秒
– **1日あたりのリクエスト数**: ユーザーあたり約250,000リクエスト/日
### **プロジェクト単位の制限**
– **1日あたりのリクエスト数**: プロジェクト全体で約1,000,000リクエスト/日
– **1秒あたりのユーザー数**: プロジェクト全体で約100ユーザーが同時にリクエスト可能
### **3. APIの使用制限を超えた場合の挙動**
– 制限を超えると、
**HTTPステータスコード429(Too Many Requests)**
や
**403(User Rate Limit Exceeded)**
のエラーが返されます。
– **対策**:
– **エラー発生時にリトライする際は、指数バックオフを実装**します。
## **使用制限を遵守するためのベストプラクティス**
### **1. キャッシュの活用**
– **データをキャッシュ**することで、同じデータに対するリクエストを減らすことができます。
### **2. リクエストの効率化**
– **必要なフィールドのみを取得**するように、`fields`パラメータを使用します。
– **バッチリクエスト**を使用して、複数の操作をまとめて行います。
### **3. リクエスト頻度の最適化**
– スクリプトの実行間隔を長めに設定し、**必要最低限の頻度でメールをチェック**します。
### **4. エラーハンドリング**
– **レート制限エラーが発生した場合のリトライ戦略**を実装します。
– **指数バックオフ**を使用して、待機時間を徐々に増やします。
### **5. メールのフィルタリング**
– Gmailのウェブインターフェースで**フィルタを設定**し、特定のラベルを付ける。
– スクリプトでは、そのラベルを持つメールのみを取得する。
## **代替案の検討**
### **1. Pub/SubとCloud Functionsの活用**
– **Gmail APIのPush通知**を使用し、メールの到着をリアルタイムで検知します。
– ただし、これはGoogle Workspace(旧G Suite)アカウントが必要です。
### **2. メールの転送とWebhookの活用**
– **Gmailのフィルタ機能を使用して、特定のメールをWebhookに転送**します。
– 直接的な方法ではないため、実現可能性を検討する必要があります。