La paginación es difícil cuando las clasificaciones de contenido pueden cambiar rápidamente, y aún más cuando esas clasificaciones difieren por usuario. (Consideremos el desplazamiento infinito como un tipo de paginación donde los enlaces son invisibles). Hay dos problemas difíciles: contenido recién agregado en la parte superior y contenido reintegrado.¿Qué esquemas de paginación pueden manejar listas de contenido que cambian rápidamente?
Olvidémonos del contenido recién agregado y aceptemos que tendrá que actualizar la página 1 para verlo. Vamos a pretender que estamos haciendo pure ORDER BY position
; si ordena por otra cosa, es posible que tenga que usar las funciones de ventana. Nuestras páginas tienen 4 filas de animales por página. Comienzan:
+----+----------+-----------+
| id | position^| animal |
+----+----------+-----------+
| 1 | 1 | Alpacas |
| 2 | 2 | Bats |
| 3 | 3 | Cows |
| 4 | 4 | Dogs |
| 5 | 5 | Elephants |
| 6 | 6 | Foxes |
| 7 | 7 | Giraffes |
| 8 | 8 | Horses |
+----+----------+-----------+
Después de que alcanzamos la página 1, y antes de ir a la página 2, se mueven muchos elementos. El DB es ahora:
+----+----------+-----------+
| id | position^| animal |
+----+----------+-----------+
| 4 | 1 | Dogs |
| 2 | 2 | Bats |
| 1 | 3 | Alpacas |
| 5 | 4 | Elephants |
| 6 | 5 | Foxes |
| 7 | 6 | Giraffes |
| 3 | 7 | Cows |
| 8 | 8 | Horses |
+----+----------+-----------+
Hay tres métodos comunes:
enfoque de compensación/límite de
Este es el típico enfoque ingenuo; en Rails, es cómo funcionan will_paginate y Kaminari. Si quiero buscar la página 2, haré
SELECT * FROM animals
ORDER BY animals.position
OFFSET ((:page_num - 1) * :page_size)
LIMIT :page_size;
que obtiene las filas 5-8. Nunca veré elefantes, y veré vacas dos veces.
Última visita enfoque ID
Reddit toma un enfoque diferente. En lugar de calcular la primera fila según el tamaño de la página, el cliente rastrea la identificación del último elemento que ha visto, como un marcador. Al llegar a "siguiente", empiezan a buscar a partir del marcador en adelante:
SELECT * FROM animals
WHERE position > (
SELECT position FROM animals
WHERE id = :last_seen_id
)
ORDER BY position
LIMIT :page_size;
En algunos casos, esto funciona mejor que la página/offset. Pero en nuestro caso, Dogs, la publicación vista por última vez, se amplió a la derecha hasta el n. ° 1. Entonces el cliente envía ?last_seen_id=4
, y mi página 2 son murciélagos, alpacas, elefantes y zorros. No me he perdido ningún animal, pero vi dos veces Murciélagos y Alpacas.
estado del lado del servidor
HackerNews (y nuestro sitio, en este momento) resuelve esto con continuaciones del lado del servidor; almacenan el conjunto de resultados entero para usted (¿o al menos varias páginas de antemano?), y el enlace "Más" hace referencia a esa continuación. Cuando busco la página 2, pido la "página 2 de mi consulta original". Utiliza el mismo cálculo de compensación/límite, pero como va en contra de la consulta original, simplemente no me importa que las cosas se hayan movido ahora. Veo Elefantes, Zorros, Jirafas y Caballos. Sin dups, sin artículos perdidos.
El inconveniente es que tenemos que almacenar un montón de estado en el servidor. En HN, eso está almacenado en la RAM, y en realidad esas continuaciones a menudo caducan antes de que puedas presionar el botón "Más", forzándote a volver a la página 1 para encontrar un enlace válido. En la mayoría de las aplicaciones, puede almacenar eso en memcached, o incluso en la base de datos en sí (usando su propia tabla, o en Oracle o PostgreSQL, usando cursores que se pueden usar). Dependiendo de su aplicación, puede haber un golpe de rendimiento; en PostgreSQL, al menos, debe encontrar la forma de volver a conectar correctamente la conexión de base de datos correcta, lo que requiere una gran cantidad de enrutamiento de estado sólido o de algún back-end inteligente.
¿Son estos los tres únicos enfoques posibles? Si no, ¿hay conceptos de ciencias de la computación que me darían Google juice para leer sobre esto? ¿Hay formas de aproximar el enfoque de continuación sin almacenar todo el conjunto de resultados? A largo plazo, hay sistemas complejos de transmisión de eventos/punto en el tiempo, donde "el resultado establecido a partir del momento en que tomé la página 1" es siempre derivable. Corto de eso ...?
Sugiero mirarlo desde un ángulo diferente. Tal vez sea posible evitar la paginación en absoluto; solo use el desplazamiento infinito + algunas secuencias de comandos extensas que actualizan la lista sin recargar páginas y muestra los símbolos ↑/↓ apropiados para la comodidad del usuario. Depende de tu caso de uso, sin embargo. Upd: FWIW, aquí está [una pregunta relacionada] (http://ux.stackexchange.com/questions/2997/best-way-to-add-items-to-a-paginated-list/2999#2999) de UX StackExchange . – Tony
Sí, eso no funciona para nuestro caso de uso ... las cosas se vuelven a programar continuamente, y no querría que la pantalla se actualice continuamente. Una gran idea, sin embargo. –
Puede almacenar estado en el cliente y enviar todas las identificaciones de los registros vistos. –