从Amazon S3读取大尺寸JSON文件时使用read()方法时出现MemoryError

时间:2018-07-30 05:54:11

标签: python postgresql amazon-s3 etl amazon-rds

我正在尝试使用Python将Amazon S3中的大尺寸JSON FILE导入AWS RDS-PostgreSQL。但是,发生了这些错误,

  

回溯(最近通话最近一次):

     

中的文件“ my_code.py”,第67行      

file_content = obj ['Body']。read()。decode('utf-8')。splitlines(True)

     

文件“ /home/user/asd-to-qwe/fgh-to-hjk/env/local/lib/python3.6/site-packages/botocore/response.py”,第76行,处于读取状态      

chunk = self._raw_stream.read(amt)

     

文件“ /home/user/asd-to-qwe/fgh-to-hjk/env/local/lib/python3.6/site-packages/botocore/vendored/requests/packages/urllib3/response.py” ,第239行,处于读取状态

     

data = self._fp.read()

     

文件“ /usr/lib64/python3.6/http/client.py”,第462行,处于读取状态

     

s = self._safe_read(self.length)

     

_safe_read中的文件“ /usr/lib64/python3.6/http/client.py”,第617行

     

返回b“”。join

     

MemoryError

// my_code.py

import sys
import boto3
import psycopg2
import zipfile
import io
import json

s3 = boto3.client('s3', aws_access_key_id=<aws_access_key_id>, aws_secret_access_key=<aws_secret_access_key>)
connection = psycopg2.connect(host=<host>, dbname=<dbname>, user=<user>, password=<password>)
cursor = connection.cursor()

bucket = sys.argv[1]
key = sys.argv[2]
obj = s3.get_object(Bucket=bucket, Key=key)

def insert_query(data):
    query = """
        INSERT INTO data_table
        SELECT
            (src.test->>'url')::varchar, (src.test->>'id')::bigint,
            (src.test->>'external_id')::bigint, (src.test->>'via')::jsonb
        FROM (SELECT CAST(%s AS JSONB) AS test) src
    """
    cursor.execute(query, (json.dumps(data),))


if key.endswith('.zip'):
    zip_files = obj['Body'].read()
    with io.BytesIO(zip_files) as zf:
        zf.seek(0)
        with zipfile.ZipFile(zf, mode='r') as z:
            for filename in z.namelist():
                with z.open(filename) as f:
                    for line in f:
                        insert_query(json.loads(line.decode('utf-8')))
if key.endswith('.json'):
    file_content = obj['Body'].read().decode('utf-8').splitlines(True)
    for line in file_content:
        insert_query(json.loads(line))


connection.commit()
connection.close()

这些问题有解决方案吗?任何帮助都可以,非常感谢!

1 个答案:

答案 0 :(得分:3)

通过避免将整个输入文件作为list行拖入内存,可以节省大量资金。

尤其是,这些行在内存使用方面非常糟糕,因为它们涉及一个bytes对象的峰值内存使用情况,该对象的大小等于整个文件的大小,外加list行的全部内容该文件也是如此:

file_content = obj['Body'].read().decode('utf-8').splitlines(True)
for line in file_content:

对于在64位Python 3.3+上具有500万行的1 GB ASCII文本文件,对于{em> just bytes对象{{ {1}},以及list中的各个str。一个程序需要2.3倍于其处理文件大小的RAM的程序将无法扩展到大文件。

要修复,请将原始代码更改为:

list

鉴于obj['Body'] appears to be usable for lazy streaming,这应该从内存中删除完整文件数据的副本。使用file_content = io.TextIOWrapper(obj['Body'], encoding='utf-8') for line in file_content: 意味着TextIOWrapper被懒惰地读取和解码(一次为几个KB),并且行也被懒惰地迭代。这样,无论文件大小如何,都可以将内存需求减少到很小的固定数量(峰值内存成本取决于最长行的长度)。

更新

看来obj['Body']没有实现StreamingBody ABC。但是它确实有its own documented API,可用于类似目的。如果您无法让io.BufferedIOBase为您完成工作(如果可以使工作更有效,更简单),则可以选择一种替代方法:

TextIOWrapper

与使用file_content = (line.decode('utf-8') for line in obj['Body'].iter_lines()) for line in file_content: 不同,它不能从块的批量解码中受益(每行都是单独解码的),但是在减少内存使用方面,它仍应具有相同的优势。