Google App Engine /数据存储/ Flask / Python应用中的内存泄漏

问题描述

我建立了一个简单的新闻聚合器网站,其中所有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 /数据存储问题吗?

谢谢。

enter image description here

解决方法

现在,如果我消除了“ for”周期,而仅保留查询,问题就消失了,内存使用率将保持在190 Mb不变,甚至在100多个请求之后也不会增加。

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

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

看一下源代码,看起来该迭代器具有用于提取查询的下一页的代码。因此,您需要进行for循环,以强制其获取结果的所有页面。实际上,在您开始迭代之前,我认为它实际上不会获取任何东西。因此,这就是为什么删除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

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

, 在另一个答案中的

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