Firebaseの料金を節約するために、ストレージを使ってリアルタイムDBを回避する

GoogleのMbaas「Firebase」は、ユーザー認証からリアルタイムDB・ストレージまで、バックエンドで必要なものを一括で提供してくれて、ものすごく便利です。

>[Firebaseの始め方 - Qiita](http://qiita.com/kohashi/items/43ea22f61ade45972881)

ちょっとしたアプリなら無料プランでもそれなりに使えるし、有料プランもストレージなどは十分にお安いライン。
しかし先の記事でも言及されているように、リアルタイムDBの料金がやや気になるところです。

そこで極力リアルタイムDBを使わずにストレージにデータを入れて節約する方法を試しました。


## Firebaseの料金プラン
2016年11月時点でこんな感じです。

[Pricing - Firebase](https://firebase.google.com/pricing/)
![Firebase.png](https://qiita-image-store.s3.amazonaws.com/0/11704/0f6a9bd0-dbd6-a2dd-0b50-eb2e0df0f455.png)

データの保存（stored）・呼び出し（downloaded）とも、リアルタイムDBがちょっと高いですね。。
特にdownloadが$5/GBは、アクセス増えたときに全部DBでさばいてるとけっこうしんどそうです。

ストレージなら同じ1GBのdownloadで$0.12。単純計算でなんと約40分の1に節約できますね。
というわけでさっそくストレージを使った節約プランを考えてみます。

※実際にはファイル化によるデータ量増加があるのと、リアルタイムDBはローカルキャッシュをうまく使ってくれるみたいなので、もうちょい差は詰まると思います。


## jsonデータをファイル化してストレージにぶっこむ
そもそもリアルタイムDBは、JSONオブジェクトにすべてのデータを保存する仕組みになっているそうです。
じゃあそのデータをそのままjsonファイルにしてストレージにつっこめばいいのでは？ということでやってみました。

Firebaseの設定が終わってる状態で、実際の処理はこんな感じ。

```js
var json = { hoge: 1, fuga: 2, piyo: 3 };

//JSONをBlobオブジェクトにする
var blob = new Blob([JSON.stringify(json)], { type: 'application\/json' });

//storageにアップロード
var uploadTask = firebase.storage().ref('json/hogehoge.json').put(blob);

//アップロードタスクのobserve
uploadTask.on('state_changed', function(snapshot){
	// Observe state change events such as progress, pause, and resume
	// See below for more detail
}, function(error) {
	// Handle unsuccessful uploads
}, function() {
	// Handle successful uploads on complete
	// For instance, get the download URL: https://firebasestorage.googleapis.com/...
});
```

### Blobオブジェクトに変換
ファイルとしてアップロードするために、JSONデータを`Blob`オブジェクトに変換してます。
`Blob`じゃなくて`File`でもよいはず。
（ただしどちらもHTML5の機能なので、ブラウザの対応バージョンはご注意ください）

>[JavaScriptプログラミング講座【Blob と File クラスについて】](http://hakuhin.jp/js/file.html#FILE_05)

### CloudStorageにアップロード
ストレージへのアップロード処理は公式のドキュメントを参照。

>[ウェブでファイルをアップロードする | Firebase](https://firebase.google.com/docs/storage/web/upload-files)

アップロードのステータスを見たいときは`state_changed`をトリガーにごにょごにょしてください。
アップロード自体はその前に`put(blob)`した段階で実行されています。

### CloudStorageのセキュリティ設定
デフォルトの設定のままで上記を実行すると権限ないよって怒られるかもしれません。
これはストレージにデフォルトでセキュリティルールが適用されているせいです。

>[Storage セキュリティ ルールを使ってみる | Firebase](https://firebase.google.com/docs/storage/security/start#sample_rules)

公式ドキュメントを参考に、適切に設定してやってください。
とりあえず動作確認したい場合は、サンプルルールの「公開」となっているのでやればアップロードできるはず。


## ストレージに保存したJSONデータを読み込む
保存したデータを読み込むときはこんな感じ。

```js
var storageRef = firebase.storage().ref('json/hoge.json');

storageRef.getDownloadURL().then(function(url) {
  $.getJSON(url, function(data) {
    console.log(data);
  });
}).catch(function(error) {
  // Handle any errors
});
```

該当jsonファイルのダウンロードURLが返ってくるので、これを`getJSON`で読み込むということをしています。

権限管理をするためにダウンロードURLを都度返してるのだと思うので、
誰でも見れてよいデータなら、最初から公開設定でファイルをアップしておいたほうが楽ちんです。

その場合読み込み時はfirebaseのAPIを使わず、直接jsonファイルの公開URLを指定して`getJSON`だけしてやればOK。


### CORS問題
上記の処理もそのまま実行するとおそらくこんなエラーになるはず。

```
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
Origin 'http://xxxx' is therefore not allowed access.
```

FirebaseのストレージはそのままGoogleCloudStorageなので、そこでCORS設定をしてやる必要があります。

>[Firebase StorageにCORSの設定をする | Qiita](http://qiita.com/niusounds/items/383a780d46ee8551e98c)


## デメリット・注意点など

- リアルタイムDBはうまいことローカルにキャッシュしてくれたりするみたいなので、実際にはそれほど通信量は発生しないかも
- チャットみたいなアプリ作りたいなら、リアルタイムDBじゃないときびしそう
- ファイルの読み書きが発生する分、ストレージ使うほうが遅くなりそう

同時更新・リアルタイム性などを考えると、やはりリアルタイムDBの恩恵が大きそうです（あたりまえか）。

またリアルタイムDBではデータを正規化せず、一覧表示用などにデータを重複保存することが推奨されてるようです。

>[データベースの構造化 | Firebase](https://firebase.google.com/docs/database/web/structure-data)

これもストレージで再現することもできるけど、個人的にはちょっとやりすぎな気も。

検索用などのインデックスっぽい使い方をする部分はリアルタイムDBに残し、メインのデータ部分をストレージに置くような使い方がいいかなーと思っています。あんまり根拠ないけど。


## まとめ
Firebase便利。