Construindo uma tabela MySQL de 1.000M de linhas

18

Esta pergunta foi publicada novamente no Stack Overflow com base em uma sugestão nos comentários, desculpas pela duplicação.

Questões

Pergunta 1: à medida que o tamanho da tabela do banco de dados aumenta, como posso ajustar o MySQL para aumentar a velocidade da chamada LOAD DATA INFILE?

Pergunta 2: usaria um cluster de computadores para carregar arquivos csv diferentes, melhorar o desempenho ou eliminá-lo? (esta é minha tarefa de marcação de benchmarking para amanhã usando os dados de carregamento e as inserções em massa)

Objetivo

Estamos testando diferentes combinações de detectores de recursos e parâmetros de agrupamento para pesquisa de imagens, como resultado, precisamos poder construir e grandes bancos de dados em tempo hábil.

Informações da máquina

A máquina possui 256 GB de RAM e existem outras 2 máquinas disponíveis com a mesma quantidade de RAM, se houver uma maneira de melhorar o tempo de criação distribuindo o banco de dados?

Esquema de tabela

o esquema da tabela se parece

+---------------+------------------+------+-----+---------+----------------+
| Field         | Type             | Null | Key | Default | Extra          |
+---------------+------------------+------+-----+---------+----------------+
| match_index   | int(10) unsigned | NO   | PRI | NULL    |                |
| cluster_index | int(10) unsigned | NO   | PRI | NULL    |                |
| id            | int(11)          | NO   | PRI | NULL    | auto_increment |
| tfidf         | float            | NO   |     | 0       |                |
+---------------+------------------+------+-----+---------+----------------+

criado com

CREATE TABLE test 
(
  match_index INT UNSIGNED NOT NULL,
  cluster_index INT UNSIGNED NOT NULL, 
  id INT NOT NULL AUTO_INCREMENT,
  tfidf FLOAT NOT NULL DEFAULT 0,
  UNIQUE KEY (id),
  PRIMARY KEY(cluster_index,match_index,id)
)engine=innodb;

Benchmarking até agora

O primeiro passo foi comparar as inserções em massa e o carregamento de um arquivo binário em uma tabela vazia.

It took:  0:09:12.394571  to do  4,000  inserts with 5,000 rows per insert
It took: 0:03:11.368320 seconds to load 20,000,000 rows from a csv file

Dada a diferença de desempenho, eu carreguei os dados de um arquivo csv binário, primeiro carreguei arquivos binários contendo 100K, 1M, 20M, 200M linhas usando a chamada abaixo.

LOAD DATA INFILE '/mnt/tests/data.csv' INTO TABLE test;

Eu matei o carregamento do arquivo binário de 200 milhões de linhas (~ arquivo de 3 GB csv) após 2 horas.

Então, eu executei um script para criar a tabela e insira diferentes números de linhas de um arquivo binário e depois solte a tabela, veja o gráfico abaixo.

insira a descrição da imagem aqui

Demorou cerca de 7 segundos para inserir 1 milhão de linhas do arquivo binário. Em seguida, decidi comparar a inserção de 1 milhão de linhas por vez para verificar se haveria um gargalo em um tamanho de banco de dados específico. Depois que o banco de dados atinge aproximadamente 59 milhões de linhas, o tempo médio de inserção cai para aproximadamente 5.000 / segundo

insira a descrição da imagem aqui

A configuração do key_buffer_size global = 4294967296 melhorou ligeiramente as velocidades para inserir arquivos binários menores. O gráfico abaixo mostra as velocidades para diferentes números de linhas

insira a descrição da imagem aqui

No entanto, para inserir linhas de 1 milhão, não melhorou o desempenho.

linhas: 1.000.000 de tempo: 0: 04: 13.761428 inserções / s: 3.940

vs para um banco de dados vazio

linhas: 1.000.000 de tempo: 0: 00: 6.339295 inserções / s: 315.492

Atualizar

