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