Как движок приложения (python) управляет памятью между запросами (превышен предел мягкой частной памяти)

У меня время от времени возникает Exceeded soft private memory limit ошибка в большом количестве обработчиков запросов в движке приложений. Я понимаю, что эта ошибка означает, что объем ОЗУ, используемый экземпляром, превысил выделенный объем, и как это вызывает отключение экземпляра.

Я хотел бы понять, каковы могут быть возможные причины ошибки, и для начала я хотел бы понять, как экземпляры python движка приложений должны управлять памятью. Мои элементарные предположения были:

  1. Экземпляр F2 начинается с 256 МБ
  2. Когда он запускается, он загружает мой код приложения - скажем, 30 МБ
  3. When it handles a request it has 226 MB available
    • so long as that request does not exceed 226 MB (+ margin of error) the request completes w/o error
    • если он превышает 226 МБ + маржа, экземпляр завершает запрос, регистрирует ошибку «Превышено мягкое ограничение частной памяти», затем завершает работу - теперь вернитесь к шагу 1
  4. Когда этот запрос возвращается, вся используемая им память освобождается, т.е. неиспользуемая оперативная память возвращается к 226 МБ
  5. Шаги 3-4 повторяются для каждого запроса, переданного экземпляру, бесконечно.

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

а) Происходит ли шаг №4?

б) Что могло привести к тому, что этого не произошло? или полностью не получиться? например как могла утечка памяти между запросами?

c) Может ли хранение в переменных уровня модуля вызвать утечку памяти? (Я не намеренно использую переменные уровня модуля таким образом)

г) Какие инструменты / методы я могу использовать для получения дополнительных данных? Например. измерить использование памяти при входе в обработчик запросов?

В ответах / комментариях, по возможности, ссылайтесь на документацию gae.

[править] Дополнительная информация: мое приложение сконфигурировано как threadsafe: false. Если это имеет отношение к ответу, укажите, пожалуйста, что это такое. В ближайшее время планирую перейти на threadsafe: true.

[править] Уточнение: Этот вопрос касается ожидаемого поведения gae в отношении управления памятью. Таким образом, хотя такие предложения, как «позвоните gc.collect()», вполне могут быть частичным решением связанных проблем, они не дают полного ответа на этот вопрос. До тех пор, пока я не пойму, как ожидается поведение gae, использование gc.collect() будет для меня ощущаться как программирование вуду.

Наконец: если у меня все это задом наперед, я заранее извиняюсь - я действительно не могу найти много полезной информации по этому поводу, поэтому в основном предполагаю ..


person tom    schedule 06.10.2015    source источник
comment
Не могли бы вы найти какие-нибудь циклические ссылки, которые можно было бы улучшить с помощью Weakref?   -  person JL Peyret    schedule 07.10.2015
comment
Первым делом я бы поставил вызов gc.collect в конец каждого обработчика запросов, а затем следил за ним.   -  person Tim Hoffman    schedule 10.10.2015
comment
@TimHoffman, спасибо, но на самом деле это не помогает мне понять, как предполагается работать. Возможно, произошла утечка памяти, из-за которой какой-то объект не подлежит сборке мусора.   -  person tom    schedule 12.10.2015
comment
К сожалению, без кода сложно давать советы. Я использую appengine с 2008 года и редко сталкиваюсь с проблемами памяти и широко использую ndb и кеширование. Я бы последовал рекомендациям Алексея, попробовав ndb с отключенным кешированием.   -  person Tim Hoffman    schedule 13.10.2015
comment
@TimHoffman, спасибо, но, к сожалению, я вижу эти ошибки, которые, по-видимому, случайным образом распределяются по широкому спектру обработчиков запросов, поэтому на самом деле нет никакого практического способа сделать это. Поэтому я придерживаюсь принципа понимания ожидаемого поведения и оттуда работаю. Я использую gae с 2009 года и раньше не видел этой ошибки. Но в последнее время я получаю примерно 1 в час, при этом экземпляры (f2s) обслуживают где-то между 200 и 1500 запросами до исчерпания памяти. Какие инструменты вы рекомендуете для профилирования памяти?   -  person tom    schedule 13.10.2015
comment
помните, что нехватка памяти может в конечном итоге повлиять на любой запрос, хотя любой запрос не может быть причиной.   -  person Tim Hoffman    schedule 14.10.2015
comment
Что касается профилирования памяти, этот пост предлагает самый последний рабочий профилировщик stackoverflow.com/questions/30742104/   -  person Tim Hoffman    schedule 14.10.2015


Ответы (3)


Интерпретатор Python в App Engine не делает ничего особенного с точки зрения управления памятью по сравнению с любым другим стандартным интерпретатором Python. Так, в частности, нет ничего особенного, что происходит «по запросу», как, например, ваш гипотетический шаг 4. Скорее, как только счетчик ссылок на какой-либо объект уменьшается до нуля, интерпретатор Python восстанавливает эту память (модуль gc предназначен только для обработки с мусором циклы - когда группа объектов никогда не получает счетчик ссылок до нуля, потому что они ссылаются друг на друга, даже если на них нет доступной внешней ссылки).

Таким образом, память может легко «просочиться» (на практике, хотя технически это не утечка) «между запросами», если вы используете любую глобальную переменную - указанные переменные переживут экземпляр класса обработчика и его (например) get метод - т.е. ваш пункт (c), хотя вы говорите, что не делаете этого.

