【问题标题】:Boto3 S3 Multipart Download of a Large Byte Range大字节范围的 Boto3 S3 多部分下载
【发布时间】:2022-11-10 18:34:05
【问题描述】:

我有一组存储在 S3 中的 4GB 文件,我需要从中提取 1GB 部分。我知道我可以通过boto3 S3 远程获取请求来完成此操作:

import boto3

s3 = boto3.client('s3')
bucket = ''
key = ''
start = 100_0000_000
end = 200_0000_000
response = s3.get_object(Bucket=bucket, Key=key, Range=f'bytes={start}-{end}')

但是,这个下载速度很慢,因为我没有利用 S3 的multipart download functionality。我了解如何使用boto3s3.Object.download_file() 方法执行多部分下载,但我不知道如何为此方法调用指定整体字节范围。

从 S3 下载大量文件时,执行多部分下载的最快和最干净的方法是什么?假设它在与 S3 存储桶位于同一区域的 EC2 实例上运行。

【问题讨论】:

  • s3.Object.get 支持 Range 参数。
  • @jarmod,s3.Object.get 是否也支持多部分获取?
  • s3.Transfer 没有将字节范围记录为允许的选项,因此除非您在源代码中找到它,否则它不是一个选项。而且,如果您确实在源代码中找到了它,那么您就接受了它会在没有警告的情况下被删除的风险。
  • 您总是可以通过在多个线程上运行基于范围的下载然后组合结果来自己实现它,但是您是否验证过它实际上更快?例如,您是否将使用传输管理器下载 1 GB 文件的时间与使用 get_object() 下载相同的 1 GB 文件的时间进行了比较?我的期望是后者将消耗所有可用带宽,因此具有多个并发下载将提供最小的改进。
  • 如果我理解,您希望下载对象的某些子集,例如1GB 对象的 50%,您希望能够通过多个并发范围获取,例如5 个并发,100MB 远程获取。 boto3 传输管理器不支持此 afaik,因此您可能必须使用多个并发线程实现自己的 DIY 解决方案。在每个线程中使用 s3.Object.get 方法将是这里的一个选项(尽管我不知道总体而言这在提高性能方面可能有多有效)。

标签: python amazon-web-services amazon-s3 amazon-ec2 boto3


【解决方案1】:

我使用ThreadPoolExecutor 提出了一个可行的解决方案,但我相信它仍然可以改进。我发现的最佳方法是设置一个线程池,其中包含指定了 range 参数的 s3_client.get_object 调用:

import math
from concurrent.futures import ThreadPoolExecutor

import boto3

KB = 1024
MB = KB * KB


def calculate_range_parameters(offset, length, chunk_size):
    num_parts = int(math.ceil(length / float(chunk_size)))
    range_params = []
    for part_index in range(num_parts):
        start_range = (part_index * chunk_size) + offset
        if part_index == num_parts - 1:
            end_range = str(length + offset - 1)
        else:
            end_range = start_range + chunk_size - 1

        range_params.append(f'bytes={start_range}-{end_range}')
    return range_params


def s3_ranged_get(args):
    s3_client, bucket, key, range_header = args
    resp = s3_client.get_object(Bucket=bucket, Key=key, Range=range_header)
    body = resp['Body'].read()
    return body


def threaded_s3_get(s3_client, bucket, key, offset, length, chunksize=10 * MB):
    args_list = [(s3_client, bucket, key, x) for x in calculate_range_parameters(offset, length, chunksize)]

    # Dispatch work tasks with our client
    with ThreadPoolExecutor(max_workers=20) as executor:
        results = executor.map(s3_ranged_get, args_list)

    content = b''.join(results)
    return content


s3 = boto3.client('s3')
bucket = ''
key = ''

content = threaded_s3_get(s3, bucket, key, 1 * MB, 101 * MB)
with open('data.bin', 'wb') as f:
    f.write(content)

calculate_range_parameters 在给定文件偏移量、长度和块大小的情况下创建范围参数输入列表,s3_ranged_get 包装boto3 s3-client get_object 方法,threaded_s3_get 设置ThreadPoolExecutor。当访问区域内 r5d.xlarge EC2 实例上打开的存储桶中的 1.3 GB 区域数据时,此代码将在 4.76 秒内下载数据。相比之下,使用boto3-native 多部分下载功能在相同条件下下载相同数量的数据需要 3.96 秒(即我的解决方案慢 20%)。

这个解决方案现在可以工作,但从长远来看,很高兴看到boto3 原生支持大字节范围的多部分读取。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-07-13
    • 2018-03-14
    • 2015-12-07
    • 1970-01-01
    • 2015-11-05
    • 2017-12-19
    • 2019-07-06
    相关资源
    最近更新 更多