You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI中异步从S3下载ML模型的实现合理性疑问

FastAPI异步更新S3存储ML模型问题解答

需求背景

为FastAPI应用添加功能,实现每小时从S3存储桶异步更新机器学习模型,且不阻塞API处理CPU密集型推理请求。

原实现代码

# Global model variable
ml_model = None

# Async function to download model from S3
async def download_model_from_s3(path="integration-tests/artifacts/MULTI.joblib"):
    global ml_model
    s3_client = boto3.client("s3")
    bucket = os.environ.get("BUCKET_BUCKET", "artifacts_bucket")
    try:
        local_model_path = './model.joblib'
        download_coroutine = s3_client.download_file(bucket, path, local_model_path)
        await download_coroutine
        ml_model = joblib.load(local_model_path)
        logging.info(f"Model updated.")

    except Exception as e:
        logging.exception(f"Error downloading or loading model: {e}")

# Asynchronous scheduler function that updates the model every interval
async def scheduler(bucket_name: str, model_key: str, interval=60):
    while True:
        # Sleep for the specified interval (in minutes)
        await asyncio.sleep(interval * 60)
        # Call the download function to update the model
        await download_model_from_s3(bucket_name, model_key)

app = FastAPI()

# Startup event to start the scheduler
@app.on_event("startup")
async def startup_event():
    # BLOCKING: Download the model once at startup to ensure it is available
    download_model_from_s3()  # Blocking, ensures model is available
    # Start the scheduler to update the model every 60 minutes (async, non-blocking)
    await scheduler(bucket_name, model_key, interval=60)

问题解答

1. 异步拉取S3数据的方式是否正确?

不正确。原代码存在两个核心问题:

  • boto3.client是同步客户端,其download_file方法是阻塞式同步调用,并非协程,不能用await关键字调用,直接await会抛出错误。
  • startup事件中直接调用download_model_from_s3()(未加await)会导致异步函数被当作普通函数执行,无法正确完成模型下载,甚至引发运行时异常。

2. 是否需要单独的异步S3客户端?

需要。boto3本身不支持异步IO,要实现真正的非阻塞S3操作,必须使用异步S3客户端:

  • 推荐使用aioboto3,它是boto3的异步封装版本,提供了与boto3一致的API接口,但所有IO操作都是异步协程,能完美配合FastAPI的异步事件循环。
  • 使用异步客户端后,S3下载操作不会阻塞事件循环,API的推理请求仍能正常处理。

3. 使用while True循环替代显式任务调度是否合适?

对于简单的每小时更新需求,这种方式完全可行,但需要注意两个关键点:

  • 启动调度任务时,不能直接await scheduler(...),否则会阻塞FastAPI的启动流程。应使用asyncio.create_task(scheduler(...))将调度任务丢到后台异步执行。
  • 如果后续需要更复杂的调度逻辑(比如固定时间点执行、任务暂停/重启、多任务调度),可以考虑使用APScheduler这类专业调度库,但仅针对每小时一次的周期性任务,while True + await asyncio.sleep足够简洁高效。

额外优化点

  • 全局变量线程安全:FastAPI默认使用多worker模式,每个worker拥有独立的ml_model副本,若需统一模型版本,可考虑将模型存储在共享内存或外部缓存(如Redis)中;
  • CPU密集操作异步处理:joblib.load是CPU密集型操作,直接在异步函数中执行会阻塞事件循环,应使用await asyncio.to_thread(joblib.load, local_model_path)将其转移到线程池执行;
  • 错误处理增强:可增加下载失败后的重试逻辑,避免单次故障导致模型无法更新。

修正后的示例代码

import asyncio
import os
import logging
import joblib
import aioboto3
from fastapi import FastAPI

# Global model variable
ml_model = None

# Async function to download model from S3 using aioboto3
async def download_model_from_s3(path="integration-tests/artifacts/MULTI.joblib"):
    global ml_model
    bucket = os.environ.get("BUCKET_BUCKET", "artifacts_bucket")
    try:
        local_model_path = './model.joblib'
        # 使用aioboto3异步客户端
        async with aioboto3.client("s3") as s3_client:
            # 异步下载文件到本地
            await s3_client.download_file(bucket, path, local_model_path)
        # 将CPU密集的模型加载操作转移到线程池,避免阻塞事件循环
        ml_model = await asyncio.to_thread(joblib.load, local_model_path)
        logging.info(f"Model updated successfully.")

    except Exception as e:
        logging.exception(f"Failed to download or load model: {str(e)}")

# Asynchronous scheduler function
async def scheduler(interval=60):
    while True:
        # 等待指定间隔(分钟转秒)
        await asyncio.sleep(interval * 60)
        await download_model_from_s3()

app = FastAPI()

# Startup event
@app.on_event("startup")
async def startup_event():
    # 启动时优先加载模型,确保API启动后即可处理请求
    await download_model_from_s3()
    # 创建后台任务执行调度,不阻塞启动流程
    asyncio.create_task(scheduler(interval=60))

内容的提问来源于stack exchange,提问作者jbuddy_13

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 15:22:34