После того, как вы объявите свой модуль threadsafe, экземпляр может одновременно обслуживать несколько запросов (вплоть до того, что вы установили как max_concurrent_requests в разделе automatic_scaling файла конфигурации вашего модуля .yaml; значение по умолчанию - 8). Итак, объем оперативной памяти вашего экземпляра должен быть кратен тому, что требуется для каждого запроса.

Что касается (d), чтобы «получить больше данных» (я полагаю, вы на самом деле имеете в виду, получить больше RAM), единственное, что вы можете сделать, это настроить больший instance_class для вашего требовательного к памяти модуля.

Чтобы использовать меньше ОЗУ, существует множество методов - которые не имеют ничего общего с App Engine, все, что связано с Python, и, в частности, все, что связано с вашим очень специфическим кодом и его очень специфическими потребностями. .

Единственная проблема, связанная с GAE, о которой я могу думать, заключается в том, что сообщалось об утечке кеширования ndb - см. https://code.google.com/p/googleappengine/issues/detail?id=9610; этот поток также предлагает обходные пути, такие как отключение ndb кэширования или переход к старому db (который не выполняет кэширования и не имеет утечек). Если вы используете ndb и не отключили его кеширование, это может быть основной причиной наблюдаемых вами проблем с «утечкой памяти».

person Alex Martelli    schedule 12.10.2015
comment
спасибо, это очень полезно. Поэтому, когда обработчик запроса возвращается (при условии, что он ничего не хранит в глобальной области), объем доступной памяти должен быть таким же, как и непосредственно перед этим запросом, то есть не происходит накопления использования памяти от одного запроса к другому. Если это теория, то я собираюсь memory_usage().current() в журнал, чтобы проверить, действительно ли я это вижу. Это правильный способ проверить использование памяти экземпляром? Кроме того, может ли часть кода gae храниться в глобальной области видимости? например RPC для БД? Думаю, я видел упоминание об этом в другом месте. - person tom; 13.10.2015
comment
Под «получением большего количества данных» я на самом деле имел в виду собрать больше информации о том, что происходит, что, я думаю, я и делаю с memory_usage().current(), но если есть другие инструменты, пожалуйста, сообщите мне. Могу ли я предположить, что когда возникает Exceeded soft private memory limit исключение, это означает, что экземпляр попытался выполнить gc.collect(), и, следовательно, использование памяти ссылочными объектами превышает предел памяти? Или может случиться так, что мне нужно вызвать gc.collect () в какой-то момент, чтобы освободить некоторую циклическую справочную память? - person tom; 13.10.2015
comment
@tom, если ничего не сохраняет что-либо глобально (в том числе в кешах, как я уже упоминал для ndb), тогда, когда все ожидающие запросы выполнены, использование памяти должно вернуться туда, где оно было при запуске. memory_usage устарел, но я не знаю никаких альтернатив. exceeded диагностируется наблюдателем за пределами вашей работы Python, поэтому он ничего не знает о вызовах gc.collect; последняя миграция помогает только в том случае, если у вас есть мусорные циклы (объекты взаимно ссылаются друг на друга). - person Alex Martelli; 14.10.2015
comment
Я собрал еще несколько данных об использовании памяти моим приложением, и я все еще вижу, что использование памяти постепенно увеличивается от одного запроса к другому. Мой следующий шаг - поиск возможных причин в моем коде (глобальные переменные и т. Д.). Прежде чем я это сделаю, я хотел бы знать, есть ли какие-либо службы / код движка приложений, которые кэшируют данные таким образом, который не может быть GCd, кроме ndb. Вы что-нибудь знаете? Было бы правильно сказать, что такое поведение (если бы оно существовало) было ошибкой? - person tom; 26.03.2016

Точка 4 является недопустимым предположением, сборщик мусора Python не возвращает память так легко, программа Python занимает эту память, но не используется, пока сборщик мусора не прошел. Между тем, если для какого-либо другого запроса требуется больше памяти, может быть выделено новое, помимо памяти из первого запроса. Если вы хотите заставить Python собирать мусор, вы можете использовать gc.collect(), как упоминалось здесь

person Ritave    schedule 06.10.2015
comment
Спасибо, это полезно знать. В настоящее время мое приложение настроено как threadsafe: false. Учитывая это, я думаю, что ваш сценарий «Тем временем, если какой-то другой запрос требует больше памяти…» не произойдет, верно? Учитывая это, пункт 4 все еще недействителен? - person tom; 12.10.2015
comment
Он по-прежнему недействителен. Например: вы завершаете один запрос, вы перестаете использовать объекты из запроса, но они по-прежнему занимают память до тех пор, пока не пройдет сборка мусора, эта память непригодна для использования. Приходит еще один запрос, который необходимо обработать перед прохождением GC. И теперь у вас есть память о двух принятых запросах, даже с threadsafe: false. ГХ освободит память обоих позже. - person Ritave; 13.10.2015

Взгляните на эти вопросы и ответы, чтобы узнать о подходах к проверке сборки мусора и возможных альтернативных объяснениях: Использование памяти запросов к базе данных Google App Engine

person Dan Cornilescu    schedule 06.10.2015
comment
спасибо, но этот вопрос на самом деле относится к использованию памяти в одном обработчике запросов, а не между обработчиками запросов. Также см. Мой комментарий к принятому ответу, ссылку на code.google и обсуждение там, которое адекватно объясняет эту конкретную проблему. - person tom; 12.10.2015