Executando o Carregamento de Dados Utilizando a Sequência a seguir vs Apenas Utilizando o Comando Load Data

SET autocommit=0;
SET foreign_key_checks=0;
SET unique_checks=0;
LOAD DATA INFILE '/mnt/imagesearch/tests/eggs.csv' INTO TABLE test_ClusterMatches;
SET foreign_key_checks=1;
SET unique_checks=1;
COMMIT;
insira a descrição da imagem aqui

Portanto, isso parece bastante promissor em termos do tamanho do banco de dados que está sendo gerado, mas as outras configurações não parecem afetar o desempenho da chamada de infile de carregamento de dados.

Tentei carregar vários arquivos de máquinas diferentes, mas o comando load data infile bloqueia a tabela, devido ao tamanho grande dos arquivos, causando o tempo limite das outras máquinas

ERROR 1205 (HY000) at line 1: Lock wait timeout exceeded; try restarting transaction

Aumentando o número de linhas no arquivo binário

rows:  10,000,000  seconds rows:  0:01:36.545094  inserts/sec:  103578.541236
rows:  20,000,000  seconds rows:  0:03:14.230782  inserts/sec:  102970.29026
rows:  30,000,000  seconds rows:  0:05:07.792266  inserts/sec:  97468.3359978
rows:  40,000,000  seconds rows:  0:06:53.465898  inserts/sec:  96743.1659866
rows:  50,000,000  seconds rows:  0:08:48.721011  inserts/sec:  94567.8324859
rows:  60,000,000  seconds rows:  0:10:32.888930  inserts/sec:  94803.3646283

Solução: Pré-computando o ID fora do MySQL em vez de usar o incremento automático

Construindo a mesa com

CREATE TABLE test (
  match_index INT UNSIGNED NOT NULL,
  cluster_index INT UNSIGNED NOT NULL, 
  id INT NOT NULL ,
  tfidf FLOAT NOT NULL DEFAULT 0,
  PRIMARY KEY(cluster_index,match_index,id)
)engine=innodb;

com o SQL

LOAD DATA INFILE '/mnt/tests/data.csv' INTO TABLE test FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n';"

insira a descrição da imagem aqui

Conseguir que o script pré-calcule os índices parece ter removido o impacto no desempenho à medida que o banco de dados aumenta de tamanho.

Atualização 2 - usando tabelas de memória

Aproximadamente três vezes mais rápido, sem levar em conta o custo de mover uma tabela na memória para uma tabela baseada em disco.

rows:  0  seconds rows:  0:00:26.661321  inserts/sec:  375075.18851
rows:  10000000  time:  0:00:32.765095  inserts/sec:  305202.83857
rows:  20000000  time:  0:00:38.937946  inserts/sec:  256818.888187
rows:  30000000  time:  0:00:35.170084  inserts/sec:  284332.559456
rows:  40000000  time:  0:00:33.371274  inserts/sec:  299658.922222
rows:  50000000  time:  0:00:39.396904  inserts/sec:  253827.051994
rows:  60000000  time:  0:00:37.719409  inserts/sec:  265115.500617
rows:  70000000  time:  0:00:32.993904  inserts/sec:  303086.291334
rows:  80000000  time:  0:00:33.818471  inserts/sec:  295696.396209
rows:  90000000  time:  0:00:33.534934  inserts/sec:  298196.501594

carregar os dados em uma tabela baseada em memória e copiá-los para uma tabela baseada em disco em pedaços teve uma sobrecarga de 10 min 59,71 segundos para copiar 107.356.741 linhas com a consulta

insert into test Select * from test2;

o que leva aproximadamente 15 minutos para carregar 100 milhões de linhas, o que é aproximadamente o mesmo que inseri-lo diretamente em uma tabela baseada em disco.

