diseño eficaz de tabla / índice de mysql para 35 millones de filas + tabla, con más de 200 columnas correspondientes (doble), cualquier combinación de las cuales se puede consultar

17

Estoy buscando consejos sobre el diseño de tablas / índices para la siguiente situación:

Tengo una tabla grande (datos del historial de precios de acciones, InnoDB, 35 millones de filas y en crecimiento) con una clave primaria compuesta (assetid (int), fecha (fecha)). Además de la información de precios, tengo 200 valores dobles que deben corresponder a cada registro.

CREATE TABLE `mytable` (
`assetid` int(11) NOT NULL,
`date` date NOT NULL,
`close` double NOT NULL,
`f1` double DEFAULT NULL,   
`f2` double DEFAULT NULL,
`f3` double DEFAULT NULL,   
`f4` double DEFAULT NULL,
 ... skip a few 
`f200` double DEFAULT NULL, 
PRIMARY KEY (`assetid`, `date`)) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE
    latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0 
    PARTITION BY RANGE COLUMNS(`date`) PARTITIONS 51;

Inicialmente almacené las 200 columnas dobles directamente en esta tabla para facilitar la actualización y la recuperación, y esto había estado funcionando bien, ya que la única consulta realizada en esta tabla era por el activo y la fecha (estos se incluyen religiosamente en cualquier consulta en esta tabla ), y las 200 columnas dobles solo se leyeron. El tamaño de mi base de datos fue de alrededor de 45 Gig

Sin embargo, ahora tengo el requisito donde necesito poder consultar esta tabla mediante cualquier combinación de estas 200 columnas (llamadas f1, f2, ... f200), por ejemplo:

select from mytable 
where assetid in (1,2,3,4,5,6,7,....)
and date > '2010-1-1' and date < '2013-4-5'
and f1 > -0.23 and f1 < 0.9
and f117 > 0.012 and f117 < .877
etc,etc

Históricamente no he tenido que lidiar con esta gran cantidad de datos antes, así que mi primer instinto fue que se necesitaban índices en cada una de estas 200 columnas, o terminaría con escaneos de tablas grandes, etc. Para mí esto significaba que Necesitaba una tabla para cada una de las 200 columnas con clave primaria, valor e índice de los valores. Así que fui con eso.

CREATE TABLE `f1` (
`assetid` int(11) NOT NULL DEFAULT '0',
`date` date NOT NULL DEFAULT '0000-00-00',
`value` double NOT NULL DEFAULT '0',
PRIMARY KEY (`assetid`, `date`),
INDEX `val` (`value`)
) ENGINE=`InnoDB` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0;

Llené e indexé las 200 tablas. Dejé la tabla principal intacta con las 200 columnas, ya que regularmente se consulta sobre assetid y el rango de fechas y se seleccionan las 200 columnas. Imaginé que dejar esas columnas en la tabla principal (sin indexar) para fines de lectura, y luego tenerlas indexadas en sus propias tablas (para el filtro de unión) sería más rentable. Corrí explica sobre la nueva forma de la consulta

select count(p.assetid) as total 
from mytable p 
inner join f1 f1 on f1.assetid = p.assetid and f1.date = p.date
inner join f2 f2 on f2.assetid = p.assetid and f2.date = p.date 
where p.assetid in(1,2,3,4,5,6,7)
and p.date >= '2011-01-01' and p.date < '2013-03-14' 
and(f1.value >= 0.96 and f1.value <= 0.97 and f2.value >= 0.96 and f2.value <= 0.97) 

De hecho, se logró el resultado deseado, explicar me muestra que las filas escaneadas son mucho más pequeñas para esta consulta. Sin embargo, terminé con algunos efectos secundarios indeseables.

1) mi base de datos pasó de 45 Gig a 110 Gig. Ya no puedo mantener la base de datos en RAM. (Sin embargo, tengo 256Gig de RAM en el camino)

2) las inserciones nocturnas de datos nuevos ahora deben realizarse 200 veces en lugar de una vez

