2012-03-07 10 views
37

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 ...?

+1

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

+0

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. –

+0

Puede almacenar estado en el cliente y enviar todas las identificaciones de los registros vistos. –

Respuesta

2

Vamos con el enfoque de estado del lado del servidor por ahora, almacenando en caché todo el resultado en la primera consulta, por lo que siempre devolvemos una lista coherente. Esto funcionará siempre que nuestra consulta ya devuelva todas las filas; eventualmente necesitaremos usar un enfoque de vecino más cercano y eso no funcionará.

Pero creo que hay una cuarta posibilidad, que escala muy bien, siempre y cuando:

  1. Usted no necesita una garantía de duplicados, solamente una alta probabilidad
  2. Eres bien con falta algo de contenido en rollos, siempre y cuando se evite duplicados

la solución es una variante de la solución "visto por última vez ID": Haga que el cliente no mantener o ne, pero 5 o 10 o 20 marcadores, algunos lo suficiente como para poder almacenarlos de manera eficiente. La consulta termina pareciéndose a:

SELECT * FROM posts 
WHERE id > :bookmark_1 
AND id > :bookmark_2 
... 
ORDER BY id 

medida que el número de marcadores crece, las probabilidades disminuyen rápidamente que son (a) comienza en algún momento pasado todos los marcadores n, pero (b) de ver el contenido duplicado de todos modos porque todos fueron reinterpretados

Si hay agujeros, o mejores respuestas en el futuro, felizmente no aceptaré esta respuesta.

4

Oracle maneja esto muy bien. Siempre que el cursor esté abierto, puede buscar tantas veces como sea necesario y sus resultados siempre reflejarán el momento en que se abrió el cursor. Utiliza datos de los registros de deshacer para deshacer prácticamente los cambios que se cometieron después de que se abrió el cursor.

Funcionará mientras que los datos de reversión necesarios aún estén disponibles. Finalmente, los registros se reciclan y los datos de reversión ya no están disponibles, por lo que existe un límite, dependiendo del espacio de registro, actividad del sistema, etc.

Desafortunadamente (IMO), no conozco ningún otro DB que funciona así Las otras bases de datos con las que he trabajado utilizan bloqueos para garantizar la coherencia de lectura, lo cual es problemático si se desea una coherencia de lectura de más de muy corta duración.

+1

Resulta que PostgreSQL también tiene cursores que se pueden fijar. En Oracle, ¿puedes golpear ese cursor desde una conexión diferente, esclavo, etc.? Los cursores de PostgreSQL son basados ​​en disco (para que no estés masticando RAM) y también funcionan en el registro de transacciones, pero solo están disponibles en la misma conexión, por lo que debes asegurarte de que sean correctos o realizar un enrutamiento de back-end. . –

5

Solución 1: "la solución hacky"

Una solución podría consistir en la pista del cliente manteniendo el contenido, una lista de ID ya se ha visto, por ejemplo. Cada vez que necesita otra página, agrega esta lista de ID a los parámetros de su llamada al servidor. Su servidor puede ordenar el contenido, eliminar el contenido ya visto y aplicar el desplazamiento para obtener la página correcta.

No lo recomendaría sin embargo e insisto en hacky. Simplemente lo escribo aquí porque es rápido y podría ajustarse a algunas necesidades.aquí están las cosas malas que se me ocurren:

1) Necesita algo de trabajo del lado del cliente para hacerlo bien (lo que significa "ya visto" en mi oración anterior, ¿qué pasa si voy a una página anterior?)

2) El pedido resultante no refleja su verdadera política de pedidos. Se podría mostrar un contenido en la página 2, aunque la política debería haberlo puesto en la página 1. Podría llevar a un malentendido del usuario. Tomemos el ejemplo del desbordamiento de pila con su anterior política de pedidos, que significa la mayoría de las respuestas subidas primero. Podríamos tener una pregunta con 6 votaciones ascendentes en la página 2, mientras que una pregunta con 4 votos ascendentes estaría en la página 1. Esto sucede cuando las 2 o más votaciones ascendentes ocurrieron mientras el usuario todavía estaba en la página 1. -> puede ser sorprendente para el usuario .

Solución 2: "solución el cliente"

Se trata básicamente de la solución equivalente del lado del cliente al que se llama "estado del lado del servidor". Entonces es útil solo si no es lo suficientemente conveniente hacer un seguimiento de la orden completa en el lado del servidor. Funciona si la lista de elementos no es infinita.

  • Llame a su servidor para obtener la (finito) para la lista completa + el número de artículos/página
  • guardarlo en el lado del cliente
  • recuperar los elementos directamente a través de los identificadores de su contenido.
1

Muy tarde para la fiesta, pero esto es algo con lo que hemos experimentado. Estamos utilizando carga continua, no páginas en las que el usuario iría y volvería.

El cliente crea una lista de todos los ID se ha mostrado, por lo que después de la primera serie que podría ser: 4,7,19,2,1,72,3

Cuando cargamos más contenido que haga la misma consulta con el mismo tipo pero añádalo: WHERE id NOT IN (4,7,19,2,1,72,3)

La lista NOT IN puede crecer con bastante rapidez. Para nosotros esto no es un problema ya que nuestra herramienta interna generalmente no tiene muchos resultados.

Quiero añadir otra idea. Tal vez una adición al lado del servidor podría aplicarse a esto. Cuando el usuario busca, agregue todos los ID que obtuvo a una tabla con un enlace a su búsqueda. Cuando el cliente quiere más, solo tiene que proporcionar el ID de búsqueda (o usar el estado del lado del servidor) y la consulta puede unirse a sus datos de búsqueda.

Cuestiones relacionadas