Ben
fonte
11
Eu acho que mudar a chave primária para apenas iddeveria ser mais rápido. (Embora eu acho que você não está olhando para isso)
davideg
Olá David, obrigado pelo comentário, infelizmente, sem a chave, as consultas que precisamos fazer não são rápidas o suficiente (a lógica por trás da seleção da chave primária é descrita neste post stackoverflow.com/questions/4282526/mysql-group-by- otimização )
Ben
11
Isso é apenas para teste? Você pode procurar no mecanismo MySQL MEMORY: dev.mysql.com/doc/refman/5.0/en/memory-storage-engine.html Se você planeja implantar isso como uma arquitetura, estou curioso para saber como você planeja recuperar de falhas, parece algo que seria melhor tratado pelo MapReduce / Hadoop.
polinomial
Oi polinomial, obrigado pela dica, no momento estamos apenas testando diferentes detectores de características em diferentes escalas, uma vez que o banco de dados é gerada não vai mudar muito (na especificação atual de qualquer maneira)
Ben

Respostas:

4

Boa pergunta - bem explicada.

como posso ajustar o MySQL para aumentar a velocidade da chamada LOAD DATA INFILE?

Você já tem uma configuração alta (ish) para o buffer de chave - mas é suficiente? Estou assumindo que esta é uma instalação de 64 bits (caso contrário, a primeira coisa que você precisa fazer é atualizar) e não está executando no MSNT. Dê uma olhada na saída do mysqltuner.pl depois de executar alguns testes.

Para usar o cache da melhor maneira possível, você pode encontrar benefícios ao agrupar / pré-classificar os dados de entrada (as versões mais recentes do comando 'sort' têm muita funcionalidade para classificar grandes conjuntos de dados). Além disso, se você gerar os números de identificação fora do MySQL, poderá ser mais eficiente.

usaria um cluster de computadores para carregar arquivos csv diferentes

Supondo (novamente) que você deseja que a saída definida se comporte como uma única tabela, os únicos benefícios que você terá são distribuir o trabalho de classificação e geração de IDs - para os quais você não precisa de mais bancos de dados. OTOH usando um cluster de banco de dados, você terá problemas com a contenção (que não devem ser vistos como problemas de desempenho).

Se você pode fragmentar os dados e manipular os conjuntos de dados resultantes independentemente, então sim, obterá benefícios de desempenho - mas isso não nega a necessidade de ajustar cada nó.

Verifique se você tem pelo menos 4 Gb para o tamanho sort_buffer_size.

Além disso, o fator limitante do desempenho é a E / S do disco. Há várias maneiras de resolver isso - mas você provavelmente deve considerar um conjunto espelhado de conjuntos de dados distribuídos em SSDs para obter o desempenho ideal.

symcbean
fonte
1
  • Considere o seu fator limitante. É quase certamente processamento de CPU de thread único.
  • Você já determinou que load data...é mais rápido que inserir, então use isso.
  • Você já determinou que arquivos muito grandes (por número de linha) atrasam bastante as coisas; você quer dividi-los em pedaços.
  • Usando chaves primárias não sobrepostas, enfileire pelo menos N * conjuntos de CPU, usando não mais de um milhão de linhas ... provavelmente menos (benchmark).
  • Use blocos seqüenciais de chaves primárias em cada arquivo.

Se você quer ser realmente eficiente, pode criar um programa multithread para alimentar um único arquivo em uma coleção de pipes nomeados e gerenciar as instâncias de inserção.

Em resumo, você não ajusta o MySQL tanto quanto ajusta sua carga de trabalho ao MySQL.

Jeff Ferland
fonte
-1

Não me lembro exatamente da sintaxe, mas se for inno db, você pode desativar a verificação de chave estrangeira.

Além disso, você pode criar o índice após a importação, pode ser realmente um ganho de desempenho.

Julien Duponchelle
fonte
Adiar a reconstrução do índice apenas melhora o desempenho onde o número de linhas que já estão na tabela é significativamente menor que o número de linhas que você está adicionando.
Symcbean #