Desempenho de importação do InnoDB

10

Estou com dificuldades para importar em massa uma tabela InnoDB bastante grande, composta por aproximadamente 10 milhões de linhas (ou 7 GB) (que para mim é a maior tabela com a qual trabalhei até agora).

Eu fiz algumas pesquisas sobre como melhorar a velocidade de importação do Inno e, no momento, minha configuração fica assim:

/etc/mysql/my.cnf/
[...]
innodb_buffer_pool_size = 7446915072 # ~90% of memory
innodb_read_io_threads = 64
innodb_write_io_threads = 64
innodb_io_capacity = 5000
innodb_thread_concurrency=0
innodb_doublewrite = 0
innodb_log_file_size = 1G
log-bin = ""
innodb_autoinc_lock_mode = 2
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit=2
innodb_buffer_pool_instances=8


import is done via bash script, here is the mysql code:
SET GLOBAL sync_binlog = 1;
SET sql_log_bin = 0;
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET AUTOCOMMIT = 0;
SET SESSION tx_isolation='READ-UNCOMMITTED';
LOAD DATA LOCAL INFILE '$filepath' INTO TABLE monster
COMMIT;

Os dados são fornecidos em um CSVarquivo.
Atualmente, testei minhas configurações com 'dumps de teste' menores, com 2 milhões, 3 milhões de ... linhas cada e uso time import_script.shpara comparar o desempenho.

A desvantagem é que eu só tenho um tempo de execução geral, portanto, tenho que esperar a importação completa terminar para obter um resultado.

Meus resultados até agora:

  • 10 000 linhas: <1 segundo
  • 100 000 linhas: 10 segundos
  • 300 000 linhas: 40 segundos
  • 2 milhões de linhas: 18 minutos
  • 3 milhões de linhas: 26 minutos
  • 4 milhões de linhas: (cancelada após 2 horas)

Parece que não existe uma solução para o 'livro de receitas' e é preciso descobrir a combinação ideal de configurações por conta própria.
Além de sugestões sobre o que mudar na minha configuração, eu também gostaria muito de obter mais informações sobre como eu poderia avaliar melhor o processo de importação / obter mais informações sobre o que está acontecendo e onde pode estar o gargalo.
Tentei ler a documentação para as configurações que estou alterando, mas, novamente, não conheço nenhum efeito colateral e se posso diminuir o desempenho com um valor mal escolhido.

No momento, gostaria de tentar uma sugestão do chat para usar MyISAMdurante a importação e alterar o mecanismo da tabela posteriormente.
Gostaria de tentar isso, mas, no momento, minha DROP TABLEconsulta também leva horas para terminar. (Que parece outro indicador, minha configuração é menor que a ideal).

Informações adicionais:
A máquina que estou usando no momento possui 8 GB de RAM e um disco rígido híbrido de estado sólido com 5400 RPM.
Embora também pretendamos remover dados obsoletos da tabela em questão, ainda preciso de uma importação um pouco rápida para
a) testar automatic data cleanup featuredurante o desenvolvimento
eb) no caso de nosso servidor travar, gostaríamos de usar nosso segundo servidor como um substituto (que precisa ser dados atualizados, a última importação levou mais de 24 horas)

mysql> SHOW CREATE TABLE monster\G
*************************** 1. row ***************************
       Table: monster
