Création d'une table MySQL de 1 000 millions de lignes

18

Cette question est republiée de Stack Overflow sur la base d'une suggestion dans les commentaires, excuses pour la duplication.

Des questions

Question 1: à mesure que la taille de la table de base de données augmente, comment puis-je régler MySQL pour augmenter la vitesse de l'appel LOAD DATA INFILE?

Question 2: utiliser un cluster d'ordinateurs pour charger différents fichiers csv, améliorer les performances ou les tuer? (c'est ma tâche de benchmarking pour demain en utilisant les données de chargement et les inserts en vrac)

Objectif

Nous essayons différentes combinaisons de détecteurs de caractéristiques et de paramètres de clustering pour la recherche d'images, nous devons donc être en mesure de créer de grandes bases de données en temps opportun.

Informations sur la machine

La machine a 256 gig de RAM et il y a 2 autres machines disponibles avec la même quantité de RAM s'il existe un moyen d'améliorer le temps de création en distribuant la base de données?

Schéma de table

le schéma de la table ressemble

+---------------+------------------+------+-----+---------+----------------+
| 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       |                |
+---------------+------------------+------+-----+---------+----------------+

créé avec

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;

Analyse comparative jusqu'à présent

La première étape consistait à comparer les insertions en masse par rapport au chargement à partir d'un fichier binaire dans une table vide.

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

Compte tenu de la différence de performances, je suis allé avec le chargement des données à partir d'un fichier CSV binaire, j'ai d'abord chargé des fichiers binaires contenant 100K, 1M, 20M, 200M lignes en utilisant l'appel ci-dessous.

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

J'ai tué le fichier binaire de 200 millions de lignes (~ 3 Go de fichier csv) après 2 heures.

J'ai donc exécuté un script pour créer la table, et insérer différents nombres de lignes à partir d'un fichier binaire puis déposer la table, voir le graphique ci-dessous.

entrez la description de l'image ici

Il a fallu environ 7 secondes pour insérer 1 million de lignes du fichier binaire. Ensuite, j'ai décidé de comparer l'insertion de 1 M de lignes à la fois pour voir s'il y aurait un goulot d'étranglement à une taille de base de données particulière. Une fois que la base de données a atteint environ 59 millions de lignes, le temps d'insertion moyen est tombé à environ 5 000 / seconde.

entrez la description de l'image ici

La définition de la clé globale key_buffer_size = 4294967296 a légèrement amélioré les vitesses d'insertion de petits fichiers binaires. Le graphique ci-dessous montre les vitesses pour différents nombres de lignes

entrez la description de l'image ici

Cependant, pour l'insertion de lignes 1M, cela n'a pas amélioré les performances.

lignes: 1 000 000 temps: 0: 04: 13,761428 insertions / s: 3 940

vs pour une base de données vide

lignes: 1 000 000 temps: 0: 00: 6,339295 insertions / sec: 315,492

Mise à jour

Faire les données de chargement en utilisant la séquence suivante vs simplement en utilisant la commande de chargement de données

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;
entrez la description de l'image ici

Cela semble donc assez prometteur en termes de taille de base de données qui est générée, mais les autres paramètres ne semblent pas affecter les performances de l'appel de chargement des données.

J'ai ensuite essayé de charger plusieurs fichiers à partir de différentes machines, mais la commande de chargement des données infile verrouille la table, en raison de la grande taille des fichiers entraînant l'expiration des autres machines avec

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

Augmenter le nombre de lignes dans un fichier binaire

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

Solution: précalculer l'ID en dehors de MySQL au lieu d'utiliser l'incrémentation automatique

Construire la table avec

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;

avec le SQL

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

entrez la description de l'image ici

Obtenir le script pour précalculer les index semble avoir supprimé le hit de performance à mesure que la base de données grandit.

Mise à jour 2 - utilisation des tables de mémoire

Environ 3 fois plus rapide, sans tenir compte du coût de déplacement d'une table en mémoire vers une table sur disque.

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

en chargeant les données dans une table basée sur la mémoire, puis en les copiant sur une table basée sur le disque en morceaux, le temps système était de 10 min 59,71 s pour copier 107 356 741 lignes avec la requête

insert into test Select * from test2;

ce qui fait environ 15 minutes pour charger 100 millions de lignes, ce qui équivaut approximativement à l'insérer directement dans une table sur disque.

