Firebaseでデータを構造化する最良の方法は何ですか?


111

私はFirebaseを使い始めたばかりですが、Firebaseでデータを構造化するための最良の方法を知りたいです。

私は簡単な例を持っています:

私のプロジェクトには応募者と応募者がいます。1人の申請者が複数の申請を行うことができます。この2つのオブジェクトをFirebaseで関連付けるにはどうすればよいですか?リレーショナルデータベースのように機能しますか?または、アプローチはデータ設計の点で完全に異なる必要がありますか?

回答:


137

更新データの構造化に関するドキュメントがあります。また、NoSQLデータ構造に関するこの優れた投稿もご覧ください。

RDBMSとは対照的に、階層データの主な問題は、データをネストできるため、ネストできることです。一般に、結合ステートメントとクエリがないにもかかわらず、データをある程度(SQLの場合と同じように)正規化する必要があります。

また、読み取り効率が問題となる場所で非正規化する必要があります。これは、すべての大規模アプリ(TwitterやFacebookなど)で使用されている手法であり、DRYの原則に反していますが、一般に、スケーラブルなアプリに必要な機能です。

ここでの要点は、読み取りを容易にするために書き込みに力を入れたいということです。別々に読み取られる論理コンポーネントを別々に保持します(たとえば、チャットルームの場合、メッセージ、ルームに関するメタ情報、およびメンバーのリストをすべて同じ場所に配置しないでください。後でグループを反復できるようにする場合)。

FirebaseのリアルタイムデータとSQL環境の主な違いは、データのクエリです。「SELECT USERS WHERE X = Y」と言う簡単な方法はありません。これは、データのリアルタイム性(データが絶えず変化し、シャーディング、調整などが行われるため、同期クライアントをチェックするためにより単純な内部モデルが必要になるため)です。

簡単な例はおそらく正しい心の状態にあなたを設定するでしょう、それでここに行きます:

/users/uid
/users/uid/email
/users/uid/messages
/users/uid/widgets

さて、階層構造になっているので、ユーザーのメールアドレスを繰り返し処理したい場合は、次のようにします。

// I could also use on('child_added') here to great success
// but this is simpler for an example
firebaseRef.child('users').once('value')
.then(userPathSnapshot => {
   userPathSnapshot.forEach(
      userSnap => console.log('email', userSnap.val().email)
   );
})
.catch(e => console.error(e));

このアプローチの問題は、私はちょうど、ユーザーのすべてをダウンロードするようにクライアントを強制していることであるmessageswidgets、あまりにも。これらの数が数千に数えられなければ、大したことはありません。しかし、1万人以上のユーザーにそれぞれ5000通以上のメッセージを送ることは大きな問題です。

これで、階層的なリアルタイム構造の最適な戦略がより明確になります。

/user_meta/uid/email
/messages/uid/...
/widgets/uid/...

この環境で非常に役立つ追加のツールはインデックスです。特定の属性を持つユーザーのインデックスを作成することで、インデックスを反復するだけでSQLクエリをすばやくシミュレートできます。

/users_with_gmail_accounts/uid/email

たとえば、Gmailユーザーへのメッセージを取得したい場合は、次のようにします。

var ref = firebase.database().ref('users_with_gmail_accounts');
ref.once('value').then(idx_snap => {
   idx_snap.forEach(idx_entry => {
       let msg = idx_entry.name() + ' has a new message!';
       firebase.database().ref('messages').child(idx_entry.name())
          .on(
             'child_added', 
             ss => console.log(msg, ss.key);
          );
   });
})
.catch(e => console.error(e));

データの非正規化に関する別のSOの投稿でいくつかの詳細を提供したのでそれらも確認してください。フランクはすでにAnantの記事を投稿しているので、ここでは繰り返し説明しませんが、それも素晴らしい読み物です。


加藤この洞察をありがとう!
ホッパー2013年

2
当面。Firebaseのv2リリースのビューには、そのプロセスを自動化するためのいくつかの優れた機能が含まれます。
加藤

ここで古いコメントスレッドを復活させていることを認識していますが、より最新の解決策を見つけるのに苦労しています。これはまだ最善のアプローチですか?つまり、すべてのusers_with_gmail_accountsを取得してからforEachを実行しますか?
owiewio

48

Firebaseはあまりありません、リレーショナルデータベースのように。何かと比較したい場合は、階層型データベースと比較します。

Anantは最近、データの非正規化についてFirebaseブログに投稿しました:https : //www.firebase.com/blog/2013-04-12-denormalizing-is-normal.html

確かに、各アプリケーションの「ID」を各アプリケーションの子として保持することをお勧めします。


フランク、ありがとう!これは本当に役に立ちます。まさに私が探していたもの!
ホッパー2013年

4

あなたの例では申請者が多くのアプリケーションを持っているので、あなたのシナリオはリレーショナルの世界では1対多のように見えます。firebase nosqlを使用すると、以下のようになります。パフォーマンスの問題なしに拡張できます。そのため、以下に述べるように非正規化が必要です。

applicants:{
applicant1:{
    .
    .
    applications:{
        application1:true,
        application3:true
    }
},
applicant2:{
    .
    .
    applications:{
        application2:true,
        application4:true
    }
}}

applications:{
application1:{
    .
    .
},
application2:{
    .
    .
},
application3:{
    .
    .
},
application4:{
    .
    .
}}

いいのですが、フォローアップがあります。FirebaseSDKを使用して、Swiftまたはどこからでもこの構造を作成するにはどうすればよいですか?また、アプリケーションノードに追加された新しいデータが、Firebase検証ルールを使用してアプリケーションリストに実際に存在することをどのように検証できますか?
トミーC.

@prateep、良い例。しかし、ここでの問題は、applications1が一部の申請者の子であるパスapplications / application1を削除する場合です。そこに存在しないパスのapplieds / application1にアクセスしようとすると、したがって、application1:{applicants:{applicant1:true} ...}のような両方の場所でインデックスを更新する必要があるので、appionion1を削除するときに、その子申請者を確認し、申請者の申請者の子ノードを更新する必要があります。:)
Satish Sojitra 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.