Create Table: CREATE TABLE `monster` (
  `monster_id` int(11) NOT NULL AUTO_INCREMENT,
  `ext_monster_id` int(11) NOT NULL DEFAULT '0',
  `some_id` int(11) NOT NULL DEFAULT '0',
  `email` varchar(250) NOT NULL,
  `name` varchar(100) NOT NULL,
  `address` varchar(100) NOT NULL,
  `postcode` varchar(20) NOT NULL,
  `city` varchar(100) NOT NULL,
  `country` int(11) NOT NULL DEFAULT '0',
  `address_hash` varchar(250) NOT NULL,
  `lon` float(10,6) NOT NULL,
  `lat` float(10,6) NOT NULL,
  `ip_address` varchar(40) NOT NULL,
  `cookie` int(11) NOT NULL DEFAULT '0',
  `party_id` int(11) NOT NULL,
  `status` int(11) NOT NULL DEFAULT '2',
  `creation_date` datetime NOT NULL,
  `someflag` tinyint(1) NOT NULL DEFAULT '0',
  `someflag2` tinyint(4) NOT NULL,
  `upload_id` int(11) NOT NULL DEFAULT '0',
  `news1` tinyint(4) NOT NULL DEFAULT '0',
  `news2` tinyint(4) NOT NULL,
  `someother_id` int(11) NOT NULL DEFAULT '0',
  `note` varchar(2500) NOT NULL,
  `referer` text NOT NULL,
  `subscription` int(11) DEFAULT '0',
  `hash` varchar(32) DEFAULT NULL,
  `thumbs1` int(11) NOT NULL DEFAULT '0',
  `thumbs2` int(11) NOT NULL DEFAULT '0',
  `thumbs3` int(11) NOT NULL DEFAULT '0',
  `neighbours` tinyint(4) NOT NULL DEFAULT '0',
  `relevance` int(11) NOT NULL,
  PRIMARY KEY (`monster_id`),
  KEY `party_id` (`party_id`),
  KEY `creation_date` (`creation_date`),
  KEY `email` (`email`(4)),
  KEY `hash` (`hash`(8)),
  KEY `address_hash` (`address_hash`(8)),
  KEY `thumbs3` (`thumbs3`),
  KEY `ext_monster_id` (`ext_monster_id`),
  KEY `status` (`status`),
  KEY `note` (`note`(4)),
  KEY `postcode` (`postcode`),
  KEY `some_id` (`some_id`),
  KEY `cookie` (`cookie`),
  KEY `party_id_2` (`party_id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=13763891 DEFAULT CHARSET=utf8
nuala
fonte
2
Você tentou com menos importações grandes, como 10 mil ou 100 mil linhas?
ypercubeᵀᴹ
11
Corra SHOW CREATE TABLE yourtable\Gpara nos mostrar a estrutura da tabela dessa tabela de 10 milhões de linhas.
RolandoMySQLDBA
@RolandoMySQLDBA então eu fiz (com nomes de campo não revelados)
Nuala
Desabilitando o buffer de gravação dupla ( innodb_doublewrite = 0), sua instalação do MySQL não é protegida contra falhas: se você tiver uma falha de energia (não uma falha no MySQL), seus dados poderão ser corrompidos silenciosamente.
Jfg956

Respostas:

13

Primeiro, você precisa saber o que está fazendo com o InnoDB ao inserir milhões de linhas em uma tabela do InnoDB. Vamos dar uma olhada na arquitetura do InnoDB.

Arquitetura InnoDB

No canto superior esquerdo, há uma ilustração do InnoDB Buffer Pool. Observe que há uma seção dedicada ao buffer de inserção. O que isso faz? É recomendado migrar alterações para índices secundários do Buffer Pool para o Insert Buffer dentro do espaço de tabela do sistema (também conhecido como ibdata1). Por padrão, innodb_change_buffer_max_size está definido como 25. Isso significa que até 25% do Buffer Pool pode ser usado para processar índices secundários.

No seu caso, você tem 6,935 GB para o buffer pool InnoDB. Um máximo de 1,734 GB será usado para processar seus índices secundários.

Agora, olhe para a sua mesa. Você tem 13 índices secundários. Cada linha que você processa deve gerar uma entrada de índice secundária, associá-la à chave primária da linha e enviá-las como um par do Insert Buffer no Buffer Pool para o Insert Buffer no ibdata1. Isso acontece 13 vezes com cada linha. Multiplique isso por 10 milhões e você quase sentirá um gargalo chegando.

Não esqueça que importar 10 milhões de linhas em uma única transação acumulará tudo em um segmento de reversão e preencherá o espaço UNDO no ibdata1.

SUGESTÕES

SUGESTÃO # 1

Minha primeira sugestão para importar essa tabela bastante grande seria

  • Solte todos os índices não exclusivos
  • Importar os dados
  • Crie todos os índices não exclusivos

SUGESTÃO # 2

Livre-se de índices duplicados. No seu caso, você tem

KEY `party_id` (`party_id`),
KEY `party_id_2` (`party_id`,`status`)

Ambos os índices começam com party_id, você pode aumentar o processamento do índice secundário em pelo menos 7,6%, livrando um índice de 13. Você precisa executar eventualmente

ALTER TABLE monster DROP INDEX party_id;

SUGESTÃO # 3

Livre-se dos índices que você não usa. Examine o código do seu aplicativo e veja se suas consultas usam todos os índices. Você pode examinar o uso do pt-index para permitir sugerir quais índices não estão sendo usados.

SUGESTÃO # 4

Você deve aumentar o innodb_log_buffer_size para 64M, pois o padrão é 8M. Um buffer de log maior pode aumentar o desempenho de E / S de gravação do InnoDB.

EPÍLOGO

Colocando as duas primeiras sugestões, faça o seguinte:

  • Solte os 13 índices não exclusivos
  • Importar os dados
  • Crie todos os índices não exclusivos, exceto o party_idíndice

Talvez o seguinte possa ajudar

CREATE TABLE monster_new LIKE monster;
ALTER TABLE monster_new
  DROP INDEX `party_id`,
  DROP INDEX `creation_date`,
  DROP INDEX `email`,
  DROP INDEX `hash`,
  DROP INDEX `address_hash`,
  DROP INDEX `thumbs3`,
  DROP INDEX `ext_monster_id`,
  DROP INDEX `status`,
  DROP INDEX `note`,
  DROP INDEX `postcode`,
  DROP INDEX `some_id`,
  DROP INDEX `cookie`,
  DROP INDEX `party_id_2`;
ALTER TABLE monster RENAME monster_old;
ALTER TABLE monster_new RENAME monster;

Importe os dados para monster. Então, execute isso

ALTER TABLE monster
  ADD INDEX `creation_date`,
  ADD INDEX `email` (`email`(4)),
  ADD INDEX `hash` (`hash`(8)),
  ADD INDEX `address_hash` (`address_hash`(8)),
  ADD INDEX `thumbs3` (`thumbs3`),
  ADD INDEX `ext_monster_id` (`ext_monster_id`),
  ADD INDEX `status` (`status`),
  ADD INDEX `note` (`note`(4)),
  ADD INDEX `postcode` (`postcode`),
  ADD INDEX `some_id` (`some_id`),
  ADD INDEX `cookie` (`cookie`),
  ADD INDEX `party_id_2` (`party_id`,`status`);

DE UMA CHANCE !!!

ALTERNATIVO

Você pode criar uma tabela chamada monster_csvcomo uma tabela MyISAM sem índices e fazer o seguinte:

CREATE TABLE monster_csv ENGINE=MyISAM AS SELECT * FROM monster WHERE 1=2;
ALTER TABLE monster RENAME monster_old;
CREATE TABLE monster LIKE monster_old;
ALTER TABLE monster DROP INDEX `party_id`;

Importe seus dados para monster_csv. Em seguida, use o mysqldump para criar outra importação

mysqldump -t -uroot -p mydb monster_csv | sed 's/monster_csv/monster/g' > data.sql

O arquivo mysqldump data.sqlestenderá os comandos INSERT, importando 10.000 a 20.000 linhas por vez.

Agora, basta carregar o mysqldump

mysql -uroot -p mydb < data.sql

Por fim, livre-se da tabela MyISAM

DROP TABLE monster_csv;
RolandoMySQLDBA
fonte
Eu nem sabia de todas essas chaves (não é o meu design), mas sua explicação parece muito convincente. Por hoje é tarde para começar outra tentativa, mas vejo alguns bons conselhos sobre o que experimentar amanhã. Manteremos você informado! <3
nuala 30/07
11
Consegui importar o banco de dados completo (não apenas a monstertabela) em menos de 20 minutos quando não havia chaves nas tabelas do InnoDB. A adição de chaves levou aprox. outros 20 min. Eu diria que isso praticamente resolve meu problema neste caso. Muito obrigado!
Nuala
8

Eu queria escrever um comentário (como essa não é uma resposta definitiva), mas ficou muito tempo:

Vou dar-lhe vários conselhos amplos e podemos entrar em detalhes para cada um, se você quiser:

  • Reduza a durabilidade (você já fez algumas delas). As versões mais recentes permitem ainda mais. Você pode desabilitar o buffer de gravação dupla, pois a corrupção não é um problema para importações.
  • Aumente o buffer em: Aumente o tamanho do log de transações e aumente o tamanho do buffer pool disponível. Monitore o uso e os pontos de verificação do arquivo de log de transações. Não tenha medo de logs enormes para uma importação.
  • Evite transações enormes - sua reversão ficará cheia de dados desnecessários. Este é provavelmente o seu maior problema.
  • O SQL será um gargalo, evite a sobrecarga do SQL (handlersocket, memcached) e / ou carregue-o em simultâneo com vários threads ao mesmo tempo. A simultaneidade deve atingir um ponto ideal, nem muito, nem muito pouco.
  • Carregar dados na fragmentação da ordem da chave primária pode ser um problema
  • Teste a compactação do InnoDB se o IO for seu gargalo e a CPU e a memória não o tornarem mais lento
  • Tente criar suas chaves secundárias posteriormente (mais rápido em alguns casos), não carregue dados indexados - DISABLE KEYS não afeta o InnoDB . Caso contrário, monitore seu buffer de inserção (talvez ultrapassando metade do seu buffer pool).
  • Altere ou desative o algoritmo de soma de verificação - provavelmente não é o seu problema, mas torna-se um gargalo nos cartões flash de última geração.
  • Último recurso: monitore seu servidor para encontrar seu gargalo atual e tente mitigar (o InnoDB é muito flexível sobre isso).

Lembre-se de que alguns deles não são seguros ou aconselháveis ​​para não importações (operação normal).

jynus
fonte
Muito obrigado! Gosto de experimentar a idéia de Rolando em relação aos índices primeiro, mas acho que esse material de "reversão de transações" ainda será um problema. Você poderia elaborar isso? Eu acho que eu quero desabilitar o máximo desta funcionalidade quanto possível durante a importação e apenas reactivar quando entrar em produção ~ Eu acho ...
Nuala
11
A sugestão de Rolando é o meu ponto # 7. Evitar a sobrecarga de reversão é tão fácil quanto uma combinação de SET SESSION tx_isolation='READ-UNCOMMITTED';(útil apenas se você importar com vários encadeamentos em paralelo) e o @ypercube comenta sobre a inserção em lotes. Você tem um exemplo completo aqui: mysqlperformanceblog.com/2008/07/03/… Verifique se você está aproveitando todos os recursos das versões mais recentes do InnoDB: mysqlperformanceblog.com/2011/01/07/…
jynus
11
Tive a impressão geral de que evitaria importar em mandris menores, mas preferiria uma operação "com tudo incluído", mas vejo que o multi-threading poderia abrir algumas possibilidades. Acho que isso é muito específico para cada caso. No entanto, aceitei a resposta de Rolando, já que esse ajuste (seu nº 7) ajudou a obter importação total em <1 hora, mas sua lista definitivamente está longe de ser inútil e acho que a utilizarei como referência logo que a taxa de nosso banco de dados estiver aumentando. me assusta :)
nuala
Eu concordo com @yoshi. Sua resposta é mais abrangente em termos de solução de problemas e melhorias de desempenho. +1
RolandoMySQLDBA
3

A maioria das boas dicas foi dada até agora, mas sem muitas explicações para as melhores. Vou dar mais detalhes.

Primeiro, atrasar a criação do índice é bom, com detalhes suficientes em outras respostas. Eu não voltarei a isso.

Um arquivo de log InnoDB maior ajudará bastante (se você estiver usando o MySQL 5.6, pois não é possível aumentá-lo no MySQL 5.5). Você está inserindo 7 GB de dados, eu recomendaria um tamanho total de log de pelo menos 8 GB (mantenha innodb_log_files_in_groupno padrão (2) e bump innodb_log_file_sizeem 4 GB). Esses 8 GB não são precisos: devem ter pelo menos o tamanho da importação no log do REDO e provavelmente dobrar ou quadruplicar esse tamanho. O raciocínio por trás do tamanho do log do InnoDB aumenta que, quando o log ficar quase cheio, o InnoDB começará a liberar agressivamente seu buffer pool em disco para evitar o log de preenchimento (quando o log estiver cheio, o InnoDB não poderá gravar nenhum banco de dados até que algum páginas do buffer pool são gravadas no disco).

Um arquivo de log InnoDB maior ajudará você, mas você também deve inserir na ordem da chave primária (classifique seu arquivo antes de inseri-lo). Se você inserir na ordem da chave primária, o InnoDB preencherá uma página, depois outra e assim por diante. Se você não inserir na ordem da chave primária, sua próxima inserção poderá acabar em uma página cheia e ocorrerá uma "divisão de página". Essa divisão de página será cara para o InnoDB e retardará sua importação.

Você já tem um buffer pool tão grande quanto sua RAM permite e, se sua tabela não couber nele, não há muito o que fazer, exceto comprar mais RAM. Porém, se a tabela se encaixar no buffer pool, mas for maior que 75% do buffer pool, tente aumentar innodb_max_dirty_pages_pctpara 85 ou 95 durante a importação (o valor padrão é 75). Esse parâmetro de configuração informa ao InnoDB para iniciar a limpeza agressiva do buffer pool quando a porcentagem de páginas sujas atingir esse limite. Ao aumentar esse parâmetro (e se você tiver sorte com o tamanho dos dados), poderá evitar E / S agressivas durante a importação e atrasar essas E / S para mais tarde.

Talvez (e isso seja um palpite) importar seus dados em muitas transações pequenas o ajude. Não sei exatamente como o log do REDO é criado, mas se ele estiver armazenado em buffer na RAM (e no disco quando seria necessária muita RAM) enquanto a transação estiver progredindo, você poderá acabar com E / S desnecessárias. Você pode tentar o seguinte: assim que seu arquivo for classificado, divida-o em vários blocos (tente com 16 MB e outros tamanhos) e importe-os um por um. Isso também permitiria controlar o andamento da sua importação. Se você não deseja que seus dados fiquem parcialmente visíveis para outro leitor enquanto importa, você pode importar usando um nome de tabela diferente, criar os índices posteriormente e renomear a tabela.

Sobre o seu disco híbrido SSD / 5400RPM, eu não sei sobre eles e como otimizar isso. O 5400RPM parece lento para um banco de dados, mas talvez o SSD esteja evitando isso. Talvez você esteja preenchendo a parte SSD do seu disco com gravações sequenciais no log do REDO e o SSD está prejudicando o desempenho. Eu não sei.

Uma dica ruim que você não deve tentar (ou tenha cuidado) é a seguinte: não use multi-thread: será muito difícil otimizar para evitar divisões de página no InnoDB. Se você deseja usar multithread, insira em tabelas diferentes (ou em diferentes partições da mesma tabela).

Se você está pensando em multithread, talvez tenha um computador com soquete múltiplo (NUMA). Nesse caso, evite o problema de insanidade de troca do MySQL .

Se você estiver usando o MySQL 5.5, atualize para o MySQL 5.6: ele tem a opção de aumentar o tamanho do log REDO e possui melhores algoritmos de liberação do buffer pool.

Boa sorte com sua importação.

jfg956
fonte