web-dev-qa-db-fra.com

Comment accélérer les performances d'insertion dans PostgreSQL

Je teste les performances d’insertion Postgres. J'ai une table avec une colonne avec un nombre comme type de données. Il existe également un index. J'ai rempli la base de données en utilisant cette requête:

insert into aNumber (id) values (564),(43536),(34560) ...

J'ai inséré 4 millions de lignes très rapidement 10 000 à la fois avec la requête ci-dessus. Une fois que la base de données a atteint 6 millions de lignes, les performances ont chuté à 1 million de lignes toutes les 15 minutes. Existe-t-il une astuce pour augmenter les performances d'insertion? J'ai besoin d'une performance d'insertion optimale sur ce projet.

Utilisation de Windows 7 Pro sur une machine disposant de 5 Go de RAM.

176
Luke101

Voir peupler une base de données dans le manuel de PostgreSQL, article excellent-de-type de depesz sur le sujet, et this SO question =.

(Notez que cette réponse concerne le chargement en bloc de données dans une base de données existante ou pour en créer une nouvelle. Si les performances de restauration de base de données vous intéressent, avec pg_restore ou psql exécution de la sortie de pg_dump, une grande partie de ceci ne s'applique pas puisque pg_dump et pg_restore font déjà des choses comme créer des déclencheurs et des index après avoir terminé une restauration de schéma + données) .

Il y a beaucoup à faire. La solution idéale serait d'importer dans une table UNLOGGED sans index, puis de la changer en journal et d'ajouter les index. Malheureusement, dans PostgreSQL 9.4, il n’est pas possible de changer de table de UNLOGGED à logé. 9.5 ajoute ALTER TABLE ... SET LOGGED pour vous permettre de le faire.

Si vous pouvez déconnecter votre base de données pour l'importation en bloc, utilisez pg_bulkload .

Autrement:

  • Désactiver tous les déclencheurs sur la table

  • Supprimez les index avant de lancer l'importation, puis recréez-les. (Il faut beaucoup moins de temps pour créer un index en une passe que pour y ajouter progressivement les mêmes données, et l'index résultant est beaucoup plus compact).

  • Si vous effectuez l'importation au sein d'une seule transaction, il est prudent de supprimer les contraintes de clé étrangère, de l'importer et de recréer les contraintes avant de valider. Ne le faites pas si l'importation est répartie sur plusieurs transactions, car vous pourriez introduire des données non valides.

  • Si possible, utilisez COPY au lieu de INSERTs

  • Si vous ne pouvez pas utiliser COPY, envisagez d'utiliser INSERTs à valeurs multiples si cela vous convient. Vous semblez déjà le faire. N'essayez pas de lister trop de nombreuses valeurs dans un seul VALUES quoique; ces valeurs doivent être mémorisées plusieurs fois, alors gardez-les à quelques centaines d'instructions.

  • Batch vos insertions dans des transactions explicites, en effectuant des centaines de milliers ou des millions d'insertions par transaction. Il n’ya pas de limite pratique, autant que je sache, mais le traitement par lots vous permettra de remédier à une erreur en marquant le début de chaque lot dans vos données d’entrée. Encore une fois, vous semblez déjà le faire.

  • Utilisez synchronous_commit=off et un énorme commit_delay pour réduire les coûts de fsync (). Cela ne vous aidera pas beaucoup si vous avez regroupé votre travail en grandes transactions.

  • INSERT ou COPY en parallèle à partir de plusieurs connexions. Combien dépend du sous-système de disque de votre matériel; En règle générale, vous souhaitez une connexion par disque dur physique si vous utilisez un stockage directement connecté.

  • Définissez une valeur checkpoint_segments élevée et activez log_checkpoints. Consultez les journaux PostgreSQL et assurez-vous qu’il ne se plaint pas des points de contrôle se produisant trop souvent.

  • Si et seulement si cela ne vous dérange pas de perdre l'intégralité de votre cluster PostgreSQL (votre base de données et toute autre sur le même cluster) en cas de corruption grave si le système se bloque pendant l'importation, vous pouvez arrêter Pg, définir fsync=off, démarrer Pg, puis effectuer votre importation. , puis (vitalement), arrêtez Pg et définissez fsync=on à nouveau. Voir configuration WAL . Ne le faites pas s'il y a déjà des données qui vous intéressent dans une base de données de votre installation PostgreSQL. Si vous définissez fsync=off, vous pouvez également définir full_page_writes=off; Encore une fois, rappelez-vous de le réactiver après votre importation pour éviter la corruption de la base de données et la perte de données. Voir paramètres non durables dans le manuel de Pg.

