【问题标题】:AWS lambda memory usage with temporary files in python codeAWS lambda 内存使用情况与 python 代码中的临时文件
【发布时间】:2019-01-10 15:27:18
【问题描述】:

写入临时文件的数据是否会影响 AWS lambda 中的内存使用?在 lambda 函数中,我将文件流式传输到临时文件。在 lambda 日志中,我看到使用的最大内存大于下载的文件。奇怪的是,如果 lambda 被快速连续调用多次,下载较小文件的调用仍会报告下载较大文件的调用所使用的最大内存。我将并发限制设置为 2。

当我在本地运行代码时,我的内存使用量符合预期,约为 20MB。在 lambda 上,它是 180MB,大约是流式传输文件的大小。该代码只是使用 python 请求库来流式传输文件下载,到 shutil.copyfileobj() 以写入 tempfile.TemporaryFile(),然后将其通过管道传输到 postgres“从标准输入复制”。

这使得 /tmp 存储似乎计入内存使用,但没有发现任何提及这一点。 lambda 文档中唯一提到 /tmp 是有 512mb 的限制。

示例代码:

import sys
import json
import os
import io
import re
import traceback
import shutil
import tempfile

import boto3
import psycopg2
import requests


def handler(event, context):
    try:
        import_data(event["report_id"])
    except Exception as e:
        notify_failed(e, event)
        raise

def import_data(report_id):
    token = get_token()
    conn = psycopg2.connect(POSTGRES_DSN, connect_timeout=30)
    cur = conn.cursor()

    metadata = load_metadata(report_id, token)
    table = ensure_table(metadata, cur, REPLACE_TABLE)
    conn.commit()
    print(f"report {report_id}: downloading")
    with download_report(report_id, token) as f:
        print(f"report {report_id}: importing data")
        with conn, cur:
            cur.copy_expert(f"COPY {table} FROM STDIN WITH CSV HEADER", f)
        print(f"report {report_id}: data import complete")
    conn.close()


def download_report(report_id, token):
    url = f"https://some_url"
    params = {"includeHeader": True}
    headers = {"authorization": f"Bearer {token['access_token']}"}

    with requests.get(url, params=params, headers=headers, stream=True) as r:
        r.raise_for_status()
        tmp = tempfile.TemporaryFile()
        print("streaming contents to temporary file")
        shutil.copyfileobj(r.raw, tmp)
        tmp.seek(0)
        return tmp


if __name__ == "__main__":
    if len(sys.argv) > 1:
        handler({"report_id": sys.argv[1]}, None)

更新: 在将代码更改为不使用临时文件而只是将下载直接流式传输到 postgres 复制命令后,内存使用量得到了修复。让我觉得 /tmp 目录对记录的内存使用有影响。

