Hintergrund:
Ich arbeite an einem Projekt, das Django mit einer Postgres-Datenbank verwendet. Wir verwenden auch mod_wsgi, falls dies wichtig ist, da einige meiner Websuchen dies erwähnt haben. Beim Senden eines Webformulars startet die Django-Ansicht einen Job, der viel Zeit in Anspruch nimmt (mehr als der Benutzer warten möchte). Daher starten wir den Job über einen Systemaufruf im Hintergrund. Der Job, der jetzt ausgeführt wird, muss lesen und in die Datenbank schreiben können. Da dieser Job so lange dauert, verwenden wir Multiprocessing, um Teile davon parallel auszuführen.
Problem:
Das Skript der obersten Ebene verfügt über eine Datenbankverbindung. Wenn untergeordnete Prozesse ausgelöst werden, scheint die Verbindung des übergeordneten Elements für die untergeordneten Prozesse verfügbar zu sein. Dann gibt es eine Ausnahme darüber, wie SET TRANSACTION ISOLATION LEVEL vor einer Abfrage aufgerufen werden muss. Untersuchungen haben ergeben, dass dies auf den Versuch zurückzuführen ist, dieselbe Datenbankverbindung in mehreren Prozessen zu verwenden. Ein Thread, den ich gefunden habe, schlug vor, connection.close () zu Beginn der untergeordneten Prozesse aufzurufen, damit Django automatisch eine neue Verbindung erstellt, wenn eine benötigt wird, und daher jeder untergeordnete Prozess eine eindeutige Verbindung hat - dh nicht gemeinsam genutzt wird. Dies hat bei mir nicht funktioniert, da der Aufruf von connection.close () im untergeordneten Prozess dazu führte, dass sich der übergeordnete Prozess beschwerte, dass die Verbindung unterbrochen wurde.
Weitere Ergebnisse:
Einige Dinge, die ich gelesen habe, scheinen darauf hinzudeuten, dass man das nicht wirklich kann und dass Multiprocessing, mod_wsgi und Django nicht gut zusammenspielen. Das scheint nur schwer zu glauben, denke ich.
Einige schlugen vor, Sellerie zu verwenden, was eine langfristige Lösung sein könnte, aber ich kann Sellerie derzeit nicht installieren, bis einige Genehmigungsverfahren abgeschlossen sind. Daher ist dies derzeit keine Option.
In SO und anderswo wurden mehrere Referenzen zu dauerhaften Datenbankverbindungen gefunden, die meines Erachtens ein anderes Problem darstellen.
Auch Verweise auf psycopg2.pool und pgpool und etwas über Türsteher gefunden. Zugegeben, ich habe das meiste, was ich darüber las, nicht verstanden, aber es hat mich mit Sicherheit nicht als das herausgesprungen, wonach ich gesucht habe.
Aktuelle "Work-Around":
Im Moment habe ich mich wieder darauf konzentriert, Dinge nur seriell auszuführen, und es funktioniert, ist aber langsamer als ich es gerne hätte.
Irgendwelche Vorschläge, wie ich Multiprocessing verwenden kann, um parallel zu laufen? Es scheint, als ob die Eltern und zwei Kinder unabhängige Verbindungen zur Datenbank haben könnten. Die Dinge wären in Ordnung, aber ich kann dieses Verhalten anscheinend nicht verstehen.
Danke und Entschuldigung für die Länge!
quelle
django.db.connections.close_all()
, um alle Verbindungen mit einem Anruf zu schließen.Wenn Sie mehrere Datenbanken verwenden, sollten Sie alle Verbindungen schließen.
from django import db for connection_name in db.connections.databases: db.connections[connection_name].close()
BEARBEITEN
Bitte verwenden Sie dasselbe wie @lechup, um alle Verbindungen zu schließen (nicht sicher, seit welcher Django-Version diese Methode hinzugefügt wurde):
from django import db db.connections.close_all()
quelle
alias
oderinfo
in demfor
Schleifenkörper, wenndb
oderclose_connection()
Stützen , dass.Für Python 3 und Django 1.9 hat dies für mich funktioniert:
import multiprocessing import django django.setup() # Must call setup def db_worker(): for name, info in django.db.connections.databases.items(): # Close the DB connections django.db.connection.close() # Execute parallel code here if __name__ == '__main__': multiprocessing.Process(target=db_worker)
Beachten Sie, dass ich dies ohne django.setup () nicht zum Laufen bringen könnte. Ich vermute, dass etwas für die Mehrfachverarbeitung erneut initialisiert werden muss.
quelle
setup
django verwenden.db.connections.databases.items()
- sie schließt die Verbindung nur mehrmals.db.connections.close_all()
funktioniert gut, solange es die Worker-Funktion genannt wird.Ich hatte Probleme mit der "geschlossenen Verbindung", als ich Django -Testfälle nacheinander ausführte. Zusätzlich zu den Tests gibt es einen weiteren Prozess, der die Datenbank während der Testausführung absichtlich ändert. Dieser Vorgang wird in jedem Testfall setUp () gestartet.
Eine einfache Lösung bestand darin, meine Testklassen von
TransactionTestCase
statt zu erbenTestCase
. Dadurch wird sichergestellt, dass die Datenbank tatsächlich geschrieben wurde und der andere Prozess eine aktuelle Ansicht der Daten hat.quelle
(keine gute Lösung, aber eine mögliche Problemumgehung)
Wenn Sie keinen Sellerie verwenden können, könnten Sie vielleicht Ihr eigenes Warteschlangensystem implementieren, indem Sie einer Aufgabentabelle Aufgaben hinzufügen und einen normalen Cron haben, der sie aufnimmt und verarbeitet? (über einen Verwaltungsbefehl)
quelle
Hey, ich bin auf dieses Problem gestoßen und konnte es durch Ausführen der folgenden Schritte beheben (wir implementieren ein System mit eingeschränkten Aufgaben).
task.py
from django.db import connection def as_task(fn): """ this is a decorator that handles task duties, like setting up loggers, reporting on status...etc """ connection.close() # this is where i kill the database connection VERY IMPORTANT # This will force django to open a new unique connection, since on linux at least # Connections do not fare well when forked #...etc
ScheduledJob.py
from django.db import connection def run_task(request, job_id): """ Just a simple view that when hit with a specific job id kicks of said job """ # your logic goes here # ... processor = multiprocessing.Queue() multiprocessing.Process( target=call_command, # all of our tasks are setup as management commands in django args=[ job_info.management_command, ], kwargs= { 'web_processor': processor, }.items() + vars(options).items()).start() result = processor.get(timeout=10) # wait to get a response on a successful init # Result is a tuple of [TRUE|FALSE,<ErrorMessage>] if not result[0]: raise Exception(result[1]) else: # THE VERY VERY IMPORTANT PART HERE, notice that up to this point we haven't touched the db again, but now we absolutely have to call connection.close() connection.close() # we do some database accessing here to get the most recently updated job id in the database
Um Rennbedingungen (mit mehreren gleichzeitigen Benutzern) zu vermeiden, ist es am besten, database.close () so schnell wie möglich aufzurufen, nachdem Sie den Vorgang abgebrochen haben. Möglicherweise besteht immer noch die Möglichkeit, dass ein anderer Benutzer irgendwo auf der ganzen Linie eine Anfrage an die Datenbank stellt, bevor Sie die Datenbank leeren können.
Um ehrlich zu sein, wäre es wahrscheinlich sicherer und intelligenter , wenn Ihre Gabel den Befehl nicht direkt aufruft, sondern stattdessen ein Skript auf dem Betriebssystem aufruft, damit die erzeugte Aufgabe in einer eigenen Django-Shell ausgeführt wird!
quelle
Sie könnten Postgre mehr Ressourcen geben, in Debian / Ubuntu können Sie Folgendes bearbeiten:
indem Sie 9.4 durch Ihre Postgre-Version ersetzen.
Hier sind einige nützliche Zeilen, die mit Beispielwerten aktualisiert werden sollten. Namen sprechen für sich:
max_connections=100 shared_buffers = 3000MB temp_buffers = 800MB effective_io_concurrency = 300 max_worker_processes = 80
Achten Sie darauf, diese Parameter nicht zu stark zu erhöhen, da dies zu Fehlern führen kann, wenn Postgre versucht, mehr Ressourcen als verfügbar zu verwenden. Die obigen Beispiele funktionieren einwandfrei auf einem Debian 8 GB Ram-Computer mit 4 Kernen.
quelle
Wenn Sie lediglich E / A-Parallelität benötigen und keine Parallelität verarbeiten, können Sie dieses Problem vermeiden, indem Sie Ihre Prozesse auf Threads umstellen. Ersetzen
from multiprocessing import Process
mit
from threading import Thread
Das
Thread
Objekt hat die gleiche Schnittstelle wieProcsess
quelle
Wenn Sie auch Verbindungspooling verwenden, hat das Folgende für uns funktioniert und die Verbindungen nach dem Verzweigen zwangsweise geschlossen. Vorher schien nicht zu helfen.
from django.db import connections from django.db.utils import DEFAULT_DB_ALIAS connections[DEFAULT_DB_ALIAS].dispose()
quelle