数据库的Python/Django轮询存在内存泄漏
我有一个运行Django for database和memcache的Python脚本,但它作为一个独立的守护进程运行(即不响应Web服务器请求)。守护进程检查Django模型请求中状态为“status=status\u NEW”的对象,然后将其标记为“status\u WORKING”,并将其放入队列 许多进程(使用多进程包创建)将从队列中提取内容,并使用传递到队列的数据库的Python/Django轮询存在内存泄漏,python,django,memory-leaks,daemon,Python,Django,Memory Leaks,Daemon,我有一个运行Django for database和memcache的Python脚本,但它作为一个独立的守护进程运行(即不响应Web服务器请求)。守护进程检查Django模型请求中状态为“status=status\u NEW”的对象,然后将其标记为“status\u WORKING”,并将其放入队列 许多进程(使用多进程包创建)将从队列中提取内容,并使用传递到队列的pr.id处理请购单。我相信内存泄漏可能存在于以下代码中(但它可能存在于队列另一侧的“Worker”代码中,尽管这不太可能,因为
pr.id
处理请购单。我相信内存泄漏可能存在于以下代码中(但它可能存在于队列另一侧的“Worker”代码中,尽管这不太可能,因为即使在没有请求出现的情况下,内存大小也在增长——即当Worker都阻塞了Queue.get()时)
其中settings.DAEMON\u POLL\u WAIT=0.01
似乎如果我让它运行一段时间(即几天),Python进程将增长到无限大,最终系统将耗尽内存
这里发生了什么(或者我怎样才能知道),更重要的是,如何运行一个能够做到这一点的守护进程
我的第一个想法是更改函数的动态,特别是通过将对新请求对象的检查放入django.core.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\u NEW
创建请购单的流程可以执行cache.set('NEW\u requisions',1)
(或者,我们可以捕获正在创建新请购单的信号或Requisition.save()事件,然后在缓存中设置标志)
但是,我不确定我在这里提出的解决方案是否解决了内存问题(这可能与垃圾收集有关-因此,通过过程\u new\u requisions
的作用域可以解决这个问题)
非常感谢您的想法和反馈。守护进程的settings.py文件是否具有DEBUG=True
?如果是这样的话,Django会在内存中保存一条到目前为止运行的所有SQL的记录,这可能会导致内存泄漏。为了调试的目的,您需要定期重置Django保存的查询列表。通常,它在每次请求后都会被清除,但由于您的应用程序不是基于请求的,因此您需要手动执行此操作:
from django import db
db.reset_queries()
另见:
- 米科
Ohtamaa:
Django跟踪所有的查询
调试目的
(连接。查询)。这个名单是
在HTTP请求结束时重置。
但在独立模式下,没有
请求。所以你需要手动操作
每次之后重置为查询列表
工作周期
- -两者兼而有之
关于将
DEBUG
设置为False
,这一点始终很重要,以及
关于使用db.reset\u querys()
清除查询列表,
在像您这样的应用程序中非常重要
除了db.reset_querys()和DEBUG=False技巧之外,还有另一种方法:
只需生成另一个执行django查询并为队列提供数据的进程。这个过程将在它自己的内存上下文中工作,在执行任务后,它将释放回您的内存
我相信,有时(如果不是总是)不可避免地要通过执行大量django事务的长时间运行的进程来控制内存问题。我有很多数据处理要做,因此,我解决这个问题的方法是使用多处理,并使用池来抵消内存膨胀
为了保持简单,我只是定义了一些“全局”(顶级,不管Python中的术语是什么)函数,而不是试图让事情变得可以pickle
这里是抽象形式:
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
将多处理导入为mp
WORKERS=16#我有7个内核,分配了16个,因为处理是I/O绑定的
#这是一个全局函数
def工作者(参数):
#做事
将某物返回给要分析的回调
#这是一个全局函数
def worker_回调(worker_返回值):
#报告材料,或通过
#我的多进程启动在一个类中
def多进程启动程序(参数):
#以某种方式定义集合
尽管如此:
如果len(集合)==0:
打破
#吃一片
池_子_批=[]
对于范围内的(工人):
如果收藏:#只要收藏中还有东西
pool\u sub\u batch.append(collection.pop())
#启动一个池,仅限于切片
池大小=工人
如果len(池子批次)<工人:
池大小=len(池子批次)
池=mp.pool(进程=池大小)
对于池子批次中的子批次:
apply\u async(worker,args=(sub\u batch),callback=worker\u callback)
pool.close()
pool.join()
#循环,更多的切片
只是一个想法。。。您是否在settings.py中使用DEBUG=True运行?调试模式保存所有查询,这看起来肯定像内存泄漏:)嘿嘿。我的确是!我忘了。我已经关闭了它(并转移到缓存解决方案),内存泄漏似乎已经减少。很好的建议!它被启用;我把它禁用了。我还切换到了缓存标志检查,因此它不会不断轮询数据库。看看这两件事是否有用。谢谢这些都是可靠的参考资料-谢谢。似乎链接已经转移到:什么意思你的应用程序不是基于请求的?就像数据库是本地的,vs not?@User一样,您可以在请求-应答周期之外使用Diango ORMs,即用于在网站中生成HTTP响应以外的用途。例如,在长时间运行的脚本中。
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