FastAPI는 빠르고 현대적인 웹 프레임워크로, Python 3.7 이상에서 사용할 수 있습니다. FastAPI는 ASGI를 기반으로 하여 비동기 프로그래밍을 쉽게 활용할 수 있도록 도와줍니다. 이 글에서는 FastAPI를 사용하여 비동기 요청 처리를 구현하는 방법을 상세히 설명하고, 예제 코드를 통해 이해를 돕겠습니다.
1. FastAPI와 비동기 프로그래밍의 이해
비동기 프로그래밍은 주로 I/O 작업이 많은 작업에서 성능을 크게 개선할 수 있는 기술입니다. 동기식 프로그래밍에서는 요청이 완료될 때까지 해당 요청을 처리하는 스레드가 대기하게 됩니다. 하지만 비동기 프로그래밍에서는 자원이 낭비되지 않도록 다른 작업을 동시에 수행할 수 있습니다.
1.1 FastAPI의 비동기 지원
FastAPI는 비동기 함수(`async def`)를 사용하여 I/O 바운드 작업을 처리할 수 있게 지원합니다. FastAPI의 경로 작업(route handler)에서 비동기 함수를 사용하면, 비동기적으로 요청을 처리할 수 있어 다른 요청이 대기하지 않고 곧바로 처리됩니다.
1.2 비동기와 동기 함수 비교
FastAPI에서 비동기 경로 작업을 구현하는 방법은 동기 함수와 유사하지만, 반드시 `async` 키워드를 사용하여 정의해야 합니다. 아래는 비동기와 동기 함수의 예제 코드입니다.
from fastapi import FastAPI
import time
app = FastAPI()
# 동기 경로 작업
@app.get("/sync")
def read_sync():
time.sleep(2) # 2초 대기
return {"message": "동기 요청 처리 완료"}
# 비동기 경로 작업
@app.get("/async")
async def read_async():
await asyncio.sleep(2) # 2초 대기
return {"message": "비동기 요청 처리 완료"}
위의 코드는 `/sync` 경로를 통해 동기적으로 요청을 처리하고, `/async` 경로를 통해 비동기적으로 요청을 처리합니다. 요청이 들어온 순서에 따라 동기 요청은 처리하는 동안 다른 요청이 대기하게 되지만, 비동기 요청은 대기하는 동안 다른 요청을 처리할 수 있습니다.
2. FastAPI에서 비동기 I/O 작업 처리하기
FastAPI에서 비동기 I/O 작업은 데이터베이스의 CRUD 작업, 웹 API 호출, 파일 업로드 등 다양한 경우에 활용됩니다. 각 I/O 작업이 비동기적으로 수행되면, 애플리케이션의 성능을 높일 수 있습니다.
2.1 비동기 데이터베이스 작업
다양한 비동기 데이터베이스 드라이버가 FastAPI와 함께 사용될 수 있습니다. 예를 들어, databases
라는 비동기 ORM 라이브러리와 함께 사용하여 비동기적으로 데이터베이스와 상호작용할 수 있습니다.
import asyncio
from fastapi import FastAPI
import databases
DATABASE_URL = "sqlite:///./test.db"
database = databases.Database(DATABASE_URL)
app = FastAPI()
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
@app.get("/items/")
async def read_items():
query = "SELECT * FROM items"
return await database.fetch_all(query)
위의 코드는 `databases` 라이브러리를 사용하여 SQLite 데이터베이스에 비동기적으로 연결하고, 아이템을 읽어오는 예시입니다. FastAPI의 생명주기 이벤트를 통해 서버가 시작할 때와 종료할 때 데이터베이스 연결을 관리할 수 있습니다.
2.2 비동기 HTTP 요청 처리
FastAPI에서는 httpx
같은 라이브러리를 사용하여 비동기 HTTP 요청도 쉽게 처리할 수 있습니다. 비동기 HTTP 요청은 외부 API와의 통신이 필요할 때 매우 유용합니다.
import httpx
@app.get("/external/")
async def read_external():
async with httpx.AsyncClient() as client:
response = await client.get('https://api.example.com/data')
return response.json()
위의 코드는 외부 API에서 데이터를 가져와서 반환하는 비동기 경로 작업입니다. 비동기 클라이언트를 사용함으로써, 데이터 요청 도중 서버가 다른 요청을 처리할 수 있도록 합니다.
3. 비동기 작업에서의 오류 처리
비동기 작업을 구현할 때, 오류 처리 또한 중요합니다. 비동기 작업이 실패할 경우, 적절한 오류 메시지를 반환해야 하며, 이때 HTTPException
을 사용할 수 있습니다.
from fastapi import HTTPException
@app.get("/data/")
async def read_data(param: str):
if param not in ["valid1", "valid2"]:
raise HTTPException(status_code=404, detail="데이터를 찾을 수 없습니다.")
return {"message": f"{param}에 대한 데이터"}
위의 코드는 파라미터가 유효하지 않을 때 HTTP 404 오류를 반환하는 예제입니다. FastAPI의 예외 처리 메커니즘은 비동기 방식에서도 동일하게 작동합니다.
4. FastAPI의 비동기 요청 처리 성능 비교
비동기 요청 처리와 동기 요청 처리의 성능 차이를 비교할 수 있는 방법도 중요합니다. 아래는 FastAPI의 비동기 및 동기 경로 작업에 대한 성능 테스트 예제입니다.
import time
import asyncio
from fastapi import FastAPI
app = FastAPI()
@app.get("/sync-test/")
def sync_test():
time.sleep(1) # 1초 대기
return {"message": "비동기 요청이 아닙니다."}
@app.get("/async-test/")
async def async_test():
await asyncio.sleep(1) # 1초 대기
return {"message": "비동기 요청입니다."}
이제 이 두 경로를 동시에 호출하여 성능을 비교할 수 있습니다. 테스트 도구(예: Apache Bench
또는 wrk
)를 사용하여 두 경로에서의 응답 시간을 측정해보세요.
5. 결론
FastAPI는 비동기 요청 처리를 지원하여 높은 성능을 기록할 수 있는 프레임워크입니다. I/O 작업을 비동기적으로 처리함으로써, 서버의 자원을 효율적으로 활용할 수 있습니다. 이번 글에서는 FastAPI의 비동기 요청 처리 방식에 대해 설명하고 여러 예제를 통해 이해를 도왔습니다.
FastAPI를 사용하면 비동기적 아키텍처를 쉽게 구현할 수 있으며, 이에 따라 생산성과 성능이 향상됩니다. 비동기 함수를 적절히 활용하여 비즈니스 로직을 구현하고, 다양한 응용 프로그램을 개발해보세요!