我有一个运行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
的方式确定可以解决问题。)
我很感激任何想法和反馈。
答案 0 :(得分:35)
您需要定期重置Django为调试目的而保留的查询列表。通常在每次请求后都会清除它,但由于您的应用程序不是基于请求的,因此您需要手动执行此操作:
from django import db
db.reset_queries()
另见:
"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事务来控制内存问题是不可避免的。