Ben
la source
1
Je pense que changer la clé primaire iddevrait être plus rapide. (Bien que je pense que vous ne cherchez pas cela)
DavidEG
Salut David, merci pour le commentaire, malheureusement sans la clé, les requêtes que nous devons faire ne sont pas assez rapides (la logique derrière la sélection de la clé primaire est décrite dans ce post stackoverflow.com/questions/4282526/mysql-group-by- optimisation )
Ben
1
Est-ce juste pour les tests? Vous voudrez peut-être regarder le moteur MySQL MEMORY: dev.mysql.com/doc/refman/5.0/en/memory-storage-engine.html Si vous prévoyez de déployer cela en tant qu'architecture, je suis curieux de savoir comment vous comptez récupérer des échecs, il semble que quelque chose qui serait mieux géré par MapReduce / Hadoop.
polynôme
Salut polynôme, merci pour l'astuce, en ce moment, nous testons simplement différents détecteurs de fonctionnalités à différentes échelles, une fois la base de données générée, elle ne changera pas beaucoup (dans les spécifications actuelles de toute façon)
Ben

Réponses:

4

Bonne question - bien expliquée.

comment puis-je régler MySQL pour augmenter la vitesse de l'appel LOAD DATA INFILE?

Vous avez déjà un paramètre élevé (ish) pour le tampon de clé - mais est-ce suffisant? Je suppose que c'est une installation 64 bits (sinon la première chose que vous devez faire est de mettre à niveau) et ne fonctionne pas sur MSNT. Jetez un œil à la sortie de mysqltuner.pl après avoir exécuté quelques tests.

Afin d'utiliser le cache au mieux, vous pouvez trouver des avantages à regrouper / pré-trier les données d'entrée (les versions les plus récentes de la commande 'sort' ont beaucoup de fonctionnalités pour trier de grands ensembles de données). De plus, si vous générez des numéros d'identification en dehors de MySQL, cela peut être plus efficace.

utiliserait un cluster d'ordinateurs pour charger différents fichiers csv

En supposant (encore une fois) que vous voulez que le jeu de sortie se comporte comme une seule table, les seuls avantages que vous obtiendrez seront de distribuer le travail de tri et de génération d'ID - pour lesquels vous n'avez pas besoin de plus de bases de données. OTOH en utilisant un cluster de base de données, vous aurez des problèmes de contention (que vous ne devriez pas voir autrement que comme des problèmes de performances).

Si vous pouvez scinder les données et gérer les ensembles de données résultants de manière indépendante, alors oui, vous obtiendrez des avantages en termes de performances, mais cela n'élimine pas la nécessité de régler chaque nœud.

Vérifiez que vous disposez d'au moins 4 Go pour le sort_buffer_size.

Au-delà de cela, le facteur limitant des performances concerne les E / S disque. Il existe de nombreuses façons de résoudre ce problème, mais vous devriez probablement envisager un ensemble en miroir d'ensembles de données agrégées par bandes sur les disques SSD pour des performances optimales.

symcbean
la source
1
  • Considérez votre facteur limitant. C'est presque certainement un traitement CPU à un seul thread.
  • Vous avez déjà déterminé que load data...c'est plus rapide que l'insertion, alors utilisez-le.
  • Vous avez déjà déterminé que les très gros fichiers (par numéro de ligne) ralentissent beaucoup les choses; vous voulez les briser en morceaux.
  • En utilisant des clés primaires qui ne se chevauchent pas, mettez en file d'attente au moins N * jeux d'UC, en utilisant pas plus d'un million de lignes ... probablement moins (référence).
  • Utilisez des blocs séquentiels de clés primaires dans chaque fichier.

Si vous voulez être vraiment flippant, vous pouvez créer un programme multi-thread pour alimenter un seul fichier vers une collection de canaux nommés et gérer les instances d'insertion.

En résumé, vous ne réglez pas MySQL pour autant que vous réglez votre charge de travail sur MySQL.

Jeff Ferland
la source
-1

Je ne me souviens pas exactement de la syntaxe, mais si ce n'est pas le cas, vous pouvez désactiver la vérification des clés étrangères.

Vous pouvez également créer l'index après l'importation, cela peut être vraiment un gain de performances.

Julien Duponchelle
la source
Le report de la reconstruction de l'index n'améliore les performances que lorsque le nombre de lignes déjà présentes dans le tableau est nettement inférieur au nombre de lignes que vous ajoutez.
symcbean