python - 使用 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
):
问题
我对数据存储的调用方式是否有问题,或者这可能是库的潜在问题?
环境是 Windows 10 上的 Python 3.7.4(也在 Docker 中的 Debian 3.8 上测试过),google-cloud-datastore==1.11.0
带有grpcio==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 库更有可能泄漏内存。
潜在相关
有一个开放的 grpc 问题,我不确定是否相关,但与我的问题有许多相似之处。
解决方案
我已将内存泄漏缩小到创建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 使用有关,我在bug 跟踪器的这张票中记录了这一点。
推荐阅读
- r - 生成的轴刻度标签中的斜体字母
- visual-studio-2019 - 如何在 VS 2019 中禁用所有代码分析?
- python - Python 计算嵌套字典中特定键的平均值(IBM Watson Speech to Text API 结果)
- c++ - 将双精度数组重新解释为 std::complex 数组
- reactjs - 使用 REDUX/RTK Query 删除项目后如何刷新页面?
- python - 在“类”PYTHON 中使用 for
- r - 优化(方法=布伦特)和优化没有给出正确的二项分布最小值(N > 1000)
- django - Django 没有在错误 500 上向管理员发送电子邮件(但电子邮件设置可以与 send_mail 一起使用!)
- keras - keras-yolov3: Preprocess_true_boxes vs yolo_eval
- excel - 将所有行(范围)从一个工作簿复制到新的 excel - 但首先删除重复项