Performances d'importation InnoDB


10

J'ai du mal à importer en masse une assez grande table InnoDB composée d'environ 10 millions de lignes (ou 7 Go) (qui est pour moi la plus grande table avec laquelle j'ai travaillé jusqu'à présent).

J'ai fait des recherches pour améliorer la vitesse d'importation d'Inno et pour l'instant ma configuration ressemble à ceci:

/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;

Les données sont fournies dans un CSVfichier.
Actuellement, je teste mes paramètres avec de plus petits «vidages de test» avec 2 millions, 3 millions,… lignes chacun et j'utilise time import_script.shpour comparer les performances.

L'inconvénient est que je ne dispose que d'un temps de fonctionnement global, je dois donc attendre la fin de l'importation complète pour obtenir un résultat.

Mes résultats jusqu'à présent:

  • 10000 lignes: <1 seconde
  • 100 000 lignes: 10 secondes
  • 300 000 lignes: 40 secondes
  • 2 millions de lignes: 18 minutes
  • 3 millions de lignes: 26 minutes
  • 4 millions de lignes: (annulé après 2 heures)

Il semble qu'il n'y ait pas de solution de «livre de cuisine» et il faut trouver par lui-même la combinaison optimale de paramètres.
Outre des suggestions sur ce qu'il faut changer dans ma configuration, j'apprécierais également plus d'informations sur la manière de mieux évaluer le processus d'importation / d'avoir plus d'informations sur ce qui se passe et où se trouve le goulot d'étranglement.
J'ai essayé de lire la documentation des paramètres que je modifie, mais là encore je ne suis pas au courant d'effets secondaires et si je pouvais même diminuer les performances avec une valeur mal choisie.

Pour le moment, je voudrais essayer une suggestion de chat à utiliser MyISAMlors de l'importation et changer le moteur de table par la suite.
J'aimerais essayer ceci mais pour le moment ma DROP TABLErequête prend également des heures pour se terminer. (Ce qui semble être un autre indicateur que mon réglage est moins qu'optimal).

Informations supplémentaires:
La machine que j'utilise actuellement dispose de 8 Go de RAM et d'un disque dur hybride à semi-conducteurs avec 5400 tr / min.
Bien que nous cherchions également à supprimer les données obsolètes du tableau en question, j'ai encore besoin d'une importation assez rapide vers
a) le test automatic data cleanup featurependant le développement et
b) au cas où notre serveur se bloquerait, nous aimerions utiliser notre 2ème serveur comme remplacement (qui a besoin de plus de données à jour, la dernière importation a pris plus de 24 heures)

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

2
Avez-vous essayé avec des importations moins importantes, comme des lignes 10K ou 100K?
ypercubeᵀᴹ

1
Veuillez courir SHOW CREATE TABLE yourtable\Gpour nous montrer la structure du tableau de ce tableau de 10 millions de lignes.
RolandoMySQLDBA du

@RolandoMySQLDBA donc je l'ai fait (avec des noms de champs obscurcis)
nuala

En désactivant le double tampon d'écriture ( innodb_doublewrite = 0), votre installation MySQL n'est pas protégée contre les pannes: si vous avez une panne de courant (pas une panne MySQL), vos données pourraient être silencieusement corrompues.
jfg956

Réponses:


13

Tout d'abord, vous devez savoir ce que vous faites à InnoDB lorsque vous labourez des millions de lignes dans une table InnoDB. Jetons un coup d'œil à l'architecture InnoDB.

Architecture InnoDB

Dans le coin supérieur gauche, il y a une illustration du pool de tampons InnoDB. Notez qu'il y a une section dédiée au tampon d'insertion. Qu'est-ce que ça fait? Il est destiné à migrer les modifications apportées aux index secondaires du pool de tampons vers le tampon d'insertion à l'intérieur de l'espace disque logique du système (alias ibdata1). Par défaut, innodb_change_buffer_max_size est défini sur 25. Cela signifie que jusqu'à 25% du pool de tampons peuvent être utilisés pour traiter les index secondaires.

