Утечка памяти в библиотеке Google ndb
Я думаю, что есть утечка памяти в ndb
библиотека но не могу найти где.
Есть ли способ избежать проблемы, описанной ниже?
У вас есть более точное представление о тестировании, чтобы выяснить, где проблема?
Вот как я воспроизвел проблему:
Я создал минималистское приложение Google App Engine с 2 файлами.app.yaml
:
application: myapplicationid
version: demo
runtime: python27
api_version: 1
threadsafe: yes
handlers:
- url: /.*
script: main.APP
libraries:
- name: webapp2
version: latest
main.py
:
# -*- coding: utf-8 -*-
"""Memory leak demo."""
from google.appengine.ext import ndb
import webapp2
class DummyModel(ndb.Model):
content = ndb.TextProperty()
class CreatePage(webapp2.RequestHandler):
def get(self):
value = str(102**100000)
entities = (DummyModel(content=value) for _ in xrange(100))
ndb.put_multi(entities)
class MainPage(webapp2.RequestHandler):
def get(self):
"""Use of `query().iter()` was suggested here:
https://code.google.com/p/googleappengine/issues/detail?id=9610
Same result can be reproduced without decorator and a "classic"
`query().fetch()`.
"""
for _ in range(10):
for entity in DummyModel.query().iter():
pass # Do whatever you want
self.response.headers['Content-Type'] = 'text/plain'
self.response.write('Hello, World!')
APP = webapp2.WSGIApplication([
('/', MainPage),
('/create', CreatePage),
])
Я загрузил приложение под названием /create
один раз.
После этого каждый звонок /
увеличивает память, используемую экземпляром. Пока не остановится из-за ошибки Exceeded soft private memory limit of 128 MB with 143 MB after servicing 5 requests total
,
Пример графика использования памяти (вы можете увидеть рост памяти и сбои):
Примечание: проблема может быть воспроизведена с другой структурой, чем webapp2
, лайк web.py
3 ответа
После дополнительных исследований и с помощью инженера Google, я нашел два объяснения моего потребления памяти.
Контекст и поток
ndb.Context
является "локальным потоком" объектом и очищается только при поступлении нового запроса в поток. Таким образом, поток удерживает его между запросами. В экземпляре GAE может существовать много потоков, и может потребоваться сотни запросов, прежде чем поток будет использован во второй раз и его контекст очищен.
Это не утечка памяти, но размер контекстов в памяти может превышать доступную память в небольшом экземпляре GAE.
Временное решение:
Вы не можете настроить количество потоков, используемых в экземпляре GAE. Поэтому лучше сохранить каждый контекст наименьшим. Избегайте кеширования в контексте и очищайте его после каждого запроса.
Очередь событий
Кажется, что NDB не гарантирует, что очередь событий очищается после запроса. Опять же, это не утечка памяти. Но это оставить Futures
в контексте вашей темы, и вы вернулись к первой проблеме.
Временное решение:
Оберните весь ваш код, который использует NDB с @ndb.toplevel
,
Существует известная проблема с NDB. Вы можете прочитать об этом здесь, и здесь есть работа:
Недетерминированность, наблюдаемая с fetch_page, связана с порядком итераций eventloop.rpcs, который передается в datastore_rpc.MultiRpc.wait_any() и apiproxy_stub_map.__check_one выбирает последний rpc из итератора.
При выборке с page_size, равным 10, rpc с count=10, limit=11 является стандартной техникой, чтобы заставить сервер более точно определить, есть ли еще результаты. Это возвращает 10 результатов, но из-за ошибки в способе раскрытия QueryIterator добавлен RPC для извлечения последней записи (с использованием полученного курсора и счетчика =1). Затем NDB возвращает пакет сущностей без обработки этого RPC. Я считаю, что этот RPC не будет оцениваться до тех пор, пока он не будет выбран случайным образом (если MultiRpc использует его до необходимого rpc), поскольку он не блокирует клиентский код.
Обходной путь: используйте iter(). Эта функция не имеет этой проблемы (количество и предел будут одинаковыми). iter() может использоваться как обходной путь для проблем производительности и памяти, связанных с выборкой страницы, вызванной выше.
Возможный обходной путь - использовать context.clear_cache() и gc.collect() в методе get.
def get(self):
for _ in range(10):
for entity in DummyModel.query().iter():
pass # Do whatever you want
self.response.headers['Content-Type'] = 'text/plain'
self.response.write('Hello, World!')
context = ndb.get_context()
context.clear_cache()
gc.collect()