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.
(value_name varchar(20), value double)
sería capaz de almacenar todo (value_name
siendof1
,f2
, ...)