Cloud Storage for Firebaseのレスポンスを爆速化した話

# 概要

Cloud Storage for Firebase（以降CSF）のレスポンスの悪さに何とかしたいと悩んでいたのですが、クックパッドさんのテックブログによりレスポンスが大幅に改善しました。
CSFの封印された能力を開放するには、具体的どうすれば改善できるかという話です。
ただし、世の中美味しい話はそうそうありません。界王拳10倍なんてやったらトレードオフで体への負担がｒｙ
残念ながら爆速化できるのは、誰でも閲覧可能、つまりGetが来たら無条件でデータを返すものに限られます。
Firebaseのルールで書くと```allow read: if true;```が対象になります。


# 読者対象

- 無条件で読み込み可能なデータがあり、速度を改善したいと思っている方


# Special thanks

クックパッドさんのこの投稿がなければ、私はこの高みに到達できませんでした。
素晴らしい投稿ありがとうございましたm(__)m
https://techlife.cookpad.com/entry/2018/11/02/100000


# Road to 爆速

## 私のお悩み

私の作ったReactアプリは、CSFにデータを取りに行って、そのデータを表示することで初回ペイントが完了します。
なので、CSFからのレスポンス時間が結構重要なのですが、キャッシュがないとおおむね500ms～1000ms程度時間を要していました。

この速度を改善したいと考えたとき、まず最初に思い浮かんだのはCSF直ではなくCDNをはさむこと。
ただし、これについては優しい事例が見つからず、そもそもできるかも含めて不安しかない:worried:

次に考えたのがFirebase Hosting（以降、FH）にデータを置く。
通常ストレージのデータをHostingに置きたいとは考えませんが、扱っているデータが小さいこと、FHがCDNを使っていて数十msの高レスポンスであることため。
しかし、Hostingにかかわらないデータのみを置くことができるかあやしいし、この解決方法はすっきりしない。

FireStoreはバンドルサイズが約100KBと大きく、初回のレスポンスは高速ではないので対象外。
＝＞当時は大きかったのですが、2021年9月リリースのv9のSDKにより約20KBのバンドルサイズに縮小されました。

そんな中見つかったのがクックパッドさんの記事でした:smile:


## クックパッドさんの記事を見て考える

改善方法を簡潔に述べてあるのがこの文章。
>さらにFirebaseのCloud Storageではなく、その背後にあるGCPのCloud Storageを直接見に行くようにすると更に速くなりました。 これは、Firebaseではリクエストの検証をし、アクセス権の有無を確認しているためと思われます。


おっしゃる通りで、CSFのレスポンスシーケンスは以下の通りと手数が多い。

1. CORSのプリフライト（実行可能化の事前チェック）のため、OPTIONメソッドでCSFにブラウザが問い合せ。
- CSFが許可できるメソッドやヘッダー類を応答。
- 許可されていればブラウザがGETメソッドでデータを取得を要求。
- CSFがダウンロードトークンというプロパティを含めてメタデータを応答。これが400ms程度と遅い。
- ブラウザがダウンロードトークンをクエリーパラメータに付与して再度GETメソッドで要求。
- CSFがデータ本体をBODYに乗せて応答。これも400ms程度と遅い。

1つのデータを取得するのに3回もCSFとやり取りをしないといけません。
通常のCORSなら4でデータ本体を返すのですが、CSFのお作法で5と6があるため、1つ通信が多い状況。
有効期間内のデータがあれば、5はかなり短縮されます。
しかしながら、初回アクセスでは必要なロスですし、何より4の応答自体が遅い。
ちなみに権限がない場合、4の応答がはじかれます。

