AWS Lambda冷启动未影响函数运行时长的现象解释问询
我写了一个维持两个Lambda函数预热状态的脚本:
PING_DELAY = 60 last_ping = -PING_DELAY while True: try: timer = Timer() timestamp_str = datetime.now().strftime("%d.%m.%Y %H:%M:%S.%f")[:-3] print(f"Ping at {timestamp_str} ", end='', flush=True) response = lambda_client.invoke( FunctionName='myfunc', InvocationType='RequestResponse', Payload=json.dumps({'command': 'ping'})) payload = json.load(response['Payload']) if 'errorMessage' in payload: raise Exception(payload['errorMessage']) else: my_time = timer.stop() stats = payload['stats'] print(f"took {my_time}ms. n_cold: {stats['n_cold']} total_init: {stats['total_init']}ms", flush=True) except Exception as e: print(f"AWS Lambda submit failed: {e}", flush=True) time.sleep(PING_DELAY)
这个脚本每分钟调用一次myfunc,myfunc会并行调用另一个Lambda函数的109个实例,并返回遭遇冷启动的实例数量以及总初始化时长。以下是部分输出:
Ping at 27.11.2023 11:01:23.180 took 581ms. n_cold: 0 total_init: 0ms Ping at 27.11.2023 11:02:23.821 took 527ms. n_cold: 2 total_init: 11531ms Ping at 27.11.2023 11:03:24.408 took 486ms. n_cold: 0 total_init: 0ms Ping at 27.11.2023 11:04:24.954 took 511ms. n_cold: 0 total_init: 0ms Ping at 27.11.2023 11:05:25.525 took 592ms. n_cold: 1 total_init: 6416ms Ping at 27.11.2023 11:06:26.119 took 525ms. n_cold: 0 total_init: 0ms Ping at 27.11.2023 11:07:26.704 took 502ms. n_cold: 0 total_init: 0ms Ping at 27.11.2023 11:08:27.236 took 605ms. n_cold: 0 total_init: 0ms Ping at 27.11.2023 11:09:27.901 took 546ms. n_cold: 0 total_init: 0ms Ping at 27.11.2023 11:10:28.503 took 497ms. n_cold: 1 total_init: 6396ms Ping at 27.11.2023 11:11:29.060 took 489ms. n_cold: 0 total_init: 0ms
令我困惑的是:当部分实例遭遇冷启动(单实例初始化约需6秒)时,myfunc仍能在1秒内返回响应,仅极少数情况会出现符合预期的长耗时:
Ping at 27.11.2023 12:09:04.775 took 6525ms. n_cold: 6 total_init: 36829ms
附录代码
myfunc统计响应的代码:
stats = response['stats'] init = stats['init_time'] if stats['cold_start']: n_cold += 1 assert(init > 0) total_init += init else: assert(init == 0)
被myfunc调用的Lambda函数代码:
import json import time import pickle def ms_now(): return int(time.time_ns() / 1000000) class Timer(): def __init__(self): self.start = ms_now() def stop(self): return ms_now() - self.start timer = Timer() started_init = ms_now() cold_start = True init_time = timer.stop() ... # 加载大型机器学习模型的代码 def compute(event): ... # 实际计算逻辑 def lambda_handler(event, context): global cold_start global init_time global started_init stats = {'cold_start': cold_start, 'started_init': started_init, 'init_time': init_time} cold_start = False init_time = 0 started_init = 0 stats['started'] = ms_now() result = compute(event) stats['finished'] = ms_now() return { 'statusCode': 200, 'headers': { 'Content-Type': 'application/json' }, 'result': result, 'stats': stats }
解释
1. myfunc采用异步调用下游Lambda,无需等待执行完成
这是核心原因:如果myfunc调用下游Lambda时使用的是异步调用模式(InvocationType='Event'),AWS收到调用请求后会立即给myfunc返回响应,myfunc不需要等待下游Lambda完成初始化(包括冷启动)或执行逻辑。此时myfunc的耗时仅仅是发起109个调用请求的时间,自然能在1秒内完成。
你看到的少数长耗时情况,大概率是myfunc在某些逻辑分支下使用了同步调用模式(InvocationType='RequestResponse'),并且等待了所有下游实例的响应返回——这种情况下,myfunc的耗时会被最慢的那个冷启动实例拖长,和你看到的6秒级耗时匹配。
2. 下游Lambda的冷启动耗时统计逻辑错误
从你提供的下游Lambda代码来看,init_time的计算时机完全错误:
timer = Timer() started_init = ms_now() cold_start = True init_time = timer.stop() # 这里立即停止计时,还没加载模型 ... # 加载大型机器学习模型的代码
init_time是在模型加载前就调用了timer.stop(),根本没统计到冷启动的核心耗时(加载模型的6秒)。你输出中的total_init数值其实是无效的,无法反映真实的冷启动时长。正确的做法是把init_time = timer.stop()移到模型加载代码的后面,这样才能准确统计初始化耗时。
3. Lambda实例复用的特性
下游Lambda的cold_start标记逻辑是正确的:模块级变量cold_start = True会在新实例启动时被重置,第一次调用后设为False。但如果myfunc调用的实例中,大部分是复用的热实例,只有少数是新启动的冷实例,结合异步调用的特性,myfunc的响应时长也不会受冷实例影响。
内容的提问来源于stack exchange,提问作者AlwaysLearning