【问题讨论】:

    标签: python amazon-web-services aws-lambda


    【解决方案1】:

    更新

    注意: 为了回答这个问题,我使用了Lambdash,尽管我不得不修改用于node8.10的lambda版本。 Lambdash 是一个简单的小库,您可以使用它从本地终端在 lambda 上运行 shell 命令。

    AWS Lambdas 上的 /tmp 目录挂载为 loop device。您可以通过运行以下命令来验证这一点(在遵循 lambdash 的设置说明之后):

    ./lambdash df -h
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/xvda1       30G  4.0G   26G  14% /
    /dev/loop0      526M  872K  514M   1% /tmp
    /dev/loop1      6.5M  6.5M     0 100% /var/task
    

    根据https://unix.stackexchange.com/questions/278647/overhead-of-using-loop-mounted-images-under-linux

    通过循环设备访问的数据必须经过两个文件系统层,每个层都有自己的缓存,因此数据最终会被缓存两次,浪费大量内存(臭名昭著的“双缓存”问题)

    但是,我的猜测是 /tmp 实际上保存在内存中。为了测试这一点,我运行了以下命令:

    ./lambdash df -h
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/xvda1       30G  4.0G   26G  14% /
    /dev/loop0      526M  1.9M  513M   1% /tmp
    /dev/loop1      6.5M  6.5M     0 100% /var/task
    
    ./lambdash dd if=/dev/zero of=/tmp/file.txt count=409600 bs=1024
    409600+0 records in
    409600+0 records out
    419430400 bytes (419 MB) copied, 1.39277 s, 301 MB/s
    
    ./lambdash df -h
     Filesystem      Size  Used Avail Use% Mounted on
     /dev/xvda1       30G  4.8G   25G  17% /
     /dev/loop2      526M  401M  114M  78% /tmp
     /dev/loop3      6.5M  6.5M     0 100% /var/task
    
    ./lambdash df -h
     Filesystem      Size  Used Avail Use% Mounted on
     /dev/xvda1       30G  4.8G   25G  17% /
     /dev/loop2      526M  401M  114M  78% /tmp
     /dev/loop3      6.5M  6.5M     0 100% /var/task
    

    请记住,每次我运行它时,都会执行 lambda。以下是 Lambda 的 Cloudwatch 日志的输出:

    07:06:30 开始请求 ID:4143f502-14a6-11e9-bce4-eff8b92bf218 版本:$LATEST 07:06:30 END RequestId: 4143f502-14a6-11e9-bce4-eff8b92bf218 07:06:30 报告请求 ID: 4143f502-14a6-11e9-bce4-eff8b92bf218 持续时间:3.60 毫秒 持续时间:100 毫秒内存大小:1536 MB 使用的最大内存:30 MB

    07:06:32 开始请求 ID:429eca30-14a6-11e9-9b0b-edfabd15c79f 版本:$LATEST 07:06:34 结束请求 ID: 429eca30-14a6-11e9-9b0b-edfabd15c79f 07:06:34 报告请求 ID: 429eca30-14a6-11e9-9b0b-edfabd15c79f 持续时间:1396.29 毫秒 持续时间:1400 毫秒内存大小:1536 MB 使用的最大内存:430 MB

    07:06:36 开始请求 ID:44a03f03-14a6-11e9-83cf-f375e336ed87 版本:$LATEST 07:06:36 结束请求 ID: 44a03f03-14a6-11e9-83cf-f375e336ed87 07:06:36 报告请求 ID: 44a03f03-14a6-11e9-83cf-f375e336ed87 持续时间:3.69 毫秒 持续时间:100 毫秒内存大小:1536 MB 使用的最大内存:431 MB

    07:06:38 开始请求 ID:4606381a-14a6-11e9-a32d-2956620824ab 版本:$LATEST 07:06:38 结束请求 ID: 4606381a-14a6-11e9-a32d-2956620824ab 07:06:38 报告请求 ID: 4606381a-14a6-11e9-a32d-2956620824ab 持续时间:3.63 毫秒 持续时间:100 毫秒内存大小:1536 MB 使用的最大内存:431 MB

    发生了什么,这意味着什么?

    lambda 被执行了 4 次。在第一次执行时,我显示了已安装的设备。在第二次执行时,我在/tmp 目录中填充了一个文件,使用了允许的 500Mb 中的 401Mb。在随后的执行中,我列出了已安装的设备,显示了它们的可用空间。

    第一次执行时的内存利用率为 30Mb。后续执行的内存利用率在 400Mb 范围内。

    这证实了/tmp 利用率确实有助于内存利用率。

    原答案

    我的猜测是,您所观察的是 python 或 lambda 容器本身,在写入操作期间将文件缓冲在内存中。

    根据https://docs.python.org/3/library/functions.html#open

    buffering 是一个可选整数,用于设置缓冲策略。传递 0 以关闭缓冲(仅在二进制模式下允许),1 以选择行缓冲(仅在文本模式下可用),并且整数 > 1 以指示固定大小的块缓冲区的大小(以字节为单位)。当没有给出缓冲参数时,默认缓冲策略如下:

    二进制文件以固定大小的块缓冲;缓冲区的大小是通过尝试确定底层设备的“块大小”并回退到 io.DEFAULT_BUFFER_SIZE 的启发式方法来选择的。在许多系统上,缓冲区通常为 4096 或 8192 字节长。 “交互式”文本文件(isatty() 返回 True 的文件)使用行缓冲。其他文本文件使用上述针对二进制文件的策略。

    tempfile.TemporaryFile()函数有一个关键字参数buffering,基本上是直接传入上述open调用的。

    所以我的猜测是tempfile.TemporaryFile() 函数使用默认的open() 函数的缓冲设置。您可以尝试使用tempfile.TemporaryFile(buffering=0) 之类的方法来禁用缓冲,或者尝试tempfile.TemporaryFile(buffering=512) 来明确设置将数据写入文件时将使用的最大内存量。

    【讨论】:

    • 可能是。本地运行的相同代码仅使用 20mb 仍然很奇怪,所以可能是 os 的问题。
    • 当多个调用一起运行时,所有调用的内存使用都会增加,这也很奇怪。例如,如果我下载一个小文件(40mb),最大内存使用量就在那里。但是,如果我在下载较大文件后的几分钟内下载该小文件,则用于相同 40mb 文件的最大内存为 180mb。这可能是 lambda 记录器的问题吗?它实际上并没有使用它报告的内容?
    • 我刚刚使用 docker 容器运行代码,虽然与我的 mac 相比,我确实看到内存使用量增加了,但它仍然小于下载文件的文件大小,这与 lambda 不同。
    • 您是否使用 SAM CLI 对其进行了测试?
    • @kylehyde215 查看我的更新,让我知道您的想法。
    【解决方案2】:

    /tmp 的使用不计入内存使用量。唯一可以关联的情况是当您将文件内容读入内存时。

    【讨论】:

    • 这很奇怪。文件内容永远不会加载到内存中。在本地运行的完全相同的代码运行大约 20MB,但下载相同文件的相同代码在 lambda 中使用 185GB。这大约是文件的大小。
    • @kylehyde215 你在本地使用什么操作系统?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-09
    • 2021-06-30
    • 1970-01-01
    • 2021-12-12
    • 2013-01-31
    • 2014-06-03
    相关资源
    最近更新 更多