Google App Engine/Datastore/Flask/Python 应用中的内存泄漏

Memory leak in Google App Engine / Datastore / Flask / Python app

我构建了一个简单的新闻聚合器站点,其中我所有 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 Mb,然后在某些请求(但不是所有请求)下,内存使用量增加 20 - 30 Mb。 (顺便说一句,这大致对应于查询结果的估计大小,虽然我不能确定这是相关信息。)这种情况一直发生,直到超过 512 Mb,当它被关闭时。它通常发生在对“/”的第 50 到第 100 个请求时。在此期间没有其他任何请求。

现在,如果我消除“for”循环,只剩下查询,问题就消失了,内存使用率保持在 190 Mb 不变,即使在 100 多个请求后也没有增加。

gc.collect() 最后没有帮助。我也尝试查看函数开头和结尾处的 tracemalloc 统计信息的差异,我没有发现任何有用的东西。

请问有没有人经历过类似的事情?任何想法这里可能出什么问题?您可以推荐哪些额外的测试/调查?这可能是我无法控制的 Google App Engine/Datastore 问题吗?

谢谢。

Now, if I eliminate the "for" cycle, and only the query remains, the problem goes away, the memory usage remains at 190 Mb flat, no increase even after 100+ requests.

query.fetch() returns 一个迭代器,而不是实际的结果数组

https://googleapis.dev/python/datastore/latest/queries.html#google.cloud.datastore.query.Query.fetch

查看源代码,该迭代器似乎包含用于获取查询的下一页的代码。所以你 for-loop 强制它获取结果的所有页面。事实上,我认为在您开始迭代之前它实际上不会获取任何内容。所以这就是为什么删除你的 for-loop 会有所作为

不幸的是,除此之外我不确定,因为当您深入研究源代码时,您很快 运行 进入 GRPC 存根并且不清楚问题是否在那里。

这个问题与您的问题类似,提问者发现实例化 datastore.Client() 涉及内存泄漏。

这最终与 GRPC 中的一个问题有关,如果不关闭 GRPC 就会泄漏 https://github.com/grpc/grpc/issues/22123

希望这能为您指明正确的方向

@Alex 在另一个答案中做了很好的研究,所以我将跟进这个建议:尝试使用 NDB Library。使用此库的所有调用都必须包装到上下文管理器中,这应保证在关闭后进行清理。这可能有助于解决您的问题:

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)