クックパッドさんのおっしゃることは十分理解済み。
それで・・・```GCPのCloud Storageを直接見に行く```・・・とは一体(;^_^A
GCPに無知な私にはよくわからないm9(^Д^)プギャー
ただし、CSFがCloud Storage(以降、CS)のラッパーであることは何となく把握済み。
ということで、CSの世界に踏み入れることに。


## CSを設定する

FirebaseコンソールのStorage->使用状況->View quotaでGCPに移動。
ナビゲーションからStorage->ブラウザを選択し、Firebaseが裏で作成していたバケットが2つあるので、stagingじゃない本番向けを選択。
するとこんな感じのバケット内一覧が表示される。

![CSF爆速化.png](https://qiita-image-store.s3.amazonaws.com/0/310578/f22411a7-f8c9-d325-5549-04b2477304c2.png)

画像はすでにだれでもアクセス可能になっている状態で、赤線の部分がその設定。
クリップアイコンクリックすると公開アドレスを取得可能。

ということで、こうするためにドキュメントをあさることになるが、公開アクセスというわかりやすいネーミングであったため、？にポイントすると詳細ドキュメントへのアンカーがある。
https://cloud.google.com/storage/docs/cloud-console?hl=ja#_sharingdata
しかしこのドキュメントは機能の説明で、実際の設定手順は別のページに誘導される。
https://cloud.google.com/storage/docs/access-control/making-data-public?hl=ja
私の場合固定のファイル名のため、CONSOLEの手順に従い、allUserを作って読み取り権限を付与して終了。
すると、画像のようにクリップアイコンが表示されるので、それをクリックするとタブが開いてデータ本体が表示される。
後はアドレスをコピーすればOK。
フォーマットとしてはこんな感じで、CSFの時よりかなりURLがすっきりした。
```https://storage.googleapis.com/＜your-project-id＞.appspot.com/＜your-file＞```

＜追記＞
コンソールで設定した後、ファイルをアップロードして更新したらアクセス権が再び非公開にorz
アップロード時のオプションでprivateとpublicのbooleanオプションあるので、それを指定するか、アップロードの後にドキュメントにあるようにPublic化のメソッドコールが必要のようです。
新しいオブジェクトなので新しい権限が必要になり、それを省略しているのだから非公開に戻るのは納得です。

Public化のドキュメントは以下ですが、SDKでアップロード時にオプション指定できると思うので、これを単独で利用するシーンは少ないはずです。
https://cloud.google.com/storage/docs/access-control/making-data-public?hl=ja#storage-make-object-public-nodejs


## どのくらい速くなったの？

初回アクセス
500ms～1000ms => 100ms～250ms
キャッシュあり
～500ms => ～50ms

数値にすると爆速とまでいえないと思うかもしれません。
しかし、実際にアプリを操作すると、明らかにデータ待ちになっていた部分が、データ取得ではなく埋め込みデータと錯覚する程に次元が異なります:laughing:

更にCSFのバンドルが不要になるので、GZIPで約10KBとそこまで大きくはないですが、バンドルサイズが小さくなるメリットがあります(*´▽｀*)


## トレードオフ:fearful:

世の中そんなに美味しい話があるとは思えず界王ｒｙ
気になっているがのCSFとCSとの無料枠の違い。

|                  |     CSF           |    CS    |
|:-----------------|:-----------------:|:------------------:|
| 保存容量          | 5GB | 5GB |
| 転送量            | 1 GB/日|       	1 GB/月      |
| アップロード      |  2 万/日|          5 千/月      |
| ダウンロード      | 5 万/日 |          5 万/月        |

保存容量に違いはないんですが、それ以外は月とすっぽんじゃなく日なんですよ。
(　ﾟдﾟ)ポカーン
CSから見たらCSFで作ったバケットは特別枠で、直接コールしてもCSFの料金体系じゃないか？という希望的観測はあるのですが、これについては経過を見て報告ですかね。
FireabaseのFunctionsの無料枠は小さいのでなんだかな～と思ってました。
だけど、FHのCDN付き無料枠10GBといい、CSFの月換算最大30GBの転送量といいFirebaseすご過ぎ・・・。

＜追記＞
ようやくCSFとCSの無料枠の違いが判明。
https://firebase.google.com/docs/storage/gcp-integration?hl=ja#google_app_engine
>また、Cloud Storage 用 Firebase SDK は、Google App Engine 無料枠にあるデフォルトのバケットを使用します。

さらりと書かれているが、これが回答そのもの。CSの枠ではなくApp Engineの枠を使っている！
App Engineって確かバックエンドのコンピューティングだけど・・・と思いつつその無料枠を探る。
そしてたどり着くドキュメントに書かれていることは、CSFの無料枠そのものと完全に一致。
つまりFirebaseが特別に無料枠が大きいのではなく、もともとGCPの大きな無料枠に乗っかっているだけの模様。
https://cloud.google.com/appengine/docs/quotas?hl=ja#Default_Gcs_Bucket

結論としては、なんのデメリットもなく爆速化が可能／(^o^)＼


# まとめ

CSFが遅かったのは手間がかかっていること＆恐らく認証部分の絡みの処理時間の問題で、CS自体は早めということが判明しました。
Firebaseのユーザーの絡みがないのであれば、直接利用のほうがいいかなと思っています。
~~CS直接利用ならCDNを介して、数十msレベルのレスポンスにもできると思います。~~
**<2020.04.02 削除>**@sudokztさんのコメントから、一般公開化するとCDNのように機能することが判明。

Firebaseのユーザが必要な場合、場合によってはFunctionsで判断するのも一つの手かもしれません。
(頻繁にアクセスされていてcold-startは基本的に生じない前提)
CSF利用する場合は、1s程度の遅延がOKのケースに限定すれば適所適材でいいと思います。

最後に改めてクックパッドさんありがとうございましたm(_ _)m