FastAPI에서 스케쥴링 작업을 구현하며, 애플리케이션 시작 시 스케줄러를 초기화하고 실행하고자했다.
이를 위해 @app.on_event("startup")과 @app.on_event("shutdown")를 사용하려고했더니, deprecated 되었다며 lifespan event handler를 사용하라는 경고가 떴다.

그래서
Lifespan이 무엇인지, 왜 @app.on_event()를 사용하던 방식에서 Lifespan를 사용하는 방식으로 변경된 것인지 살펴보고자한다.
Lifespan의 사용 방법과 동작 방식에 대해서도 간략하게 살펴보았으니, 사용하기 전 읽어본다면 도움될 것이다!
이 글에서는:
위 주제들에 대해 다루고자한다.
@app.on_event()와 Lifespan의 사용@app.on_event()와 Lifespan에 대해 설명하기 전에, 이것들이 언제 왜 사용되는지 설명하고자한다.
FastAPI 애플리케이션을 개발하다 보면, 애플리케이션 시작 시와 종료 시 특정 작업이 필요할 때가 있다.
예를 들어 다음과 같다.
이처럼, 애플리케이션의 수명 주기(lifecycle) 동안 리소스를 초기화하거나 정리해야 하는 경우에 FastAPI는 @app.on_event()을, 아니 이제는 Lifespan를 사용한다.
덧,
참고로 만약 FastAPI() 선언 시 lifespan 매개변수를 사용하면 startup 및 shutdown 이벤트 핸들러는 더 이상 호출되지 않는다.
즉, lifespan 방식과 이벤트 핸들러 방식은 함께 사용할 수 없으며 둘 중 하나만 선택해야한다.
from fastapi import FastAPI
app = FastAPI()
@app.on_event("startup")
async def startup():
# 애플리케이션이 시작되기 전 실행
print("데이터베이스 연결 설정 중...")
@app.on_event("shutdown")
async def shutdown():
# 애플리케이션이 종료되기 전 실행
print("리소스 정리 중...")
애플리케이션이 시작될 때 필요한 작업은 별도의 함수로 정의한 후, 해당 함수에 @app.on_event("startup") 데코레이터를 적용하여 등록할 수 있다.
마찬가지로, 종료 시 실행되어야 할 작업은 @app.on_event("shutdown") 데코레이터를 사용해 동일한 방식으로 정의하면 된다.
해당 함수들은 app = FastAPI()을 선언한 이후 작성하면 된다.
@app.on_event()는 여러 함수에 적용할수도 있다.
from fastapi import FastAPI
app = FastAPI()
@app.on_event("startup")
async def startup_task_1():
print("Startup Task 1: Initialize database connection.")
@app.on_event("startup")
async def startup_task_2():
print("Startup Task 2: Initialize background scheduler.")
@app.on_event("shutdown")
async def shutdown_task_1():
print("Shutdown Task 1: Close database connection.")
@app.on_event("shutdown")
async def shutdown_task_2():
print("Shutdown Task 2: Stop background scheduler.")
실행 결과
# FastAPI 애플리케이션 실행 시
Startup Task 1: Initialize database connection.
Startup Task 2: Initialize background scheduler.
INFO: Application startup complete.
...
# FastAPI 애플리케이션 종료 시:
Shutdown Task 1: Close database connection.
Shutdown Task 2: Stop background scheduler.
INFO: Application shutdown complete.
같은 이벤트(startup 또는 shutdown)에 대해 여러 개의 함수를 등록할 수 있으며, 이 함수들은 등록된 순서대로 실행된다.
주의할 점은 이벤트 핸들러는 등록된 순서대로 실행되므로, 의존성이 있는 경우 선언 순서를 적절히 조정해야한다.
Lifespan event은 애플리케이션 시작과 종료 시 필요한 로직을 각각 개별적으로 등록하는 대신, 하나의 함수에서 관리한다.
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
# 애플리케이션이 시작되기 전 실행
print("데이터베이스 연결 설정 중...")
yield
# 애플리케이션이 종료되기 전 실행
print("리소스 정리 중...")
app = FastAPI(lifespan=lifespan)
yield 키워드를 기준으로, 이전에는 애플리케이션 시작 시 실행되는 작업, 이후에는 애플리케이션 종료 시 실행되는 작업을 정의한다.
Lifespan 함수는 Python의 contextlib 모듈에서 제공하는 @asynccontextmanager 데코레이터를 사용하며, FastAPI 인스턴스를 생성할 때 lifespan 매개변수로 등록한다.
@app.on_event()와 달리 Lifespan event는 애플리케이션당 한 번만 정의할 수 있다.
즉, lifespan은 하나만 정의할 수 있지만, 함수 내부에서 여러 초기화 작업이나 정리 작업을 포함할 수 있다.
작업을 분리해서 관리하려면 lifespan 내부에 여러 함수를 호출하면 된다.
from contextlib import asynccontextmanager
from fastapi import FastAPI
async def initialize_database():
print("Database initialized.")
async def initialize_scheduler():
print("Scheduler initialized.")
async def clean_up_database():
print("Database connection closed.")
async def clean_up_scheduler():
print("Scheduler stopped.")
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup tasks
await initialize_database()
await initialize_scheduler()
print("All startup tasks completed.")
yield
# Shutdown tasks
await clean_up_database()
await clean_up_scheduler()
print("All shutdown tasks completed.")
app = FastAPI(lifespan=lifespan)
실행 결과
# FastAPI 애플리케이션 실행 시
Database initialized.
Scheduler initialized.
All startup tasks completed.
INFO: Application startup complete.
...
# FastAPI 애플리케이션 종료 시:
Database connection closed.
Scheduler stopped.
All shutdown tasks completed.
INFO: Application shutdown complete.
사실 사용 방법은 쉽다. 구현을 위해서는 이 정도의 지식만 알고있어도 충분하다.
그렇다면 왜 어떠한 이유때문에 @app.on_event()가 아닌 Lifespan event를 사용하는 것을 권장하는 것일까?
애플리케이션을 개발할 때, 시작과 종료 작업이 서로 연결되는 경우가 많다.
예를 들어
이처럼 시작 시 리소스를 확보(초기화)하고, 종료 시 이를 해제(정리)해야 하는 패턴이 일반적이다.
@app.on_event() 방식의 한계하지만 기존의 @app.on_event("startup") 및 @app.on_event("shutdown") 방식을 사용하면,
시작과 종료 로직이 별도의 함수로 분리되어 있어, 이들 간에 데이터를 공유하려면 전역 변수나 기타 우회적인 방법을 사용해야 한다.
반면, Lifespan은 Python의 비동기 컨텍스트 관리자(asynccontextmanager)를 활용하여, yield를 기준으로 초기화한 데이터를 함수 내부의 상태로 유지할 수 있다.
app.state를 통한 데이터 공유
from fastapi import FastAPI
from contextlib import asynccontextmanager
# Lifespan 정의
@asynccontextmanager
async def lifespan(app: FastAPI):
# 시작 시 데이터베이스 연결 풀 생성 및 저장
app.state.db = await create_db_connection()
yield
# 종료 시 연결 풀 닫기
await app.state.db.close()
app = FastAPI(lifespan=lifespan)
@app.get("/")
async def read_root():
# 애플리케이션 상태에서 데이터베이스 연결 가져오기
db = app.state.db
return {"message": "Database connected", "db": str(db)}
app.state.db에 데이터베이스 연결을 저장.app.state.db를 통해 직접 접근 가능, 전역 변수 없이 상태 유지 가능.yield를 통한 상태 유지
from fastapi import FastAPI
from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app: FastAPI):
resource = {"connection": "connected"}
yield resource # yield를 통해 생성한 객체를 유지
resource["connection"] = "disconnected"
app = FastAPI(lifespan=lifespan)
@app.get("/")
async def get_resource():
return {"status": app.extra["connection"]}
yield를 통해 시작 시 생성한 객체가 종료 시점까지 유지됨.app.extra를 통해 객체 접근이 가능.Lifespan은 Python의 asynccontextmanager를 기반으로 하여, yield를 활용해 비동기 리소스를 구조적으로 관리할 수 있다.
모든 비동기 작업이 하나의 컨텍스트에서 실행되므로 예외 발생 시 처리가 용이하다.
또한, FastAPI는 ASGI(Asynchronous Server Gateway Interface) 기반으로 동작하며, Lifespan은 ASGI의 수명 주기 관리 프로토콜을 준수하여 비동기 서버 환경과 자연스럽게 연동된다.
FastAPI의 Lifespan에 대해 설명해달라고 GPT에게 물었더니 다음과 같이 답변해주었다.
FastAPI에서 lifespan은 애플리케이션의 수명 주기(Lifespan)를 컨텍스트를 통해 관리하기 위해 사용되는 개념으로, 애플리케이션이 실행되거나 종료될 때 특정 작업을 수행할 수 있는 훅(Hook)을 제공합니다.
이를 통해 애플리케이션의 시작과 종료 시 실행되어야 할 초기화 및 정리 작업을 처리할 수 있습니다.
대충 애플리케이션 시작과 종료 시 실행되어야할 코드들을 한 곳에 정의해두는 함수라고 생각하면 될 것 같다.
근데… 애플리케이션 수명 주기? 컨텍스트…? 대략적인 의미는 알 것 같지만, 좀 더 자세히 살펴보고자한다.
애플리케이션의 수명 주기(lifecycle)는 애플리케이션이 시작되어 실행되고 종료되기까지의 과정을 말한다.
예를 들어:
애플리케이션 수명 주기의 예시
FastAPI 애플리케이션은 웹 서버가 실행될 때 시작하고, 서버가 종료될 때 멈춥니다.
uvicorn main:app 실행 → 서버 시작.컨텍스트는 애플리케이션이 특정 작업을 수행하기 위해 필요한 상태와 환경입니다.
컨텍스트 관리란, 이러한 초기화와 정리 작업을 자동으로 처리하여 리소스 누수를 방지하고 코드의 가독성을 높이는 것을 의미한다.
운영체제를 공부할 때 많이 들었던, CPU의 컨텍스트 스위칭에서의 컨텍스트와 비슷한 개념이라 생각하면 좋다.
Lifespan event 동작 방식을 살펴볼려면 아래의 개념들을 이해하는 것이 도움된다.
FastAPI의 Lifespan은 ASGI의 Lifespan Protocol을 기반으로 애플리케이션의 시작(Startup) 및 종료(Shutdown) 이벤트를 처리한다. Lifespan의 동작 방식은 내부적으로 이벤트 루프와의 연동을 통해 리소스를 초기화하고 해제하는 구조로 설계되어 있으며, 이를 통해 애플리케이션의 수명 주기를 안정적으로 관리할 수 있다.
간략하게 설명하면 ASGI 서버는 애플리케이션 시작 시 lifespan.startup 메시지를 보내고,종료 시 lifespan.shutdown 메시지를 통해 리소스를 정리한다. FastAPI는 Starlette의 lifespan 핸들러를 통해 해당 메시지를 처리한다.
FastAPI의 lifespan의 실질적인 구현은 Starlette이다. (참고로 FastAPI는 Starlette를 기반으로 만들어진 프레임워크이다.)
FastAPI 클래스는 Starlette의 lifespan 매개변수를 받아 애플리케이션 생애 주기를 관리한다.
asynccontextmanagerLifespan이 컨텍스트를 기반으로 수명 주기를 관리한다는 점에서, Python의 컨텍스트 관리자의 개념을 안다면 도움될 것이다.
FastAPI의 Lifespan 기능은 Python의 비동기 컨텍스트 관리자(asynccontextmanager)를 기반으로 구현되었다.
‘yield 키워드를 기준으로 초기화 → 실행 → 종료 단계로 나뉜다’라는 개념은 너무 쉬우나,
비동기 환경에서는 여러 작업이 동시에 실행되므로, 컨텍스트 관리자가 리소스를 올바르게 동기화 및 보호하는 역할을 한다는 점을 고려하면 마냥 쉽지만은 않다.
Lifespan 함수는 yield 기반의 의존성 주입과 매우 유사한 개념으로 동작한다.
FastAPI에서 의존성 주입 패턴을 이해하면, Lifespan을 통한 리소스 관리의 구조와 활용 방식을 보다 쉽게 이해할 수 있다.