Утечка памяти в приложении Google App Engine / Datastore / Flask / Python
Я создал простой сайт-агрегатор новостей, на котором использование памяти всеми моими экземплярами App Engine продолжает расти, пока не достигнет предела и, следовательно, не будет отключено.
Я начал удалять все из своего приложения, чтобы получить минимально воспроизводимую версию. Вот что у меня сейчас:
app = Flask(__name__)
datastore_client = datastore.Client()
@app.route('/')
def root():
query = datastore_client.query(kind='source')
query.order = ['list_sequence']
sources = query.fetch()
for source in sources:
pass
Статистика показывает типичную картину «пилы»: при запуске инстанса она увеличивается до 190 - 210 Мбайт, затем при некоторых запросах, но НЕ ВСЕХ, использование памяти увеличивается на 20 - 30 Мбайт. (Это, кстати, примерно соответствует предполагаемому размеру результатов запроса, хотя я не могу быть уверен, что это релевантная информация.) Это продолжается до тех пор, пока он не превысит 512 МБ, когда он выключен. Обычно это происходит в районе 50–100-го запроса к "/". Никаких других запросов ни к чему другому пока не поступало.
Теперь, если я исключу цикл «for», и останется только запрос, проблема исчезнет, использование памяти останется на уровне 190 Мбайт, без увеличения даже после 100+ запросов.
gc.collect () в конце не помогает. Я также попытался посмотреть разницу в статистике tracemalloc в начале и в конце функции, но не нашел ничего полезного.
Кто-нибудь испытывал что-нибудь подобное, пожалуйста? Есть идеи, что здесь может пойти не так? Какие дополнительные тесты / исследования вы можете порекомендовать? Возможно, это проблема Google App Engine / Datastore, которую я не могу контролировать?
Спасибо.
Ответы
@Alex в другом ответе провел довольно хорошее исследование, поэтому я буду следовать этой рекомендации: попробуйте использовать библиотеку NDB . Все вызовы этой библиотеки должны быть заключены в диспетчер контекста, который должен гарантировать очистку после закрытия. Это может помочь решить вашу проблему:
ndb_client = ndb.Client(**init_client)
with ndb_client.context():
query = MyModel.query().order(MyModel.my_column)
sources = query.fetch()
for source in sources:
pass
# if you try to query DataStore outside the context manager, it will raise an error
query = MyModel.query().order(MyModel.my_column)
Теперь, если я исключу цикл «for», и останется только запрос, проблема исчезнет, использование памяти останется на уровне 190 Мбайт, без увеличения даже после 100+ запросов.
query.fetch() возвращает итератор, а не фактический массив результатов
https://googleapis.dev/python/datastore/latest/queries.html#google.cloud.datastore.query.Query.fetch
Глядя на исходный код, похоже, что у этого итератора есть код для выборки следующих страниц запроса. Итак, цикл for заставляет его извлекать все страницы результатов. На самом деле я не думаю, что он действительно что-то извлекает, пока вы не начнете повторять. Вот почему удаление вашего цикла for будет иметь значение
К сожалению, помимо этого, я не уверен, поскольку, копаясь в исходном коде, вы довольно быстро сталкиваетесь с заглушками GRPC, и неясно, в чем проблема.
Есть вопрос, который похож на ваш, когда спрашивающий обнаружил утечку памяти, связанную с созданием экземпляра datastore.Client(). Как мне расследовать утечку памяти при использовании библиотек Python в Google Cloud Datastore?
В конечном итоге это было связано с проблемой в GRPC, из-за которой утечка GRPC, если она не закрывается. https://github.com/grpc/grpc/issues/22123
Надеюсь, это укажет вам правильное направление