3) el mantenimiento / desfragmentación de las nuevas 200 tablas toma 200 veces más tiempo que solo la 1 tabla. No se puede completar en una noche.

4) las consultas contra las tablas f1, etc. no son necesariamente efectivas. por ejemplo:

 select min(value) from f1 
 where assetid in (1,2,3,4,5,6,7) 
 and date >= '2013-3-18' and date < '2013-3-19'

la consulta anterior, mientras que la explicación muestra que parece <1000 filas, puede tardar más de 30 segundos en completarse. Supongo que esto se debe a que los índices son demasiado grandes para caber en la memoria.

Como esa era una gran cantidad de malas noticias, busqué más y encontré particiones. Implementé particiones en la tabla principal, particionada en la fecha cada 3 meses. Mensualmente parecía tener sentido para mí, pero he leído que una vez que obtienes más de 120 particiones, el rendimiento sufre. Particionar trimestralmente me dejará en eso durante los próximos 20 años más o menos. cada partición está un poco por debajo de 2 Gig. Corrí explicar las particiones y todo parece estar podando correctamente, así que independientemente de que sienta que la partición fue un buen paso, al menos para analizar / optimizar / reparar.

Pasé mucho tiempo con este artículo.

http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html

mi tabla actualmente está particionada con la clave primaria todavía en ella. El artículo menciona que las claves primarias pueden hacer que una tabla particionada sea más lenta, pero si tiene una máquina que puede manejarla, las claves primarias en la tabla particionada serán más rápidas. Sabiendo que tengo una gran máquina en camino (256 G de RAM), dejé las teclas.

así que como lo veo, aquí están mis opciones

Opción 1

1) elimine las 200 tablas adicionales y deje que la consulta realice escaneos de tablas para encontrar los valores f1, f2, etc. Los índices no únicos pueden dañar el rendimiento en una tabla particionada correctamente. ejecutar una explicación antes de que el usuario ejecute la consulta y denegarla si el número de filas analizadas supera el umbral que defino. ahórreme el dolor de la base de datos gigante. De todos modos, pronto estará todo en la memoria.

subpregunta:

¿Parece que he elegido un esquema de partición apropiado?

opcion 2

Particione todas las 200 tablas usando el mismo esquema de 3 meses. disfrute de los escaneos de filas más pequeños y permita a los usuarios ejecutar consultas más grandes. ahora que están particionados, al menos puedo administrarlos 1 partición a la vez para fines de mantenimiento. De todos modos, pronto estará todo en la memoria. Desarrolle una forma eficiente de actualizarlos todas las noches.

subpregunta:

¿Ves una razón por la que puedo evitar los índices de clave primaria en estas tablas f1, f2, f3, f4 ..., sabiendo que siempre tengo el ID de activo y la fecha cuando realizo consultas? me parece contrario a la intuición, pero no estoy acostumbrado a conjuntos de datos de este tamaño. eso reduciría la base de datos un montón, supongo

Opción 3

Suelte las columnas f1, f2, f3 en la tabla maestra para reclamar ese espacio. hacer 200 uniones si necesito leer 200 funciones, tal vez no sea tan lento como parece.

Opcion 4

Todos ustedes tienen una mejor manera de estructurar esto de lo que he pensado hasta ahora.

* NOTA: Pronto agregaré otros 50-100 de estos valores dobles a cada elemento, así que necesito diseñar sabiendo que está por llegar.

Gracias por cualquier y toda la ayuda

Actualización n. ° 1: 24/03/2013

Fui con la idea sugerida en los comentarios que recibí a continuación y creé una nueva tabla con la siguiente configuración:

create table 'features'{
  assetid int,
  date    date,
  feature varchar(4),
  value   double
}

Particioné la tabla en intervalos de 3 meses.

Volé las 200 tablas anteriores para que mi base de datos volviera a 45 Gig y comencé a llenar esta nueva tabla. ¡Un día y medio después, se completó, y mi base de datos ahora se encuentra en un gordito de 220 conciertos!

Sí permite la posibilidad de eliminar estos 200 valores de la tabla maestra, ya que puedo obtenerlos de una combinación, pero eso realmente solo me devolvería 25 Gigs o tal vez

