Django多处理和数据库连接

时间:2011-11-23 13:18:11

标签: django multiprocessing

背景:

我正在使用一个使用Django和Postgres数据库的项目。我们也在使用mod_wsgi以防万一,因为我的一些网络搜索提到了它。在Web表单提交中,Django视图启动了一项需要花费大量时间的工作(超过用户想要等待的时间),因此我们在后台通过系统调用启动工作。现在运行的作业需要能够读取和写入数据库。因为这项工作需要很长时间,所以我们使用多处理来并行运行部分工作。

问题:

顶级脚本具有数据库连接,当它生成子进程时,似乎父级的连接可供子级使用。然后有一个例外,关于如何在查询之前调用SET TRANSACTION ISOLATION LEVEL。研究表明,这是因为尝试在多个进程中使用相同的数据库连接。我发现一个线程建议在子进程的开头调用connection.close(),以便Django在需要时自动创建一个新连接,因此每个子进程都有一个唯一的连接 - 即不共享。这对我不起作用,因为在子进程中调用connection.close()会导致父进程抱怨连接丢失。

其他调查结果:

我读过的一些东西似乎表明你不能真正做到这一点,并且多处理,mod_wsgi和Django不能很好地协同工作。我猜这似乎很难相信。

有些人建议使用芹菜,这可能是一个长期的解决方案,但我目前无法安装芹菜,等待一些批准程序,所以现在不能选择。

在SO和其他地方找到了关于持久数据库连接的几个参考资料,我认为这是一个不同的问题。

还找到了对psycopg2.pool和pgpool的引用以及关于bouncer的内容。不可否认,我对大部分内容都不了解,但我当然没有把它当作我正在寻找的东西。

目前的“解决方案”:

现在,我已经恢复了只是连续运行的东西,它可以工作,但比我想要的慢。

关于如何使用多处理并行运行的任何建议?好像我可以让父母和两个孩子都拥有与数据库的独立连接,事情就可以了,但我似乎无法得到这种行为。

谢谢,抱歉这个长度!

10 个答案:

答案 0 :(得分:63)

多处理在进程之间复制连接对象,因为它会分叉进程,因此会复制父进程的所有文件描述符。话虽这么说,与SQL服务器的连接只是一个文件,您可以在linux下的/ proc // fd / ....中看到它。任何打开的文件都将在分叉进程之间共享。您可以找到有关分叉here的更多信息。

我的解决方案只是在启动进程之前简单地关闭数据库连接,每个进程在需要时自动重新创建连接(在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]))   

答案 1 :(得分:15)

使用多个数据库时,应关闭所有连接。

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

答案 2 :(得分:3)

对于Python 3和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 __name__ == '__main__':
    multiprocessing.Process(target=db_worker)

请注意,如果没有django.setup(),我无法使用它。我猜测需要为多处理再次初始化一些东西。

答案 3 :(得分:2)

依次运行Django 测试用例时,出现“关闭连接”问题。除测试外,还有另一个过程在测试执行期间有意修改数据库。此过程在每个测试用例setUp()中启动。

一个简单的解决方法是从TransactionTestCase而不是TestCase继承我的测试类。这样可以确保实际上已写入数据库,并且其他进程对数据具有最新视图。

答案 4 :(得分:1)

(不是一个很好的解决方案,但可能是一种解决方法)

如果你不能使用芹菜,也许你可以实现自己的排队系统,基本上将任务添加到某个任务表并有一个常规的cron来挑选它们并处理? (通过管理命令)

答案 5 :(得分: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()。在你有机会刷新数据库之前,可能仍然有可能在线下的另一个用户完全向db发出请求。

说实话,你的fork可能不会直接调用命令,而是调用操作系统上的脚本以便生成 更安全,更智能 任务在自己的django shell中运行!

答案 6 :(得分:1)

您可以为Postgre提供更多资源,在Debian / Ubuntu中您可以编辑:

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

用你的postgre版本替换9.4。

以下是一些有用的行,应该使用示例值进行更新,名称不言自明:

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

注意不要过多地增加这些参数,因为它可能导致Postgre尝试获取比可用资源更多的错误。上面的例子在配备4核的Debian 8GB Ram机器上正常运行。

答案 7 :(得分:0)

如果您只需要I / O并行性而不处理并行性,则可以通过将进程切换到线程来避免此问题。取代

from multiprocessing import Process

from threading import Thread

Thread对象与Procsess

具有相同的界面

答案 8 :(得分:0)

如果您还使用连接池,则以下对我们有用,在分叉后强制关闭连接。以前似乎没有帮助。

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

connections[DEFAULT_DB_ALIAS].dispose()

答案 9 :(得分:0)

覆盖线程类并在线程结束时关闭所有数据库连接。波纹管代码对我有用:

class MyThread(Thread):
    def run(self):
        super().run()

        connections.close_all()

def myasync(function):
    def decorator(*args, **kwargs):
        t = MyThread(target=function, args=args, kwargs=kwargs)
        t.daemon = True
        t.start()

    return decorator

当你需要异步调用函数时:

@myasync
def async_function():
    ...