FastAPI를 엄청나게 빠르게 만드는 10가지 방법: 코드에서 프로덕션까지
James Reed
Infrastructure Engineer · Leapcell

10가지 FastAPI 성능 최적화 팁: 코드에서 배포까지의 엔드투엔드 속도 향상
FastAPI는 비동기 작업 지원, 자동 문서화, 강력한 유형 검증 덕분에 Python API 개발을 위한 선호되는 프레임워크 중 하나가 되었습니다. 그러나 높은 동시성 시나리오에서 최적화되지 않은 서비스는 지연 시간 증가와 처리량 감소로 어려움을 겪을 수 있습니다. 이 기사는 FastAPI의 성능 잠재력을 극대화하는 데 도움이 되도록 구현 단계와 설계 원칙을 포함하여 10가지 실용적인 최적화 솔루션을 컴파일합니다.
1. 비동기적 이점을 낭비하지 않도록 async/await 우선 순위 지정
구현 방법: 뷰 함수, 종속성 및 데이터베이스 작업에 비동기 구문을 사용하고 aiohttp
(HTTP 요청용) 및 sqlalchemy.ext.asyncio
(데이터베이스용)와 같은 비동기 라이브러리와 연결합니다.
from fastapi import FastAPI import aiohttp app = FastAPI() @app.get("/async-data") async def get_async_data(): async with aiohttp.ClientSession() as session: async with session.get("https://api.example.com/data") as resp: return await resp.json() # 이벤트 루프를 차단하지 않고 비동기 일시 중단
설계 원칙: FastAPI는 ASGI 프로토콜을 기반으로 하며, 이벤트 루프가 핵심입니다. 동기 함수(def
로 정의됨)는 이벤트 루프 스레드를 독점합니다. 예를 들어 데이터베이스 응답을 기다리는 동안 CPU는 완전히 유휴 상태로 유지되지만 다른 요청을 처리할 수 없습니다. async/await
를 사용하면 I/O 작업이 일시 중단될 때 이벤트 루프는 다른 작업을 예약하여 CPU 사용률을 3~5배 높일 수 있습니다.
2. 종속성 인스턴스를 재사용하여 재초기화 오버헤드 감소
구현 방법: 데이터베이스 엔진 및 구성 객체와 같은 상태 비저장 종속성의 경우 lru_cache
또는 싱글톤 패턴을 사용하여 인스턴스를 캐시합니다.
from fastapi import Depends from functools import lru_cache from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine @lru_cache(maxsize=1) # 전역 재사용을 위해 엔진 인스턴스를 1개만 생성합니다. def get_engine(): return create_async_engine("postgresql+asyncpg://user:pass@db:5432/db") async def get_db(engine=Depends(get_engine)): async with AsyncSession(engine) as session: yield session
설계 원칙: 기본적으로 FastAPI는 각 요청에 대해 새 종속성 인스턴스를 만듭니다. 그러나 데이터베이스 엔진 및 HTTP 클라이언트와 같은 구성 요소 초기화 (예: 연결 풀 설정)는 시간과 리소스를 소비합니다. 인스턴스 캐싱은 연결 풀의 과도한 생성으로 인한 과도한 데이터베이스 압력을 방지하면서 초기화 오버헤드를 90% 이상 줄일 수 있습니다.
3. Pydantic 모델을 단순화하여 유효성 검사 비용 절감
구현 방법:
- API에 필요한 필드만 유지합니다.
exclude_unset
을 사용하여 직렬화된 데이터 감소- 간단한 시나리오에서는 Pydantic 대신
typing
을 사용하십시오.
from pydantic import BaseModel class UserResponse(BaseModel): id: int name: str # 프런트 엔드를 위해 "created_at_timestamp"와 같은 사용하지 않는 필드 제거 @app.get("/users/{user_id}", response_model=UserResponse) async def get_user(user_id: int, db=Depends(get_db)): user = await db.get(User, user_id) return user.dict(exclude_unset=True) # 직렬화 시간을 줄이기 위해 기본값이 아닌 값만 반환합니다.
설계 원칙: Pydantic은 리플렉션을 통해 유형 유효성 검사를 구현합니다. 모델에 필드가 많을수록 중첩이 깊을수록 리플렉션 오버헤드가 커집니다. 높은 동시성 시나리오에서 복잡한 모델의 유효성 검사 및 직렬화는 요청 지연 시간의 40%를 차지할 수 있습니다. 모델을 단순화하면 리플렉션 작업이 직접적으로 줄어들어 응답 속도가 20%~30% 향상됩니다.
4. Uvicorn + Gunicorn을 사용하여 멀티 코어 CPU 사용률 극대화
구현 방법: 프로덕션 환경에서는 Gunicorn을 사용하여 프로세스 관리를 수행하고 CPU 코어 수와 동일한 Uvicorn 작업자 프로세스를 시작합니다.
# 4코어 CPU의 예: 포트 8000에 바인딩된 4개의 Uvicorn 프로세스 gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
설계 원칙: Python의 GIL(Global Interpreter Lock)은 단일 프로세스가 여러 코어를 사용하는 것을 방지합니다. Uvicorn은 순수 비동기 ASGI 서버이지만 단일 프로세스는 하나의 코어에서만 실행할 수 있습니다. Gunicorn은 여러 프로세스를 관리하여 각 Uvicorn 프로세스가 하나의 코어를 차지할 수 있도록 하여 코어 수에 따라 처리량이 선형적으로 향상됩니다.
5. 고주파수 데이터를 캐시하여 반복적인 쿼리/계산 감소
구현 방법: fastapi-cache2
+ Redis를 사용하여 인기 있는 데이터 (예: 구성, 순위표)를 캐시하고 합리적인 만료 시간을 설정합니다.
from fastapi_cache2 import CacheMiddleware, caches, cache from fastapi_cache2.backends.redis import CACHE_KEY, RedisCacheBackend app.add_middleware(CacheMiddleware) caches.set(CACHE_KEY, RedisCacheBackend("redis://redis:6379/0")) @app.get("/popular-products") @cache(expire=300) # 복잡한 SQL의 반복적인 실행을 피하기 위해 5분 동안 캐시 async def get_popular_products(db=Depends(get_db)): return await db.execute("SELECT * FROM products ORDER BY sales DESC LIMIT 10")
설계 원칙: API 성능 병목 현상은 종종 "반복적인 시간 소모적인 작업" (예: 대용량 테이블 스캔, 복잡한 알고리즘)에서 발생합니다. 캐싱은 결과를 일시적으로 저장하여 후속 요청이 데이터를 직접 읽을 수 있도록 하여 지연 시간을 수백 밀리초에서 밀리초로 줄입니다. 분산 캐싱은 여러 인스턴스 간의 공유도 지원하므로 클러스터 배포에 적합합니다.
6. 데이터베이스 최적화: 연결 풀 + 인덱스 + N+1 방지
구현 방법:
- 비동기 연결 풀을 사용하여 연결 수를 제어합니다.
- 쿼리 필드에 대한 인덱스 생성
select_related
를 사용하여 N+1 쿼리 방지:
# 한 번에 사용자 및 관련 주문을 쿼리하여 "10명의 사용자 쿼리 + 10개의 주문 쿼리"의 N+1 문제 방지 async def get_user_with_orders(user_id: int, db: AsyncSession = Depends(get_db)): return await db.execute( select(User).options(select_related(User.orders)).where(User.id == user_id) ).scalar_one_or_none()
설계 원칙: 데이터베이스는 대부분의 API의 성능 병목 현상입니다.
- 연결 설정은 시간이 오래 걸립니다(연결 풀은 연결을 재사용합니다).
- 전체 테이블 스캔은 느립니다(인덱스는 쿼리 복잡성을 O(n)에서 O(log n)으로 줄입니다).
- N+1 쿼리는 여러 I/O 작업을 발생시킵니다(단일 쿼리로 이 문제를 해결합니다.). 이러한 세 가지 최적화는 데이터베이스 지연 시간을 60% 이상 줄일 수 있습니다.
7. 정적 파일을 Nginx/CDN에 위임 - FastAPI에 과부하를 주지 마십시오.
구현 방법: Nginx를 정적 리소스에 대한 역방향 프록시로 사용하고 대규모 프로젝트의 경우 CDN과 페어링합니다.
server { listen 80; server_name api.example.com; # Nginx는 1일 캐시로 정적 파일을 처리합니다. location /static/ { root /path/to/app; expires 1d; } # API 요청을 FastAPI로 전달 location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; } }
설계 원칙: FastAPI는 애플리케이션 서버이며 정적 파일 처리 효율성은 Nginx보다 10배 이상 낮습니다. Nginx는 비동기 비차단 모델을 사용하며, 특히 정적 파일 전송에 최적화되어 있습니다. CDN은 에지 노드를 통해 콘텐츠를 배포하여 사용자 지연 시간을 더욱 줄입니다.
8. 미들웨어를 간소화하여 요청 인터셉션 오버헤드 감소
구현 방법: 핵심 미들웨어 (예: CORS, 인증)만 유지하고 디버깅 미들웨어 제거:
from fastapi.middleware.cors import CORSMiddleware # CORS 미들웨어만 유지하고 허용된 출처 및 메서드 지정 app.add_middleware( CORSMiddleware, allow_origins=["https://example.com"], # 보안 위험 및 성능 손실을 줄이려면 와일드카드 *를 피하십시오. allow_credentials=True, allow_methods=["GET", "POST"], # 필요한 메서드만 엽니다. )
설계 원칙: 미들웨어는 모든 요청/응답을 가로챕니다. 추가되는 각 미들웨어는 요청에 추가 처리 계층을 추가합니다. 미들웨어가 I/O 작업(예: 로깅)을 포함하는 경우 이벤트 루프를 차단할 수도 있습니다. 미들웨어를 간소화하면 요청 체인 지연 시간을 15%~20% 줄일 수 있습니다.
9. 차단을 방지하기 위해 비동기 뷰에서 동기 함수 호출을 피하십시오.
구현 방법:
- 비동기 라이브러리 우선 순위 지정 (
requests
대신aiohttp
사용) - 동기 함수가 불가피한 경우
asyncio.to_thread
로 래핑합니다.
import asyncio import requests # 동기 라이브러리 - 비동기 뷰에서 직접 호출할 수 없습니다. @app.get("/sync-data") async def get_sync_data(): # 이벤트 루프를 차단하지 않고 스레드 풀에서 동기 함수 실행 resp = await asyncio.to_thread(requests.get, "https://api.example.com/sync-data") return resp.json()
설계 원칙: 동기 함수는 이벤트 루프 스레드를 차지하여 다른 비동기 작업이 대기열에 추가되도록 합니다. asyncio.to_thread
는 동기 함수를 스레드 풀로 오프로드하여 이벤트 루프가 다른 요청을 계속 처리하고 동기 라이브러리 사용과 성능의 균형을 맞출 수 있도록 합니다.
10. 프로파일링 도구를 사용하여 병목 현상 파악 - 맹목적인 최적화 방지
구현 방법:
cProfile
을 사용하여 느린 요청 분석- 메트릭 모니터링을 위해 Prometheus + Grafana 사용:
import cProfile @app.get("/profile-me") async def profile_me(): pr = cProfile.Profile() pr.enable() result = await some_expensive_operation() # 분석할 비즈니스 로직 pr.disable() pr.print_stats(sort="cumulative") # 병목 현상을 식별하기 위해 누적 시간별로 정렬 return result
설계 원칙: 최적화의 전제는 병목 현상을 식별하는 것입니다. 시간이 오래 걸리지 않는 함수에 캐싱을 추가하는 것은 의미가 없습니다. 프로파일링 도구는 시간이 오래 걸리는 지점(예: 지연 시간의 80%를 차지하는 SQL 쿼리)을 정확하게 찾아내고, 모니터링 도구는 온라인 문제(예: 피크 시간대의 갑작스러운 지연 시간 급증)를 감지하여 목표에 맞는 최적화를 보장합니다.
요약
FastAPI 성능 최적화의 핵심 로직은 "차단 감소, 리소스 재사용, 중복 작업 방지"입니다. async/await
및 단순화된 모델과 같은 코드 수준 최적화부터 서버 조합 및 CDN과 같은 배포 수준 개선, 캐싱 및 데이터베이스 최적화와 같은 데이터 수준 개선에 이르기까지 이러한 팁을 엔드투엔드로 구현하면 FastAPI 서비스가 높은 동시성에서도 낮은 지연 시간과 높은 처리량을 유지할 수 있습니다.
Leapcell: 최고의 서버리스 웹 호스팅
마지막으로 Python 서비스 배포에 이상적인 플랫폼인 Leapcell 을 추천합니다.
🚀 좋아하는 언어로 빌드
JavaScript, Python, Go 또는 Rust로 손쉽게 개발하십시오.
🌍 무제한 프로젝트를 무료로 배포
사용한 만큼만 지불하십시오. 요청이나 요금이 없습니다.
⚡ 사용한 만큼 지불, 숨겨진 비용 없음
유휴 요금이 없으며 원활한 확장성만 있습니다.
🔹 Twitter에서 팔로우하세요: @LeapcellHQ