Firebase Realtime DatabaseとFirestoreを使い分けていこうなという話

こんにちは、今年もあっというまでしたね〜@fnobiです。この記事はTech KAYAC Advent Calendarの12日目になります。

毎年アドベントカレンダーでは、その時お世話になった技術の話をしているんですが、今回は去年に引き続きfirebaseで行かせていただきます。いちおう専門はWebフロントのはずなんですが、最近WebフロントエンジニアというよりはFirebaseエンジニアです。

Firebase Realtime DatabaseとFirestore

そもそもfirebaseとはなんぞや?という話は去年も書いたのでよろしければ読んでみてください。

さて現在Firebaseでは、データベースとしての機能がRealtime DatabaseFirestoreの2種類提供されています。いちおう公式でも並列に扱われていたり、比較を書いてくれたりしているんですが、全体を通してなんとなく後発のFirestoreの方が推され気味に見えます。

f:id:nobiii:20191212133640p:plain
データベース未作成のときの表示。この格差である。

ではRealtime Databaseはもうオワコンなのでしょうか?個人的にはそうは思っていません。実際今年、自分が携わったプロジェクトでのRealtime DatabaseとFirestoreの使用頻度は ちょうど半々くらい でした。

そんなわけで今回は、個人的に感じているRealtime Database・Firestoreそれぞれのメリットと、使い分けの基準を紹介したいと思います。

両者に共通すること

まずは、どちらを選んだとしても共通してできること・共通する特徴についての紹介です。

リアルタイム性

realtime database・Firestoreどちらも、データベース上のある場所への参照をつくれば、以後サーバー側で更新があった際リアルタイムにクライアント側に通知されるリアルタイム機能が入っています。

自分が所属するクライアントワーク事業部でfirebaseの導入事例が多いのは、このリアルタイム機能を使ったインタラクションがWeb案件・展示案件等エンターテイメントコンテンツとの相性がいいことが理由として大きいでしょう。

「とにかくリアルタイムに最新情報が飛んでくるから、クライアント側はそれを画面に反映することだけ考えようぜ!」という構造自体も、REST APIによる連携より考え方がシンプルになって個人的には好きです。

スキーマレス

いわゆるRDBと異なりfirebaseのデータベースはどちらもスキーマレスです。つまりどこにどんなデータを突っ込んだとしても基本的にはエラーにならず、情報構造を柔軟に変えることができます。(validationという機能を使って各フィールドに個別に制約を付けていくことは可能です)

これは自由度が高くとっつきやすい反面、データを投入するクライアント側でルールを作っておかないとすぐに混沌が訪れてしまうので注意が必要です。(Webから触る場合はTypeScript導入をおすすめしてます)

ちなみに、Firestoreにはデータ型の概念があり、投入できるものが決まっています。ただこれはあくまで、追加されたデータがデータベース上で型を持っているというだけで、おなじdocumentの同じフィールドが同じデータ型であることを制約するものではありません。ちょっとまぎらわしいですね。

f:id:nobiii:20191212133737p:plain
管理画面からFirestoreにデータを追加するとき。がっつり選択肢が出てきます。

両者の違い

情報に階層構造がある

realtime databaseでFirestoreでもっとも異なる点は、土台となるデータモデルです。

realtime databaseに保存できるデータは、ほぼほぼjsonデータそのままという理解で良いと思います。keyとvalueでできたオブジェクトがあり、valueとして同じようにkey valueのオブジェクトがあり…というツリー構造が無限に続いていくかたちです。使う側の認識として「ここはユーザー情報を入れる場所で、ここは記事情報が入っていて…」という構造を持たせることはもちろんできますが、firebase側からこれらは意味的には区別されず、ただの大きいjsonデータとして保持されます。

f:id:nobiii:20191212133817p:plain
realtime databaseの管理画面

一方Firestoreでは、 コレクション / ドキュメント という概念でデータに階層構造を作っています。具体的なデータがぶら下がる単位がドキュメントで、同じ種類のドキュメントを束ねたものがコレクション、という考え方です。ただしドキュメントは、その下層に サブコレクション を持つことができ、更にその中にドキュメントを持つことができ…といった具合で、ツリー構造が無限につついていくという全体感はrealtime databaseと共通しています。それでもこのコレクション / ドキュメントを区別することで、Firestoreは後述するようないくつかの優位性を持っています。

f:id:nobiii:20191212133903p:plain
Firestoreの管理画面。コレクション・ドキュメント・ドキュメントの中のフィールドがくっきり別れている

firestoreの優位性

参照するデータの範囲をコントロールしやすい

どちらのデータベースの場合でも、クライアント側からデータを取得するときには、たとえば /members/1/ というようなかたちで、データベースのrootからのパスを渡して参照をつくるのですが、realtime databaseの場合はこのパスの下層にあるデータを原則まるごとすべて取得 してしまいますし、リアルタイムに更新されるかたちで参照した場合は 下層のデータすべての更新でトリガー されてしまいます。これにより、うっかりデータベースの根本の方から参照を作ってしまった場合、ありとあらゆる変更でイベントがトリガーされる危ないアプリができてしまう可能性があるわけです。

