webentwicklung-frage-antwort-db.com.de

Firestore - wie man ein Feed- und Follow-System strukturiert

Ich habe die Firebase-Echtzeitdatenbank für meine Test-App für soziales Netzwerk verwendet, in der Sie einfach folgen und Beiträge von Personen erhalten können, denen Sie folgen. Ein traditionelles soziales Netzwerk ... Ich habe meine Datenbank so strukturiert: 

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

Ich habe auch einen anderen Knoten "Content", der nur die ID des gesamten Benutzers enthält. Wenn also "A" auf "B" folgte, wurden alle Post-IDs von B zur Zeitleiste von A hinzugefügt. Und wenn B etwas gepostet hat, wurde es auch in die gesamte Timeline seines Nachfolgers eingefügt.

Nun war dies meine Lösung für Echtzeitdatenbanken, aber es gab offensichtlich einige Skalierbarkeitsprobleme 

  • wenn jemand 10.000 Follower hat, wurde ein neuer Beitrag in die gesamte 10.000-Follower-Zeitleiste eingefügt. 
  • Wenn jemand eine große Anzahl von Posts hat, erhielt jeder neue Follower alle diese Posts in seiner Timeline.

Dies waren einige der Probleme.

Nun, ich denke, das Ganze auf Firestore zu verschieben, da es "Scalable" genannt wurde. Wie sollte ich meine Datenbank so strukturieren, dass Probleme, die ich in Echtzeitdatenbanken hatte, im Firestore beseitigt werden können.

31
Zicsus

Ich habe Ihre Frage etwas später gesehen, werde aber auch versuchen, Ihnen die beste Datenbankstruktur zu bieten, die ich mir vorstellen kann. Ich hoffe, Sie finden diese Antwort hilfreich.

Ich denke an ein Schema, das drei Top-Level-Sammlungen für users, users that a user is following und posts enthält.

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

wenn jemand 10.000 Follower hat, wurde ein neuer Beitrag in die gesamte 10.000-Follower-Zeitleiste eingefügt.

Das ist überhaupt kein Problem, denn aus diesem Grund sind die Kollektionen in Firestore. Laut der offiziellen Dokumentation der Modellierung einer Cloud Firestore-Datenbank :

Cloud Firestore ist für das Speichern großer Sammlungen kleiner Dokumente optimiert.

Aus diesem Grund habe ich userFollowing als Sammlung hinzugefügt und nicht als einfaches Objekt/Map, das andere Objekte aufnehmen kann. Denken Sie daran, dass die maximale Größe eines Dokuments gemäß der offiziellen Dokumentation in Bezug auf Limits und Quota1 MiB (1,048,576 bytes) ist. Im Falle einer Sammlung gibt es keine Beschränkung hinsichtlich der Anzahl der Dokumente unterhalb einer Sammlung. Tatsächlich ist Firestore für diese Art von Strukturen für optimiert.

Wenn man diese 10.000 Anhänger auf diese Weise hat, funktioniert das einwandfrei. Darüber hinaus können Sie die Datenbank so abfragen, dass Sie nichts kopieren müssen.

Wie Sie sehen, ist die Datenbank so ziemlich denormalized, dass Sie sie sehr einfach abfragen können. Nehmen wir ein Beispiel, aber bevor wir eine Verbindung zur Datenbank herstellen, erhalten Sie die uid des Benutzers unter Verwendung der folgenden Codezeilen:

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

Wenn Sie die Datenbank abfragen möchten, um alle Benutzer abzurufen, die ein Benutzer verfolgt, können Sie einen Aufruf von get() für die folgende Referenz verwenden:

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

Auf diese Weise können Sie alle Benutzerobjekte abrufen, denen ein Benutzer folgt. Mit drei Benutzerkonten können Sie einfach alle Beiträge bekommen.

Angenommen, Sie möchten die letzten drei Beiträge jedes Benutzers in Ihre Timeline aufnehmen. Der Schlüssel zur Lösung dieses Problems, wenn sehr große Datensätze verwendet werden, ist das Laden der Daten in kleinere Blöcke. Ich habe in meiner Antwort aus diesem post eine empfohlene Methode erläutert, mit der Sie Abfragen paginieren können, indem Sie Abfragecursor mit der limit()-Methode kombinieren. Ich empfehle Ihnen auch, dieses video zum besseren Verständnis anzuschauen. Um die neuesten drei Beiträge aller Benutzer zu erhalten, sollten Sie diese Lösung in Betracht ziehen. Sie müssen also zuerst die ersten 15 Benutzerobjekte abrufen, denen Sie folgen, und dann basierend auf ihrer uid, um die neuesten drei Einträge zu erhalten. Um die letzten drei Beiträge eines einzelnen Benutzers zu erhalten, verwenden Sie bitte die folgende Abfrage:

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

Laden Sie während des Bildlaufs weitere 15 Benutzerobjekte, und erhalten Sie die neuesten drei Einträge usw. Neben date können Sie auch weitere Eigenschaften zu Ihrem post-Objekt hinzufügen, wie z. B. die Anzahl von Likes, Kommentaren, Freigaben usw.

Wenn jemand eine große Anzahl von Posts hat, erhielt jeder neue Follower alle diese Posts in seiner Timeline.

Auf keinen Fall. Es ist nicht nötig, so etwas zu tun. Ich habe oben bereits erklärt warum.

15
Alex Mamo

Ich denke, das Ganze auf Firestore zu verschieben

Gute Entscheidung. Warum?


