При опросе базы данных Python / Django произошла утечка памяти - PullRequest
15 голосов
/ 26 февраля 2010

У меня есть сценарий Python, выполняющий Django для базы данных и memcache, но он заметно работает как автономный демон (то есть не отвечает на запросы веб-сервера). Демон проверяет реквизицию модели Django для объектов с status=STATUS_NEW, затем помечает их как STATUS_WORKING и помещает их в очередь.

Ряд процессов (созданных с использованием многопроцессорного пакета) извлекают данные из очереди и выполняют работу над заявкой с pr.id, который был передан в очередь. Я полагаю, что утечка памяти, вероятно, заключается в следующем коде (но это может быть в «рабочем» коде на другой стороне очереди, хотя это маловероятно, потому что размер памяти увеличивается, даже когда не появляются никакие реквизиты - т.е. когда все работники блокируют 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 ]

36 голосов
/ 26 февраля 2010

Вам необходимо регулярно сбрасывать список запросов, которые Django хранит для целей отладки. Обычно он очищается после каждого запроса, но поскольку ваше приложение не основано на запросах, вам нужно сделать это вручную:

from django import db

db.reset_queries()

Смотри также:

  • "Отладка утечки памяти в Django с помощью TrackRefs и Guppy" by Mikko Ohtamaa:

    Django отслеживает все запросы для цели отладки (connection.queries). Этот список сбрасывается в конце HTTP-запроса. Но в автономном режиме нет Запросы. Так что нужно вручную сбросить список запросов после каждого рабочий цикл

  • "Почему у Джанго утечка памяти?" в Django FAQ - он говорит как о настройке DEBUG в False, что всегда важно, и об очистке списка запросов, используя db.reset_queries(), важно в таких приложениях, как ваше.

5 голосов
/ 26 февраля 2010

Имеет ли файл settings.py для процесса-демона DEBUG = True? Если это так, Django хранит в памяти записи всех SQL, которые он до сих пор выполнял, что может привести к утечке памяти.

2 голосов
/ 17 сентября 2013

У меня было много перебора данных, поэтому мое решение этой проблемы заключалось в том, чтобы использовать многопроцессорную обработку и использование пулов для противодействия раздуванию памяти.

Для простоты я просто определил некоторые "глобальные" (на верхнем уровне, независимо от того, какой термин используется в 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
1 голос
/ 11 марта 2011

Помимо db.reset_queries () и DEBUG = False tricks, существует еще один подход: Просто создайте другой процесс, который выполняет запрос django и передает очередь. Этот процесс будет работать в своем собственном контексте памяти, и после выполнения вашей задачи он освободит вашу память.

Я считаю, что иногда (если не всегда) неизбежно контролировать проблемы с памятью с помощью долго выполняющегося процесса, который выполняет тяжелые транзакции django.

...