java - query - Firestore: cómo estructurar un sistema de alimentación y seguimiento
firestore query (5)
Hay dos situaciones
-
Los usuarios en su aplicación tienen una pequeña cantidad de seguidores.
-
Los usuarios en su aplicación tienen una gran cantidad de seguidores. Si vamos a almacenar seguidores completos en una sola matriz en un solo documento en Firestore. Luego alcanzará el límite del almacén de incendios de 1 MiB por documento.
-
En la primera situación, cada usuario debe mantener un documento que almacena la lista de seguidores en un solo documento en una sola matriz. Mediante el uso de
arrayUnion()
yarrayRemove()
es posible administrar eficientemente la lista de seguidores. Y cuando va a publicar algo en su línea de tiempo, debe agregar la lista de seguidores en el documento de publicación.Y use la consulta dada a continuación para buscar publicaciones
postCollectionRef.whereArrayContains("followers", userUid).orderBy("date");
-
En la segunda situación, solo necesita dividir el documento siguiente del usuario según el tamaño o el recuento de la matriz de seguidores. Después de alcanzar el tamaño de la matriz en un tamaño fijo, la identificación del siguiente seguidor debe agregarse al siguiente documento. Y el primer documento debe mantener el campo "hasNext", que almacena un valor booleano. Al agregar una nueva publicación, debe duplicar el documento de publicación y cada documento consta de una lista de seguidores que se rompe antes. Y podemos hacer la misma consulta que se da arriba para buscar documentos.
Estaba usando la base de datos en tiempo real Firebase para mi aplicación de prueba de red social en la que puedes seguir y recibir publicaciones de las personas que sigues. Una red social tradicional. Estructuré mi base de datos de esta manera:
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
También tengo otro nodo "Contenido" que solo contenía la identificación de todas las publicaciones del usuario. Entonces, si "A" siguió a "B", entonces toda la identificación de publicación de B se agregó a la línea de tiempo de A. Y si B publicó algo, también se agregó a toda la línea de tiempo de su seguidor.
Esta fue mi solución para la base de datos en tiempo real, pero claramente tiene algunos problemas de escalabilidad.
- si alguien tiene 10,000 seguidores, entonces se agregó una nueva publicación a la línea de tiempo de 10,000 seguidores.
- Si alguien tiene una gran cantidad de publicaciones, cada nuevo seguidor recibió todas esas publicaciones en su línea de tiempo.
Estos fueron algunos de los problemas.
Ahora, estoy pensando en cambiar todo esto en Firestore, ya que se ha dicho "escalable". Entonces, ¿cómo debo estructurar mi base de datos para que los problemas que enfrenté en la base de datos en tiempo real puedan eliminarse en el almacén de incendios.
He estado luchando un poco con las soluciones sugeridas, principalmente debido a una brecha técnica, así que pensé en otra solución que funcione para mí.
Para cada usuario tengo un documento con todas las cuentas que siguen, pero también una lista de todas las cuentas que siguen a ese usuario.
Cuando se inicia la aplicación, obtengo una lista de las cuentas que siguen a este usuario actual, y cuando un usuario realiza una publicación, parte del objeto de publicación es la matriz de todos los usuarios que las siguen.
Cuando el usuario B también quiere obtener todas las publicaciones de las personas a las que sigue, simplemente
whereArrayContains("followers", currentUser.uid)
a la consulta un simple
whereArrayContains("followers", currentUser.uid)
.
Me gusta este enfoque porque todavía me permite ordenar los resultados por cualquier otro parámetro que desee.
Residencia en:
- 1mb por documento, que según una búsqueda en Google que he realizado parece contener 1,048,576 caracteres.
- El hecho de que Firestore generó UID parece tener alrededor de 28 caracteres de longitud.
- El resto de la información en el objeto no toma demasiado tamaño.
Este enfoque debería funcionar para usuarios que tienen hasta aproximadamente 37,000 seguidores.
He visto su pregunta un poco más tarde, pero también intentaré proporcionarle la mejor estructura de base de datos que se me ocurra. Espero que encuentres útil esta respuesta.
Estoy pensando en un esquema que tiene tres colecciones de nivel superior para
users
,
users that a user is following
y
posts
:
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
si alguien tiene 10,000 seguidores, entonces se agregó una nueva publicación a la línea de tiempo de 10,000 seguidores.
Eso no será un problema en absoluto porque esta es la razón por la cual las colecciones se encuentran en Firestore. Según la documentación oficial de modelar una base de datos de Cloud Firestore :
Cloud Firestore está optimizado para almacenar grandes colecciones de documentos pequeños.
Esta es la razón por la que he agregado
userFollowing
como una colección y no como un simple objeto / mapa que puede contener otros objetos.
Recuerde, el tamaño máximo de un documento de acuerdo con la documentación oficial sobre
límites y cuota
es de
1 MiB (1,048,576 bytes)
.
En caso de recopilación, no hay limitación con respecto al número de documentos debajo de una recopilación.
De hecho, para este tipo de estructuras está optimizado Firestore.
Entonces tener esos 10,000 seguidores de esta manera funcionará perfectamente bien. Además, puede consultar la base de datos de tal manera que no sea necesario copiar nada en ningún lado.
Como puede ver, la base de datos está bastante
desnormalizada,
lo que le permite consultarla de manera muy simple.
Tomemos un ejemplo, pero antes creemos una conexión a la base de datos y obtengamos el
uid
del usuario usando las siguientes líneas de código:
FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
Si desea consultar la base de datos para obtener todos los usuarios que sigue un usuario, puede usar una llamada
get()
en la siguiente referencia:
CollectionReference userFollowingRef = rootRef.collection("following/" + uid + "/userFollowing");
De esta manera, puede obtener todos los objetos de usuario que un usuario está siguiendo. Teniendo sus uid, simplemente puede obtener todas sus publicaciones.
Digamos que desea obtener en su línea de tiempo las últimas tres publicaciones de cada usuario.
La clave para resolver este problema cuando se utilizan conjuntos de datos muy grandes es cargar los datos en fragmentos más pequeños.
En mi respuesta de esta
post
he explicado una forma recomendada en la que puede paginar consultas combinando cursores de consulta con el método
limit()
.
También te recomiendo que mires este
video
para comprenderlo mejor.
Por lo tanto, para obtener las últimas tres publicaciones de cada usuario, debe considerar usar esta solución.
Entonces, primero debe obtener los primeros 15 objetos de usuario que está siguiendo y luego, según su
uid
, para obtener sus últimas tres publicaciones.
Para obtener las últimas tres publicaciones de un solo usuario, utilice la siguiente consulta:
Query query = rootRef.collection("posts/" + uid + "/userPosts").orderBy("date", Query.Direction.DESCENDING)).limit(3);
A medida que se desplaza hacia abajo, cargue otros 15 objetos de usuario y obtenga sus últimas tres publicaciones, etc.
Además de la
date
, también puede agregar otras propiedades a su objeto de
post
, como la cantidad de me gusta, comentarios, acciones compartidas, etc.
Si alguien tiene una gran cantidad de publicaciones, cada nuevo seguidor recibió todas esas publicaciones en su línea de tiempo.
De ninguna manera. No hay necesidad de hacer algo como esto. Ya he explicado anteriormente por qué.
Editar 20 de mayo de 2019:
Otra solución para optimizar la operación en la que el usuario debería ver todas las publicaciones recientes de todos los que sigue, es almacenar las publicaciones que el usuario debería ver en un documento para ese usuario.
Entonces, si tomamos un ejemplo, digamos facebook, necesitará tener un documento que contenga el feed de facebook para cada usuario. Sin embargo, si hay demasiados datos que puede contener un solo documento ( 1 Mib ), debe colocar esos datos en una colección, como se explicó anteriormente.
Necesitas mantener relaciones entre seguidores:
Followers
-leading_id
-follower_id
-created_at
A continuación, creo que no necesitas una tabla de línea de tiempo. Cuando abra un feed, obtenga todos los seguidores y únase a sus publicaciones, además, puede usar algún tipo de orden y filtro a pedido.
En su estructura, la tabla de línea de tiempo duplica la información sobre las publicaciones, creo que no es normal para la base de datos.
Revisé parte de la documentación de Firebase, y estoy confundido sobre por qué la implementación sugerida en https://firebase.google.com/docs/database/android/structure-data#fanout no funcionaría en su caso . Algo como esto:
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
La guía continúa mencionando "Esta es una redundancia necesaria para las relaciones bidireccionales. Le permite obtener de manera rápida y eficiente la membresía de Ada, incluso cuando la lista de usuarios o grupos se eleva a millones", por lo que no parece esa escalabilidad es exclusivamente una cosa de Firestore.
A menos que me falte algo, el principal problema parece ser la existencia del nodo de la línea de tiempo. Entiendo que hace que sea más fácil generar una vista de la línea de tiempo de un usuario en particular, pero eso tiene el costo de tener que mantener todas esas relaciones y está retrasando significativamente su proyecto. ¿Es demasiado ineficiente usar consultas para construir una línea de tiempo sobre la marcha desde una estructura similar a la anterior, basada en un usuario enviado?