Утечка памяти в приложении Google App Engine / Datastore / Flask / Python

Sep 15 2020

Я создал простой сайт-агрегатор новостей, на котором использование памяти всеми моими экземплярами 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, которую я не могу контролировать?

Спасибо.

Ответы

1 yedpodtrzitko Sep 17 2020 at 02:48

@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)
2 Alex Sep 16 2020 at 20:03

Теперь, если я исключу цикл «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

Надеюсь, это укажет вам правильное направление