首页 > 解决方案 > 使用 Google Cloud Datastore Python 库时,我应该如何调查内存泄漏?

问题描述

我有一个使用谷歌数据存储的网络应用程序,并且在足够的请求后内存不足。

我已将其范围缩小到 Datastore 查询。下面提供了最低 PoC ,包含内存测量的稍长版本在 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显示了一个平面图(run like mprof run python datastore_test.py):

100 次数据存储提取的 mprof 输出

问题

我对数据存储的调用方式是否有问题,或者这可能是库的潜在问题?

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

编辑 1

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

来自 Kubernetes 的内存使用图,显示了进程运行时内存使用量的线性增长

由此可见:

编辑 2

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

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

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

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

潜在相关

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

标签: pythonmemory-leaksgoogle-cloud-datastore

解决方案


我已将内存泄漏缩小到创建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 中的应用程序如下所示:

来自 Kubernetes 的内存使用图,在禁用 grpc 后显示一条平线(没有内存增加)

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


推荐阅读