¿Cómo diseñar una base de datos para almacenar una lista ordenada?

42

Estoy buscando almacenar una lista ordenada dentro de una base de datos. Quiero realizar las siguientes operaciones de manera eficiente.

  1. Insertar (x): inserte el registro x en la tabla
  2. Eliminar (x): eliminar el registro x de la tabla
  3. Before (x, n): devuelve los registros 'n' que preceden al registro x en la lista ordenada.
  4. After (x, n): devuelve los registros 'n' que suceden al registro x en la lista ordenada.
  5. Primero (n): devuelve los primeros registros 'n' de la lista ordenada.
  6. Último (n): devuelve los últimos registros 'n' de la lista ordenada.
  7. Compare (x, y): dados dos registros x e y de la tabla, encuentre si x> y.

El método simple que se me ocurre es almacenar algún tipo de atributo de 'rango' en la tabla y consultar ordenando ese atributo. Pero en este método, insertar / modificar un registro con un rango se convierte en una operación costosa. hay algun metodo mejor?

Específicamente, estoy buscando implementar la tabla usando SimpleDB de Amazon. Pero una respuesta general para una base de datos relacional también debería ser útil.

Actualización en el perfil de carga:

Como estoy planeando esto para una aplicación web, depende de la cantidad de usuarios que usen la aplicación.

Si hay 100k usuarios activos (súper optimismo: P), entonces mi estimación aproximada por día sería

500k selecciona, 100k inserta y elimina, 500k actualizaciones

Esperaría que la mesa crezca hasta 500k en total.

Estoy buscando optimizar las actualizaciones, la inserción y las operaciones de comparación. El rango de los elementos cambiará constantemente y necesito mantener la tabla actualizada.

chitti
fuente
Explique un poco su perfil de carga esperado. ¿Cuántas selecciones / inserciones / actualizaciones por día? ¿Para qué operaciones desea optimizar más? ¿Qué tan grande esperas que la mesa crezca por día o que se vuelva total?
Nick Chammas
¿Es esto para un tablero de clasificación de jugadores? De todos modos, he actualizado mi respuesta a continuación con comentarios basados ​​en su perfil de carga proyectado.
Nick Chammas
no, no es un tablero de clasificación de jugadores.
chitti
¿Qué enfoque terminaste usando?
Nick Chammas
Ni siquiera estoy seguro de qué se pregunta aquí o qué no necesita hacer de la lista de cosas que debe hacer.
Evan Carroll

Respuestas:

22

Si el rango no es completamente arbitrario, sino que se puede derivar de alguna otra propiedad (por ejemplo, nombre, puntaje del jugador, etc.), eche un vistazo a la respuesta de Joel .

Si es una propiedad arbitraria de sus datos, debe almacenarse como una columna en su tabla de registros. Suponiendo que el SimpleDB de Amazon es similar al RDBMS típico, puede indexar esta columna y satisfacer rápidamente todas sus consultas anteriores con la estrategia de indexación adecuada. Esto es normal para un RDBMS.

Dado que espera una alta actividad de inserción y actualización, pero también una actividad de lectura relativamente alta, le recomiendo hacer lo siguiente:

  • Agrupe la tabla en el rango, especialmente si la gran mayoría de sus consultas están en contra del rango. Si no es así, o si la elección de una clave de agrupación no está disponible en SimpleDB, simplemente cree un índice con la clasificación como la columna inicial. Esto satisfaría las consultas 3-6.
  • Un índice en el registro primero y luego en el rango (o, en el mundo de SQL Server, solo registre y INCLUDEclasifique el rango, o simplemente registre si se ha agrupado en el rango) satisfaría la consulta 7.
  • Las operaciones 1 y 2 se pueden optimizar espaciando sus datos adecuadamente (es decir, configurando FILLFACTORen SQL Server). Esto es especialmente importante si se agrupa en rango.
  • A medida que inserte o actualice rangos, mantenga la mayor brecha posible entre los números de rango para minimizar la posibilidad de que necesite volver a clasificar un registro existente para acomodar un rango de inserción o actualización. Por ejemplo, si clasifica sus registros en pasos de 1000, deja suficiente espacio para aproximadamente la mitad de esos cambios e inserciones con una probabilidad mínima de que necesite volver a clasificar un registro que no esté directamente involucrado en esos cambios.
  • Todas las noches re-clasifica todos los registros para restablecer las brechas de rango entre ellos.
  • Puede ajustar la frecuencia de las re-clasificaciones masivas, así como el tamaño de la brecha de rango para acomodar su número esperado de inserciones o actualizaciones en relación con el número de registros existentes. Entonces, si tiene 100K registros y espera que sus inserciones y actualizaciones sean el 10% de eso, deje suficiente espacio para 10K nuevos rangos y vuelva a clasificar por las noches.
  • Re-clasificar los registros de 500K es una operación costosa, pero una vez al día o una semana fuera de horario debería estar bien para una base de datos como esa. Esta re-clasificación masiva fuera del horario laboral para mantener las brechas de rango es lo que le ahorra tener que volver a clasificar muchos registros para cada actualización de rango o insertar durante sus horas normales y pico.

