web-dev-qa-db-ja.com

Firestore-フィードを構成してシステムをフォローする方法

私は、テスト用のソーシャルネットワークアプリにFirebaseリアルタイムデータベースを使用していました。このアプリでは、フォローしている人の投稿をフォローして受信するだけです。従来のソーシャルネットワーク。データベースを次のように構成しました-

Users
--USER_ID_1
----name
----email
--USER_ID_2
----name
----email

Posts
--POST_ID_1
----image
----userid
----date
--POST_ID_2
----image
----userid
----date

Timeline
--User_ID_1
----POST_ID_2
------date
----POST_ID_1
------date

また、すべてのユーザー投稿のIDを含む別のノード「コンテンツ」もあります。そのため、Aのタイムラインに追加されたBのすべての投稿IDよりも「A」が「B」に続く場合。また、Bが何かを投稿すると、フォロワーのタイムラインにも追加されます。

今、これはリアルタイムデータベースの私のソリューションでしたが、明らかにいくつかのスケーラビリティの問題があります

  • 誰かが10,000人のフォロワーを持っている場合、10,000人のフォロワーのタイムラインのすべてに新しい投稿が追加されました。
  • 誰かが大量の投稿を持っている場合、すべての新しいフォロワーが自分のタイムラインでそれらの投稿をすべて受け取りました。

これらはいくつかの問題でした。

今、私は「スケーラブル」であると主張されているように、このすべてを暖炉でシフトすることを考えています。リアルタイムデータベースで直面した問題をファイアストアで排除できるように、データベースをどのように構成する必要があります。

42
Zicsus

あなたの質問は少し後で見ましたが、私が考えられる最高のデータベース構造を提供するようにも努めます。したがって、この回答が役立つことを願っています。

usersusers that a user is following、およびpostsの3つのトップレベルコレクションがあるスキーマを考えています。

Firestore-root
   |
   --- users (collection)
   |     |
   |     --- uid (documents)
   |          |
   |          --- name: "User Name"
   |          |
   |          --- email: "[email protected]"
   |
   --- following (collection)
   |      |
   |      --- uid (document)
   |           |
   |           --- userFollowing (collection)
   |                 |
   |                 --- uid (documents)
   |                 |
   |                 --- uid (documents)
   |
   --- posts (collection)
         |
         --- uid (documents)
              |
              --- userPosts (collection)
                    |
                    --- postId (documents)
                    |     |
                    |     --- title: "Post Title"
                    |     |
                    |     --- date: September 03, 2018 at 6:16:58 PM UTC+3
                    |
                    --- postId (documents)
                          |
                          --- title: "Post Title"
                          |
                          --- date: September 03, 2018 at 6:16:58 PM UTC+3

誰かが10,000人のフォロワーを持っている場合、10,000人のフォロワーのタイムラインのすべてに新しい投稿が追加されました。

これがコレクションがFirestoreで管理される理由であるため、これはまったく問題になりません。 Cloud Firestoreデータベースのモデリング の公式ドキュメントによると:

Cloud Firestoreは、小さなドキュメントの大規模なコレクションを保存するために最適化されています。

これが、他のオブジェクトを保持できる単純なオブジェクト/マップとしてではなく、コレクションとしてuserFollowingを追加した理由です。 limits and quota に関する公式ドキュメントによると、ドキュメントの最大サイズは1 MiB (1,048,576 bytes)です。コレクションの場合、コレクションの下にあるドキュメントの数に制限はありません。実際、この種の構造ではFirestoreが最適化されています。

したがって、この方法で10,000人のフォロワーがいると、完全にうまく機能します。さらに、どこにでもコピーする必要のない方法でデータベースを照会できます。

ご覧のとおり、データベースはほとんどdenormalizedであり、非常に簡単にクエリを実行できます。例を見てみましょうが、データベースへの接続を作成する前に、次のコード行を使用してユーザーのuidを取得しましょう。

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();

データベースを照会して、ユーザーがフォローしているすべてのユーザーを取得する場合は、次の参照でget()呼び出しを使用できます。

CollectionReference userFollowingRef = rootRef.collection("following/" + uid + "/userFollowing");