Vous devriez également chercher à régler votre système:

  • Utilisez de bonne qualité disques SSD pour le stockage autant que possible. De bons disques SSD dotés de caches d’écriture fiables, protégés contre l’alimentation, accélèrent considérablement les taux de validation. Ils sont moins bénéfiques si vous suivez les conseils ci-dessus - ce qui réduit le nombre de vidages sur le disque/le nombre de fsync()s -, mais ils peuvent quand même être d'une grande aide. N'utilisez pas de disques SSD bon marché sans une protection adéquate contre les pannes de courant, à moins que vous ne teniez pas à conserver vos données.

  • Si vous utilisez RAID 5 ou RAID 6 pour un stockage directement connecté, arrêtez maintenant. Sauvegardez vos données, restructurez votre matrice RAID en RAID 10 et réessayez. Les RAID 5/6 sont sans espoir pour les performances d'écriture en masse - bien qu'un bon contrôleur RAID avec un cache important puisse y contribuer.

  • Si vous avez la possibilité d'utiliser un contrôleur RAID matériel avec un grand cache de réécriture sauvegardé par batterie, cela peut réellement améliorer les performances en écriture pour les charges de travail comportant de nombreux validations. Cela n'aidera pas autant si vous utilisez une validation async avec un commit_delay ou si vous effectuez moins de grosses transactions lors du chargement en bloc.

  • Si possible, stockez WAL (pg_xlog) sur un disque/une matrice de disques distinct. Il ne sert à rien d'utiliser un système de fichiers séparé sur le même disque. Les gens choisissent souvent d’utiliser une paire RAID1 pour WAL. Encore une fois, cela a plus d'effet sur les systèmes avec des taux de validation élevés, et très peu si vous utilisez une table non journalisée comme cible de chargement de données.

Vous pouvez également être intéressé par Optimiser PostgreSQL pour des tests rapides .

433
Craig Ringer

Utilisez COPY table TO ... WITH BINARY qui, selon la documentation, est " n peu plus rapide que les formats texte et CSV ." Ne le faites que si vous avez des millions de lignes à insérer et si vous maîtrisez les données binaires.

Voici un exemple de recette en Python utilisant psycopg2 avec une entrée binaire .

12
Mike T

En plus de l'excellent post de Craig Ringer et du blog de depesz, si vous souhaitez accélérer vos insertions via l'interface ODBC ( psqlodbc ) en utilisant des insertions d'instructions préparées dans une transaction, Pour que cela fonctionne rapidement, vous devez effectuer quelques tâches supplémentaires:

  1. Définissez le niveau des erreurs d'annulation sur "Transaction" en spécifiant Protocol=-1 dans la chaîne de connexion. Par défaut, psqlodbc utilise le niveau "Instruction", ce qui crée un SAVEPOINT pour chaque instruction plutôt qu'une transaction complète, ce qui ralentit les insertions.
  2. Utilisez les instructions préparées côté serveur en spécifiant UseServerSidePrepare=1 dans la chaîne de connexion. Sans cette option, le client envoie l'intégralité de l'instruction insert avec chaque ligne insérée.
  3. Désactiver l'auto-validation sur chaque instruction à l'aide de SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
  4. Une fois que toutes les lignes ont été insérées, validez la transaction en utilisant SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);. Il n'est pas nécessaire d'ouvrir explicitement une transaction.

Malheureusement, psqlodbc "implémente" SQLBulkOperations en émettant une série d'instructions insert non préparées, de sorte que pour obtenir l'insertion la plus rapide, il est nécessaire de coder manuellement les étapes ci-dessus.

11
Maxim Egorushkin

J'ai passé environ 6 heures sur le même sujet aujourd'hui. Les inserts vont à une vitesse "normale" (moins de 3 secondes par 100K) jusqu'à 5 lignes (sur un total de 30MI), puis la performance chute de façon drastique (jusqu'à 1 minute par 100K).

Je ne vais pas énumérer toutes les choses qui ne fonctionnaient pas et aller droit au but.

I supprimé une clé primaire sur la table cible (qui était un GUID) et mes 30MI ou lignes se sont heureusement écoulés vers leur destination à une vitesse constante inférieure à 3 s par 100K.

7
Dennis

Pour optimiser les performances d’insertion, désactivez l’index si cette option vous convient. Autre que cela, un meilleur matériel (disque, mémoire) est également utile

1
Icarus