Google ndb库中的内存泄漏

时间:2015-10-09 10:49:31

标签: python google-app-engine memory-leaks app-engine-ndb webapp2

我认为ndb库中存在内存泄漏,但我无法找到。

有没有办法避免下面描述的问题?
您是否有更准确的测试想法来确定问题所在?

我是如何重现这个问题的:

我创建了一个包含2个文件的简约Google App Engine应用程序 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而停止。

内存使用图的例子(你可以看到内存增长和崩溃): enter image description here

注意:问题可以使用webapp2以外的其他框架重现,例如web.py

3 个答案:

答案 0 :(得分:5)

经过更多的调查,并在谷歌工程师的帮助下,我找到了两个解释我的记忆消耗。

上下文和主题

ndb.Context是一个“线程本地”对象,仅在线程中有新请求时才会被清除。所以线程在请求​​之间保持不变。 GAE实例中可能存在许多线程,并且在第二次使用线程之前可能需要数百个请求并清除它的上下文。
这不是内存泄漏,但内存中的上下文大小可能超过小GAE实例中的可用内存。

解决方法:
您无法配置GAE实例中使用的线程数。因此,最好尽量保持每个上下文的最小化。避免使用上下文缓存,并在每次请求后清除它。

事件队列

似乎NDB不保证请求后事件队列被清空。同样,这不是内存泄漏。但是它会在你的线程上下文中留下Futures,并且你又回到了第一个问题。

解决方法:
将所有使用NDB的代码包裹在@ndb.toplevel

答案 1 :(得分:3)

NDB存在一个已知问题。您可以阅读it here并了解作品around here

  

使用fetch_page观察到的非确定性是由于eventloop.rpcs的迭代顺序,它被传递给datastore_rpc.MultiRpc.wait_any()和apiproxy_stub_map .__ check_one从迭代器中选择 last rpc

     

使用page_size为10获取一个rpc,其count = 10,limit = 11,这是一种标准技术,可以强制后端更准确地确定是否有更多结果。这将返回10个结果,但由于QueryIterator被解开的方式存在错误,因此添加了RPC以获取最后一个条目(使用获得的游标和count = 1)。然后NDB返回批量实体而不处理此RPC。我相信在随机选择之前不会评估此RPC(如果MultiRpc在必要的rpc之前使用它),因为它不会阻止客户端代码。

     

解决方法:使用iter()。此功能没有此问题(计数和限制将相同)。 iter()可以用作与上面引起的获取页面相关的性能和内存问题的解决方法。

答案 2 :(得分:2)

可能的解决方法是在get方法中使用 context.clear_cache() gc.collect()

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()