Dans votre cas, vous disposez de 6,935 Go pour le pool de mémoire tampon InnoDB. Un maximum de 1,734 Go sera utilisé pour traiter vos index secondaires.

Maintenant, regardez votre table. Vous disposez de 13 index secondaires. Chaque ligne que vous traitez doit générer une entrée d'index secondaire, la coupler avec la clé primaire de la ligne et les envoyer en tant que paire à partir du tampon d'insertion dans le pool de tampons dans le tampon d'insertion dans ibdata1. Cela se produit 13 fois avec chaque ligne. Multipliez cela par 10 millions et vous pouvez presque sentir un goulot d'étranglement venir.

N'oubliez pas que l'importation de 10 millions de lignes en une seule transaction empilera tout dans un segment d'annulation et remplira l'espace UNDO dans ibdata1.

SUGGESTIONS

SUGGESTION # 1

Ma première suggestion pour importer ce tableau assez grand serait

  • Supprimer tous les index non uniques
  • Importez les données
  • Créez tous les index non uniques

SUGGESTION # 2

Débarrassez-vous des index en double. Dans votre cas, vous avez

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

Les deux index commencent par party_id, vous pouvez augmenter le traitement des index secondaires d'au moins 7,6% en supprimant un index sur 13. Vous devez éventuellement exécuter

ALTER TABLE monster DROP INDEX party_id;

SUGGESTION # 3

Débarrassez-vous des index que vous n'utilisez pas. Examinez le code de votre application et voyez si vos requêtes utilisent tous les index. Vous voudrez peut-être examiner l'utilisation de pt-index pour lui permettre de suggérer quels index ne sont pas utilisés.

SUGGESTION # 4

Vous devez augmenter la taille innodb_log_buffer_size à 64 Mo, car la valeur par défaut est 8 Mo. Un tampon de journal plus important peut augmenter les performances d'E / S d'écriture d'InnoDB.

ÉPILOGUE

Pour mettre en place les deux premières suggestions, procédez comme suit:

  • Supprimez les 13 index non uniques
  • Importez les données
  • Créer tous les index non uniques à l'exception de l' party_idindex

Peut-être que ce qui suit peut aider

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;

Importez les données dans monster. Ensuite, exécutez cette

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`);

ESSAIE !!!

ALTERNATIVE

Vous pouvez créer une table appelée en monster_csvtant que table MyISAM sans index et procédez comme suit:

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`;

Importez vos données dans monster_csv. Ensuite, utilisez mysqldump pour créer une autre importation

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

Le fichier mysqldump data.sqlétendra les commandes INSERT en important 10 000 à 20 000 lignes à la fois.

Maintenant, chargez juste le mysqldump

mysql -uroot -p mydb < data.sql

Enfin, débarrassez-vous de la table MyISAM

DROP TABLE monster_csv;

Je n'étais même pas au courant de toutes ces clés (ce n'est pas ma conception) mais votre explication semble très convaincante. Pour aujourd'hui, il est trop tard pour recommencer, mais je vois de bons conseils pour essayer demain. Vous tiendra informé! <3
nuala

1
J'ai réussi à importer la base de données complète (pas seulement la monstertable) en moins de 20 minutes lorsque je n'avais pas de clés sur les tables InnoDB. L'ajout de clés a pris environ. encore 20 min. Je dirais que cela résout à peu près mon problème dans ce cas. Merci beaucoup!
nuala

8

