Estou procurando conselhos sobre design de tabela / índice para a seguinte situação:
Eu tenho uma tabela grande (dados do histórico de preços das ações, InnoDB, 35 milhões de linhas e em crescimento) com uma chave primária composta (assetid (int), data (data)). além das informações de preços, tenho 200 valores duplos que precisam 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, eu armazenei as 200 colunas duplas diretamente nesta tabela para facilitar a atualização e a recuperação, e isso estava funcionando bem, pois a única consulta feita nessa tabela era pelo ID do ativo e pela data (elas são religiosamente incluídas em qualquer consulta nesta tabela) ) e as 200 colunas duplas foram lidas apenas. O tamanho do meu banco de dados estava em torno de 45 Gig
No entanto, agora eu tenho o requisito em que preciso poder consultar esta tabela por qualquer combinação dessas 200 colunas (denominadas f1, f2, ... f200), por exemplo:
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
historicamente, eu não tive que lidar com essa grande quantidade de dados antes, então meu primeiro instinto foi o de que eram necessários índices em cada uma dessas 200 colunas, ou acabaria com grandes varreduras de tabelas etc. Para mim, isso significava que Eu precisava de uma tabela para cada uma das 200 colunas com chave primária, valor e indexar os valores. Então eu fui com isso.
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;
Enchi e indexei todas as 200 tabelas. Deixei a tabela principal intacta com todas as 200 colunas, pois ela é consultada regularmente sobre o ativo e o período e todas as 200 colunas são selecionadas. Eu imaginei que deixar essas colunas na tabela pai (não indexadas) para fins de leitura e, adicionalmente, tê-las indexadas em suas próprias tabelas (para filtragem de junção) seria o melhor desempenho. Eu corri explica sobre a nova forma da 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)
Na verdade, meu resultado desejado foi alcançado, o explicar mostra que as linhas verificadas são muito menores para esta consulta. No entanto, acabei com alguns efeitos colaterais indesejáveis.
1) meu banco de dados passou de 45 Gig para 110 Gig. Não consigo mais manter o banco de dados na RAM. (eu tenho 256Gig de RAM no caminho, no entanto)
2) inserções noturnas de novos dados agora precisam ser feitas 200 vezes em vez de uma vez
3) a manutenção / desfragmentação das novas 200 mesas leva 200 vezes mais tempo do que apenas a 1 mesa. Não pode ser concluído em uma noite.
4) consultas em relação às tabelas f1, etc, não são necessariamente de bom desempenho. por exemplo:
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'
a consulta acima, enquanto o explicar mostra que ela está olhando para <1000 linhas, pode levar mais de 30 segundos para ser concluída. Presumo que isso ocorre porque os índices são muito grandes para caber na memória.
Como essas eram muitas más notícias, procurei mais e encontrei particionamentos. Eu implementei partições na tabela principal, particionadas na data a cada 3 meses. Mensalmente parecia fazer sentido para mim, mas eu li que uma vez que você obtém mais de 120 partições, o desempenho sofre. o particionamento trimestral me deixará abaixo disso pelos próximos 20 anos. cada partição tem um pouco menos de 2 gig. Eu corri para explicar partições e tudo parece estar funcionando corretamente, portanto, independentemente de sentir que o particionamento foi um bom passo, pelo menos para fins de análise / otimização / reparo.
Passei muito tempo com este artigo
http://ftp.nchu.edu.tw/MySQL/tech-resources/articles/testing-partitions-large-db.html
minha tabela atualmente está particionada com a chave primária ainda nela. O artigo menciona que as chaves primárias podem tornar uma tabela particionada mais lenta, mas se você tiver uma máquina capaz de lidar com isso, as chaves primárias na tabela particionada serão mais rápidas. Sabendo que tenho uma grande máquina a caminho (256 G RAM), deixei as teclas ativadas.
então, a meu ver, aqui estão minhas opções
Opção 1
1) remova as 200 tabelas extras e deixe a consulta fazer varreduras de tabela para encontrar os valores de f1, f2 etc. índices não exclusivos podem realmente prejudicar o desempenho em uma tabela particionada corretamente. execute uma explicação antes que o usuário execute a consulta e negue-a se o número de linhas varridas estiver acima de algum limite que eu defino. salvar-me a dor do banco de dados gigante. Heck, tudo estará na memória em breve de qualquer maneira.
sub-pergunta:
soa como se eu tivesse escolhido um esquema de partição apropriado?
opção 2
Particione todas as 200 tabelas usando o mesmo esquema de 3 meses. aproveite as verificações de linha menores e permita que os usuários executem consultas maiores. agora que eles estão particionados, pelo menos, posso gerenciá-los 1 partição por vez para fins de manutenção. Heck, tudo estará na memória em breve de qualquer maneira. Desenvolva uma maneira eficiente de atualizá-los todas as noites.
sub-pergunta:
Você vê uma razão para eu evitar índices de chave primária nessas tabelas f1, f2, f3, f4 ..., sabendo que sempre tenho o assetid e a data na consulta? parece contra-intuitivo para mim, mas não estou acostumado a conjuntos de dados desse tamanho. que encolheria o banco de dados um monte eu assumo
Opção 3
Solte as colunas f1, f2, f3 na tabela principal para recuperar esse espaço. 200 junções se eu precisar ler 200 recursos, talvez não seja tão lento quanto parece.
Opção 4
Todos vocês têm uma maneira melhor de estruturar isso do que eu pensava até agora.
* OBSERVAÇÃO: em breve adicionarei outros 50 a 100 desses valores duplos a cada item, por isso preciso projetar sabendo que está por vir.
Obrigado por toda e qualquer ajuda
Atualização # 1 - 24/3/2013
Eu segui a ideia sugerida nos comentários que obtive abaixo e criei uma nova tabela com a seguinte configuração:
create table 'features'{
assetid int,
date date,
feature varchar(4),
value double
}
Particionei a mesa em intervalos de 3 meses.
Afastei as 200 tabelas anteriores para que meu banco de dados voltasse para 45 Gig e comecei a preencher essa nova tabela. Um dia e meio depois, ele foi concluído, e meu banco de dados agora está em um 220 Gigs gordinho !
Ele permite a possibilidade de remover esses 200 valores da tabela principal, pois eu posso obtê-los de uma junção, mas isso realmente me devolveria apenas 25 Gigs, talvez
Pedi para que ele criasse uma chave primária para identificação de ativos, data, recurso e um índice de valor, e após 9 horas de execução, ele realmente não havia afetado e parecia congelar, então acabei com essa parte.
Eu reconstruí algumas partições, mas elas não pareciam recuperar muito / nenhum espaço.
Portanto, essa solução parece que provavelmente não será o ideal. As linhas ocupam significativamente mais espaço do que as colunas, será que é por isso que essa solução ocupou muito mais espaço?
Me deparei com este artigo:
http://www.chrismoos.com/2010/01/31/mysql-partitioning-tables-with-millions-of-rows
isso me deu uma ideia. Diz:
No começo, pensei sobre o particionamento RANGE por data e, embora esteja usando a data em minhas consultas, é muito comum uma consulta ter um intervalo de datas muito grande, o que significa que poderia facilmente abranger todas as partições.
Agora também estou particionando o intervalo por data, mas também permitirei pesquisas por um grande período, o que diminuirá a eficácia do meu particionamento. Sempre terei um intervalo de datas ao pesquisar, mas também terei sempre uma lista de ativos. Talvez minha solução deva ser particionada por assetid e data, onde identifico os intervalos de assetid normalmente pesquisados (que podem ser encontrados, existem listas padrão, S&P 500, Russell 2000, etc.). Dessa forma, quase nunca examinaria todo o conjunto de dados.
Por outro lado, eu sou o principal responsável pelo ativo e pela data, de modo que talvez isso não ajude muito.
Mais pensamentos / comentários serão apreciados.
(value_name varchar(20), value double)
seria capaz de armazenar tudo (value_name
sendof1
,f2
...)