Le pedí que creara una clave principal en assetid, fecha, característica y un índice de valor, y después de 9 horas de cambios realmente no había hecho mella y parecía congelarse, así que eliminé esa parte.

Reconstruí un par de particiones, pero no parecía reclamar mucho / ningún espacio.

Parece que esa solución probablemente no sea la ideal. Me pregunto si las filas ocupan mucho más espacio que las columnas, ¿podría ser por eso que esta solución tomó mucho más espacio?

Me encontré con este artículo:

http://www.chrismoos.com/2010/01/31/mysql-partitions-tables-with-millions-of-rows

Me dio una idea. Dice:

Al principio, pensé en la partición RANGE por fecha, y aunque estoy usando la fecha en mis consultas, es muy común que una consulta tenga un rango de fechas muy grande, y eso significa que podría abarcar fácilmente todas las particiones.

Ahora también estoy dividiendo el rango por fecha, pero también permitiré búsquedas por gran rango de fechas, lo que disminuirá la efectividad de mi partición. Siempre tendré un rango de fechas cuando busque, sin embargo, también siempre tendré una lista de activos. Quizás mi solución debería ser la partición por ID de activo y fecha, donde identifico los rangos de ID de búsqueda típicamente buscados (que puedo encontrar, hay listas estándar, S&P 500, Russell 2000, etc.). De esta manera, casi nunca miraría todo el conjunto de datos.

Por otra parte, tengo la clave principal en assetid y la fecha de todos modos, así que tal vez eso no ayudaría mucho.

Cualquier comentario / comentario más sería apreciado.

Dyeryn
fuente
2
No entiendo por qué necesitas 200 mesas. Una mesa individual con (value_name varchar(20), value double)sería capaz de almacenar todo ( value_namesiendo f1, f2, ...)
a_horse_with_no_name
Gracias. la razón por la que los puse individualmente fue para obtener el límite de 50 índices en una tabla. Había pensado en ponerlos en 5 tablas, 40 valores cada una, pero estoy insertando aproximadamente 17000 registros por día para cada una y no sabía cómo sería el rendimiento de inserción en una tabla con 40 índices. tenga en cuenta que cada combinación de assetid, date obtiene sus propios valores f1, f2 ... ¿Está sugiriendo una sola tabla con (assetid, date, value_name, value), con la clave principal assetid, fecha, quizás indexado en (value_name, value)? esa tabla tendría 35 mil * 200 = 7 mil millones de filas, pero ¿tal vez bien particionada funcionaría?
Dyeryn
publicación actualizada con mis experiencias probando este método
dyeryn
Tengo la solución final en desarrollo, actualizaré cuando termine. Es esencialmente la solución de tabla única propuesta aquí con particionamiento específico y fragmentación lógica.
dyeryn
¿Podría ayudar un motor de almacenamiento diferente? En lugar de InnoDb, ¿quizás intente con InfiniDB? Los datos en columna, los patrones de acceso parecen una gran actualización por lotes, lecturas basadas en rangos y un mantenimiento mínimo de la tabla.
desordenado

Respuestas:

1