このようにして、ユーザーがフォローしているすべてのユーザーオブジェクトを取得できます。 uidを持っていると、単にすべての投稿を取得できます。

タイムライン上で、すべてのユーザーの最新の3つの投稿を取得するとします。非常に大きなデータセットを使用する場合、この問題を解決するための鍵は、小さなチャンクでデータをロードすることです。私はこれからの回答で説明しましたpostクエリカーソルとlimit()メソッド。また、理解を深めるために、このビデオをご覧になることをお勧めします。したがって、すべてのユーザーの最新の3つの投稿を取得するには、このソリューションの使用を検討する必要があります。したがって、最初にフォローしている最初の15個のユーザーオブジェクトを取得し、uidに基づいて最新の3つの投稿を取得する必要があります。 1人のユーザーの最新の3つの投稿を取得するには、次のクエリを使用してください。

Query query = rootRef.collection("posts/" + uid + "/userPosts").orderBy("date", Query.Direction.DESCENDING)).limit(3);

下にスクロールしながら、他の15個のユーザーオブジェクトを読み込み、最新の3つの投稿などを取得します。 dateの他に、postオブジェクトに、いいね!、コメント、共有などの数のような他のプロパティを追加することもできます。

誰かが大量の投稿を持っている場合、すべての新しいフォロワーが自分のタイムラインでそれらの投稿をすべて受け取りました。

とんでもない。このようなことをする必要はありません。すでに上記で理由を説明しました。

2019年5月20日編集:

ユーザーがフォローしているすべてのユーザーの最近の投稿をすべて表示する操作を最適化する別のソリューションは、ユーザーに表示する投稿をそのユーザーのドキュメントに保存することです。

したがって、例としてfacebookを取り上げる場合、各ユーザーのfacebookフィードを含むドキュメントが必要になります。ただし、単一のドキュメントが保持できるデータが多すぎる場合( 1 Mib )、上記で説明したように、そのデータをコレクションに入れる必要があります。

30
Alex Mamo

2つの状況があります

  1. アプリのユーザーには少数のフォロワーがいます。

  2. アプリのユーザーには多数のフォロワーがいます。 firestoreの単一のドキュメントの単一の配列にフォロワー全体を保存する場合。その後、ドキュメントごとに1 MiBのファイヤーストア制限に達します。


  1. 最初の状況では、各ユーザーはフォロワーのリストを単一の配列の単一のドキュメントに保存するドキュメントを保持する必要があります。 arrayUnion()およびarrayRemove()を使用することにより、フォロワーリストを効率的に管理できます。また、タイムラインに何かを投稿する場合は、投稿ドキュメントにフォロワーのリストを追加する必要があります。

    そして、以下のクエリを使用して投稿を取得します

    postCollectionRef.whereArrayContains("followers", userUid).orderBy("date");
    
  2. 2番目の状況では、フォロワー配列のサイズまたは数に基づいて、ユーザーのフォロー文書を中断する必要があります。配列のサイズが固定サイズに達した後、次のフォロワーのIDは次のドキュメントに追加する必要があります。そして、最初のドキュメントは、ブール値を格納するフィールド「hasNext」を保持する必要があります。新しい投稿を追加するときは、投稿ドキュメントを複製する必要があります。各ドキュメントは、以前に壊れたフォロワーリストで構成されています。そして、ドキュメントを取得するために上記で与えられた同じクエリを作成できます。

2
Niyas

私はこのすべてを暖炉でシフトしようと考えています

良い決断。 なぜ?


それでは、トピックを見てみましょう。既に指摘したように、ソリューションには明らかにいくつかの欠陥があります。ここに私が理解できる2つがあります。

  • さまざまな種類のリソースを非常に大量に浪費しています。これには、データクォータ、クラウド内のストレージ、システムメモリ、CPUパワーが含まれます。
  • 投稿を時系列に配置することに関連する問題。

ソリューションは、データベースの完全な再設計であり、異なる方法を使用してそれぞれデータをプルします。


データベースの再設計

データの重複を完全に避けます。以下は、ソーシャルメディアのデータベース構造の良い例です。