Si espera lecturas de 100K + en una tabla de tamaño de 100K +, no recomiendo usar el enfoque de lista vinculada. No escalará bien a esos tamaños.

Nick Chammas
fuente
Los rangos son modificables. Espero que las filas cambien constantemente y que se inserten nuevos registros constantemente. Me preocupa el caso cuando inserto un nuevo elemento con un rango, entonces los rangos de todos los registros debajo del nuevo registro en orden de clasificación deben cambiar. ¿No es una operación costosa cuando tengo miles de registros en mi base de datos?
chitti
@chitti - Ah, eso es una preocupación. Puede espaciar sus clasificaciones (por ejemplo, 0, 1000, 2000, 3000, ...) y volver a clasificar periódicamente todos los registros a medida que se llenan los vacíos de clasificación. Sin embargo, esto no escalará si espera mucho más que unas pocas decenas de miles de registros.
Nick Chammas
1
@chitti - Esto es un poco divertido, en realidad. Este es exactamente el problema que enfrentan los motores de bases de datos al indexar datos, porque lo están ordenando y reordenando a medida que se agregan o cambian los datos. Si miras hacia arriba FILLFACTOR, verás que básicamente está destinado a crear ese espacio extra para los registros en un índice, tal como las brechas de rango que describí crean espacio para cambios de rango e inserciones.
Nick Chammas
2
Gracias por la respuesta actualizada. El 'rango' es una propiedad arbitraria de mis datos. Estoy casi convencido de que una columna de índice personalizado es lo que necesito. Mira este enlace SO con una pregunta similar. La respuesta principal proporciona recomendaciones sobre cómo manejar una columna de rango.
chitti
@chitti: la respuesta aceptada a esa pregunta SO es genial. Sugiere el mismo enfoque que he detallado aquí, con la sugerencia adicional de usar decimales en lugar de enteros para expandir en gran medida su flexibilidad en la asignación y cambio de rangos. Gran hallazgo.
Nick Chammas
13

Generalmente uso el método de "rango" que usted describe. En lugar de perder el tiempo con la actualización de filas cuando los elementos debían reordenarse, a menudo he podido eliminar todos los registros de la lista y volver a insertar nuevos elementos en el orden correcto. Este método está claramente optimizado para su recuperación.

Un enfoque alternativo sería modelar los registros como una lista vinculada mediante el uso de una columna de clave externa reflexiva "predecesora" en la tabla:

ID   setID   item       predecessor
---  ------  ------     ------------
1    1       Apple      null
2    1       Orange     1
3    2       Cucumber   null
4    1       Pear       2
5    1       Grape      4
6    2       Carrot     3

Puede recuperar fácilmente una lista y agregar y quitar elementos con poca sobrecarga, pero sacar los registros en el orden correcto será complicado. Quizás haya una manera inteligente de hacerlo en una sola consulta, probablemente con muchas uniones de tabla con alias.

Utilizo este último enfoque a menudo cuando estoy modelando una relación de estilo de árbol (categorías, carpetas, conjuntos y subconjuntos). En general, he tenido una función recursiva de algún tipo para reconstruir el árbol completo en mi aplicación.

bpanulla
fuente
2
El modelo de lista vinculada es ordenado. Para recuperar dicha jerarquía en orden en SQL Server, usaría un CTE recursivo .
Nick Chammas
Sin embargo, construir esa jerarquía sería bastante costoso para una mesa alta. La ventaja es que los cambios de rango / inserciones / etc. se pueden hacer fácilmente. Dependiendo del perfil de carga esperado de chitti, este puede ser el mejor enfoque.
Nick Chammas
La opción de lista vinculada parece la mejor idea para todas las operaciones, excepto Comparar. ¿Alguna idea de cómo implementaría Comparar sin tener que rastrear el camino entre los dos elementos que se comparan?
chitti
Si tiene los ID de los elementos, creo que Comparar () sería sencillo, a menos que haya entendido mal lo que quiere decir con Comparar (). Cuando dijo: "buscar si x> y" ¿quiso decir "buscar si x precede a y"? No puedo ver que sea fácil sin un índice personalizado o un procedimiento almacenado que recorra la lista (o esa interesante característica CTE mencionada por @Nick).
bpanulla
55
Este tipo de solución también se aproxima a un modelo de datos gráficos ( en.wikipedia.org/wiki/Graph_theory ). Un sistema de almacenamiento optimizado para almacenar nodos y bordes gráficos podría ser una mejor solución que un RDBMS. Las tiendas de triple y cuádruple y las bases de datos de gráficos como Neo4J son bastante buenas en esto.
bpanulla
6

Creo que lo que hay que hacer es almacenar la propiedad o propiedades que se utilizan para calcular el rango y luego construir un índice sobre ellas. En lugar de intentar forzar a la base de datos a almacenar físicamente los datos en orden de clasificación o usar una lista vinculada administrada manualmente, ¿por qué no dejar que el motor de la base de datos haga lo que fue diseñado para hacer?

