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é.

42
demandé sur Wayne Koorts 2011-03-04 22:33:37

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.

3
répondu Paddy Carroll 2011-12-02 07:00:35

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ù.

  1. est la destination de chaque SELECT dans votre test.sql vraiment un simple |séparées fichier de résultats?
  2. la non-portabilité (postgrès-spécificité) est-elle acceptable?
  3. votre dossier Postgres 8.2 ou plus récent?
  4. 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'.

2
répondu vladr 2011-09-27 14:10:46

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.

1
répondu Shamit Verma 2011-03-16 15:53:51

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

0
répondu tentimes 2011-09-03 14:14:31