-root
    -users
        -0001
            -name:"name"
            -profile_image:"https://www.example.com/profileimages/profileimage"
            -followings:"002, 003"
            -posts
                -0001
                    -timestamp:"1535650853"
                    -title:"title"
                    -content: "This is a dummy content"
                    -media: "https://www.example.com/medias/media"
                -0002
                    -timestamp:"1535650853"
                    -title:"title"
                    -content: "This is a dummy content"
                    -media: "https://www.example.com/medias/media"
        -0002
            -name:"name"
            -profile_image:"https://www.example.com/profileimages/profileimage"
            -posts
                -0001
                    -timestamp:"1535650853"
                    -title:"title"
                    -content: "This is a dummy content"
                    -media: "https://www.example.com/medias/media"
        -0003
            -name:"name"
            -profile_image:"https://www.example.com/profileimages/profileimage"
            -followings:"001"


投稿を取得する方法

複数の場所から投稿を取得する必要があるため、このようなことをする必要があります。

step 1 : Get a list of UIDs of all following users
step 2 : Take first UID
step 3 : Get all post with the UID and add to list of posts
step 4 : If next UID exists do step 3 with it
step 5 : Sort all according to the timestamp

ページネーションが必要な場合は、さらに複雑なソリューションを選択できます。明らかに、パフォーマンスが大幅に向上します。

step 1 : Get a list of UIDs of all following users
step 2 : Take first UID
step 3 : Get the latest post with the UID (using orderByChild(), limitToLast()) and add to a priority queue in appropriate position.If no element exists, skip the step.
         (A priority queue means an array in of elements which is about to be added to the resultant array. It should be sorted in such a way that the first element can be the next element in the resultant array.)
step 4 : If next UID exists do step 3 with it. Other wise, it means One cycle completed. Go to next step in that case.
step 5 : If limit is not exceeded, get the top element from the queue and add it to resultant array. Then remove from the priority queue. Stop otherwise.
step 6 : Get the next element from the array and add to the priority queue in appropriate position. If no element exists, skip the step.
step 7 : Go to step 5
2
Anees

Firebaseのドキュメントのいくつかを調べましたが、 https://firebase.google.com/docs/database/Android/structure-data#fanout で推奨される実装がなぜなのか混乱しています。あなたの場合は動作しません。このようなもの:

users
--userid(somedude)
---name
---etc
---leaders: 
----someotherdude
----someotherotherdude

leaders:
--userid(someotherdude)
---datelastupdated
---followers
----somedude
----thatotherdude
---posts
----postid

posts
--postid
---date
---image
---contentid

postcontent
--contentid
---content

このガイドでは、「これは双方向の関係に必要な冗長性です。ユーザーまたはグループのリストが数百万に拡大する場合でも、Adaのメンバーシップを迅速かつ効率的に取得できます。」そのスケーラビリティは、もっぱらFirestoreのものです。

私が何かを見逃していない限り、主な問題はタイムラインノード自体の存在のようです。特定のユーザーのタイムラインのビューを簡単に生成できるようになりますが、それらの関係をすべて維持しなければならず、プロジェクトが大幅に遅れます。送信されたユーザーに基づいて、クエリを使用して上記のような構造からその場でタイムラインを作成するのはあまりにも効率的ですか?

私は主に技術的なギャップのため、提案された解決策に少し苦労していましたので、私は私のために働く別の解決策を考えました。

すべてのユーザーについて、フォローしているすべてのアカウントを含むドキュメントがありますが、そのユーザーをフォローしているすべてのアカウントのリストもあります。

アプリが起動すると、この現在のユーザーをフォローしているアカウントのリストを取得します。ユーザーが投稿を行うと、投稿オブジェクトの一部はフォローしているすべてのユーザーの配列になります。

ユーザーBがフォローしている人のすべての投稿も取得したい場合は、クエリに単純なwhereArrayContains("followers", currentUser.uid)を追加します。

このアプローチが好きなのは、必要な他のパラメーターで結果を並べ替えることができるためです。

に基づく:

  • Googleの検索では、ドキュメントあたり1 MBで、1,048,576人のchaarecterを保持しているようです。
  • Firestoreが生成したUIDの長さは約28文字のようです。
  • オブジェクト内の残りの情報はあまりサイズをとりません。

このアプローチは、最大約37,000人のフォロワーを持つユーザーに有効です。

0
Tsabary