coincidentemente, también estoy buscando uno de los servicios de atención al cliente donde diseñamos la estructura de pares clave-valor para la flexibilidad y actualmente la tabla tiene más de 1.5B filas y ETL es demasiado lento. Bueno, hay muchas otras cosas en mi caso, pero ¿has pensado en ese diseño? tendrá una fila con el valor presente de las 200 columnas, esa fila se convertirá en 200 filas en el diseño de pares clave-valor. usted obtendrá una ventaja de espacio con este diseño dependiendo de un AssetID y Fecha dados, ¿cuántas filas tiene realmente todos los valores de 200 f1 a f200 presentes? si dice que incluso el 30% de las columnas od tienen un valor NULL, ese es su ahorro de espacio. porque en el diseño de pares clave-valor si el ID de valor NULL esa fila no necesita estar en la tabla pero en el diseño de estructura de columna existente, incluso NULL ocupa espacio. (No estoy 100% seguro, pero si tiene más de 30 columnas NULL en la tabla, entonces NULL toma 4bytes). Si ve este diseño y asume que todas las filas de 35M tienen valores en las 200 columnas, su base de datos actual se convertirá en 200 * 35M = 700M filas en la tabla de inmediato. pero no será muy alto en el espacio de la tabla lo que tenía con todas las columnas en una sola tabla, ya que solo estamos transponiendo las columnas a la fila. en esta operación de transposición, en realidad no tendremos filas donde los valores sean NULL. para que pueda ejecutar la consulta en esta tabla y ver cuántos valores nulos hay y estimar el tamaño de la tabla de destino antes de implementarla realmente. pero no será muy alto en el espacio de la tabla lo que tenía con todas las columnas en una sola tabla, ya que solo estamos transponiendo las columnas a la fila. en esta operación de transposición, en realidad no tendremos filas donde los valores sean NULL. para que pueda ejecutar la consulta en esta tabla y ver cuántos valores nulos hay y estimar el tamaño de la tabla de destino antes de implementarla realmente. pero no será muy alto en el espacio de la tabla lo que tenía con todas las columnas en una sola tabla, ya que solo estamos transponiendo las columnas a la fila. en esta operación de transposición, en realidad no tendremos filas donde los valores sean NULL. para que pueda ejecutar la consulta en esta tabla y ver cuántos valores nulos hay y estimar el tamaño de la tabla de destino antes de implementarla realmente.

La segunda ventaja es el rendimiento de lectura. como mencionó que la nueva forma de consultar los datos es cualquier combinación de esta columna f1 a f200 en la cláusula where. con el diseño de pares de valores clave f1 a f200 están presentes en una columna, digamos "FildName" y sus valores están presentes en la segunda columna, digamos "FieldValue". puede tener índice CLUSTERADO en ambas columnas. su consulta será UNIÓN de esas selecciones.

WHERE (FiledName = 'f1' y FieldValue entre 5 y 6)

UNIÓN

(FiledName = 'f2' y FieldValue entre 8 y 10)

etc .....

Le daré algunos números de rendimiento del servidor real de productos. Tenemos 75 columnas de precios para cada TICKER de seguridad.

Anup Shah
fuente
1

Al tratar con este tipo de datos donde necesita insertar muchas filas y también necesita un rendimiento de consulta analítica realmente bueno (supongo que este es el caso aquí), puede encontrar que un RDBMS columnar es un buen ajuste . Eche un vistazo a Infobright CE e InfiniDB CE (ambos motores de almacenamiento en columna conectados a MySQL), y también a Vertica CE (más similar a PostgreSQL en lugar de MySQL) ... todas estas ediciones de la comunidad son gratuitas (aunque Vertica no es código abierto, escala a 3 nodos y 1Tb de datos de forma gratuita). Los RDBMS en columnas suelen ofrecer tiempos de respuesta de "consulta grande" que son 10-100X mejores que los basados ​​en filas, y tiempos de carga que son 5-50X mejores. Debe usarlos correctamente o apestan (no realice operaciones de una sola fila ... realice todas las operaciones en un enfoque masivo), pero si se usan correctamente, realmente se balancean. ;-)

HTH, Dave Sisk

Dave Sisk
fuente
1
Tenemos casi mil millones de filas de datos de tipo de secuencia de clics (no muy diferentes de los datos de teletipo estándar) en una instalación de Vertica de 3 nodos ... podemos cargar datos de un día entero en aproximadamente 15 segundos, y obtenemos tiempos de respuesta de consulta en el rango de 500 milisegundos. En su caso, ciertamente parece que valdría la pena echarle un vistazo.
Dave Sisk
Puedo responder por lo mismo. En mi última empresa, teníamos un clúster Vertica de 8 nodos con aproximadamente el mismo número de filas y consultas agregadas simples sobre todo el conjunto devuelto en 1-3 segundos (en promedio). También era aproximadamente 1/4 de costo de nuestro clúster Greenplum anterior.
bma