MemoryError при использовании метода read () при чтении файла JSON большого размера из Amazon S3

1

Я пытаюсь импортировать большой размер JSON FILE из Amazon S3 в AWS RDS-PostgreSQL с помощью Python. Но эти ошибки возникли,

Traceback (последний последний вызов):

Файл "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)

Файл "/usr/lib64/python3.6/http/client.py", строка 617, в _safe_read

return b "". join (s)

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()

Есть ли какие-либо решения этих проблем? Любая помощь сделана, спасибо вам большое!

Теги:
amazon-s3
etl
amazon-rds

1 ответ

2
Лучший ответ

Значительная экономия может быть достигнута, если вы не вложите весь свой входной файл в память в виде list строк.

В частности, эти строки ужасны при использовании памяти, поскольку они предполагают использование пиковой памяти объекта bytes размером всего вашего файла плюс list строк с полным содержимым файла:

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

Для текстового файла ASCII емкостью 1 ГБ с 5 миллионами строк на 64-битном Python 3. 3+ требуется максимальная потребность в памяти примерно 2,3 ГБ только для объекта bytes, list и отдельной str в list. Программа, которая требует в 2,3 раза больше ОЗУ, чем размер обрабатываемых файлов, не будет масштабироваться для больших файлов.

Чтобы исправить, измените этот исходный код на:

file_content = io.TextIOWrapper(obj['Body'], encoding='utf-8')
for line in file_content:

Учитывая, что obj['Body'] можно использовать для ленивой потоковой передачи, это должно удалить обе копии полных файлов из памяти. Использование TextIOWrapper означает, что obj['Body'] лениво читается и декодируется в кусках (по несколько килобайт за раз), а также прокручиваются также лениво; это уменьшает потребность в памяти до небольшой, в значительной степени фиксированной суммы (пиковая стоимость памяти будет зависеть от длины самой длинной линии), независимо от размера файла.

Обновить:

Похоже, StreamingBody не реализует io.BufferedIOBase ABC. У этого есть свой собственный документированный API, хотя, который может использоваться для аналогичной цели. Если вы не можете заставить TextIOWrapper выполнять эту работу для вас (это намного эффективнее и проще, если ее можно заставить работать), альтернативой было бы сделать следующее:

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

В отличие от использования TextIOWrapper, он не использует массовое декодирование блоков (каждая строка декодируется отдельно), но в остальном она должна по-прежнему достигать тех же преимуществ с точки зрения сокращения использования памяти.

  • 1
    Это поднимает ошибку. Traceback (последний вызов был последним): файл "my_code.py", строка 67, в <module> file_content = io.TextIOWrapper (obj ['Body'], encoding = 'utf-8') AttributeError: объект 'StreamingBody' имеет нет атрибута для чтения
  • 0
    @GeraldMilan: Тьфу. Они сделали своего рода файловый объект, который на самом деле не совместим с io ABC . Вы можете обернуть StreamingBody в простой файловый класс, который реализует ABC BufferedIOBase , или, если он уже очень близок к тому, что вам нужно, monkey-patch в нужном материале (например, obj['Body'].readable = True для установки этот конкретный атрибут).
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню