AWS Lambda中的Python没有正确地进行垃圾收集?

AWS Lambda中的Python没有正确地进行垃圾收集?,python,pandas,memory-management,aws-lambda,garbage-collection,Python,Pandas,Memory Management,Aws Lambda,Garbage Collection,场景:我编写了一个AWS Lambda函数,该函数在CSV文件上传到S3存储桶时启动,并将文件按x大小的MB块流式分割到多个Gzip拼花地板文件(用于均匀分布处理/加载的红移集群上的切片数)。这里的想法是,如果我有一个3GB的Lambda函数,并收到一个8GB或更大的CSV文件,我应该能够以1GB的块处理它,而不必将整个8GB读入内存并超过3GB的限制 import sys import pandas as pd import awswrangler as wr import io s3 =

场景:我编写了一个AWS Lambda函数,该函数在CSV文件上传到S3存储桶时启动,并将文件按x大小的MB块流式分割到多个Gzip拼花地板文件(用于均匀分布处理/加载的红移集群上的切片数)。这里的想法是,如果我有一个3GB的Lambda函数,并收到一个8GB或更大的CSV文件,我应该能够以1GB的块处理它,而不必将整个8GB读入内存并超过3GB的限制

import sys
import pandas as pd
import awswrangler as wr
import io

s3 = boto3.client('s3')

def split_file(file_size_in_MB, source_bucket, source_bucket_key):
    body = s3.get_object(Bucket=source_bucket, Key=source_bucket_key)['Body'] #streaming body
    chunk_size = 1024 * 1024 * file_size_in_MB # bytes
    newline = '\r\n'.encode()
    partial_chunk = b''
    counter = 0
    while (True):
        data = body.read(chunk_size)
        if counter == 0:
            header = data[0:data.find(newline)] # determine header on first pass
            chunk = partial_chunk + data
        else:
            chunk = header + partial_chunk + data
        if chunk == b'':
            break
        last_newline = chunk.rfind(newline)
        result = chunk[0:last_newline+1].decode('utf-8')
        print('1 mem size of chunk', round(sys.getsizeof(result)/1024/1024,2))
        if len(result) != 0:
            df = pd.read_csv(io.StringIO(result))
            print('2 mem size of df', round(sys.getsizeof(df)/1024/1024,2))
            wr.s3.to_parquet(df=df*1,
                             path=f's3://{target_stage_bucket}/results{counter}.parquet.gzip',
                             compression='gzip')
        else:
            break
        partial_chunk = chunk[last_newline+1:]
        counter+=1

split_file(file_size_in_MB=50,
               source_bucket=source_bucket,
               source_bucket_key=source_bucket_key)
免责声明:我知道这段代码可以做一些改进,比如换行符拆分、while(True)以及可能需要处理的超时,我明白了,但请记住,这是开发代码,我想重点关注在AWS Lambda中触发时发生的明显内存泄漏这一特定问题-请参见以下内容:

如果我在一个1GB大小的文件上本地运行此函数,并将其流成100MB的块,我可以看到每个块传递的大小及其等效值(如预期的那样有一点开销):

在这里,您可以看到,在脚本的持续时间内,内存轨迹相对平坦,每个正在处理的块的预期峰值如下:

但是,问题是,当我在分配了512 MB内存的Lambda中实现相同的代码时,我收到以下错误:

以及下面的日志输出,您可以看到代码仅进入100MB数据的第一个循环:


所以我的问题是-这里发生了什么?我认为512MB应该有足够的内存来处理这些100MB的数据块,但在Lambda中,我在第一次运行时就用完了内存,有什么想法吗?

我不确定您用于绘制图形的是什么。您可以使用类似的内容来分析您的程序

我在一个程序上运行
memory\u profiler
,该程序包含与程序的单个迭代类似的步骤。我还将一些语句拆分为多个语句,因为
@profile
在每个语句之后测量内存使用量,而不是在语句之间(据我所知,GC可以在再次测量内存使用量之前启动)。我使用
del
在临时变量不再需要时删除它们,模拟我认为在unsplitted语句中发生的情况。下面是使用100MB块的结果:

