2010-03-08 7 views
6

Me gustaría paginar a través de una lista ordenada aleatoriamente de modelos ActiveRecord (filas de la base de datos MySQL).Clasificación aleatoria estable/repetible (MySQL, Rails)

Sin embargo, esta aleatorización debe continuar por sesión, de modo que otras personas que visiten el sitio web también reciban una lista de registros aleatoria y paginable.

Digamos que hay suficientes entidades (decenas de miles) que el almacenamiento de los valores de ID ordenados aleatoriamente en la sesión o una cookie es demasiado grande, por lo que debo persistir temporalmente de alguna otra manera (MySQL, archivo, etc.)

Inicialmente pensé que podía crear una función basada en la ID de sesión y la ID de página (devolviendo los ID de objeto para esa página), pero dado que los valores ID de objeto en MySQL no son secuenciales (hay espacios), eso parecía desmoronarse mientras lo estaba hurgando. Lo bueno es que requeriría un almacenamiento nulo o mínimo, pero las desventajas son que probablemente sea bastante complejo de implementar y probablemente intensivo en la CPU.

Mi sensación es que debería crear una tabla de intersección, algo así como:

random_sorts(sort_id, created_at, user_id NULL if guest) 

random_sort_items(sort_id, item_id, position) 

Y luego simplemente almacenar el 'sort_id' en la sesión. Entonces, puedo paginar los random_sorts WHERE sort_id = n ORDER BY position LIMIT ... como de costumbre.

Por supuesto, tendría que poner algún tipo de segador allí para eliminarlos después de un período de inactividad (basado en random_sorts.created_at).

Desafortunadamente, tendría que invalidar la clasificación a medida que se creaban nuevos objetos (y/o se eliminaban los objetos viejos, aunque la eliminación es muy rara). Y, a medida que aumenta la carga, el tamaño/rendimiento de esta tabla (incluso indexado correctamente) disminuye.

Parece que esto debería ser un problema resuelto pero no puedo encontrar ningún complemento de raíles que haga esto ... ¿Alguna idea? ¡¡Gracias!!

Respuesta

6

MySQL tiene una función RAND que puede usar en su cláusula ORDER, pasando una semilla vinculada a la sesión del usuario.

ORDER BY RAND (?)

Dónde? es un valor inicial de la sesión. Esto le dará un orden repetible a través de las solicitudes.

+0

Sí, eso funciona siempre que las filas de la tabla nunca cambien (si se agrega una nueva, entiendo que todo el conjunto puede cambiar). Además, ocasiona un escaneo de tabla cada vez, que puede ser un golpe de gran rendimiento ... –

+0

tu comentario parece válido (aunque a veces * quieres * que todo se recurra si la tabla subyacente agrega algo nuevo). Lamentablemente, no puedo pensar en una forma de evitar el escaneo de toda la mesa cada vez, aunque tal vez un 'orden por id límite 10' sea lo suficientemente inteligente como para salir temprano ...). Sin embargo, si no hace una "orden por", agregar una nueva entrada podría aleatorizar su salida, AFAIK, por lo que esta puede ser una pregunta difícil de hacer bien ... – rogerdpack

+0

parece ordenar por rand luego el límite "no "al menos una exploración de tabla completa (en mysql) aunque puede haber trucos que podría usar para evitar acelerar esto en tablas grandes: http://stackoverflow.com/questions/211329/quick-selection-of-a- random-row-from-a-large-table-in-mysql/211388 – rogerdpack

2

probablemente estoy perdiendo algo, pero no sería algo así como este trabajo

select ... order by sha1(concat($session_id,item_id)) limit m,n;

para darle una lista paginada-aleatoria ordenado, repetible por sesión? No es muy agradable en el uso del índice pero evitas cualquier tabla/invalidación previa al llenado/tmp.

0

Personalmente, para ahorrar espacio de almacenamiento y cordura, usaría una semilla aleatoria con su user_id.

srand user_id 
items.sort_by{ rand } 
+0

Eso utilizaría una gran cantidad de memoria ruby ​​para ordenar toda la matriz, ¿verdad? –

+0

Estoy un poco más allá de mi profundidad cuando se trata de las partes internas, pero dado que solo está ordenando punteros, ¿cuál sería una forma más eficiente? Tal vez un custom. Shuffle! método con una semilla al azar pasado? http://stackoverflow.com/questions/2039902/how-does-rubys-sort-by-rand-work – ghoppe

+0

Otra posible dificultad aquí es que srand se comparte en todo el proceso de ruby ​​(AFAICT), por lo que si eres multi Enhebrado, y no sincronizado, podría existir la posibilidad de condiciones de carrera.Dicho esto, en 1.9.2 creo que hay una nueva clase de Random que podrías usar en su lugar (pero sí, estarías cargando todo el conjunto en la RAM, que puedes evitar, ver a Toby Hede de una manera misteriosa)) – rogerdpack

Cuestiones relacionadas