Lambda调用S3 list_objects_v2报Rate Exceeded错误解决方案
问题场景
第三方应用每日向个人名下S3存储桶的指定前缀路径下上传约10000个对象,业务需求为拉取该存储桶+指定前缀路径下最近24小时内上传的所有对象,当前该路径下存储的文件总量极大。原有实现调用分页接口时产生多次S3 API请求触发限流报错,使用的Python Lambda函数代码如下:
import json import boto3 import time from datetime import datetime, timedelta def lambda_handler(event, context): s3 = boto3.client("s3") from_date = datetime.today() - timedelta(days=1) string_from_date = from_date.strftime("%Y-%m-%d, %H:%M:%S") print("Date :", string_from_date) s3_paginator = s3.get_paginator('list_objects_v2') list_of_buckets = ['kush-dragon-data'] bucket_wise_list = {} for bucket in list_of_buckets: response = s3_paginator.paginate(Bucket=bucket,Prefix='inside-bucket-level-1/', PaginationConfig={"PageSize": 1000}) filtered_iterator = response.search( "Contents[?to_string(LastModified)>='\"" + string_from_date + "\"'].Key") keylist = [] for key_data in filtered_iterator: if "/" in key_data: splitted_array = key_data.split("/") if len(splitted_array) > 1: if splitted_array[-1]: keylist.append(splitted_array[-1]) else: keylist.append(key_data) bucket_wise_list.update({bucket: keylist}) print("Total Number Of Object = ", bucket_wise_list) # TODO implement return { 'statusCode': 200, 'body': json.dumps(bucket_wise_list) }
报错信息
执行上述Lambda函数时,抛出如下错误:
"Calling the invoke API action failed with this message: Rate Exceeded."
根因分析
- 核心问题是逻辑需要全量遍历指定前缀下的所有历史对象再做时间过滤,当路径下存量文件规模极大时,短时间内会发起大量
list_objects_v2请求,很容易触发S3接口的每秒请求数限流阈值。 - 原有代码初始化S3客户端时未配置自定义重试策略,默认重试逻辑没有适配限流场景的退避规则,触发限流后请求直接失败,不会自动重试。
- 时间过滤逻辑存在缺陷:使用本地时间生成的字符串直接和S3返回的UTC时间LastModified字段做字符串比对,既有时区偏差问题,也存在格式不匹配导致的比对结果错误。
- JMESPath过滤是在客户端拉取全部分页结果后才执行,不会减少实际发送到S3的API请求量,无法降低限流概率。
修复方案
长期最优方案(从根源消除List请求)
直接给目标存储桶配置S3事件通知,规则匹配指定前缀下的s3:ObjectCreated:*事件,将事件投递到SQS队列或EventBridge事件总线。需要拉取近24小时对象时,直接消费对应时间窗口内的事件即可,完全不需要遍历S3路径,从根源上避免大量List接口请求,同时结果准确性100%,不会出现漏查。
如果暂时无法改造事件链路,可使用下面的代码层优化方案兼容现有逻辑。
代码层兼容优化方案
- 初始化S3客户端时开启
adaptive自适应重试模式,设置合理的最大重试次数:该模式会在客户端侧自动做速率适配,触发限流时按指数退避规则自动重试,绝大多数Rate Exceeded错误可以自动恢复。 - 修正时间比对逻辑:统一使用UTC时区计算时间阈值,直接用datetime对象比对LastModified字段,避免字符串比对、时区不匹配导致的结果错误。
- 分页请求间增加微小延迟平滑请求速率,避免短时间请求量突增触发限流。
- 如果对象Key本身携带日期时间前缀(如
yyyy/mm/dd/结构),可按时间范围拆分List请求的前缀,进一步缩小遍历范围,减少总请求数。
优化后的可直接运行代码如下:
import json import boto3 import time from datetime import datetime, timedelta, timezone def lambda_handler(event, context): # 初始化S3客户端,配置自适应重试,最大重试10次 s3 = boto3.client( "s3", config=boto3.session.Config( retries={ "max_attempts": 10, "mode": "adaptive" } ) ) # 统一使用UTC时区计算24小时阈值,和S3 LastModified时区对齐 threshold_time = datetime.now(timezone.utc) - timedelta(days=1) print(f"Query objects modified after: {threshold_time.isoformat()}") s3_paginator = s3.get_paginator("list_objects_v2") target_bucket = "kush-dragon-data" target_prefix = "inside-bucket-level-1/" keylist = [] page_iterator = s3_paginator.paginate( Bucket=target_bucket, Prefix=target_prefix, PaginationConfig={"PageSize": 1000} ) for page in page_iterator: for obj in page.get("Contents", []): # 直接比对datetime对象,无格式/时区误差 if obj["LastModified"] >= threshold_time: key = obj["Key"] # 提取末尾文件名 file_name = key.rsplit("/", 1)[-1] if file_name: keylist.append(file_name) # 页间加100ms延迟,平滑请求速率 time.sleep(0.1) result = {target_bucket: keylist} print(f"Matched object count: {len(keylist)}") return { "statusCode": 200, "body": json.dumps(result) }
额外配置注意事项
- 如果全量遍历耗时较长触发Lambda超时,需要适当调大Lambda函数的超时时间(建议初始设为5分钟,根据实际遍历耗时调整),同时可适当提升Lambda内存配置:Lambda内存和CPU、网络带宽正相关,内存越高执行速度越快。
- 如果指定前缀下存量对象超过千万级,建议按子前缀拆分遍历任务,通过Step Functions分布式调度执行,避免单Lambda任务执行时间过长。
- 不要为了加速遍历调高PageSize到超过1000,
list_objects_v2接口单页最多返回1000个对象,设置更大的值不会生效。
内容的提问来源于stack exchange,提问作者Kush Patel
相关产品推荐
相关产品推荐

