内存高效的大型数据集流式传输到S3

时间:2017-12-01 14:57:35

标签: database amazon-s3 sqlalchemy buffer python-3.6

我正在尝试使用SQL alchemy复制S3大数据集(大于RAM)。 我的约束是:

  1. 我需要使用sqlalchemy
  2. 我需要将记忆压力保持在最低
  3. 我不想使用本地filsystem作为中间步骤将数据发送到s3
  4. 我只想以高效的方式将数据从数据库传输到S3

    我可以使用数据集(使用下面的逻辑)正常工作但是使用更大的数据集我遇到了缓冲区问题。

    我解决的第一个问题是执行查询通常会将结果缓冲到内存中。我使用fetchmany()方法。

    engine = sqlalchemy.create_engine(db_url)
    engine.execution_options(stream_results=True)
    
    results=engine.execute('SELECT * FROM tableX;')
    while True:
      chunk = result.fetchmany(10000)
      if not chunk:
        break
    

    另一方面,我有一个StringIO缓冲区,我使用fetchmany数据检查。然后我将其内容发送到s3。

    from io import StringIO
    import boto3
    import csv
    
    s3_resource = boto3.resource('s3')
    csv_buffer = StringIO()
    csv_writer = csv.writer(csv_buffer, delimiter=';')
    csv_writer.writerows(chunk)
    s3_resource.Object(bucket, s3_key).put(Body=csv_buffer.getvalue())
    

    我遇到的问题本质上是一个设计问题,如何让这些部分协同工作。它甚至可以在同一个运行时吗?

    engine = sqlalchemy.create_engine(db_url)
    s3_resource = boto3.resource('s3')
    csv_buffer = StringIO()
    csv_writer = csv.writer(csv_buffer, delimiter=';')
    
    engine.execution_options(stream_results=True)
    results=engine.execute('SELECT * FROM tableX;')
    while True:
        chunk = result.fetchmany(10000)
        csv_writer = csv.writer(csv_buffer, delimiter=';')
        csv_writer.writerows(chunk)
        s3_resource.Object(bucket, s3_key).put(Body=csv_buffer.getvalue())
        if not chunk:
            break
    

    我可以让它适用于fetchmany的一个循环,但不是几个循环。有什么想法吗?

1 个答案:

答案 0 :(得分:2)

我假设通过“让这些部分一起工作”你的意思是你想在S3中使用单个文件而不仅仅是部分文件?您需要做的就是创建一个文件对象,在读取时,将为下一个批处理和缓冲区发出查询。我们可以使用python的生成器:

def _generate_chunks(engine):
    with engine.begin() as conn:
        conn = conn.execution_options(stream_results=True)
        results = conn.execute("")
        while True:
            chunk = results.fetchmany(10000)
            if not chunk:
                break
            csv_buffer = StringIO()
            csv_writer = csv.writer(csv_buffer, delimiter=';')
            csv_writer.writerows(chunk)
            yield csv_buffer.getvalue().encode("utf-8")

这是你的文件块的流,所以我们需要做的就是将这些(当然懒惰地)拼接到一个文件对象中:

class CombinedFile(io.RawIOBase):
    def __init__(self, strings):
        self._buffer = ""
        self._strings = iter(strings)

    def read(self, size=-1):
        if size < 0:
            return self.readall()
        if not self._buffer:
            try:
                self._buffer = next(self._strings)
            except StopIteration:
                pass
        if len(self._buffer) > size:
            ret, self._buffer = self._buffer[:size], self._buffer[size:]
        else:
            ret, self._buffer = self._buffer, b""
        return ret

chunks = _generate_chunks(engine)
file = CombinedFile(chunks)
upload_file_object_to_s3(file)

将文件对象流式传输到S3留给读者练习。 (你可以使用put_object。)