javascript database firebase angularfire2 google-cloud-firestore

javascript - Clasificación de clasificación con Firebase



database angularfire2 (3)

Tengo un proyecto que necesito para mostrar una tabla de clasificación de los 20 mejores, y si el usuario no está en la tabla de clasificación, aparecerá en el lugar 21 con su clasificación actual.

¿Hay una manera eficiente de esto?

Estoy usando Cloud Firestore como base de datos. Creo que fue un error elegirlo en lugar de MongoDB, pero estoy en el medio del proyecto, así que debo hacerlo con Cloud Firestore.

La aplicación será utilizada por 30K usuarios. ¿Hay alguna manera de hacerlo sin obtener todos los 30 mil usuarios?

this.authProvider.afs.collection(''profiles'', ref => ref.where(''status'', ''=='', 1) .where(''point'', ''>'', 0) .orderBy(''point'', ''desc'').limit(20))

Este es el código que hice para obtener los 20 mejores, pero ¿cuál será la mejor práctica para obtener el rango actual de usuarios registrados si no están entre los 20 mejores?


Encontrar un rango de jugador arbitrario en la tabla de clasificación, de una manera que se escala, es un problema difícil común con las bases de datos.

Hay algunos factores que impulsarán la solución que deberá elegir, como:

  • Número total de jugadores
  • Califica que los jugadores individuales suman puntajes
  • Califique que se agregan nuevos puntajes (jugadores concurrentes * arriba)
  • Rango de puntaje: limitado o ilimitado
  • Distribución de puntaje (uniforme, o son sus ''puntajes calientes'')

Enfoque simplista

El enfoque simplista típico es contar todos los jugadores con una puntuación más alta, por ejemplo, SELECT count(id) FROM players WHERE score > {playerScore} .

Este método funciona a baja escala, pero a medida que su base de jugadores crece, rápidamente se vuelve lenta y costosa en recursos (tanto en MongoDB como en Cloud Firestore).

Cloud Firestore no admite el count forma nativa, ya que es una operación no escalable. Deberá implementarlo en el lado del cliente simplemente contando los documentos devueltos. Alternativamente, puede usar Cloud Functions for Firebase para hacer la agregación en el lado del servidor para evitar el ancho de banda adicional de los documentos que regresan.

Actualización periódica

En lugar de darles una clasificación en vivo, cámbiela a solo actualizarla cada cierto tiempo, como cada hora. Por ejemplo, si observa las clasificaciones de , solo se actualizan diariamente.

Para este enfoque, puede programar una función o programar App Engine si tarda más de 540 segundos en ejecutarse. La función escribiría la lista de jugadores como en una colección de ladder con un nuevo campo de rank poblado con el rango de jugadores. Cuando un jugador ve la escalera ahora, puede obtener fácilmente la X superior + el rango propio del jugador en el tiempo O (X).

Mejor aún, también podría optimizar y escribir explícitamente la X superior como un solo documento, por lo que para recuperar la escalera solo necesita leer 2 documentos, top-X y reproductor, ahorrando dinero y haciéndolo más rápido.

Este enfoque realmente funcionaría para cualquier número de jugadores y cualquier tasa de escritura ya que se realiza fuera de banda. Sin embargo, es posible que deba ajustar la frecuencia a medida que crezca, dependiendo de su disposición a pagar. 30,000 jugadores por hora serían $ 0.072 por hora ($ 1.73 por día) a menos que hiciera optimizaciones (por ejemplo, ignore a todos los jugadores con puntaje 0 ya que sabe que están empatados en último lugar)

Índice invertido

En este método, crearemos una especie de índice invertido. Este método funciona si hay un rango de puntaje acotado que es significativamente menor si se desea el número de jugadores (p. Ej., Puntajes de 0-999 frente a 30,000 jugadores). También podría funcionar para un rango de puntaje ilimitado donde el número de puntajes únicos aún era significativamente menor que el número de jugadores.

Usando una colección separada llamada ''puntajes'', tiene un documento para cada puntaje individual (inexistente si nadie tiene ese puntaje) con un campo llamado número de player_count .

Cuando un jugador obtiene un nuevo puntaje total, harás 1-2 escrituras en la colección de scores . Una escritura es hacer +1 en player_count para su nuevo puntaje y, si no es la primera vez, -1 en su puntaje anterior. Este enfoque funciona para las escaleras de estilo "Su puntaje más reciente es su puntaje actual" y "Su puntaje más alto es su puntaje actual".

Encontrar el rango exacto de un jugador es tan fácil como algo como SELECT sum(player_count)+1 FROM scores WHERE score > {playerScore} .