Kommen wir nun zum Thema. Wie Sie bereits erwähnt haben, weist Ihre Lösung eindeutig einige Mängel auf. Hier sind zwei, die ich verstehen könnte.

  • Es verschwendet sehr unterschiedliche Ressourcen. Dazu gehören die Datenquote, die Speicherung in der Cloud, der Systemspeicher und die CPU-Leistung.
  • Probleme im Zusammenhang mit der chronologischen Anordnung von Posts.

Die Lösungen sind eine vollständige Neugestaltung Ihrer Datenbank und verwenden jeweils eine andere Methode zum Abrufen von Daten.


Neugestaltung der Datenbank

Vermeiden Sie die vollständige Vervielfältigung von Daten. Hier ist ein gutes Beispiel für eine Datenbankstruktur für ein Social Media.

-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"


So holen Sie die Beiträge ab

Da Sie Beiträge von mehreren Standorten abrufen müssen, müssen Sie Folgendes tun.

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

Falls Sie eine Paginierung benötigen, können Sie sich für eine komplizierte Lösung entscheiden. Offensichtlich führt dies zu einer erheblichen Verbesserung der Leistung.

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

Ich habe einige der Firebase-Dokumentation durchgesehen und bin verwirrt, warum die vorgeschlagene Implementierung unter https://firebase.google.com/docs/database/Android/structure-data#fanout nicht durchgeführt werden sollte Arbeit in Ihrem Fall. Etwas wie das: 

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

Im Leitfaden heißt es weiter: "Dies ist eine notwendige Redundanz für wechselseitige Beziehungen. Sie ermöglicht es Ihnen, Adas Mitgliedschaften schnell und effizient abzurufen, selbst wenn die Liste der Benutzer oder Gruppen in die Millionen reicht.", Scheint es nicht Diese Skalierbarkeit ist ausschließlich eine Firestore-Sache. 

Wenn mir etwas fehlt, scheint das Hauptproblem das Vorhandensein des Timeline-Knotens selbst zu sein. Ich verstehe, dass es einfacher ist, eine Ansicht der Zeitleiste eines bestimmten Benutzers zu generieren. Dies ist jedoch mit den Kosten verbunden, die durch die Pflege all dieser Beziehungen entstehen und das Projekt erheblich verzögern. Ist es zu ineffizient, Abfragen zu verwenden, um ausgehend von einer Struktur, die der obigen Struktur ähnelt, eine Timeline zu erstellen, die auf einem übergebenen Benutzer basiert?

Es gibt zwei Situationen

  1. Nutzer in Ihrer App haben nur wenige Follower.

  2. Nutzer in Ihrer App haben eine große Anzahl von Followern. Wenn wir ganze Anhänger in einem einzigen Array in einem einzigen Dokument im Firestore speichern möchten. Dann wird das Firestore-Limit von 1 MiB pro Dokument erreicht.


  1. In der ersten Situation muss jeder Benutzer ein Dokument führen, das die Liste der Follower in einem einzigen Dokument in einem einzigen Array speichert. Durch Verwendung von arrayUnion() und arrayRemove() ist es möglich, Follower-Listen effizient zu verwalten. Und wenn Sie etwas in Ihrer Timeline veröffentlichen möchten, müssen Sie die Liste der Follower im Postdokument hinzufügen.

    Verwenden Sie die unten stehende Abfrage, um Beiträge abzurufen

    postCollectionRef.whereArrayContains("followers", userUid).orderBy("date");
    
  2. In der zweiten Situation müssen Sie lediglich die Benutzerfolgennavigation basierend auf der Größe oder Anzahl der Follower-Arrays unterbrechen. Nachdem Sie die Größe des Arrays in einer festen Größe erreicht haben, muss die ID des nächsten Nachfolgers in das nächste Dokument eingefügt werden. Und das erste Dokument muss das Feld "hasNext" behalten, in dem ein boolescher Wert gespeichert wird. Wenn Sie einen neuen Beitrag hinzufügen, müssen Sie das Beitragsdokument duplizieren, und jedes Dokument besteht aus einer früheren Liste von Anhängern Dieselbe Abfrage, die oben zum Abrufen von Dokumenten angegeben ist.

0
Niyas

Ich habe ein bisschen mit den vorgeschlagenen Lösungen gekämpft, hauptsächlich aufgrund einer technischen Lücke, also habe ich eine andere Lösung gefunden, die für mich funktioniert.

Für jeden Benutzer habe ich ein Dokument mit allen Konten, denen er folgt, aber auch eine Liste aller Konten, die diesem Benutzer folgen.

Wenn die App gestartet wird, erhalte ich eine Liste der Konten, die diesem aktuellen Benutzer folgen, und wenn ein Benutzer einen Beitrag erstellt, ist ein Teil des Beitragsobjekts das Array aller Benutzer, die diesen folgen.

Wenn Benutzer B auch alle Posts der Personen erhalten möchte, denen er folgt, füge ich der Abfrage einfach eine whereArrayContains("followers", currentUser.uid) hinzu.

Ich mag diesen Ansatz, weil ich die Ergebnisse trotzdem nach beliebigen anderen Parametern ordnen kann.

Beyogen auf:

  • 1 MB pro Dokument, das nach einer von mir durchgeführten Google-Suche 1.048.576 Einträge zu enthalten scheint.
  • Die von Firestore generierten UIDs scheinen etwa 28 Zeichen lang zu sein.
  • Der Rest der Informationen im Objekt nimmt nicht zu viel Größe an.

Dieser Ansatz sollte für Benutzer mit bis zu ca. 37.000 Followern funktionieren.

0
Tsabary