Djangoマルチプロセッシングとデータベース接続


83

バックグラウンド:

私はPostgresデータベースでDjangoを使用するプロジェクトに取り組んでいます。私のWeb検索のいくつかがそれについて言及しているので、重要な場合に備えてmod_wsgiも使用しています。Webフォームの送信時に、Djangoビューはかなりの時間(ユーザーが待ちたいと思うよりも長い時間)かかるジョブを開始するため、バックグラウンドでシステムコールを介してジョブを開始します。現在実行中のジョブは、データベースの読み取りと書き込みができる必要があります。このジョブには非常に時間がかかるため、マルチプロセッシングを使用してその一部を並行して実行します。

問題:

最上位のスクリプトにはデータベース接続があり、子プロセスを生成すると、親の接続が子に利用可能であるように見えます。次に、クエリの前にSET TRANSACTION ISOLATIONLEVELを呼び出す方法について例外があります。調査によると、これは複数のプロセスで同じデータベース接続を使用しようとしたためです。私が見つけた1つのスレッドは、子プロセスの開始時にconnection.close()を呼び出すことを提案しました。これにより、Djangoは必要なときに新しい接続を自動的に作成するため、各子プロセスには一意の接続があります。つまり、共有されません。子プロセスでconnection.close()を呼び出すと、親プロセスが接続が失われたと文句を言うため、これは機能しませんでした。

その他の調査結果:

私が読んだいくつかのことは、あなたが実際にこれを行うことができないこと、そしてマルチプロセッシング、mod_wsgi、およびDjangoが一緒にうまく機能しないことを示しているようでした。それは私が推測することを信じるのは難しいようです。

長期的な解決策となる可能性のあるセロリの使用を提案する人もいましたが、承認プロセスが保留されているため、現時点ではセロリをインストールできません。

永続的なデータベース接続について、SOや他の場所でいくつかの参照が見つかりましたが、これは別の問題であると私は信じています。

また、psycopg2.poolとpgpoolへの参照、および用心棒に関する何かが見つかりました。確かに、私はそれらについて読んでいることのほとんどを理解していませんでしたが、それは確かに私が探していたものとして私に飛び出しませんでした。

現在の「回避策」:

今のところ、私は物事を連続して実行することに戻りました、そしてそれは動作しますが、私が望むより遅いです。

マルチプロセッシングを使用して並列実行する方法に関する提案はありますか?親と2人の子がすべてデータベースに独立して接続できるとしたら、問題はないようですが、その動作を取得できないようです。

ありがとう、そして長さをお詫びします!

回答:


70

マルチプロセッシングは、プロセスをフォークするため、プロセス間で接続オブジェクトをコピーします。したがって、親プロセスのすべてのファイル記述子をコピーします。そうは言っても、SQLサーバーへの接続は単なるファイルであり、Linuxの/ proc // fd / ....で確認できます。開いているファイルはすべてフォークされたプロセス間で共有されます。フォークの詳細については、こちらをご覧ください

私の解決策は、プロセスを起動する直前にデータベース接続を閉じるだけでした。各プロセスは、必要なときに接続自体を再作成します(django 1.4でテスト済み)。

from django import db
db.connections.close_all()
def db_worker():      
    some_paralell_code()
Process(target = db_worker,args = ())

Pgbouncer / pgpoolは、マルチプロセッシングの意味でスレッドに接続されていません。これは、リクエストごとに接続を閉じないための解決策です=高負荷時にpostgresへの接続を高速化します。

更新:

データベース接続の問題を完全に取り除くには、データベースに接続されているすべてのロジックをdb_workerに移動するだけです-引数としてQueryDictを渡したいと思いました... IDのリストを渡すだけです... QueryDictとvalues_list( 'id'、flat =を参照してくださいTrue)、そしてそれをリストに変えることを忘れないでください!db_workerに渡す前のlist(QueryDict)。そのおかげで、モデルデータベース接続をコピーしません。

def db_worker(models_ids):        
    obj = PartModelWorkerClass(model_ids) # here You do Model.objects.filter(id__in = model_ids)
    obj.run()


model_ids = Model.objects.all().values_list('id', flat=True)
model_ids = list(model_ids) # cast to list
process_count = 5
delta = (len(model_ids) / process_count) + 1

# do all the db stuff here ...

# here you can close db connection
from django import db
db.connections.close_all()

for it in range(0:process_count):
    Process(target = db_worker,args = (model_ids[it*delta:(it+1)*delta]))   

クエリセットから自己回答型の質問へのIDの受け渡しについて少し説明していただけますか?
Jharwood 2012

1
マルチプロセッシングは、プロセスをフォークするため、プロセス間で接続オブジェクトをコピーします。したがって、親プロセスのすべてのファイル記述子をコピーします。そうは言っても、mysqlサーバーへの接続は単なるファイルであり、Linuxの/ proc / <PID> / fd / ....の下で見ることができます。開いているファイルはすべてフォークされたプロセスAFAIK間で共有されます。stackoverflow.com/questions/4277289/...
ヴラド-ardelean

1
それはスレッドにも当てはまりますか?例えば。メインスレッドのdbconnを閉じてから、各スレッドのdbにアクセスします。各スレッドは、独自の接続を取得しますか?
James Lin