Dado que Cloud Firestore no admite sum() , haría lo anterior, pero suma en el lado del cliente. El +1 se debe a que la suma es el número de jugadores por encima de ti, por lo que agregar 1 te da el rango de ese jugador.

Con este enfoque, deberá leer un máximo de 999 documentos, con un promedio de 500 para obtener un rango de jugadores, aunque en la práctica esto será menor si elimina las puntuaciones que tienen cero jugadores.

Es importante comprender la tasa de escritura de nuevos puntajes, ya que solo podrá actualizar un puntaje individual una vez cada 2 segundos * en promedio, lo que para un rango de puntaje perfectamente distribuido de 0-999 significaría 500 nuevos puntajes / segundo **. Puede aumentar esto usando contadores distribuidos para cada puntaje.

* Solo 1 nuevo puntaje por 2 segundos ya que cada puntaje genera 2 escrituras
** Suponiendo un tiempo de juego promedio de 2 minutos, 500 nuevos puntajes / segundo podrían admitir 60000 jugadores concurrentes sin contadores distribuidos. Si está utilizando un "puntaje más alto es su puntaje actual", esto será mucho más alto en la práctica.

Árbol N-ario fragmentado

Este es, con mucho, el enfoque más difícil, pero podría permitirle tener posiciones de clasificación más rápidas y en tiempo real para todos los jugadores. Se puede considerar como una versión optimizada de lectura del enfoque del Índice invertido anterior, mientras que el enfoque del Índice invertido anterior es una versión de escritura optimizada de esto.

Puede seguir este artículo relacionado para ''Clasificación rápida y confiable en el almacén de datos'' en un enfoque general que sea aplicable. Para este enfoque, querrá tener un puntaje acotado (es posible con no acotado, pero requerirá cambios de los siguientes).

No recomendaría este enfoque, ya que necesitará hacer contadores distribuidos para los nodos de nivel superior para cualquier escalera con actualizaciones semi frecuentes, lo que probablemente negaría los beneficios del tiempo de lectura.

Pensamientos finales

Dependiendo de la frecuencia con que muestres la tabla de clasificación para los jugadores, puedes combinar enfoques para optimizar esto mucho más.

La combinación de ''Índice invertido'' con ''Actualización periódica'' en un período de tiempo más corto puede darle acceso de clasificación O (1) para todos los jugadores.

Siempre y cuando todos los jugadores vean la tabla de clasificación> 4 veces durante la ''Actualización periódica'', ahorrará dinero y tendrá una tabla de clasificación más rápida.

Esencialmente cada período, digamos 5-15 minutos, lee todos los documentos de scores en orden descendente. Con esto, mantenga un total players_count de players_count . Vuelva a escribir cada puntaje en una nueva colección llamada scores_ranking con un nuevo campo players_above . Este nuevo campo contiene el total player_count excluyendo los puntajes actuales player_count .

Para obtener el rango de un jugador, todo lo que necesita hacer ahora es leer el documento del puntaje del jugador en score_ranking -> Su rango es players_above + 1.


Una solución no mencionada aquí que estoy a punto de implementar en mi juego en línea y que puede ser utilizable en su caso de uso, es estimar el rango del usuario si no está en ninguna tabla de clasificación visible porque, francamente, el usuario no sabrá (¿o le importa?) si están en el puesto 22.882 o 22.838.

Si el puesto 20 tiene un puntaje de 250 puntos y hay un total de 32,000 jugadores, entonces cada punto vale en promedio 127 lugares, aunque es posible que desee usar algún tipo de curva para que al subir un punto no salten exactamente 127 lugares cada vez: la mayoría de los saltos en rango deberían estar más cerca de cero puntos.

Depende de usted si desea identificarlo como una estimación o no, y puede agregar algo de sal al número para que se vea auténtico:

// Real rank: 22,838 // Display to user: player rank: ~22.8k // rounded player rank: 22,882nd // rounded with random salt of 44

Haré lo último.


Una solución que no ha sido mencionada por Dan es el uso de reglas de seguridad combinadas con Google Cloud Functions.

Crea el mapa de la puntuación más alta. Ejemplo:

  • HighScores (top20)

Entonces:

  1. Otorgue a los usuarios acceso de escritura / lectura a puntajes altos.
  2. Otorgue al documento / mapa highScores la puntuación más pequeña en una propiedad.
  3. Permita que los usuarios solo escriban en highScores si su puntaje> puntaje más pequeño.
  4. Cree un activador de escritura en Google Cloud Functions que se activará cuando se escriba un nuevo puntaje alto. En esa función, elimine la puntuación más pequeña.

Esto me parece la opción más fácil. También es en tiempo real.