Je suis Charlie

Autres trucs

Accueil

Seulement les RFC

Seulement les fiches de lecture

Ève

Insérer beaucoup de tuples COPY ou INSERT ?

Première rédaction de cet article le 6 septembre 2010
Dernière mise à jour le 10 septembre 2010


Tiens, un petit problème de performance avec un SGBDR. Pour insérer un grand nombre de tuples, est-ce plus rapide avec une boucle sur le traditionnel INSERT ou bien avec une autre commande SQL, COPY ?

Le problème vient de DNSmezzo qui doit parfois insérer plusieurs millions de tuples en une seule exécution (chaque tuple représentant une requête ou une réponse DNS). Cela prend du temps et cela s'aggrave avec le remplissage de la base. La documentation de PostgreSQL suggère d'utiliser COPY plutôt qu'INSERT. Qu'y gagne t-on exactement ?

Les tests ont été effectués avec PostgreSQL 8.3 sur un PC/Debian. Deux programmes en C, speed-insert.c et speed-copy.c ont été comparés, pour une table composée de trois champs (dont deux seulement sont spécifiés lors de l'insertion) :

CREATE TABLE Shadoks (id SERIAL, name TEXT, value INTEGER);

Voici d'abord l'insertion d'une table sans index, pour dix millions de tuples :

% time ./speed-insert dbname=essais 10000000
./insert dbname=essais 10000000  53.35s user 53.72s system 15% cpu 11:49.85 total

% time ./speed-copy dbname=essais 10000000      
./copy dbname=essais 10000000  8.61s user 0.14s system 11% cpu 1:15.79 total

Bref, COPY est dix fois plus rapide. (Si vous regardez le source de speed-copy, vous verrez une constante INCREMENT dont la valeur a été déterminée empiriquement. Mettez-la trop bas, à 10 par exemple, et les performances chutent.)

Et avec des index ? Notons que la documentation de PostgreSQL, citée plus haut, recommande de les débrayer avant d'insérer en masse et de les rétablir ensuite. Créons le :

CREATE INDEX name_idx on Shadoks(name);

Et, en effet, les performances chutent dramatiquement : même avec COPY, au bout de quinze minutes, j'ai interrompu le programme. Utiliser la méthode suggérée par la documentation est bien plus rapide :

% time sh -c '(psql -c "DROP INDEX name_idx" essais ;  \
            ./copy dbname=essais 10000000; \
            psql -c "CREATE INDEX name_idx on Shadoks(name)" essais)'
DROP INDEX
CREATE INDEX
sh -c   8.56s user 0.14s system 3% cpu 4:01.39 total

Il existe une troisième solution à la question de la « copie en masse » mais elle nécessite l'ajout d'un logiciel supplémentaire, pgbulkload. Je ne l'ai pas testé mais, questions performances, les auteurs annoncent des résultats intéressants. Mais attention : rien n'est gratuit. Ces gains de performance reposent essentiellement sur l'absence de support des triggers et des rules, et surtout il ne valide pas les données en entrée. Du coup ça n'est utilisable que pour restaurer une sauvegarde de confiance. Dans tous les cas, ces copies rapides (COPY ou pgbulkload) peuvent poser des problèmes de compatibilité avec d'éventuels mécanismes de réplication qui ont été mis en place donc lisez bien la documentation de votre système de réplication, si vous en avez un. D'autre part, les techniques « rapides » (tout ce qui n'est pas INSERT) peuvent poser des problèmes en cas de parallélisme (voir un exemple avec COPY).

Merci à Malek Shabou et Dimitri Fontaine pour leurs conseils et informations. Un bon article sur la question est « How to insert data to database - as fast as possible ».

Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)

Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)