一方でFirestoreの場合、ドキュメントに参照をつくった場合取得できる情報はそのドキュメント自身のデータまでで、 サブコレクション・更にその下層のドキュメントについては取得されない(できない) ようになっています。

つまりコレクション / ドキュメントの設計を適切に行うことで、リアルタイムに参照する範囲を小さく管理することができるようになっています。ありがてえ。

複雑なクエリを使ったデータの検索がかけられる

更にrealtime databaseで困るポイントといえば、データの検索に弱いというものがありました。いちおう、データを参照する際に絞り込み・ソートの条件をわたすことはできるのですが、条件として記述できるのはデータの特定の値に対する一種類の条件のみで、中規模くらいのアプリケーションを組もうとすると「あっ、この条件絞り込みできない…」となる場面が多くあります。

Firestoreではこの点、制約はあるもののドキュメントのいくつかの値に対する複数の条件を適用して絞り込みができるようになっています。おそらくですが、コレクションとドキュメントを分離したことで検索の最適化が行われたことで可能になった機能なのではないかなと思います。

とはいえ依然として制約はあり、RDBにおけるsqlほど自由度が高いわけではないので注意は必要です。何でもできると思わないようにしましょう。

realtime databaseの優位性

さて、改めてFirestoreはrealtime databaseの上位互換なのでは?という印象になっているかもしれないのですが、個人的にrealtime databaseの優位性と呼べると思っている部分が下記です。

インポートエクスポート・一括更新に強い

f:id:nobiii:20191212134036p:plain

階層のない情報構造になっているおかげでできることの一つとして、インポート・エクスポート機能があります。realtime databaseの管理画面では、特定のパス以下のデータのインポート・エクスポートが簡単にできるようになっています。

これにより、例えば別の場所でjsonでつくっていたconfigをrealtime database上に引っ越したり、データを丸ごとコピーして別のデータベースや別のパスに移動したり…といった小回りの聞くプレーが可能です。また同様の事情なのか、日次でのデータ自動バックアップもrealtime databaseのみの機能となっています。

f:id:nobiii:20191212134113p:plain
1クリックで日次バックアップをONにしてくれます。よいこ。

またちょっとトリッキーな機能として、データの部分的な更新もrealtime databaseの特技になっています。例えば下記のようなデータがあったとして、

{
    "user1": {
        "name": "hoge"
    },
    "user2": {
        "name": "moge"
    },
    "user3": {
        "name": "foo"
    },
    "user4": {
        "name": "bar"
    }
}

この全てのユーザーに、type: 'normal' というフィールドを追加したい!という状況があったとしましょう。Firestoreの場合は、このuser(にあたるドキュメント)の参照をひとつずつ作っていって、ひとつずつアップデートするしか方法がありません。

一方realtime databaseの場合、update 関数を使った以下のような書き方で、一括で更新をいれることができます。※javascriptの場合

ref.update({
    "user1/type": "normal",
    "user2/type": "normal",
    "user3/type": "normal",
    "user4/type": "normal"
});

スラッシュを使うことで、更新箇所をかなりピンポイントに指定できるということですね。なかなか器用です。

(使い方によっては)安く済む

これは自分も、今回改めて調べてみて気づいたことなのですが、従量課金プランを使った場合の課金形態がrealtime databaseとFirestoreでけっこう異なります。

https://firebase.google.com/pricing

  • Firestoreの場合

    • 書き込み $0.18/10 万
    • 読み込み $0.06/10 万
  • realtime databaseの場合

    • ストレージ $5/GB
    • ダウンロード $1/GB

Firestoreの場合は書き込み・読み込みの 回数 、realtime databaseの場合はダウンロード・保存の 容量 で課金額が決まります。つまり、小さい容量のデータをものすごく頻繁に読み書きするアプリケーションの場合はFirestoreの料金が上がりやすく、逆に大きめのサイズのデータを保持させておく用途が多い場合はrealtime databaseの料金が上がりやすい、ということになります。なんとなく、ここからも想定している使用ケースが見えてくる気がしますね。

まとめ

  • ある程度複雑なデータ検索が必須・スケーリングも必須な場合は基本的にFirestoreの方がよい。
  • 規模の上限がある程度見えている場合、複雑なデータ構造で細かい書き込みの頻度がかなり高い場合、realtime databaseの方が推奨できる局面も多そう。
  • firebaseには他にも、hostingとかstorageとかremote configとかデータを保持するための機能がたくさんあるので、リアルタイム性が必要ない場合には積極的に併用していこう!

といったところでしょうか。

自分の今年の仕事の中では、照明システムとリアルタイムに連動するWebAR演出を作った際、大量にパーツデータを持つメーカーコンテンツの管理画面を作った際などには特に、realtime databaseの小回りの効く性質に大変助けられました。危うく死ぬところでしたね。

それでは最後までお読みいただきありがとうございました!引き続きカヤックのアドベントカレンダーをお楽しみください〜。