Published on

FastAPI 성능 최적화: 초고속 API 만들기 🚄

Authors
  • avatar
    Name
    devnmin
    Twitter

FastAPI 성능 최적화: 로켓 🚀처럼 빠른 API 만들기!

안녕하세요! 오늘은 여러분의 FastAPI를 더욱 빠르게 만드는 방법을 알아볼 거예요. 이미 빠른 FastAPI를 더 빠르게? 네! 가능합니다! 함께 알아볼까요? 😎

1. 비동기 처리 최적화 ⚡

비동기 데이터베이스 쿼리

# database.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession

# 비동기 엔진 생성 (부우우웅~ 🏎️)
DATABASE_URL = "postgresql+asyncpg://user:password@localhost/dbname"
async_engine = create_async_engine(DATABASE_URL, echo=True)

# 비동기 세션
async def get_async_session():
    async with AsyncSession(async_engine) as session:
        try:
            yield session
        finally:
            await session.close()

# API 엔드포인트
@app.get("/items/")
async def get_items(session: AsyncSession = Depends(get_async_session)):
    # 비동기로 데이터 조회
    result = await session.execute(select(Item))
    items = result.scalars().all()
    return items

병렬 처리

# 여러 작업 동시 처리하기
import asyncio

@app.get("/dashboard/")
async def get_dashboard():
    # 여러 API를 동시에 호출! (시간 절약! ⏰)
    user_task = asyncio.create_task(get_user_data())
    stats_task = asyncio.create_task(get_statistics())
    posts_task = asyncio.create_task(get_recent_posts())

    # 모든 결과를 한번에 기다려요
    user_data, stats, posts = await asyncio.gather(
        user_task, stats_task, posts_task
    )

    return {
        "user": user_data,
        "stats": stats,
        "posts": posts
    }

2. 캐싱 도입하기 💾

Redis 캐시 설정

# cache.py
from fastapi_cache import FastAPICache
from fastapi_cache.backends.redis import RedisBackend
from fastapi_cache.decorator import cache
import aioredis

# Redis 연결 설정
@app.on_event("startup")
async def startup():
    redis = aioredis.from_url("redis://localhost", encoding="utf8")
    FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache:")

# 캐시 적용 (자주 바뀌지 않는 데이터는 캐시해두면 좋아요! 📦)
@app.get("/products/{id}")
@cache(expire=3600)  # 1시간 동안 캐시
async def get_product(id: int):
    return await get_product_from_db(id)

인메모리 캐싱

from functools import lru_cache

# 메모리에 결과를 캐시해두고 재사용해요!
@lru_cache(maxsize=1000)
def get_settings():
    return Settings()

3. 데이터베이스 최적화 📊

N+1 문제 해결

# 이렇게 하면 안 돼요! ❌
@app.get("/users/")
async def get_users_bad():
    users = await db.query(User).all()
    # N+1 문제 발생!
    for user in users:
        await db.query(Post).filter(Post.user_id == user.id).all()

# 이렇게 하세요! ✅
from sqlalchemy.orm import joinedload

@app.get("/users/")
async def get_users_good():
    # 한 번의 쿼리로 모든 데이터를 가져와요!
    users = await db.query(User).options(
        joinedload(User.posts)
    ).all()

인덱스 최적화

# models.py
from sqlalchemy import Index

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True)
    email = Column(String, index=True)  # 자주 검색하는 필드에 인덱스!
    username = Column(String)

    # 복합 인덱스도 만들 수 있어요!
    __table_args__ = (
        Index('idx_username_email', 'username', 'email'),
    )

4. 응답 최적화 📦

응답 압축

from fastapi.middleware.gzip import GZipMiddleware

# 응답을 쏙~ 압축해서 보내요! 🎁
app.add_middleware(GZipMiddleware, minimum_size=1000)

JSON 직렬화 최적화

from fastapi.encoders import jsonable_encoder
from orjson import orjson
from fastapi.responses import ORJSONResponse

# orjson은 기본 json보다 훨씬 빨라요! 🏃‍♂️
app = FastAPI(default_response_class=ORJSONResponse)

5. 부하 테스트 🔨

# locustfile.py
from locust import HttpUser, task, between

class FastAPIUser(HttpUser):
    wait_time = between(1, 3)

    @task
    def get_items(self):
        self.client.get("/items/")
# 부하 테스트 실행
locust -f locustfile.py --host=http://localhost:8000

6. 프로파일링 🔍

import cProfile
import pstats

def profile_endpoint():
    profiler = cProfile.Profile()
    profiler.enable()
    # 여기에 테스트할 코드를 넣어요
    profiler.disable()

    stats = pstats.Stats(profiler).sort_stats('cumtime')
    stats.print_stats()

실전 최적화 팁! 💡

  1. 배치 처리
# 한 번에 여러 데이터 처리하기
async def bulk_create_items(items: List[Item]):
    await db.execute(insert(Item), items)
  1. 페이지네이션
@app.get("/items/")
async def get_items(skip: int = 0, limit: int = 10):
    return await db.query(Item).offset(skip).limit(limit).all()
  1. 백그라운드 작업
from fastapi.background import BackgroundTasks

@app.post("/send-email/")
async def send_email(background_tasks: BackgroundTasks):
    # 메일 전송은 백그라운드에서 처리! ✉️
    background_tasks.add_task(send_email_task)
    return {"message": "이메일 전송이 시작되었어요!"}

성능 모니터링 📊

from prometheus_fastapi_instrumentator import Instrumentator

# 성능 지표 수집 (우리 앱이 얼마나 빠른지 볼까요? 📈)
Instrumentator().instrument(app).expose(app)

자주 하는 실수들 🚨

  1. 불필요한 데이터베이스 쿼리

    • 필요한 필드만 선택하세요!
  2. 캐시 키 설계 미흡

    • 적절한 캐시 키와 만료 시간을 설정하세요!
  3. 너무 큰 페이로드

    • 응답 데이터는 꼭 필요한 것만!

다음 단계는? 🎯

  1. FastAPI 마이크로서비스
  2. GraphQL with FastAPI
  3. 실시간 처리

Pro Tip: 성능 최적화는 실제 문제가 있을 때 하세요! 너무 이른 최적화는 독이 될 수 있어요! 📊

유용한 자료들 📚

다음 시간에는... FastAPI로 마이크로서비스 아키텍처 구현하는 방법을 알아볼 거예요! 기대해주세요! 🚀