web-dev-qa-db-ja.com

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

バックグラウンド:

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

問題:

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

その他の調査結果:

私が読んだものの中には、あなたが実際にこれを行うことができないことを示しているように見え、マルチプロセッシング、mod_wsgi、およびDjangoが一緒にうまく動作しない。

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

SOおよびその他の場所で永続データベース接続に関するいくつかの参照を見つけましたが、これは別の問題だと思います。

また、psycopg2.poolとpgpoolへの参照、およびバウンサーに関する情報も見つかりました。確かに、私はそれらについて読んでいたもののほとんどを理解していませんでしたが、それは確かに私が探していたものであるとして私に飛び出しませんでした。

現在の「回避策」:

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

マルチプロセッシングを使用して並列実行する方法に関する提案はありますか?親と2人の子がすべてデータベースへの独立した接続を持つことができるように思えますが、物事は大丈夫ですが、私はその動作を得ることができないようです。

ありがとう、そして長さでごめんなさい!

78
daroo

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

私の解決策は、プロセスを起動する直前に単にdb接続を閉じるだけで、各プロセスは必要なときに接続自体を再作成します(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)、リストに入れることを忘れないでください! list(QueryDict)db_workerに渡す前。そのおかげで、モデルのデータベース接続をコピーしません。

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]))   
65
lechup

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

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

[〜#〜] edit [〜#〜]

すべての接続を閉じるために言及した@lechupと同じものを使用してください(このメソッドが追加されたDjangoバージョン以来不明):

from Django import db
db.connections.close_all()
16
Mounir

Python 3 and Django 1.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 __== '__main__':
    multiprocessing.Process(target=db_worker)

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

2
Kevin Nasto

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

簡単な修正は、TransactionTestCaseではなく TestCase からテストクラスを継承することでした。これにより、データベースが実際に書き込まれ、他のプロセスがデータに関する最新のビューを持つようになります。

2
Juuso Ohtonen

(素晴らしい解決策ではありませんが、可能な回避策)

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

1
second

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 8GB Ramマシンで正常に動作しています。

1
user4453877

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

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()を呼び出すのが最善です。ただし、データベースをフラッシュする機会が得られる前に、別のユーザーが完全にdbにリクエストを送信する可能性があります。

正直なところ、フォークでコマンドを直接呼び出さずに、代わりにスクリプトを呼び出すと、safeer and smarterになります。オペレーティングシステム上で、生成されたタスクが独自のDjango= Shell!

1
Mike McMahon

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

from multiprocessing import Process

from threading import Thread

Threadオブジェクトには、Procsessと同じインターフェースがあります

0
Zags