Python / Django数据库轮询有内存泄漏

时间:2010-02-25 22:08:13

标签: python django memory-leaks daemon

我有一个运行Django的Python脚本用于数据库和内存缓存,但它特别是作为一个独立的守护进程运行(即没有响应webserver请求)。守护进程检查Django模型申请单中是否有status=STATUS_NEW的对象,然后将它们标记为STATUS_WORKING并将它们放入队列中。

许多进程(使用多进程包创建)将从队列中取出并使用传递给队列的pr.id对申请单进行操作。我相信内存泄漏可能在以下代码中(但它可能在Queue另一端的'Worker'代码中虽然这不太可能,因为即使没有请购单即将出现,内存大小也在增长 - 即当工作者都在Queue.get()上阻塞时。

from requisitions.models import Requisition # our Django model
from multiprocessing import Queue

while True:
    # Wait for "N"ew requisitions, then pop them into the queue.
    for pr in Requisition.objects.all().filter(status=Requisition.STATUS_NEW):
        pr.set_status(pr.STATUS_WORKING)
        pr.save()
        queue.put(pr.id)

    time.sleep(settings.DAEMON_POLL_WAIT)

settings.DAEMON_POLL_WAIT=0.01

似乎如果我让它运行一段时间(即几天),Python进程将增长到无限大,最终系统将耗尽内存。

这里发生了什么(或者我怎么能找到),更重要的是 - 你怎么能运行这样做的守护进程?

我的第一个想法是改变功能的动态,特别是通过检查新的申请单对象到django.core.cache cache,即

from django.core.cache import cache

while True:
    time.sleep(settings.DAEMON_POLL_WAIT)
    if cache.get('new_requisitions'):
       # Possible race condition
       cache.clear()
       process_new_requisitions(queue)

 def process_new_requisitions(queue):
    for pr in Requisition.objects.all().filter(status=Requisition.STATUS_NEW):
        pr.set_status(pr.STATUS_WORKING)
        pr.save()
        queue.put(pr.id)

使用status=STATUS_NEW创建申请单的流程可以执行cache.set('new_requisitions', 1)(或者我们可以捕获正在创建新申请单的信号或Requisition.save()事件,然后设置标记来自那里的缓存。)

但是我不确定我在这里提出的解决方案是否解决了内存问题(可能与垃圾收集有关 - 所以通过process_new_requisitions的方式确定可以解决问题。)

我很感激任何想法和反馈。

4 个答案:

答案 0 :(得分:35)

您需要定期重置Django为调试目的而保留的查询列表。通常在每次请求后都会清除它,但由于您的应用程序不是基于请求的,因此您需要手动执行此操作:

from django import db

db.reset_queries()

另见:

    Mikko的
  • "Debugging Django memory leak with TrackRefs and Guppy" Ohtamaa:

      

    Django会跟踪所有查询   调试目的   (connection.queries)。这个清单是   在HTTP请求结束时重置。   但在独立模式下,没有   要求。所以你需要手动   在每个之后重置为查询列表   工作周期

  • "Why is Django leaking memory?" in Django FAQ - 它同时谈到 关于将DEBUG设置为False,这一直很重要,并且 关于使用db.reset_queries()清除查询列表, 在像你这样的应用程序中很重要。

答案 1 :(得分:5)

守护程序进程的settings.py文件是否有DEBUG = True?如果是这样,Django会在内存中记录到目前为止运行的所有SQL,这可能会导致内存泄漏。

答案 2 :(得分:2)

我有很多数据需要处理,因此,我对此问题的解决方案是使用多处理,并使用池来抵消正在发生的任何内存膨胀。

为了简单起见,我只是定义了一些“全局”(顶层,无论Python中的术语是什么)功能,而不是试图使事情变得容易。

这是抽象形式:

import multiprocessing as mp

WORKERS = 16 # I had 7 cores, allocated 16 because processing was I/O bound

# this is a global function
def worker(params):
  # do stuff
  return something_for_the_callback_to_analyze

# this is a global function
def worker_callback(worker_return_value):
  # report stuff, or pass

# My multiprocess_launch was inside of a class
def multiprocess_launcher(params):
  # somehow define a collection
  while True:
    if len(collection) == 0:
      break
    # Take a slice
    pool_sub_batch = []
    for _ in range(WORKERS):
      if collection: # as long as there's still something in the collection
        pool_sub_batch.append( collection.pop() )
    # Start a pool, limited to the slice
    pool_size = WORKERS
    if len(pool_sub_batch) < WORKERS:
      pool_size = len(pool_sub_batch)
    pool = mp.Pool(processes=pool_size)
    for sub_batch in pool_sub_batch:
      pool.apply_async(worker, args = (sub_batch), callback = worker_callback)
    pool.close()
    pool.join()
    # Loop, more slices

答案 3 :(得分:1)

除了db.reset_queries()和DEBUG = False技巧之外,还有另一种方法: 只产生另一个执行django查询并提供队列的进程。这个过程可以在自己的内存环境中工作,在执行完任务后,它会释放你的记忆。

我相信有时候(如果不是总是)通过长时间运行的进程执行繁重的django事务来控制内存问题是不可避免的。