Je voulais écrire un commentaire (car ce n'est pas une réponse définitive), mais c'est devenu trop long:

Je vais vous donner plusieurs conseils généraux, et nous pouvons entrer dans les détails de chacun, si vous le souhaitez:

  • Réduisez la durabilité (vous en avez déjà fait une partie). Les dernières versions permettent même d'en faire plus. Vous pouvez aller jusqu'à désactiver le tampon d'écriture double, car la corruption n'est pas un problème pour les importations.
  • Augmentez la mise en mémoire tampon de: augmentez la taille du journal des transactions et augmentez la taille du pool de mémoire tampon disponible. Surveillez l'utilisation des fichiers journaux de transactions et les points de contrôle. N'ayez pas peur d'énormes journaux pour une importation.
  • Évitez les transactions énormes - votre restauration deviendra pleine de données inutiles. C'est probablement votre plus gros problème.
  • SQL sera un goulot d'étranglement, évitez la surcharge SQL (handlersocket, memcached) et / ou chargez-le en simultané avec plusieurs threads en même temps. La concurrence doit atteindre un point idéal, ni trop, ni trop peu.
  • Le chargement des données dans la fragmentation de l'ordre des clés primaires peut être un problème
  • Testez la compression InnoDB si l'IO est votre goulot d'étranglement et que le CPU et la mémoire ne la ralentissent pas
  • Essayez de créer vos clés secondaires par la suite (plus rapidement dans certains cas), ne chargez pas les données indexées - DISABLE KEYS n'affecte pas InnoDB . Sinon, surveillez votre tampon d'insertion (dépassant peut-être la moitié de votre pool de tampons).
  • Modifiez ou désactivez l'algorithme de somme de contrôle - ce n'est probablement pas votre problème, mais cela devient un goulot d'étranglement sur les cartes flash haut de gamme.
  • Dernier recours: surveillez votre serveur pour trouver votre goulot d'étranglement actuel et essayez d'atténuer (InnoDB est très flexible à ce sujet).

N'oubliez pas que certains d'entre eux ne sont pas sécurisés ou recommandés pour les non-importations (fonctionnement normal).


Merci beaucoup! J'aime d'abord essayer l' idée de Rolando concernant les index mais je suppose que ce truc de "transaction-rollback" sera toujours un problème. Pouvez-vous développer? Je pense que je veux désactiver autant de fonctionnalités que possible lors de l'importation et juste réactiver lors de la mise en production ~ Je pense ...
nuala

1
La suggestion de Rolando est mon point # 7. Éviter les frais généraux de restauration est aussi simple qu'une combinaison de SET SESSION tx_isolation='READ-UNCOMMITTED';(uniquement utile si vous importez avec plusieurs threads en parallèle) et le commentaire @ypercube sur l'insertion dans des lots. Vous avez un exemple complet ici: mysqlperformanceblog.com/2008/07/03/… Assurez-vous que vous bénéficiez de toutes les fonctionnalités des dernières versions d'InnoDB: mysqlperformanceblog.com/2011/01/07/…
jynus

1
J'avais l'impression générale que l'on éviterait d'importer dans des mandrins plus petits, mais plutôt d'aller pour une opération "tout compris" mais je vois que le multi-threading pourrait ouvrir certaines possibilités. Je suppose que c'est très spécifique à chaque cas. Cependant, j'ai accepté la réponse de Rolando car ce tweak (votre # 7) à lui seul m'a aidé à obtenir une importation complète en <1 heure, mais votre liste est certainement loin d'être sans valeur et je suppose que je l'utiliserai comme référence assez rapidement car le taux de notre base de données augmente un peu me fait peur :)
nuala

Je suis d'accord avec @yoshi. Votre réponse est plus complète en termes de dépannage et d'amélioration des performances. +1
RolandoMySQLDBA

3

La plupart des bons conseils ont été donnés jusqu'à présent, mais sans beaucoup d'explications pour les meilleurs. Je donnerai plus de détails.

Tout d'abord, retarder la création d'un index est une bonne chose, avec suffisamment de détails dans d'autres réponses. Je n'y reviendrai pas.

