Améliorer les performances de requête psycopg2 de Postgres pour Python au même niveau que le pilote JDBC de Java
vue d'ensemble
j'essaie d'améliorer les performances de nos requêtes de base de données pour SQLAlchemy. On utilise psycopg2. Dans notre système de production, Nous choisissons Java parce qu'il est simplement plus rapide d'au moins 50%, sinon plus proche de 100%. Donc, j'espère que quelqu'un dans la communauté de débordement de pile a un moyen d'améliorer ma performance.
je pense que ma prochaine étape sera de corriger la bibliothèque psycopg2 pour qu'elle se comporte comme le pilote JDBC. Si c'est le case et quelqu'un a déjà fait cela, ce serait très bien, mais j'espère que j'ai encore un réglage ou un ajustement de retouche que je peux faire à partir de Python.
Détails
j'ai une requête simple" SELECT * FROM someLargeDataSetTable". L'ensemble de données est GBs en taille. Un rapide tableau des performances est comme suit:
Calendrier De Table
Records | JDBC | SQLAlchemy[1] | SQLAlchemy[2] | Psql -------------------------------------------------------------------- 1 (4kB) | 200ms | 300ms | 250ms | 10ms 10 (8kB) | 200ms | 300ms | 250ms | 10ms 100 (88kB) | 200ms | 300ms | 250ms | 10ms 1,000 (600kB) | 300ms | 300ms | 370ms | 100ms 10,000 (6MB) | 800ms | 830ms | 730ms | 850ms 100,000 (50MB) | 4s | 5s | 4.6s | 8s 1,000,000 (510MB) | 30s | 50s | 50s | 1m32s 10,000,000 (5.1GB) | 4m44s | 7m55s | 6m39s | n/a -------------------------------------------------------------------- 5,000,000 (2.6GB) | 2m30s | 4m45s | 3m52s | 14m22s -------------------------------------------------------------------- [1] - With the processrow function [2] - Without the processrow function (direct dump)
je pourrais ajouter plus (nos données peuvent être autant que des téraoctets), mais je pense que le changement de pente est évident à partir des données. JDBC juste fonctionne beaucoup mieux que la taille de l'ensemble de données augmente. Quelques notes...
Tableau De Synchronisation Notes:
- les données sont approximatives, mais elles devraient vous donner une idée de la quantité de données.
- j'utilise L'outil' time ' d'une ligne de commande Linux bash.
- Les temps sont à l'horloge murale fois (c'est à dire vrai).
- j'utilise Python 2.6.6 et je cours avec
python -u
- Taille de l'Extraction est 10,000
- Je ne m'inquiète pas vraiment du timing de la Psql, c'est juste un point de référence. Je ne peut pas avoir correctement défini fetchsize.
- Je ne suis pas non plus vraiment inquiet du timing en dessous de la taille de fetch car moins de 5 secondes est négligeable pour mon application.
- Java et Psql semblent prendre 1 go de mémoire de ressources; Python est plus de 100 MO (yay!!!!).
- j'utilise le [cdecimals] bibliothèque.
- j'ai remarqué un [article récent] discuter de quelque chose de semblable. Il semble que la conception du pilote JDBC soit totalement différente de celle de psycopg2 (ce qui me semble assez ennuyeux étant donné la différence de performances).
- mon cas d'utilisation est essentiellement que je dois exécuter un processus quotidien (avec environ 20.000 étapes différentes... plusieurs requêtes) sur de très grands ensembles de données et j'ai une fenêtre de temps très spécifique où je peux finir ceci processus. Le Java que nous utilisons n'est pas simplement JDBC, c'est un wrapper "intelligent" sur le dessus du moteur JDBC... nous ne voulons pas utiliser Java et nous aimerions arrêter d'utiliser la partie "intelligente" de celui-ci.
- j'utilise une des boîtes de notre système de production (Base de données et processus d'arrière-plan) pour exécuter la requête. Donc, c'est notre meilleur des cas, le timing. Nous avons des boîtes QA et Dev qui tournent beaucoup plus lentement et le temps de requête supplémentaire peut devenir important.
testSqlAlchemy.py
#!/usr/bin/env python # testSqlAlchemy.py import sys try: import cdecimal sys.modules["decimal"]=cdecimal except ImportError,e: print >> sys.stderr, "Error: cdecimal didn't load properly." raise SystemExit from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker def processrow (row,delimiter="|",null="N"): newrow = [] for x in row: if x is None: x = null newrow.append(str(x)) return delimiter.join(newrow) fetchsize = 10000 connectionString = "postgresql+psycopg2://usr:pass@server:port/db" eng = create_engine(connectionString, server_side_cursors=True) session = sessionmaker(bind=eng)() with open("test.sql","r") as queryFD: with open("/dev/null","w") as nullDev: query = session.execute(queryFD.read()) cur = query.cursor while cur.statusmessage not in ['FETCH 0','CLOSE CURSOR']: for row in query.fetchmany(fetchsize): print >> nullDev, processrow(row)
Après le timing, j'ai aussi couru un cProfile et c'est le cliché des pires délinquants:
Calendrier de Profil (avec processrow)
Fri Mar 4 13:49:45 2011 sqlAlchemy.prof 415757706 function calls (415756424 primitive calls) in 563.923 CPU seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.001 0.001 563.924 563.924 {execfile} 1 25.151 25.151 563.924 563.924 testSqlAlchemy.py:2() 1001 0.050 0.000 329.285 0.329 base.py:2679(fetchmany) 1001 5.503 0.005 314.665 0.314 base.py:2804(_fetchmany_impl) 10000003 4.328 0.000 307.843 0.000 base.py:2795(_fetchone_impl) 10011 0.309 0.000 302.743 0.030 base.py:2790(__buffer_rows) 10011 233.620 0.023 302.425 0.030 {method 'fetchmany' of 'psycopg2._psycopg.cursor' objects} 10000000 145.459 0.000 209.147 0.000 testSqlAlchemy.py:13(processrow)
profil temporel (sans processrow)
Fri Mar 4 14:03:06 2011 sqlAlchemy.prof 305460312 function calls (305459030 primitive calls) in 536.368 CPU seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.001 0.001 536.370 536.370 {execfile} 1 29.503 29.503 536.369 536.369 testSqlAlchemy.py:2() 1001 0.066 0.000 333.806 0.333 base.py:2679(fetchmany) 1001 5.444 0.005 318.462 0.318 base.py:2804(_fetchmany_impl) 10000003 4.389 0.000 311.647 0.000 base.py:2795(_fetchone_impl) 10011 0.339 0.000 306.452 0.031 base.py:2790(__buffer_rows) 10011 235.664 0.024 306.102 0.031 {method 'fetchmany' of 'psycopg2._psycopg.cursor' objects} 10000000 32.904 0.000 172.802 0.000 base.py:2246(__repr__)
Derniers Commentaires
malheureusement, la fonction processrow doit rester sauf s'il y a un moyen dans SQLAlchemy de spécifier null = 'userDefinedValueOrString' et delimiter = 'userDefinedValueOrString' de la sortie. Le Java que nous utilisons actuellement le fait déjà, donc la comparaison (avec processrow) devait être de pommes à pommes. S'il y a un moyen d'améliorer les performances de processrow ou SQLAlchemy avec pur Python ou un tweak de paramètres, Je suis très intéressé.
4 réponses
Ce n'est pas une réponse hors de la boîte, avec tous les clients/db choses vous devrez peut-être faire un peu de travail pour déterminer exactement ce qui ne va pas
sauvegarde de postgresql.changement de conf
log_min_duration_statement to 0
log_destination = 'csvlog' # Valid values are combinations of
logging_collector = on # Enable capturing of stderr and csvlog
log_directory = 'pg_log' # directory where log files are written,
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern,
debug_print_parse = on
debug_print_rewritten = on
debug_print_plan output = on
log_min_messages = info (debug1 for all server versions prior to 8.4)
Arrêter et redémarrer votre serveur de base de données ( rechargement ne peut pas ramasser les modifications ) Reproduisez vos tests en vous assurant que l'heure du serveur et l'heure du client concordent et que vous enregistrez les heures de début, etc.
copier le fichier journal off une importation dans l'éditeur de votre choix (excel ou autre tableur peut être utile pour obtenir la manipulation d'avance pour sql & plans etc)
examinez maintenant les timings du côté du serveur et notez:
est le sql signalé sur le serveur les mêmes dans chaque cas
si la même chose vous devriez avoir les mêmes timings
le client génère-t-il un curseur au lieu de passer sql
est-ce qu'un pilote fait beaucoup de casting / conversion entre jeux de caractères ou conversion implicite d'autres types tels que dates ou horodateurs.
et ainsi de suite
les données du régime seront incluses à des fins d'exhaustivité, ce qui peut indiquer s'il y a des différences flagrantes dans la SQL soumise par les clients.
les choses ci-dessous visent probablement au-dessus et au-delà de ce que vous avez en tête ou ce qui est jugé acceptable dans votre environnement, mais je vais mettre l'option sur la table juste au cas où.
- est la destination de chaque
SELECT
dans votretest.sql
vraiment un simple|
séparées fichier de résultats? - la non-portabilité (postgrès-spécificité) est-elle acceptable?
- votre dossier Postgres 8.2 ou plus récent?
- le script de s'exécuter sur le même hôte que le backend de la base de données, ou serait-il acceptable de générer le
|
-fichier(s) de résultats séparé (s) à partir du backend (par exemple vers un partage?)
Si la réponse à toutes les questions ci-dessus est oui, ensuite, vous pouvez transformer votre SELECT ...
instructions COPY ( SELECT ... ) TO E'path-to-results-file' WITH DELIMITER '|' NULL E'\N'
.
une alternative pourrait être D'utiliser ODBC. Cela suppose que le pilote ODBC Python fonctionne bien.
PostgreSQL a des pilotes ODBC pour Windows et Linux.
Comme quelqu'un qui a programmé principalement en assembleur, il y a une chose qui colle comme une évidence. Vous perdez du temps dans les frais généraux, et les frais généraux sont ce qui doit aller.
plutôt que d'utiliser python, qui s'enroule dans quelque chose d'autre qui s'intègre avec quelque chose qui est un enveloppeur C autour de la base de données.... ecris juste le code en C. Je veux dire, combien de temps ça peut prendre? Postgres n'est pas difficile d'interfacer avec (bien au contraire). C est un langauge facile. Les opérations sont la performance semble assez simple. Vous pouvez également utiliser SQL intégré dans C, c'est juste une question de pré-compilation. Pas besoin de traduire ce que vous pensez - il suffit de l'écrire là avec le C et d'utiliser le compilateur ECPG fourni (lire le manuel postgres chapitre 29 iirc).
retirez autant de choses interfaciales que vous pouvez, découpez l'intermédiaire et parlez à la base de données nativement. Il me semble qu'en essayant de rendre le système plus simple vous faites il est plus compliqué que cela doit l'être. Quand les choses sont vraiment en désordre, je me pose habituellement la question "quel morceau du code ai-je le plus peur de toucher?"- cela m'indique habituellement ce qui doit être changé.
Désolé pour le babillage, mais peut-être un pas en arrière et un peu d'air frais va aider ;)