Wie kann ich eine SQLAlchemy angetriebene Anwendung profilieren?

Hat jemand Erfahrung, eine Python / SQLAlchemy App zu profilieren? Und was ist der beste Weg, um Engpässe und Designfehler zu finden?

Wir haben eine Python-Anwendung, bei der die Datenbank-Schicht von SQLAlchemy behandelt wird. Die Anwendung verwendet ein Batch-Design, so dass viele Datenbankanforderungen sequentiell und in einer begrenzten Zeitspanne durchgeführt werden. Es dauert derzeit etwas zu lange, um zu laufen, also ist eine Optimierung erforderlich. Wir verwenden die ORM-Funktionalität nicht, und die Datenbank ist PostgreSQL.

  • Verwenden Sie scipy.interpolate.interpn, um ein N-dimensionales Array zu interpolieren
  • Python Glob ohne den ganzen Pfad - nur den Dateinamen
  • Schleife über Widgets in Tkinter
  • Scikit-Learn predict_proba gibt falsche Antworten
  • Sortierung einer Liste in Bezug auf die nächste Nummer in der Liste python
  • Welche Pfade macht python ctypes Modul nach Bibliotheken auf Mac OS?
  • Python3 tk, tooltip auf Leinwandbereich
  • Trunkieren Unicode so passt es eine maximale Größe, wenn für die Übertragung übertragen codiert
  • Was bedeuten diese beiden 'x' in diesem Python-Code: self.x = x?
  • Numpy Aufgabe wie 'numpy.take'
  • Leistung von Bibliothek itertools im Vergleich zu Python-Code
  • Liest fortran unformatierte datei mit python
  • 3 Solutions collect form web for “Wie kann ich eine SQLAlchemy angetriebene Anwendung profilieren?”

    Manchmal einfach nur SQL-Protokollierung (aktiviert über Python-Logging-Modul oder über die echo=True Argument auf create_engine() ) kann Ihnen eine Idee, wie lange Dinge nehmen. Zum Beispiel, wenn Sie etwas nach einer SQL-Operation protokollieren, sehen Sie so etwas in Ihrem Protokoll:

     17:37:48,325 INFO [sqlalchemy.engine.base.Engine.0x...048c] SELECT ... 17:37:48,326 INFO [sqlalchemy.engine.base.Engine.0x...048c] {<params>} 17:37:48,660 DEBUG [myapp.somemessage] 

    Wenn Sie myapp.somemessage direkt nach der Operation angemeldet myapp.somemessage , wissen Sie, dass es 334ms dauerte, um den SQL-Teil der Dinge zu vervollständigen.

    Logging SQL wird auch veranschaulichen, ob Dutzende / Hunderte von Abfragen ausgegeben werden, die besser in viel weniger Abfragen über Joins organisiert werden können. Bei der Verwendung des SQLAlchemy ORMs wird die "eager loading" -Funktion teilweise ( contains_eager() ) oder voll ( eagerload() , eagerload_all() ) automatisiert diese Aktivität, aber ohne das ORM bedeutet es einfach, Joins zu verwenden, so dass Ergebnisse über Mehrere Tabellen können in einer Ergebnismenge geladen werden, anstatt die Anzahl der Abfragen zu multiplizieren, wenn mehr Tiefe hinzugefügt wird (dh r + r*r2 + r*r2*r3 …)

    Wenn die Protokollierung zeigt, dass einzelne Abfragen zu lange dauern, benötigen Sie einen Ausfall, wie viel Zeit in der Datenbank verarbeitet wurde, die die Abfrage verarbeitet, Ergebnisse über das Netzwerk gesendet, vom DBAPI behandelt und schließlich von der SQLAlchemy-Ergebnismenge empfangen wird Und / oder ORM-Schicht. Jede dieser Etappen kann je nach Besonderheit ihre eigenen individuellen Engpässe präsentieren.

    Dazu müssen Sie Profiling verwenden, wie zB cProfile oder hotshot. Hier ist ein Dekorateur ich benutze:

     import cProfile as profiler import gc, pstats, time def profile(fn): def wrapper(*args, **kw): elapsed, stat_loader, result = _profile("foo.txt", fn, *args, **kw) stats = stat_loader() stats.sort_stats('cumulative') stats.print_stats() # uncomment this to see who's calling what # stats.print_callers() return result return wrapper def _profile(filename, fn, *args, **kw): load_stats = lambda: pstats.Stats(filename) gc.collect() began = time.time() profiler.runctx('result = fn(*args, **kw)', globals(), locals(), filename=filename) ended = time.time() return ended - began, load_stats, locals()['result'] 

    Um einen Abschnitt des Codes zu profilieren, legen Sie ihn in eine Funktion mit dem Dekorateur:

     @profile def go(): return Session.query(FooClass).filter(FooClass.somevalue==8).all() myfoos = go() 

    Die Ausgabe von Profiling kann verwendet werden, um eine Idee zu geben, wo die Zeit ausgegeben wird. Wenn Sie z. B. die ganze Zeit in cursor.execute() haben, ist das der DBAPI-Aufruf in der Datenbank, und es bedeutet, dass Ihre Abfrage optimiert werden soll, indem Sie entweder Indizes hinzufügen oder die Abfrage und / oder das zugrunde liegende Schema umstrukturieren. Für diese Aufgabe würde ich empfehlen, pgadmin zusammen mit seinem grafischen EXPLAIN-Dienstprogramm zu sehen, welche Art von Arbeit die Abfrage macht.

    Wenn Sie viele Tausende von Anrufen im Zusammenhang mit dem Abrufen von Zeilen sehen, kann es bedeuten, dass Ihre Abfrage mehr Zeilen als erwartet zurückgibt – ein kartesisches Produkt als Ergebnis einer unvollständigen Verknüpfung kann dieses Problem verursachen. Noch ein anderes Problem ist die Zeit, die in der Handhabung des Typs verbracht wird – ein SQLAlchemy-Typ, wie Unicode , führt String-Codierung / Decodierung auf Bindungsparameter und Ergebnisspalten durch, die in allen Fällen nicht benötigt werden.

    Die Ausgabe eines Profils kann ein wenig entmutigend sein, aber nach einigen Übungen sind sie sehr leicht zu lesen. Es war einmal jemand auf der Mailing-Liste behauptet Langsamkeit, und nachdem er ihn nach den Ergebnissen des Profils, konnte ich zeigen, dass die Geschwindigkeitsprobleme waren aufgrund der Netzwerk-Latenz – die Zeit in cursor.execute () sowie alle Python verbracht Methoden waren sehr schnell, während die Mehrheit der Zeit auf socket.receive () ausgegeben wurde.

    Wenn Sie sich ehrgeizig fühlen, gibt es auch ein ähnlicher Beispiel für SQLAlchemy Profiling innerhalb der SQLAlchemy Unit Tests, wenn Sie um http://www.sqlalchemy.org/trac/browser/sqlalchemy/trunk/test/aaa_profiling stoßen. Dort haben wir Tests mit Dekoratoren, die eine maximale Anzahl von Methodenaufrufen für bestimmte Operationen verwenden, behaupten, so dass, wenn etwas ineffizient eingecheckt wird, die Tests es zeigen wird (es ist wichtig zu beachten, dass in Python Funktionsaufrufe die höchsten haben Overhead einer Operation, und die Anzahl der Anrufe ist häufiger als nicht annähernd proportional zur Zeit verbracht). Von den Anmerkungen sind die "zoomark" -Tests, die ein Phantasie-"SQL-Capturing" -Schema verwenden, das den Overhead des DBAPI aus der Gleichung ausschneidet – obwohl diese Technik nicht wirklich notwendig ist für die Garten-Vielfalt Profiling.

    Es gibt ein äußerst nützliches Profiling Rezept auf der SQLAlchemy Wiki

    Mit ein paar kleinen Änderungen,

     from sqlalchemy import event from sqlalchemy.engine import Engine import time import logging logging.basicConfig() logger = logging.getLogger("myapp.sqltime") logger.setLevel(logging.DEBUG) @event.listens_for(Engine, "before_cursor_execute") def before_cursor_execute(conn, cursor, statement, parameters, context, executemany): context._query_start_time = time.time() logger.debug("Start Query:\n%s" % statement) # Modification for StackOverflow answer: # Show parameters, which might be too verbose, depending on usage.. logger.debug("Parameters:\n%r" % (parameters,)) @event.listens_for(Engine, "after_cursor_execute") def after_cursor_execute(conn, cursor, statement, parameters, context, executemany): total = time.time() - context._query_start_time logger.debug("Query Complete!") # Modification for StackOverflow: times in milliseconds logger.debug("Total Time: %.02fms" % (total*1000)) if __name__ == '__main__': from sqlalchemy import * engine = create_engine('sqlite://') m1 = MetaData(engine) t1 = Table("sometable", m1, Column("id", Integer, primary_key=True), Column("data", String(255), nullable=False), ) conn = engine.connect() m1.create_all(conn) conn.execute( t1.insert(), [{"data":"entry %d" % x} for x in xrange(100000)] ) conn.execute( t1.select().where(t1.c.data.between("entry 25", "entry 7800")).order_by(desc(t1.c.data)) ) 

    Ausgabe ist so etwas wie:

     DEBUG:myapp.sqltime:Start Query: SELECT sometable.id, sometable.data FROM sometable WHERE sometable.data BETWEEN ? AND ? ORDER BY sometable.data DESC DEBUG:myapp.sqltime:Parameters: ('entry 25', 'entry 7800') DEBUG:myapp.sqltime:Query Complete! DEBUG:myapp.sqltime:Total Time: 410.46ms 

    Dann, wenn du eine seltsam langsame Abfrage findest, kannst du die Abfrage-String nehmen, in den Parametern formatieren (kann der % String-Formatierungsoperator für psycopg2 mindestens getan werden), mit "EXPLAIN ANALYZE" präfixieren und die Abfrageplanausgabe schieben In http://explain.depesz.com/ (gefunden über diesen guten Artikel über PostgreSQL Leistung )

    Ich habe etwas Erfolg bei der Verwendung von cprofile und Blick auf die Ergebnisse in runnakerun. Das hat mir zumindest gesagt, welche Funktionen und Anrufe, wo lange dauern und wenn die Datenbank war das Problem. Die Dokumentation ist hier . Du brauchst wxpython. Die Präsentation ist gut, um dich zu beginnen.
    Es ist so einfach wie

     import cProfile command = """foo.run()""" cProfile.runctx( command, globals(), locals(), filename="output.profile" ) 

    Dann

    Python runnake.py output.profile

    Wenn Sie schauen, um Ihre Fragen zu optimieren, benötigen Sie postgrsql Profiling .

    Es lohnt sich auch, sich anzumelden, um die Abfragen aufzuzeichnen, aber es gibt keinen Parser dafür, dass ich weiß, um die lang laufenden Abfragen zu bekommen (und es wird nicht für gleichzeitige Anfragen nützlich sein).

     sqlhandler = logging.FileHandler("sql.log") sqllogger = logging.getLogger('sqlalchemy.engine') sqllogger.setLevel(logging.info) sqllogger.addHandler(sqlhandler) 

    Und stellen Sie sicher, dass Ihre create Engine-Anweisung echo = True hat.

    Als ich es tat, war es eigentlich mein Code, der das Hauptproblem war, also half die cprofile Sache.

    Python ist die beste Programmiersprache der Welt.