Un fichier journal InnoDB plus grand vous aidera beaucoup (si vous utilisez MySQL 5.6 car il n'est pas possible de l'augmenter dans MySQL 5.5). Vous insérez 7 Go de données, je recommanderais une taille totale de journal d'au moins 8 Go (conservez innodb_log_files_in_groupsa valeur par défaut (2) et augmentez innodb_log_file_sizeà 4 Go). Ce 8 Go n'est pas exact: il devrait être au moins de la taille d'importation dans le journal REDO et probablement doubler ou quadrupler cette taille. Le raisonnement derrière la taille du journal InnoDB augmente le fait que lorsque le journal deviendra presque plein, InnoDB commencera à vider agressivement son pool de tampons sur le disque pour éviter que le journal ne se remplisse (lorsque le journal est plein, InnoDB ne peut faire aucune écriture de base de données jusqu'à ce que certains pages du pool de mémoire tampon sont écrites sur le disque).

Un fichier journal InnoDB plus grand vous sera utile, mais vous devez également insérer dans l'ordre des clés primaires (trier votre fichier avant l'insertion). Si vous insérez dans l'ordre des clés primaires, InnoDB remplira une page, puis une autre, et ainsi de suite. Si vous n'insérez pas dans l'ordre des clés primaires, votre prochaine insertion peut se retrouver dans une page pleine et entraîner un "fractionnement de page". Ce fractionnement de page coûtera cher à InnoDB et ralentira votre importation.

Vous avez déjà un pool de tampons aussi grand que votre RAM vous le permet et si votre table ne s'y adapte pas, vous ne pouvez pas faire grand-chose à part acheter plus de RAM. Mais si votre table tient dans le pool de tampons mais est supérieure à 75% de votre pool de tampons, vous pouvez essayer d'augmenter innodb_max_dirty_pages_pctà 85 ou 95 lors de l'importation (la valeur par défaut est 75). Ce paramètre de configuration indique à InnoDB de commencer à vider agressivement le pool de tampons lorsque le pourcentage de pages sales atteint cette limite. En augmentant ce paramètre (et si vous avez de la chance sur la taille des données), vous pouvez éviter les E / S agressives lors de votre importation et retarder ces E / S plus tard.

Peut-être (et c'est une supposition) que l'importation de vos données dans de nombreuses petites transactions vous aidera. Je ne sais pas exactement comment le journal REDO est construit, mais s'il est mis en mémoire tampon dans la RAM (et sur le disque quand trop de RAM serait nécessaire) pendant que la transaction progresse, vous pourriez vous retrouver avec des E / S inutiles. Vous pouvez essayer ceci: une fois votre fichier trié, divisez-le en plusieurs morceaux (essayez avec 16 Mo et d'autres tailles) et importez-les un par un. Cela vous permettrait également de contrôler la progression de votre importation. Si vous ne souhaitez pas que vos données soient partiellement visibles par un autre lecteur pendant que vous effectuez votre importation, vous pouvez importer en utilisant un nom de table différent, créer les index ultérieurement, puis renommer la table.

À propos de votre disque SSD hybride / 5400 tr / min, je ne sais pas ceux-ci et comment l'optimiser. 5400 tr / min semble lent pour une base de données, mais peut-être que le SSD évite cela. Vous remplissez peut-être la partie SSD de votre disque avec des écritures séquentielles dans le journal REDO et le SSD nuit aux performances. Je ne sais pas.

Un mauvais conseil que vous ne devriez pas essayer (ou faire attention) est le suivant: n'utilisez pas de multi-thread: il sera très difficile d'optimiser pour éviter les sauts de page dans InnoDB. Si vous souhaitez utiliser plusieurs threads, insérez-les dans différentes tables (ou dans différentes partitions de la même table).

Si vous envisagez le multi-thread, vous avez peut-être un ordinateur multi-socket (NUMA). Dans ce cas, assurez-vous d'éviter le problème de folie du swap MySQL .

Si vous utilisez MySQL 5.5, passez à MySQL 5.6: il a la possibilité d'augmenter la taille du journal REDO et a de meilleurs algorithmes de vidage de pool de mémoire tampon.

Bonne chance avec votre importation.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.