Firebase Realtime Databaseの現状と課題
サンプルアプリ構築で分かった利点と、多くのデベロッパーが感じるであろう問題点について
この記事はReact.jsベースのウェブ・アプリの構築をした際に書かれました。Firebase JavaScript SDKを前提にお読み下さい。
Firebase Database採用による恩恵はなにか?
筆者は過去にはRailsやNode.js + ExpressなどでAPIサーバを構築して来ており、その際に「大変だった」ことがどれくらい軽減されるかを考えてみました。
APIバックエンドのインプリが不要
Firebase DatabaseはSDKからJSON形式のデータをpushするだけでデータが保存され、それがそのままスキーマの定義になります。アクセス・コントロールのための「ルール」や「インデックス」を定義する必要はありますが、APIバックエンドを実装するための作業はほぼ0です。
MySQLなどのデータベースでスキーマの設計を行い、RailsやExpress.jsのモデルやコントローラを実装(そしてテスト→デバッグ)する作業がほぼ不要というのは画期的です。
APIサーバの環境構築・デプロイが不要
また、ウェブ・コンソールなどからプロジェクトを作成すれば即バックエンドが動作するため、環境構築・デプロイも不要です。AWSでVPCを構築し、サブネットを設定してセキュリティー・ルールを定め…といった作業が不要となります。
運用の負担が軽減
サーバのモニタリングなどの運用も(アプリが正常に動作するかというレベルでは監視は必要ですが)Googleに任せることができます。
価格が安い
Firebaseの料金プランはこちらにありますが、月々$25の「Spark」プランでも、そこそこの規模のアプリケーションをサポートできるパフォーマンスです。
ここで書いたことはBaaSなんだから当たり前ではありますが、工数で計算するとなかなかの日数を削ることができます。
パフォーマンス
以前Parseを試した際は、サーバが米国にしかなかったこともあってRest APIが結果を返すと数百msの後半という状況でした。ブラウザでの手動テストで正確な値とは言えないものの、ウェブのプロジェクトでは採用を見送りました。
Firebaseは後述する初回リクエスト時のラグはあるものの、Rest APIから20件程度のJSONを取得して200ms前後というパフォーマンスでした(こちらもブラウザ上で手動で何回か計測して確認したものです)。プロジェクトによって要件は異なると思いますが、筆者はひとまず「問題ない」と感じました。
データ構造
Firebase DatabaseはいわゆるNoSQL DBです。すでにあちこちで書かれていますが、RDBのように参照idを元に複数のテーブルのデータを統合するような機能は提供しておらず、データの設計の段階で考え方を切り替える必要があります。
個々のユーザーが閲覧するデータが相互に関係しないチャットやToDoなどにはあまり影響はないと思います。ざっくりですが、シャーディングが可能なアプリは対応しやすいのではないかと。逆に、複数のデータを統合するようなケース(カレンダーとか)は苦労が予想されます。
なお、クライアント側で複数回リクエストを行うことで、リレーショナルなデータ取得を行うことは可能です。Firebase SDKは、複数のリクエストをとりまとめる仕組みになっているとのことで、1回目のリクエストでIDのリストを取得し、さらに20件程度のリクエストを送信するようなコードを書いても、先述のパフォーマンスに近い速度が実現できていました。
クエリ
NoSQL的なデータ構造に加えて、クエリが限定的であることも特徴です。
データベースのツリー構造の中からパスを指定し、キーで指定した子要素の値でソート→条件を指定してフィルタという流れになりますが、フィルタに使用できるのは「startAt」「endAt」「equalTo」と限定されていて、クライアントからのリクエスト時のフィルタはほとんどしない前提が必要です。
また一度のAPIリクエストでは、一つのコンディションしか設定することができません。例えば上記の様に「user_countが3以上のニュースのみを指定」してフェッチする場合、そこにさらに「created_atで過去3日分だけをフィルターする」といった条件を追加することができません。
後述するように、データ列をページングする場合には、ページング以外のクエリが指定できない=テーブルは全てフィルタ済みにしておく必要があるのです。この制限はなかなか面倒です。
Firebase Databaseを使ってみて見えてきた課題
初回リクエスト時のタイムラグ
Firebase SDKを使ってAPIリクエストを行うと、初回のみ+500ms程度の時間がかかっているようです。恐らくAPIサーバへの接続など初期化のためと思われます。2度目以降のリクエストは200ms程度で返してくるのですが、例えばブログ・サービスを構築するような場合には初回のロード・タイムが大きくユーザーの印象を左右するため、このままでは正直きついと感じました。
筆者のテスト・アプリでは、JavaScriptのHTTPクライアントAxiosを使って、Rest API経由でデータを取得するようにしてこの問題を回避しています。この場合は、Firebaseの売りである「リアルタイム」機能を活用することはできませんが、ワンショットでその後の更新を行わないデータでは有効なワークアラウンドです。
Reactベースのプロダクトであれば、例えばReduxのようなデータ取得のレイヤーでHTTP API経由かFirebaseのリアルタイム(常時接続)API経由かを切り替えると良いでしょう。
全文検索機能の欠如
いまのところ、Firebase Databaseに全文検索を行うAPIはありません。こちらの記事では、Google Cloud PlatformのElastic Search にインデックスを作成する方法が掲載されています
ページングの制約
先に書いたように、Firebase Databaseは非常に限定的なクエリの仕組みしか提供しておらず、しかもリクエスト時に一つの子要素しか対象にすることができません。
大量のデータを扱う場合、最新20件を取得した後に一番古いIDを指定してそれより小さなIDの20件を取得するような「ページング」を行うことが一般的ですが、このために例えば「created_at」要素を指定してリクエストを行うと、それだけでクエリを使い切ってしまうのでそれ以上のフィルタ条件を指定することができません。
筆者のテスト・アプリでは「3人以上のユーザーが言及した記事」を抽出していますが、このリクエストでページングを行うことができず困りました。カテゴリを指定してブログ記事を抽出、など「データをフィルタした上でページング」というユースケースは頻出するので、細かい問題ですが、一番面倒であると感じました。
- クエリはページング専用と決めて、フィルタする必要がないデータ構造にする
- 複雑なクエリを行う際は、Firebase Functionsを使って結果をテンポラリなデータとしてDB上に作成し、再度そのデータ列にたいしてクエリを行う
などの回避策が考えられますが、やはり公式にページングをサポートして欲しいところです。
クロンジョブ
こちらはFirebase DatabaseというよりFirebase Functionsの範囲ですが、定期的にコードを実行してデータを作成する場合に、Firebase Functionsにはスケジューリングの機能が提供されていません。
サンプルとして、Google App Engineを使って定期的にFirebase Functionsを呼び出すコードが提供されていますが、App Engineは一番安価なB1クラスでも1時間あたり$0.05かかります。これはAWSでいうとt2.mediumクラスに匹敵します。筆者の場合、1日あたり500円程度がスケジューリングのみで請求されました。
小規模なアプリケーションの場合、このコストが結構大きいということになるので注意が必要です。
おまけ: 請求はどこでチェックできるの?
Firebase Consoleに請求に関する項目が見つからず、かなりドキドキしました。FirebaseはGoogle Cloud Platformの一部という位置づけのようで、GCPコンソールの請求のところに金額がでていました。恐らく多くの人が同じことを疑問に感じると思うので、ここに付記しておきます。
まとめ
ウェブ・アプリのバックエンドという視点で、Firebase Databaseにできることと出来ないことをまとめてみました。
筆者は小規模なチームで開発を行うことが多く、個人アプリなども作るためBaaSには常に関心があります。Firebaseは実用性という点でも大きなジャンプがあったプロダクトで、今後もフォローを続けていきたいと思います。