Acelere la búsqueda de publicaciones para mi aplicación de red social utilizando consultas en lugar de observar un solo evento repetidamente

98

Tengo una serie de claves que me llevan a publicar objetos para mi red social como / posts / id / (información de la publicación)

Cuando cargo las publicaciones, cargo / posts / 0 y luego / posts / 1, etc. usando el observeSingleEventOfType(.Value)método.

Utilizo un lazyTableViewpara cargar 30 a la vez y es bastante lento. ¿Hay alguna forma en que pueda usar uno de los métodos de consulta u otra forma de hacerlo más rápido incluso si tengo que reestructurar los datos en mi árbol JSON?

Vengo de Parse reimplementando mi aplicación y hasta ahora la experiencia ha sido bastante buena. Solo en esto estoy un poco atascado. ¡Gracias de antemano por la ayuda!

EDITAR:

func loadNext(i: Int) { 

    // check if exhists
    let ideaPostsRef = Firebase(url: "https://APPURL")

    ideaPostsRef.childByAppendingPath(i.description).observeSingleEventOfType(.Value, withBlock: {
        (snapshot) in

        if i % 29 == 0 && i != 0 && !self.hitNull { return }
            // false if nil
            // true if not nil
        if !(snapshot.value is NSNull) {
            let postJSON  = snapshot.value as! [String: AnyObject]
            print("GOT VALID \(postJSON)")
            let post = IdeaPost(message: postJSON["message"] as! String, byUser: postJSON["user"] as! String, withId: i.description)
            post.upvotes = postJSON["upvotes"] as! Int
            self.ideaPostDataSource.append(post)
            self.loadNext(i + 1)
        } else {
            // doesn't exhist
            print("GOT NULL RETURNING AT \(i)")
            self.doneLoading = true
            self.hitNull = true
            return
        }
    }
}

Esta función recursiva esencialmente se ejecuta obteniendo el valor de la clave número i de firebase. Si es NSNULL, sabe que es la última publicación posible para cargar y nunca lo vuelve a hacer. Si NSNULL no se ve afectado, pero i % 29 == 0luego regresa como caso base, por lo que solo se cargan 30 publicaciones a la vez (0 indexadas). Cuando me puse doneLoadinga true, tableView.reloadData()se llama usando un observador de la propiedad.

Aquí hay una muestra de cómo se ve la matriz que estoy recuperando

"ideaPosts" : [ {
    "id" : 0,
    "message" : "Test",
    "upvotes" : 1,
    "user" : "Anonymous"
  }, {
    "id" : 1,
    "message" : "Test2",
    "upvotes" : 1,
    "user" : "Anonymous"
  } ]
Big_Mac
fuente
1
Será mucho más fácil ayudar si nos muestra su código en lugar de describirlo. Incluya el JSON mínimo (como texto, no una captura de pantalla) y el código para reproducir el problema en su pregunta y veremos cómo se puede mejorar. Leer más sobre un MCVE .
Frank van Puffelen
Editado para incluir la explicación del código
Big_Mac

Respuestas:

124

Actualización: ahora también cubrimos esta pregunta en un episodio de AskFirebase .

La carga de muchos elementos desde Firebase no tiene por qué ser lenta, ya que puede canalizar las solicitudes. Pero su código lo hace imposible, lo que de hecho conducirá a un rendimiento subóptimo.

En su código, solicita un artículo del servidor, espera a que ese artículo regrese y luego carga el siguiente. En un diagrama de secuencia simplificado que se parece a:

Your app                     Firebase 
                             Database

        -- request item 1 -->
                               S  L
                               e  o
                               r  a
                               v  d
                               e  i
        <-  return item  1 --  r  n
                                  g
        -- request item 2 -->
                               S  L
                               e  o
                               r  a
                               v  d
                               e  i
                               r  n
        <-  return item  2 --     g
        -- request item 3 -->
                 .
                 .
                 .
        -- request item 30-->
                               S  L
                               e  o
                               r  a
                               v  d
                               e  i
                               r  n
                                  g
        <-  return item 30 --

En este escenario, está esperando 30 veces el tiempo de ida y vuelta + 30 veces el tiempo que lleva cargar los datos del disco. Si (en aras de la simplicidad) decimos que los viajes de ida y vuelta toman 1 segundo y cargar un elemento desde el disco también toma un segundo que al menos es 30 * (1 + 1) = 60 segundos.

En las aplicaciones de Firebase, obtendrá un rendimiento mucho mejor si envía todas las solicitudes (o al menos un número razonable de ellas) de una sola vez:

Your app                     Firebase 
                             Database

        -- request item 1 -->
        -- request item 2 -->  S  L
        -- request item 3 -->  e  o
                 .             r  a
                 .             v  d
                 .             e  i
        -- request item 30-->  r  n
                                  g
        <-  return item  1 --     
        <-  return item  2 --      
        <-  return item  3 --
                 .
                 .
                 .
        <-  return item 30 --

Si asumimos nuevamente un viaje de ida y vuelta de 1 segundo y 1 segundo de carga, está esperando 30 * 1 + 1 = 31 segundos.

Entonces: todas las solicitudes pasan por la misma conexión. Teniendo en cuenta que, la única diferencia entre get(1), get(2), get(3)y getAll([1,2,3])es algo de sobrecarga para los marcos.

Configuré un jsbin para demostrar el comportamiento . El modelo de datos es muy simple, pero muestra la diferencia.

function loadVideosSequential(videoIds) {
  if (videoIds.length > 0) {
    db.child('videos').child(videoIds[0]).once('value', snapshot => {
      if (videoIds.length > 1) {
        loadVideosSequential(videoIds.splice(1), callback)
      }
    });
  }
}

function loadVideosParallel(videoIds) {
  Promise.all(
    videoIds.map(id => db.child('videos').child(id).once('value'))
  );
}

A modo de comparación: cargar secuencialmente 64 elementos toma 3.8 segundos en mi sistema, mientras que cargarlos en canalización (como lo hace el cliente de Firebase de forma nativa) toma 600ms. Los números exactos dependerán de su conexión (latencia y ancho de banda), pero la versión canalizada siempre debería ser significativamente más rápida.

Frank van Puffelen
fuente
12
¡Bien, Puf! Además, encadenar promises (jQuery.whenAll (), q.all () o Promise.all ()) puede ser muy útil aquí si necesita cargar todos los elementos, pero aún desea tomarlos en paralelo, antes de tomar alguna acción.
Kato
5
Frio. Ni siquiera pensé en eso, a pesar de que lo he estado usando. :-)
Frank van Puffelen
2
@FrankvanPuffelen Tienes razón desde el punto de vista del rendimiento, pero ¿qué pasa si una de estas llamadas no regresa debido a algún tipo de error? ¿Cómo se puede 'cancelar' el resto de las solicitudes pendientes si alguien de estas falla? En caso de solicitudes secuenciales, podemos conocer en código qué solicitud falló. Por favor comparta sus pensamientos. Gracias.
Perry
1
" El método Promise.all () [...] rechaza con la razón de la primera promesa que rechaza".
pejalo
4
¿Cómo podemos hacer Promise.all en Android? Cómo podemos cargar todos los datos en Android
Muhammad chhota