为何Boto3 S3 Resource的objects.filter查询远慢于Client?
我需要从一个包含数十亿对象的S3桶中获取对象列表,该桶下有数百个文件夹,每个文件夹内有数百万级对象。目前遇到的核心问题是:使用boto3 S3 Resource的bucket.objects.filter方法时速度极慢,能否在保留Resource便捷性的同时,达到与Client相同的性能?
测试结果
以下是不同实现方式的耗时对比:
s3 = boto3.resource("s3") s3bucket = s3.Bucket("myBucket") # folder1/下有数千万级对象,folder1/hello09_前缀下有数百万级对象(示例:s3://myBucket/folder1/hello09_file0000001) s3objects = s3bucket.objects.filter(Prefix="folder1/hello09_") # 尝试设置page_size(1000)无明显变化
- 列表推导式遍历:耗时约21分钟
tmp = [s3obj.key for s3obj in s3objects] - 普通循环遍历:耗时约20分钟
tmp = [] for s3obj in s3objects: tmp.append(s3obj.key) - S3 Client批量循环(指定完整前缀):耗时约2分钟
client = boto3.client('s3') # 使用NextContinuationToken循环,每次获取1000个对象 response = client.list_objects_v2(Bucket="myBucket", Prefix="folder1/hello09_") # 后续循环处理NextContinuationToken逻辑省略 - S3 Client批量循环(仅指定文件夹前缀):耗时约31分钟
额外的10分钟耗时可能源于需要在本地过滤出符合client = boto3.client('s3') # 使用NextContinuationToken循环,每次获取1000个对象 response = client.list_objects_v2(Bucket="myBucket", Prefix="folder1/") # 后续循环处理NextContinuationToken逻辑省略hello09_前缀的对象,并逐个追加到数组中
最初猜测
我最初以为bucket.objects.filter会先查询folder1/下的所有对象,再在本地过滤出符合hello09_前缀的结果,而非像Client那样直接向S3发起带完整前缀的查询请求。也曾怀疑Resource是逐个查询对象而非批量获取1000条,但在少量对象的测试场景中,resource.bucket.objects.filter与client.list_objects_v2的性能表现一致。
这是否是Boto3过滤逻辑的bug?有没有办法规避这个性能问题,让Resource达到Client的效率?
更新1:请求路径验证与后台开销分析
通过详细日志验证,Resource实际发送了正确的带完整前缀的过滤请求。日志中虽存在少量错误重试、区域重定向、DNS检查等操作,但这些不足以导致额外18分钟的耗时。推测问题出在Resource的后台数据处理开销上——相比Client,Resource可能在返回结果后做了更多额外的对象实例化或数据处理工作。
目前无法使用S3 Inventory Report(存在非实时等问题),那是否只能放弃Resource改用Client?难道S3 Resource仅在处理少量对象时性能与Client一致,处理大量对象时天生存在内部效率问题?我希望通过参数调整让Resource达到相同性能,但如果是底层实现的问题,可能无法实现——毕竟Resource无需手动管理continuationTokens,使用起来更便捷。
相关请求日志:
botocore.endpoint [DEBUG] Sending http request: <AWSPreparedRequest stream_output=False, method=GET, url=https://myBucket.s3.xxxx.amazonaws.com/?prefix=myFolder1%2Fhello09_&encoding-type=url, headers={'User-Agent': b'Boto3/1.20.24 Python/3.8.0 Windows Botocore/1.27.59 Resource', 'X-Amz-Date': b'xxx', 'X-Amz-Content-SHA256': b'xxx', 'Authorization': b'xxx', 'amz-sdk-invocation-id': b'xxx', 'amz-sdk-request': b'attempt=1'}> ... botocore.parsers [DEBUG] Response headers: ... ... [DEBUG] Event needs-retry.s3.ListObjects: calling handler <botocore.retryhandler.RetryHandler object at 0x000000????> botocore.retryhandler [DEBUG] No retry needed.
更新2:API版本与请求参数差异分析
对比Resource与Client的HTTP请求,发现关键差异:
- Client使用的是S3 ListObjects V2 API(URL中包含
list-type=2),并通过continuation-token实现续传 - Resource使用的是旧版ListObjects V1 API,通过
marker参数实现续传
这可能是性能差异的核心原因:ListObjects V1在处理大量对象时,S3服务端计算续传位置的效率低于V2;此外,Resource会为每个返回的对象创建对应的Python对象实例,数百万级的对象实例化操作会带来额外的开销,进一步拖慢整体速度。
请求URL对比:
Client请求URL
url=https://myBucket.s3.xxx.amazonaws.com/ ?list-type=2& prefix=myFolder1%2Fhello_& continuation-token=xxx encoding-type=url,
Resource请求URL
url=https://myBucket.s3.xxx.amazonaws.com/ ? prefix=myFolder1%2Fhello_& marker=myFolder1%2Fhello_world001& encoding-type=url,
内容的提问来源于stack exchange,提问作者Ranald Fong