Joel Brown
fuente
2
¿Qué pasa si las 'propiedades que se usan para calcular el rango' son arbitrarias? Por ejemplo: un conjunto de entradas del carrito de compras que se reordena en función de las acciones arbitrarias del usuario.
chitti
Cuando dices que el rango es arbitrario, ¿qué quieres decir? Tiene que haber un algoritmo que use para calcular cuál debería ser el rango. Por ejemplo: "según las entradas del carrito de compras": ¿cómo? Debe haber algo almacenado en la base de datos que sea el controlador para el cálculo del rango. Puede ser una combinación de varias cosas, pero estas cosas de alguna manera deben almacenarse en la tabla del cliente o en tablas relacionadas con el cliente. Si está en los datos, puede crear una función que los calcule. Si puede calcularlo, puede almacenarlo e indexarlo.
Joel Brown el
Digamos que necesitamos mantener el orden de los artículos en un carrito de compras y el usuario puede cambiar el orden 'arbitrariamente' utilizando una interfaz de usuario web. ¿Cómo almacenaría dicha lista de elementos en una base de datos y cómo mantendría el orden de clasificación?
chitti
Si lo entiendo correctamente, al "cambiar arbitrariamente" el orden de los artículos en un carrito de compras, quiere decir que el usuario puede arrastrar los artículos hacia arriba y hacia abajo en una lista y soltarlos donde quiera. Supongo que eso me parece un poco artificial. ¿Por qué los usuarios harían eso? Si pudieran hacerlo, ¿lo harían mucho? ¿El uso de una secuencia simple de artículos dentro de un carrito es realmente un problema de rendimiento? Me parece que un número de secuencia del uno al número de artículos en el carrito + el FK del pedido le daría el índice que necesita. Simplemente actualice los elementos cuando uno sea arrastrado.
Joel Brown el
3
El carrito de compras es solo un ejemplo que di para mostrar que hay casos en los que el 'rango' puede ser arbitrario. Puede ser que no haya sido un gran ejemplo. La cola de DVD de Netflix puede ser un mejor ejemplo. Solo por el argumento, imagine una cola de netflix con 100k elementos que el usuario puede reordenar arbitrariamente y lo hace cada minuto. ¿Cómo diseñaría una base de datos para almacenar esa lista ordenada de películas en esta aplicación hipotética?
chitti
1

Estas son las limitaciones de un no RDBMS como simpleDB. Las características que necesita no se pueden implementar en el lado de DB en simpleDB, deben implementarse desde el lado de la programación / aplicación.

Para un RDBMS como SQL server, las características que necesita son rudimentarias para el índice agrupado.

  • Insertar (x): inserte el registro x en la tabla> Insertar simple.
  • Eliminar (x): eliminar el registro x de la tabla> Eliminar simple.
  • Before (x, n): devuelve los registros 'n' que preceden al registro x en la lista ordenada. > Seleccione los mejores resultados n donde x menos que el valor y ordene por cláusula.

  • After (x, n): devuelve los registros 'n' que suceden al registro x en la lista ordenada. > Seleccione n resultados superiores donde x sea mayor que el valor y ordene por cláusula.

  • Primero (n): devuelve los primeros registros 'n' de la lista ordenada. > Seleccione los mejores n resultados.

  • Último (n): devuelve los últimos registros 'n' de la lista ordenada. > Seleccione los mejores resultados después de ordenar por desc.

  • Compare (x, y): dados dos registros x e y de la tabla, encuentre si x> y. > Declaración TSQL IF.
StanleyJohns
fuente
SimpleDB proporciona índices automáticos, clasificación y un lenguaje de consulta básico . Mi problema seguirá siendo incluso si elijo un RDBMS. El problema se debe a que la clasificación de los datos en mi base de datos cambia arbitrariamente y no se pueden capturar como una sola propiedad (a menos que use una columna de clasificación personalizada) que se pueda indexar.
chitti
0

Esto es lo que solía volver a clasificar mi tabla de Postgres después de cada inserción:

CREATE OR REPLACE FUNCTION re_rank_list() RETURNS trigger AS $re_rank_list$
DECLARE
    temprow record;
    row_idx integer := 1;    
BEGIN
    FOR temprow IN
    SELECT * FROM your_schema.your_list WHERE list_id = NEW.list_id ORDER BY rank ASC
    LOOP
        UPDATE your_schema.your_list SET rank = row_idx * 100 WHERE id = temprow.id;
        row_idx := row_idx + 1;
    END LOOP;
    RETURN NEW;
END;
$re_rank_list$ LANGUAGE plpgsql;


CREATE TRIGGER re_rank_list AFTER UPDATE ON your_schema.your_list_value
    FOR EACH ROW 
    WHEN (pg_trigger_depth() = 0)
    EXECUTE PROCEDURE re_rank_list();

Para mi caso de uso, el rendimiento no es una preocupación, pero la confianza de que nunca se romperá o actuará de manera extraña es importante.

marca
fuente