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

Posted

技术标签:

【中文标题】Google App Engine / Datastore / Flask / Python app 中的内存泄漏【英文标题】:Memory leak in Google App Engine / Datastore / Flask / Python app 【发布时间】:2021-01-02 00:10:33 【问题描述】:

我建立了一个简单的新闻聚合器站点,其中我所有 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 stats 的差异,我没有发现任何有用的东西。

请问有没有人遇到过类似的情况?有什么想法可能会出错吗?您可以推荐哪些额外的测试/调查?这可能是我无法控制的 Google App Engine/Datastore 问题吗?

谢谢。

【问题讨论】:

【参考方案1】:

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

【讨论】:

谢谢。我会试试这个并报告回来。但是,这个库不是要弃用了吗?这是我在开始这个项目时在 Google 的应用引擎页面上发现的:“新的 Python 3 应用应该使用 Datastore 模式客户端库而不是 Cloud NDB。” 状态:正在实施中。困难在于我的网站是实时的,我无法使用最初由数据存储创建的 ndb 查询实体。具体来说,我在存储字典列表的数据存储实体中有一个“数组”字段。尝试在 ndb 中迭代查询时,出现错误,“无法包装列表。”。我仍在努力解决这个问题。 结果:按照上面 yedpodtrzitko 的指示,我已经完全重写了我的应用程序以使用 ndb。问题基本没有了。 (内存使用量的持续增长仍然非常低,但这让我忽略了这个问题。)另外,我的响应时间明显更快,另外,应用程序现在至少可以在较低的实例类上运行将我的每日费用减半。所以这是成功的,谢谢你的帮助。当然,原来datastore api的问题还在,我在github上提了一个issue。 很高兴听到这有帮助 不幸的是,我过早地宣布成功。即使使用 ndb,与内存泄漏相关的实例关闭仍然会发生,尽管这种情况很少发生。这对我来说不是一场灾难,但我想我是为了全貌而提到这一点。最重要的是,我仍然不了解根本原因。 (请参阅上面的当前内存使用图表 - 它下降到零,这就是实例关闭的地方。)【参考方案2】:

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

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

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

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

不幸的是,我不确定,因为当您深入研究源代码时,您很快就会遇到 GRPC 存根,并且不清楚问题是否存在。

有一个与您的问题类似的问题,提问者发现与实例化datastore.Client() 有关的内存泄漏。 How should I investigate a memory leak when using Google Cloud Datastore Python libraries?

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

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

【讨论】:

谢谢。这些似乎是您所指出的相关对话,但有点超出我目前的理解范围。唯一实际的建议是设置 GOOGLE_CLOUD_DISABLE_GRPC,我试过了,不幸的是没有影响。我正在搜索。

以上是关于Google App Engine / Datastore / Flask / Python app 中的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

连接 Google App Engine 和 Google Compute Engine

Google App Engine Flexible 和 Google Container Engine 之间的区别?

如何在 Google Cloud App Engine 上使用 PubSub 创建订阅者,该订阅者通过 Publisher 从 Google Cloud App Engine Flex 收听消息?

将 Meteor 部署到 Google App Engine 2017

Google BigQuery 的 Google App Engine 授权

Google App Engine 通过内部网络与 Compute Engine 通信