Line#内存使用增量发生次数行内容
============================================================
35 70.0 MiB 70.0 MiB 1@配置文件
36 def按块读取(文件大小以MB为单位):
37 70.0 MiB 0.0 MiB 1,打开('data.bin','rb')作为f:
38 70.0 MiB 0.0 MiB 1块大小=2**20*文件大小(单位:MB)
39 170.0 MiB 100.0 MiB 1块=f.read(块大小)
40#结果
41 270.0 MiB 100.0 MiB 1块_部分=块[:-10]
42 370.0 MiB 100.0 MiB 1结果=块\部分解码('utf-8')
43 270.0 MiB-100.0 MiB 1 del chunk_零件
44#数据帧
45 670.0 MiB 400.0 MiB 1 s=StringIO(结果)
46 770.9 MiB 100.9 MiB 1 df=pd.read_csv
我无法理解为什么
StringIO
需要四倍于它所包装字符串的内存。好消息是,你不需要它,你也不需要自己解码块。您可以将
BytesIO
传递给
pd。改为读取\u csv

Line#内存使用增量发生次数行内容
============================================================
49 70.0 MiB 70.0 MiB 1@配置文件
50 def逐块读取(文件大小以MB为单位):
51 70.0 MiB 0.0 MiB 1,打开('data.bin','rb')作为f:
52 70.0 MiB 0.0 MiB 1块大小=2**20*文件大小(单位:MB)
53 170.0 MiB 100.0 MiB 1块=f.read(块大小)
54#拆分此语句不会更改内存使用情况,因为
55#BytesIO保留对区块部分的引用
56 270.0 MiB 100.0 MiB 1 bytesio=io.bytesio(块[:-10])
57 371.8 MiB 101.8 MiB 1 df=pd.read_csv(字节)

通常,您可以通过调用
delname来避免复制和强制垃圾收集不需要的数据,从而减少内存使用;gc.collect()

这可能是因为lambda是作为热启动执行的,这意味着重用现有容器以避免昂贵的启动开销。在热启动时的解剖结构上可以看到这一点。由于重用了现有容器,因此不能保证在下一次调用开始时清除容器的内存。看看这个问题,你会发现一个类似的问题

这意味着您不能总是依赖于在运行时为lambda提供最大容量

我认为这里更好的选择是使用queue或step函数来处理数据的分块。例如,Lambda进程为每个后续Lambda定义开始流式传输的开始/结束行,并向队列发送消息,从而并行触发多个Lambda。这只是一个选项,当然,您必须在lambda等中失败


祝你好运:)

我不是s3方面的专家,但一些讨论可能会对你有所帮助。对我来说,它看起来像身体,数据,块,结果使用了大量的内存。您只跟踪结果参数,而总内存使用率则有所下降
running...
1 mem size of chunk 100.0
2 mem size of df 132.02
1 mem size of chunk 100.0
2 mem size of df 131.97
.....
1 mem size of chunk 100.0
2 mem size of df 132.06
1 mem size of chunk 24.0
2 mem size of df 31.68
1 mem size of chunk 0.0
completed in 0:02:38.995711
{
  "errorType": "MemoryError",
  "stackTrace": [
    "  File \"/var/task/lambda_function.py\", line 38, in lambda_handler\n    split_file(file_size_in_MB=100,\n",
    "  File \"/var/task/lambda_function.py\", line 79, in split_file\n    result = chunk[0:last_newline+1].decode('utf-8')\n"
  ]
}
1 mem size of chunk 100.0
[ERROR] MemoryError
Traceback (most recent call last):
  File "/var/task/lambda_function.py", line 38, in lambda_handler
    split_file(file_size_in_MB=100,
  File "/var/task/lambda_function.py", line 79, in split_file
    result = chunk[0:last_newline+1].decode('utf-8')END