大型celery任务内存泄漏

6

我有一个庞大的celery任务,基本上是这样工作的:

 @task
 def my_task(id):
   if settings.DEBUG:
     print "Don't run this with debug on."
     return False

   related_ids = get_related_ids(id)

   chunk_size = 500

   for i in xrange(0, len(related_ids), chunk_size):
     ids = related_ids[i:i+chunk_size]
     MyModel.objects.filter(pk__in=ids).delete()
     print_memory_usage()

我还有一个manage.py命令,只需运行my_task(int(args [0]))即可将其排队或在命令行上运行。
在命令行中运行时,print_memory_usage()显示使用的内存量相对稳定。
当在celery中运行时,print_memory_usage()显示内存不断增加,直到进程被杀死(我正在使用具有1GB内存限制的Heroku,但其他主机会有类似的问题)。内存泄漏似乎对应于chunk_size;如果增加chunk_size,则每次打印时内存消耗会增加。这似乎表明celery本身正在记录查询,或者我的堆栈中的其他东西正在记录。
Celery是否在其他地方记录查询?
其他注意事项:
-DEBUG关闭。 -无论是使用RabbitMQ还是Amazon的SQS作为队列,都会发生这种情况。 -这在本地和Heroku上都发生(尽管由于拥有16 GB的RAM而在本地未被杀死)。 -任务实际上会做更多的事情,而不仅仅是删除对象。稍后,它通过MyModel.objects.get_or_create()创建新对象。这也表现出相同的行为(在celery下内存增长,在manage.py下不增长)。

尝试使用 itertools.islice(related_ids, i, i + chunk_size) 而不是 related_ids[i:i+chunk_size]。这可能不是唯一的因素,但这样做可以减少一些复制。 - Steve Howard
哪个Django版本?Django 1.4的“QuerySet.delete”在删除之前总是将实例加载到内存中。我建议尝试使用原始SQL的“DELETE”语句,看看会发生什么。 - Vasiliy Faronov
@VasiliyFaronov:即使对象超出范围,也是这样吗?此外,这并不能解释为什么在manage.py命令内部内存使用量保持不变,而在celery中却不是这样。 - GDorn
4个回答

2
一些过时的评论,但这可以帮助未来的人们。虽然最好的解决方案应该是跟踪问题的来源,但有时这是不可能的,因为问题的来源超出了我们的控制范围。在这种情况下,您可以在启动Celery工作进程时使用--max-memory-per-child选项。

这对于内存泄漏确实很有帮助。顺便提一下,如果超过限制,它不会终止正在运行的任务,而是在任务完成后终止工作进程。 - Miguel

2
这与celery无关,而是由New Relic的日志记录器消耗了所有内存。尽管将DEBUG设置为False,但它仍会将每个SQL语句存储在内存中以准备发送到其日志记录服务器。我不知道它是否仍然以这种方式工作,但它在任务完全完成之前不会刷新该内存。
解决方法是为每个ID块使用子任务,在有限数量的项目上执行删除操作。
当作为管理命令运行时,此问题没有问题,因为New Relic的日志记录器未集成到命令框架中。
其他解决方案试图减少分块操作的开销,这对于O(N)缩放问题没有帮助,或者强制celery任务在超过内存限制时失败(该功能在当时不存在,但可能最终可以通过无限重试来实现)。

0

你可以使用--autoscale n,0选项来运行worker。如果池的最小数量为0,celery将会杀死未使用的worker并释放内存。

但这不是一个好的解决方案。

Django的Collector使用了大量的内存 - 在删除之前,它会收集所有相关对象并首先删除它们。你可以在模型字段上设置on_delete为SET_NULL。

另一个可能的解决方案是按限制删除对象,例如每小时一些对象。这将降低内存使用率。

Django没有raw_delete。你可以使用原始SQL语句来实现。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接