design eficaz de tabela / índice mysql para 35 milhões de linhas + tabela, com mais de 200 colunas correspondentes (duplo), qualquer combinação das quais pode ser consultada

17

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.

dyeryn
fonte
2
Não vejo por que você precisa de 200 mesas. Uma única tabela com (value_name varchar(20), value double)seria capaz de armazenar tudo ( value_namesendo f1, f2...)
a_horse_with_no_name
obrigado. a razão pela qual eu os coloquei individualmente foi obter o limite de 50 índices em uma tabela. Eu tinha pensado em colocá-los em 5 tabelas, 40 valores cada, mas estou inserindo 17000 registros por dia para cada um deles e não sabia como seria o desempenho da inserção em uma tabela com 40 índices. observe que cada combinação de data do ativo obtém seus próprios valores de f1, f2 .... Você está sugerindo uma única tabela com (assetid, date, value_name, value), com chave primária assetid, date, talvez indexada em (value_name, value)? essa tabela teria 35 mil * 200 = 7 bilhões de linhas, mas talvez uma partição bem funcionasse?
dyeryn
pós atualizado com minhas experiências que tentam este método
dyeryn
eu tenho a solução final em desenvolvimento, atualizarei quando terminar. é essencialmente a solução de tabela única proposta aqui com particionamento específico e sharding lógico.
dyeryn
Um mecanismo de armazenamento diferente pode ajudar? Em vez de InnoDb, talvez tente o InfiniDB? Dados em colunas, padrões de acesso se parecem com grandes atualizações em lote, leituras baseadas em intervalos e manutenção mínima da tabela.
confuso

Respostas:

1

coincidentemente, também estou pesquisando um dos serviços de suporte ao cliente, onde projetamos a estrutura de pares de valores-chave para flexibilidade e atualmente a tabela tem mais de 1.5B linhas e o ETL é muito lento. Bem, existem muitas outras coisas no meu caso, mas você já pensou sobre esse design. você terá uma linha com o valor presente de todas as 200 colunas; essa linha será convertida em 200 linhas no design do par Key-Value. você obterá vantagem de espaço com esse design, dependendo de um determinado AssetID e Date, quantas linhas atualmente existem todos os valores de 200 a f200 a 200 se você disser que até 30% das colunas têm valor NULL, isso significa economia de espaço. porque no design do par de valores-chave, se o ID de valor NULL dessa linha não precisar estar na tabela. mas no design da estrutura de colunas existente, mesmo NULL ocupa espaço. (Não tenho 100% de certeza, mas se você tiver mais de 30 colunas NULL na tabela, NULL terá 4 bytes). se você vir esse design e presumir que todas as linhas de 35 milhões têm valores em todas as 200 colunas, o banco de dados atual se tornará 200 * 35M = 700 milhões de linhas na tabela imediatamente. mas não será muito alto no espaço de tabela o que você tinha com todas as colunas em uma única tabela, pois estamos apenas Transpondo as colunas para a linha. nesta operação de transposição, na verdade, não teremos linhas em que os valores sejam NULL. para que você possa executar a consulta nessa tabela e ver quantos nulos existem e estimar o tamanho da tabela de destino antes de implementá-la. mas não será muito alto no espaço de tabela o que você tinha com todas as colunas em uma única tabela, pois estamos apenas Transpondo as colunas para a linha. nesta operação de transposição, na verdade, não teremos linhas em que os valores sejam NULL. para que você possa executar a consulta nessa tabela e ver quantos nulos existem e estimar o tamanho da tabela de destino antes de implementá-la. mas não será muito alto no espaço de tabela o que você tinha com todas as colunas em uma única tabela, pois estamos apenas Transpondo as colunas para a linha. nesta operação de transposição, na verdade, não teremos linhas em que os valores sejam NULL. para que você possa executar a consulta nessa tabela e ver quantos nulos existem e estimar o tamanho da tabela de destino antes de implementá-la.

segunda vantagem é o desempenho da leitura. como você mencionou, a nova maneira de consultar os dados é qualquer combinação desta coluna f1 a f200 na cláusula where. com o design do par de valores-chave, f1 a f200 estão presentes em uma coluna, digamos "FildName" e seus valores estão presentes na segunda coluna, digamos "FieldValue". você pode ter o índice CLUSTERED nas duas colunas. sua consulta será UNION daqueles Selects.

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

UNIÃO

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

etc .....

Vou lhe dar alguns números de desempenho do servidor prod real. temos 75 colunas de preços para cada TICKER de segurança.

Anup Shah
fonte
1

Ao lidar com esse tipo de dados em que você precisa inserir muitas linhas e também precisa de um bom desempenho de consulta analítica (suponho que esse seja o caso aqui), você pode achar que um RDBMS colunar é um bom ajuste . Dê uma olhada no Infobright CE e InfiniDB CE (ambos os mecanismos de armazenamento colunares conectados ao MySQL) e ao Vertica CE também (mais semelhantes ao PostgreSQL em vez do MySQL) ... todas essas edições da comunidade são gratuitas (embora o Vertica não seja código-fonte aberto, ele pode ser escalado para 3 nós e 1 TB de dados gratuitamente). Os RDBMSs em colunas geralmente oferecem tempos de resposta de "grande consulta" 10 a 100 vezes melhores que os baseados em linhas e tempos de carregamento 5-50 vezes melhores. Você precisa usá-los corretamente ou eles fedem (não faça operações de linha única ... faça todas as operações em uma abordagem em massa), mas, quando usados ​​corretamente, eles realmente são ótimos. ;-)

Dave Sisk HTH

Dave Sisk
fonte
1
Temos quase um bilhão de linhas de dados do tipo clickstream (não muito diferentes dos dados do stock ticker) em uma instalação Vertica de 3 nós ... podemos carregar dados de um dia inteiro em cerca de 15 segundos e obtemos tempos de resposta de consulta em a faixa de 500 milissegundos. No seu caso, certamente parece que vale a pena dar uma olhada.
Dave Sisk
Eu posso garantir o mesmo. Na minha última empresa, tínhamos um cluster Vertica de 8 nós com aproximadamente o mesmo número de linhas e consultas agregadas simples ao longo de todo o conjunto retornadas em 1-3 segundos (em média). Também custou cerca de 1/4 do nosso cluster Greenplum anterior.
BMA