【问题标题】:How should I investigate a memory leak when using Google Cloud Datastore Python libraries?使用 Google Cloud Datastore Python 库时,我应该如何调查内存泄漏?
【发布时间】:2020-08-07 08:21:31
【问题描述】:

我有一个使用 Google 数据存储的网络应用程序,但在请求足够多后内存不足。

我已将其范围缩小到数据存储区查询。下面提供了最低 PoC,包含内存测量的 slightly longer version 在 Github 上。

from google.cloud import datastore
from google.oauth2 import service_account

def test_datastore(entity_type: str) -> list:
    creds = service_account.Credentials.from_service_account_file("/path/to/creds")
    client = datastore.Client(credentials=creds, project="my-project")
    query = client.query(kind=entity_type, namespace="my-namespace")
    query.keys_only()
    for result in query.fetch(1):
        print(f"[+] Got a result: {result}")

for n in range(0,100):
    test_datastore("my-entity-type")

分析过程 RSS 显示每次迭代大约 1 MiB 增长。即使没有返回结果,也会发生这种情况。以下是我的 Github gist 的输出:

[+] Iteration 0, memory usage 38.9 MiB bytes
[+] Iteration 1, memory usage 45.9 MiB bytes
[+] Iteration 2, memory usage 46.8 MiB bytes
[+] Iteration 3, memory usage 47.6 MiB bytes
..
[+] Iteration 98, memory usage 136.3 MiB bytes
[+] Iteration 99, memory usage 137.1 MiB bytes

但同时,Python 的mprof 显示了一个平面图(像mprof run python datastore_test.py 一样运行):

问题

我调用 Datastore 的方式是否有问题,或者这可能是库的潜在问题?

环境是 Windows 10 上的 Python 3.7.4(也在 Docker 中的 Debian 3.8 上进行了测试),google-cloud-datastore==1.11.0grpcio==1.28.1

编辑 1

澄清这不是典型的 Python 分配器行为,它从操作系统请求内存,但不会立即从内部竞技场/池中释放它。下面是来自 Kubernetes 的图表,我的受影响的应用程序在其中运行:

这表明:

  • 内存线性增长,直到大约 2GiB,应用程序实际上崩溃了,因为它内存不足(从技术上讲,Kubernetes 驱逐了 pod,但这与这里无关)。
  • 运行相同的 Web 应用程序,但没有与 GCP 存储或数据存储区交互。
  • 仅添加了与 GCP 存储的交互(随着时间的推移非常轻微的增长,可能是正常的)。
  • 仅添加了与 GCP 数据存储的交互(更大的内存增长,一小时内大约 512MiB)。 Datastore 查询与本文中的 PoC 代码完全相同。

编辑 2

为了绝对确定 Python 的内存使用情况,我使用 gc 检查了垃圾收集器的状态。退出前,程序报告:

gc: done, 15966 unreachable, 0 uncollectable, 0.0156s elapsed

我还在循环的每次迭代期间使用gc.collect() 手动强制垃圾收集,这没有任何区别。

由于没有不可回收的对象,内存泄漏似乎不太可能来自使用 Python 的内部内存管理分配的对象。因此,外部 C 库更有可能发生内存泄漏。

可能相关

有一个open grpc issue 我不能确定是否相关,但与我的问题有许多相似之处。

【问题讨论】:

    标签: python memory-leaks google-cloud-datastore


    【解决方案1】:

    我已将内存泄漏缩小到创建 datastore.Client 对象。

    对于以下概念验证代码,内存使用量不会增长:

    from google.cloud import datastore
    from google.oauth2 import service_account
    
    def test_datastore(client, entity_type: str) -> list:
        query = client.query(kind=entity_type, namespace="my-namespace")
        query.keys_only()
        for result in query.fetch(1):
            print(f"[+] Got a result: {result}")
    
    creds = service_account.Credentials.from_service_account_file("/path/to/creds")
    client = datastore.Client(credentials=creds, project="my-project")
    
    for n in range(0,100):
        test_datastore(client, "my-entity-type")
    

    这对于可以创建一次并在请求之间安全共享的 client 对象的小脚本来说是有意义的。

    在许多其他应用程序中,安全地传递客户端对象更难(或不可能)。我希望库在客户端超出范围时释放内存,否则任何长时间运行的程序都可能出现此问题。

    编辑 1

    我已将其范围缩小到 grpc。环境变量GOOGLE_CLOUD_DISABLE_GRPC 可以设置(为任何值)来禁用grpc。

    设置完成后,我在 Kubernetes 中的应用程序如下所示:

    对 valgrind 的进一步调查表明它可能与 grpc 中的 OpenSSL 使用有关,我在错误跟踪器上的 this ticket 中记录了这一点。

    【讨论】:

    • 这是垃圾收集器上的一个问题,正如您提到的,重新使用客户端对象是一种很好的做法。您可以在 for 循环后调用 gc 以删除所有超出范围的变量,在此链接中是有关 GC 在循环后删除所有未使用的变量的示例digi.com/resources/documentation/digidocs/90001537/references/…
    • @JAHDZP 您是否有一些与此模块(或其他 Google 客户端库)相关的特定链接?正如您从编辑 2 中看到的我已经尝试过 gc.collect() 的问题,它没有任何区别。我还打开了 gc status 调试以获取您建议的相同数据。
    猜你喜欢
    • 2021-01-02
    • 1970-01-01
    • 1970-01-01
    • 2013-10-11
    • 1970-01-01
    • 2016-06-11
    • 2021-01-10
    • 2010-11-23
    • 1970-01-01
    相关资源
    最近更新 更多