Antipatrones populares: paginación

Hola, mi nombre es Dmitry Karlovsky y a mí ... no me gusta leer libros, porque mientras pasas la página, sales de una historia fascinante. Y vale la pena dudar un poco cuando se olvida en qué terminó la última oración de la página anterior y tiene que desplazarse hacia atrás para volver a leerla. Y si no es tan aterrador con los libros físicos, con la emisión de un servidor de descanso todo es mucho más triste: después de todo, ahora hay algunos datos en la página y, después de un segundo, es completamente diferente. Pensemos cómo sucedió, quién tiene la culpa y, lo más importante, qué hacer.


Puginadores misceláneos


El problema


Por lo tanto, debemos emitir todos los mensajes para la "paginación" de la consulta, comenzando por el más reciente (los últimos modificados desde arriba ) o en un orden complicado. Todo está bien, siempre que tengamos menos de un centenar de estos mensajes; solo hacemos una selección de la base de datos y devolvemos los datos:


Solicitud del cliente:


GET /message/text=/ 

Solicitud de base de datos:


 SELECT FROM Message WHERE text LICENE "" ORDER BY changed DESC 

Esquema de respuesta JSON para el cliente:


 Array<{ id : number , text : string }> 

Pero la cantidad de mensajes está creciendo y tenemos los siguientes problemas:


  1. Las consultas a la base de datos son cada vez más lentas a medida que se recopilan más datos.
  2. El envío de datos a través de la red lleva cada vez más tiempo.
  3. La representación de estos datos en el cliente es cada vez más larga.

A partir de cierto umbral, los retrasos se vuelven tan importantes que resulta imposible usar nuestro sitio. Si, por supuesto, aún no se había acostado, cansado de una gran cantidad de pesadas solicitudes paralelas.


La solución más simple, que tal vez se te ocurra primero, y ahora puedes encontrarla en cualquier tostadora, para proporcionar datos no todos a granel, sino divididos en páginas. Todo lo que necesitamos hacer es lanzar un parámetro adicional del cliente a la solicitud de la base de datos:


 GET /message/text=/page=5/ 

 SELECT FROM Message WHERE text LICENE "" ORDER BY changed DESC SKIP 5 * 10 LIMIT 10 

 SELECT count(*) FROM Message WHERE text LICENE "" 

 { pageItems : Array<{ id : number , text : string }> totalCount : number } 

Bueno, sí, aún teníamos que volver a contar todos los mensajes para que el cliente pudiera dibujar una lista de páginas o calcular la altura del rollo virtual, pero al menos no necesitábamos obtener todos estos mensajes de 100500 de la base de datos.


Y todo estaría bien si tuviéramos algún tipo de foro no popular durante mucho tiempo sin temas relevantes. Pero nos escriben y escriben, escriben y escriben, y mientras el usuario lee la quinta página, la lista de mensajes cambia más allá del reconocimiento: se agregan nuevos y se eliminan los antiguos. Por lo tanto, obtenemos dos tipos de problemas desde el punto de vista del usuario:


  1. En la página siguiente, pueden aparecer nuevamente mensajes que ya estaban en la anterior.
  2. El usuario no verá algunos mensajes, ya que logró pasar de la página 6 a la 5 exactamente entre la transición del usuario de 5 a 6.

Además, todavía tenemos problemas de rendimiento. Cada transición a la página siguiente lleva al hecho de que necesitamos hacer hasta dos consultas de búsqueda en la base de datos con un número creciente de elementos omitidos de páginas anteriores.


Sí, y una implementación competente en el lado del cliente no es tan simple: siempre debe estar preparado para el hecho de que cualquier respuesta del servidor puede devolver un nuevo número total de mensajes, lo que significa que tendremos que volver a dibujar el paginador y redirigirlo a otra página si la actual de repente esta vacio Y, por supuesto, no puede caer en caso de duplicados.


Además, a veces el cliente necesita actualizar los resultados de búsqueda, pero la carga seguirá recibiendo datos que ya puede tener de solicitudes anteriores.


Como puede ver, la paginación tiene muchos problemas. ¿Realmente no hay mejor solución?


Solución


Primero, prestemos atención al trabajar con la base de datos hay 2 operaciones que son esencialmente diferentes:


  1. Buscar Operación relativamente pesada de encontrar punteros a datos para alguna consulta.
  2. Muestreo Una operación relativamente simple de obtener datos realmente.

Sería ideal:


  1. Una vez que busque y en algún lugar para recordar sus resultados en forma de una instantánea en un determinado momento.
  2. Seleccione rápidamente los datos en pequeñas porciones según sea necesario.

¿Dónde almacenar las instantáneas? hay 2 opciones:


  1. En el servidor Pero luego lo obstruimos con un montón de basura con resultados de búsqueda que deben limpiarse con el tiempo.
  2. Al cliente Pero luego debe transferir inmediatamente toda la instantánea al cliente.

Calculemos el tamaño de la instantánea, que es solo una lista de identificadores. Es dudoso que el usuario haya tenido la paciencia de rodar al menos 100 páginas sin utilizar filtros y ordenaciones. Digamos que tenemos 20 elementos por página. Cada identificador ocupará no más de 10 bytes en la representación json. Multiplique y obtenga no más de 20kb. Y muy probablemente mucho menos. Sería razonable establecer un límite estricto en el tamaño de la salida en, digamos, 1000 elementos.


 GET /message/text=/ 

 SELECT id FROM Message WHERE text LICENE "" ORDER BY changed DESC LIMIT 1000 

 Array<number> 

Ahora el cliente puede dibujar al menos un paginador, al menos un pergamino virtual, solicitando datos solo para identificadores de interés para él.


 GET /message=49,48,47,46,45,42,41,40,39,37/ 

 SELECT FROM Message WHERE id IN [49,48,47,46,45,42,41,40,39,37] 

 Array< { id : number , text : string } | { id : number , error : string } > 

Lo que finalmente conseguimos:


  1. API normalizada: busque por separado, seleccione los datos por separado.
  2. Minimice el número de consultas de búsqueda.
  3. No puede solicitar datos que ya se hayan descargado ni actualizarlos en segundo plano.
  4. Código relativamente simple y universal en el lado del cliente.

De las deficiencias, solo se puede observar:


  1. Para mostrar algo, el usuario debe realizar al menos 2 solicitudes consecutivas.
  2. Es necesario manejar el caso cuando el identificador es, y los datos en él ya no están disponibles.

Source: https://habr.com/ru/post/es413133/


All Articles