FastAPI에서 Pydantic V2 마이그레이션 후 성능 개선

배경

운영 중인 FastAPI 기반 API 서버가 Pydantic 1.x를 사용하고 있었다. Pydantic V2가 출시된 지 2년이 지났고, Rust 기반 재작성으로 큰 성능 개선이 있다는 이야기를 들어 마이그레이션을 진행했다.

주요 Breaking Changes

1. Config 클래스 방식 변경

# Before (V1)
class User(BaseModel):
    name: str
    
    class Config:
        orm_mode = True

# After (V2)
from pydantic import ConfigDict

class User(BaseModel):
    model_config = ConfigDict(from_attributes=True)
    name: str

2. validator 데코레이터 변경

# Before
from pydantic import validator

class Item(BaseModel):
    price: int
    
    @validator('price')
    def check_price(cls, v):
        if v < 0:
            raise ValueError('must be positive')
        return v

# After
from pydantic import field_validator

class Item(BaseModel):
    price: int
    
    @field_validator('price')
    @classmethod
    def check_price(cls, v):
        if v < 0:
            raise ValueError('must be positive')
        return v

3. json() 메서드 deprecated

# Before
user.json()

# After
user.model_dump_json()

성능 측정

10,000개 객체를 JSON으로 직렬화하는 벤치마크를 돌렸다.

import time
from pydantic import BaseModel

class Product(BaseModel):
    id: int
    name: str
    price: float
    tags: list[str]

products = [Product(id=i, name=f"item-{i}", price=i*1.5, tags=["tag1", "tag2"]) for i in range(10000)]

start = time.time()
result = [p.model_dump_json() for p in products]
print(f"Elapsed: {time.time() - start:.3f}s")
  • Pydantic V1: 0.847s
  • Pydantic V2: 0.456s

약 46% 성능 향상을 확인했다.

마이그레이션 팁

  1. pydantic.v1 호환 레이어를 활용하면 점진적 마이그레이션이 가능하다
  2. mypypyright 타입 체커를 돌려서 deprecated 사용을 미리 찾아내는 게 효율적이었다
  3. 테스트 커버리지가 낮은 프로젝트라면 마이그레이션 전 테스트 작성을 권장한다

결론

마이그레이션 작업은 하루 정도 소요됐고, API 응답 시간이 평균 15% 개선됐다. JSON 직렬화가 많은 엔드포인트일수록 효과가 컸다. FastAPI 최신 버전과의 호환성도 좋아져서 만족스러운 작업이었다.