1
を使用django.db.connections.close_all()して、1回の呼び出しですべての接続を閉じる必要があります。
デニスマリノフスキー2016

1
うーん...これはdjangoの人々の間の非常に興味深い話です:code.djangoproject.com/ticket/20562多分それはこのトピックにいくつかの光を当てるでしょうか?基本的に、接続は「フォーク可能ではありません」...各プロセスには独自の接続が必要です。
lechup 2017

18

複数のデータベースを使用する場合は、すべての接続を閉じる必要があります。

from django import db
for connection_name in db.connections.databases:
    db.connections[connection_name].close()

編集

すべての接続を閉じるために言及された@lechupと同じものを使用してください(このメソッドが追加されたdjangoバージョン以降はわかりません):

from django import db
db.connections.close_all()

9
これはdb.close_connectionを複数回呼び出すだけです
ibz 2014

2
エイリアスや情報をどこにも使用せずにこれがどのように機能するかわかりません。
RemcoGerlich 2016

これは...動作しません。@Mounir、またはそれをサポートする場合は、ループ本体で使用するように変更するalias必要infoがありforます。dbclose_connection()
0atman 2017年

5

Python3とDjango1.9の場合、これが私にとってうまくいったことです。

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)

django.setup()がないと、これを機能させることができなかったことに注意してください。マルチプロセッシングのために何かを再度初期化する必要があると思います。


ありがとう!これは私にとってはうまくいき、おそらくdjangoの新しいバージョンでは今受け入れられている答えになるはずです。
krischan 2017

djangoの方法は、スタンドアロンのラッパースクリプトを作成するのではなく、管理コマンドを作成することです。管理コマンドを使用しない場合setupdjangoを使用する必要があります。
lechup

2
forループは実際には何もしていません db.connections.databases.items()-接続を数回閉じているだけです。db.connections.close_all()ワーカー関数と呼ばれている限り、正常に機能します。
tao_oat

2

Djangoテストケースを順番に実行すると、「接続が閉じられました」という問題が発生しました。テストに加えて、テストの実行中にデータベースを意図的に変更する別のプロセスもあります。このプロセスは、各テストケースsetUp()で開始されます。

簡単な修正は、のTransactionTestCase代わりにからテストクラスを継承することでしたTestCase。これにより、データベースが実際に作成され、他のプロセスがデータを最新の状態で表示できるようになります。


1

(優れた解決策ではありませんが、考えられる回避策)

セロリを使用できない場合は、独自のキューイングシステムを実装して、基本的にタスクをタスクテーブルに追加し、それらを選択して処理する通常のcronを使用できますか?(管理コマンドを介して)


おそらく-そのレベルの複雑さを回避することを望んでいましたが、それが唯一の解決策である場合、私はその道を進む必要があるかもしれません-提案に感謝します。セロリがベストアンサーですか?もしそうなら、私はそれを取得するためにプッシュすることができるかもしれませんが、それは時間がかかります。一台のマシン上で並列処理ではなく、多分それのそれとの経験の私の不足。..として分散処理とI准セロリ
daroo

2
セロリは、要求-応答サイクル外で必要な処理に適してい

1

ちょっと私はこの問題に遭遇し、以下を実行することによってそれを解決することができました(私たちは限られたタスクシステムを実装しています)

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

正直なところ、(複数の同時ユーザーがいる)競合状態を防ぐには、プロセスをフォークした後、できるだけ早くdatabase.close()を呼び出すのが最善です。ただし、データベースをフラッシュする前に、別のユーザーがデータベースに完全にリクエストを送信する可能性があります。

正直なところ、フォークでコマンドを直接呼び出さずに、オペレーティングシステムでスクリプトを呼び出して、生成されたタスクが独自のdjangoシェルで実行されるようにする方が安全で賢明です。


以前ではなくフォークの内側を閉じるというあなたのアイデアを使用して、ワーカー関数に追加するデコレータを作成しました。
Rebs

1

Postgreにもっと多くのリソースを与えることができます。Debian/ Ubuntuでは編集できます:

nano /etc/postgresql/9.4/main/postgresql.conf

9.4をpostgreバージョンに置き換えることによって。

これを行うためにサンプル値で更新する必要があるいくつかの便利な行があります。名前はそれ自体を物語っています:

max_connections=100
shared_buffers = 3000MB
temp_buffers = 800MB
effective_io_concurrency = 300
max_worker_processes = 80

Postgreが利用可能なリソースよりも多くのリソースを取得しようとするとエラーが発生する可能性があるため、これらのパラメーターをブーストしすぎないように注意してください。上記の例は、4コアを搭載したDebian 8GBRAMマシンで正常に実行されています。


0

必要なのがI / O並列処理であり、処理並列処理ではない場合は、プロセスをスレッドに切り替えることでこの問題を回避できます。交換

from multiprocessing import Process

from threading import Thread

Threadオブジェクトは、同じインターフェースを有していますProcsess


0

接続プーリングも使用している場合は、次のように機能し、フォークされた後に接続を強制的に閉じます。以前は役に立たなかったようです。

from django.db import connections
from django.db.utils import DEFAULT_DB_ALIAS

connections[DEFAULT_DB_ALIAS].dispose()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.