본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 7. · 12 Views
FastAPI로 모델 서빙 완벽 가이드
FastAPI를 사용하여 머신러닝 모델을 API로 서빙하는 방법을 초급자 눈높이에서 설명합니다. Pydantic을 활용한 데이터 검증부터 비동기 처리, 에러 핸들링, Swagger 문서화까지 실무에 필요한 모든 내용을 다룹니다.
목차
1. FastAPI 기초 복습
김개발 씨는 회사에서 머신러닝 모델을 웹 서비스로 배포하라는 미션을 받았습니다. Flask는 써봤지만 선배가 "요즘은 FastAPI가 대세야"라고 추천해주었습니다.
도대체 FastAPI가 뭐길래 이렇게 인기가 많은 걸까요?
FastAPI는 Python으로 API를 만들 때 사용하는 현대적인 웹 프레임워크입니다. 마치 고속도로를 달리는 스포츠카처럼 빠르고, 네비게이션처럼 친절한 자동 문서화 기능을 제공합니다.
타입 힌트를 기반으로 자동 검증과 문서 생성이 이루어지기 때문에 개발 생산성이 크게 향상됩니다.
다음 코드를 살펴봅시다.
from fastapi import FastAPI
# FastAPI 앱 인스턴스 생성
app = FastAPI(title="ML Model API", version="1.0.0")
# 기본 라우트 - 서버 상태 확인용
@app.get("/")
def read_root():
return {"status": "healthy", "message": "ML API is running"}
# 경로 매개변수 사용 예시
@app.get("/models/{model_name}")
def get_model_info(model_name: str):
return {"model": model_name, "status": "loaded"}
김개발 씨는 입사 6개월 차 주니어 개발자입니다. 데이터 사이언스 팀에서 만든 추천 모델을 웹 서비스로 배포해야 하는데, 어떤 프레임워크를 써야 할지 고민이 깊어졌습니다.
"Flask 쓰면 되지 않나요?" 김개발 씨가 물었습니다. 선배 개발자 박시니어 씨가 고개를 저었습니다.
"Flask도 좋지만, 모델 서빙에는 FastAPI가 더 적합해요. 비동기 처리도 쉽고, 문서화도 자동으로 되거든요." 그렇다면 FastAPI란 정확히 무엇일까요?
쉽게 비유하자면, FastAPI는 마치 최신형 스마트폰과 같습니다. 기존의 피처폰(Flask)도 전화는 잘 되지만, 스마트폰은 앱 설치, 인터넷, 카메라 등 훨씬 많은 기능을 기본으로 제공합니다.
FastAPI도 마찬가지로 검증, 문서화, 비동기 처리 같은 현대적 기능을 기본 탑재하고 있습니다. Flask나 Django 시절에는 어땠을까요?
API 문서를 따로 작성해야 했습니다. Swagger 문서를 수동으로 만들고, 코드가 바뀔 때마다 문서도 업데이트해야 했습니다.
요청 데이터 검증 코드도 일일이 작성해야 했고, 비동기 처리를 위해서는 별도의 설정이 필요했습니다. 바로 이런 불편함을 해결하기 위해 FastAPI가 등장했습니다.
FastAPI를 사용하면 타입 힌트만으로 자동 검증이 가능해집니다. 또한 코드를 작성하는 것만으로 Swagger 문서가 자동 생성됩니다.
무엇보다 async/await 문법을 네이티브로 지원하여 높은 동시 처리 성능을 얻을 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
먼저 FastAPI() 인스턴스를 생성합니다. 여기에 title과 version을 지정하면 자동 생성되는 문서에 표시됩니다.
@app.get("/")는 GET 요청을 처리하는 데코레이터입니다. 함수의 반환값은 자동으로 JSON으로 변환됩니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 이미지 분류 서비스를 만든다고 가정해봅시다.
사용자가 이미지를 업로드하면 모델이 분류 결과를 반환하는 API가 필요합니다. FastAPI를 사용하면 파일 업로드 처리, 결과 검증, API 문서화를 모두 간결하게 처리할 수 있습니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 동기 함수와 비동기 함수를 혼용하는 것입니다.
CPU 집약적인 작업을 async 함수 안에서 그냥 실행하면 오히려 성능이 저하될 수 있습니다. 이 부분은 뒤에서 자세히 다루겠습니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 눈이 반짝였습니다.
"와, 문서가 자동으로 만들어진다니 정말 편하겠네요!" FastAPI의 기초를 이해했다면, 이제 본격적으로 모델 서빙을 위한 준비를 시작해봅시다.
실전 팁
💡 - FastAPI 앱 실행은 uvicorn main:app --reload 명령어를 사용합니다
- 개발 중에는
--reload옵션으로 코드 변경 시 자동 재시작이 가능합니다 /docs경로에서 Swagger UI를 바로 확인할 수 있습니다
2. Pydantic으로 요청 응답 정의
김개발 씨가 API를 만들기 시작했는데, 사용자가 이상한 데이터를 보내면 어떻게 처리해야 할지 막막했습니다. 숫자가 와야 하는 곳에 문자열이 오거나, 필수 필드가 빠진 요청이 들어오면요.
일일이 if문으로 검사해야 할까요?
Pydantic은 데이터 검증과 설정 관리를 위한 Python 라이브러리입니다. 마치 공항의 보안 검색대처럼 들어오는 데이터를 꼼꼼히 검사하고, 문제가 있으면 즉시 알려줍니다.
FastAPI와 함께 사용하면 요청과 응답 데이터의 형식을 명확하게 정의하고 자동으로 검증할 수 있습니다.
다음 코드를 살펴봅시다.
from pydantic import BaseModel, Field
from typing import List, Optional
# 예측 요청 스키마 정의
class PredictionRequest(BaseModel):
features: List[float] = Field(..., min_length=1, description="입력 특성 배열")
model_version: Optional[str] = Field(default="v1", description="모델 버전")
# 예측 응답 스키마 정의
class PredictionResponse(BaseModel):
prediction: float = Field(..., description="예측 결과값")
confidence: float = Field(..., ge=0, le=1, description="신뢰도 (0-1)")
model_version: str
# API 엔드포인트에서 스키마 사용
@app.post("/predict", response_model=PredictionResponse)
def predict(request: PredictionRequest):
return PredictionResponse(prediction=0.85, confidence=0.92, model_version=request.model_version)
김개발 씨는 예측 API를 만들면서 고민에 빠졌습니다. 사용자가 보내는 데이터가 올바른지 어떻게 확인할 수 있을까요?
"이거 if문으로 하나하나 검사해야 하나요?" 김개발 씨가 한숨을 쉬며 물었습니다. 박시니어 씨가 웃으며 대답했습니다.
"Pydantic을 쓰면 그럴 필요 없어요. 스키마만 정의하면 알아서 검증해줍니다." 그렇다면 Pydantic이란 정확히 무엇일까요?
쉽게 비유하자면, Pydantic은 마치 은행 창구의 서류 검토 직원과 같습니다. 대출 신청서를 제출하면 직원이 필수 항목이 빠지진 않았는지, 숫자 칸에 숫자가 제대로 적혀있는지 꼼꼼히 확인합니다.
문제가 있으면 "여기 다시 작성해주세요"라고 정확히 알려주죠. Pydantic도 마찬가지로 들어오는 데이터를 정의된 규칙에 따라 검증합니다.
Pydantic이 없던 시절에는 어땠을까요? 개발자들은 모든 필드를 수동으로 검사해야 했습니다.
if 'features' not in data:, if not isinstance(data['features'], list): 같은 코드가 끝없이 이어졌습니다. 검증 로직이 비즈니스 로직보다 길어지는 경우도 흔했습니다.
바로 이런 문제를 해결하기 위해 Pydantic이 등장했습니다. Pydantic을 사용하면 클래스 정의만으로 검증 규칙을 표현할 수 있습니다.
또한 타입 변환도 자동으로 처리됩니다. 문자열 "123"이 들어와도 정수형 필드라면 자동으로 123으로 변환해줍니다.
무엇보다 명확한 에러 메시지를 제공하여 클라이언트가 무엇을 잘못 보냈는지 바로 알 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다.
BaseModel을 상속받은 클래스가 바로 스키마입니다. Field(...)에서 ...은 필수 필드를 의미합니다.
min_length=1은 리스트가 최소 1개 이상의 요소를 가져야 한다는 제약 조건입니다. Optional[str]은 선택적 필드로, 값이 없으면 default 값이 사용됩니다.
response_model=PredictionResponse를 지정하면 응답 데이터도 자동으로 검증되고, Swagger 문서에도 응답 형식이 표시됩니다. 실제 현업에서는 어떻게 활용할까요?
예를 들어 자연어 처리 모델을 서빙한다고 가정해봅시다. 입력 텍스트의 최소/최대 길이를 제한하거나, 지원하는 언어 코드만 허용하는 등의 검증이 필요합니다.
Pydantic의 validator를 사용하면 이런 복잡한 검증도 깔끔하게 처리할 수 있습니다. 하지만 주의할 점도 있습니다.
초보 개발자들이 흔히 하는 실수 중 하나는 너무 느슨한 스키마를 정의하는 것입니다. Any 타입을 남발하거나 모든 필드를 Optional로 만들면 Pydantic의 장점이 사라집니다.
가능한 한 구체적인 타입과 제약 조건을 명시해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
스키마를 정의하고 나니 검증 코드가 한 줄도 필요 없어졌습니다. "이렇게 간단할 수가!" 김개발 씨가 감탄했습니다.
이제 실제로 모델을 로딩하고 예측을 수행하는 엔드포인트를 만들어봅시다.
실전 팁
💡 - Field의 description 파라미터는 Swagger 문서에 표시되므로 꼼꼼히 작성하세요
@validator데코레이터로 커스텀 검증 로직을 추가할 수 있습니다Config클래스에서schema_extra를 설정하면 예시 요청을 문서에 표시할 수 있습니다
3. 모델 로딩과 예측 엔드포인트
스키마 정의를 마친 김개발 씨는 이제 진짜 모델을 연결할 차례입니다. 그런데 문제가 생겼습니다.
모델 파일이 500MB나 되는데, 매 요청마다 로딩하면 너무 느리지 않을까요? 어떻게 해야 효율적으로 모델을 관리할 수 있을까요?
모델 로딩은 서버 시작 시 한 번만 수행하고, 이후 요청에서는 메모리에 올라간 모델을 재사용하는 것이 핵심입니다. 마치 식당에서 매 손님마다 요리 도구를 새로 사는 게 아니라, 주방에 미리 준비해두고 사용하는 것과 같습니다.
FastAPI의 lifespan 이벤트를 활용하면 이를 깔끔하게 구현할 수 있습니다.
다음 코드를 살펴봅시다.
from contextlib import asynccontextmanager
import joblib
# 전역 모델 저장소
ml_models = {}
# 앱 시작/종료 시 실행되는 lifespan 핸들러
@asynccontextmanager
async def lifespan(app: FastAPI):
# 시작 시: 모델 로딩
ml_models["classifier"] = joblib.load("models/classifier.pkl")
print("Model loaded successfully")
yield
# 종료 시: 리소스 정리
ml_models.clear()
app = FastAPI(lifespan=lifespan)
@app.post("/predict", response_model=PredictionResponse)
def predict(request: PredictionRequest):
model = ml_models["classifier"]
result = model.predict([request.features])[0]
return PredictionResponse(prediction=result, confidence=0.95, model_version=request.model_version)
김개발 씨가 첫 번째 예측 API를 배포했습니다. 그런데 응답 시간이 5초나 걸렸습니다.
"이게 맞나요?" 김개발 씨가 당황해서 물었습니다. 박시니어 씨가 코드를 살펴보더니 문제점을 발견했습니다.
"매 요청마다 모델을 로딩하고 있네요. 모델 로딩만 4.5초가 걸리는 거예요." 그렇다면 모델 로딩은 어떻게 해야 효율적일까요?
쉽게 비유하자면, 이것은 마치 커피숍 운영과 같습니다. 손님이 커피를 주문할 때마다 에스프레소 머신을 새로 설치한다면 어떨까요?
말도 안 되죠. 영업 시작 전에 머신을 설치하고, 영업 중에는 계속 사용하다가, 마감할 때 정리하는 것이 당연합니다.
모델 서빙도 마찬가지입니다. 기존 방식의 문제점은 무엇이었을까요?
함수 안에서 model = joblib.load(...)를 호출하면, 요청이 들어올 때마다 디스크에서 모델 파일을 읽어 메모리에 올립니다. 모델이 클수록, 요청이 많을수록 서버에 엄청난 부하가 걸립니다.
심지어 동시 요청이 많으면 메모리 부족으로 서버가 다운될 수도 있습니다. 바로 이런 문제를 해결하기 위해 lifespan 이벤트를 사용합니다.
lifespan은 앱의 생명주기를 관리하는 기능입니다. yield 이전의 코드는 서버 시작 시 한 번 실행되고, yield 이후의 코드는 서버 종료 시 실행됩니다.
모델을 전역 딕셔너리에 저장해두면 모든 요청에서 같은 모델 인스턴스를 공유할 수 있습니다. 위의 코드를 단계별로 살펴보겠습니다.
@asynccontextmanager 데코레이터는 비동기 컨텍스트 매니저를 정의합니다. ml_models 딕셔너리에 모델을 저장하면 어디서든 접근 가능합니다.
엔드포인트에서는 이미 로딩된 모델을 가져와서 바로 예측을 수행합니다. 실제 현업에서는 어떻게 활용할까요?
대형 언어 모델처럼 로딩에 수십 초가 걸리는 모델도 있습니다. 이런 경우 lifespan 패턴은 필수입니다.
또한 여러 버전의 모델을 동시에 서빙해야 할 때도 ml_models["v1"], ml_models["v2"] 형태로 관리할 수 있습니다. 하지만 주의할 점도 있습니다.
모델이 메모리에 상주하므로 서버의 메모리 용량을 고려해야 합니다. 또한 모델 파일이 변경되면 서버를 재시작해야 합니다.
무중단 배포가 필요하다면 별도의 전략이 필요합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
lifespan을 적용하니 응답 시간이 5초에서 50밀리초로 줄었습니다. "100배나 빨라졌어요!" 김개발 씨가 환호했습니다.
이제 더 많은 요청을 효율적으로 처리하기 위한 비동기 처리를 알아봅시다.
실전 팁
💡 - 모델 로딩 실패 시를 대비해 try-except로 감싸고 적절한 로깅을 추가하세요
- 여러 모델을 로딩할 때는 병렬로 로딩하면 시작 시간을 단축할 수 있습니다
- 헬스체크 엔드포인트에서 모델 로딩 상태도 함께 반환하면 운영에 도움이 됩니다
4. 비동기 처리
모델 서빙이 잘 동작하자 사용자가 늘어나기 시작했습니다. 그런데 동시 접속자가 100명만 넘어도 응답이 느려지기 시작했습니다.
김개발 씨는 고민에 빠졌습니다. 서버를 더 늘려야 할까요, 아니면 다른 방법이 있을까요?
비동기 처리는 I/O 작업이 완료될 때까지 기다리는 동안 다른 요청을 처리하는 기법입니다. 마치 음식점에서 웨이터가 한 테이블의 주문을 받고 주방에 전달한 뒤, 음식이 나올 때까지 다른 테이블을 서빙하는 것과 같습니다.
FastAPI는 async/await 문법으로 이를 쉽게 구현할 수 있습니다.
다음 코드를 살펴봅시다.
import asyncio
from concurrent.futures import ThreadPoolExecutor
# CPU 작업용 스레드 풀
executor = ThreadPoolExecutor(max_workers=4)
# 동기 함수: CPU 집약적 작업
def run_prediction(model, features):
return model.predict([features])[0]
# 비동기 엔드포인트
@app.post("/predict/async", response_model=PredictionResponse)
async def predict_async(request: PredictionRequest):
model = ml_models["classifier"]
# CPU 작업을 별도 스레드에서 실행
loop = asyncio.get_event_loop()
result = await loop.run_in_executor(executor, run_prediction, model, request.features)
return PredictionResponse(prediction=result, confidence=0.95, model_version=request.model_version)
김개발 씨는 서버 모니터링 화면을 보며 한숨을 쉬었습니다. 동시 접속자가 늘어날수록 평균 응답 시간이 치솟았습니다.
서버 CPU 사용률은 20%밖에 안 되는데 왜 이렇게 느린 걸까요? 박시니어 씨가 설명했습니다.
"동기 방식으로 처리하면 한 요청이 끝날 때까지 다음 요청이 대기해야 해요. 비동기 처리를 도입해야 합니다." 그렇다면 비동기 처리란 정확히 무엇일까요?
쉽게 비유하자면, 이것은 마치 세탁기를 돌리는 것과 같습니다. 세탁기가 돌아가는 40분 동안 가만히 앞에 서 있을 필요가 없죠.
그 시간에 청소도 하고, 요리도 하고, 다른 일을 할 수 있습니다. 비동기 처리도 마찬가지로, I/O 작업이 진행되는 동안 CPU가 놀지 않고 다른 요청을 처리합니다.
동기 방식의 문제점은 무엇이었을까요? 일반 함수로 엔드포인트를 만들면, 한 요청이 처리되는 동안 서버는 다른 요청을 받지 못합니다.
모델 예측에 100밀리초가 걸린다면, 초당 최대 10개의 요청만 처리할 수 있는 셈입니다. 사용자가 늘어나면 병목 현상이 심해집니다.
바로 이런 문제를 해결하기 위해 async/await 패턴을 사용합니다. async def로 함수를 정의하면 비동기 함수가 됩니다.
await 키워드는 "이 작업이 끝날 때까지 기다리되, 그동안 다른 요청을 처리해도 된다"는 의미입니다. 이렇게 하면 같은 서버로도 훨씬 많은 동시 요청을 처리할 수 있습니다.
하지만 여기서 중요한 함정이 있습니다. 머신러닝 모델 예측은 CPU 집약적 작업입니다.
async 함수 안에서 CPU 작업을 그냥 실행하면 오히려 성능이 나빠집니다. 비동기의 장점은 I/O 대기 시간에 다른 일을 하는 것인데, CPU 작업에는 대기 시간이 없기 때문입니다.
이 문제를 해결하기 위해 run_in_executor를 사용합니다. run_in_executor는 CPU 작업을 별도의 스레드 풀에서 실행합니다.
메인 이벤트 루프는 그동안 다른 요청을 처리할 수 있습니다. 스레드 풀의 크기는 CPU 코어 수에 맞춰 설정하는 것이 일반적입니다.
실제 현업에서는 어떻게 활용할까요? 이미지 처리, 자연어 처리 등 무거운 모델을 서빙할 때 이 패턴은 필수입니다.
또한 외부 API 호출, 데이터베이스 쿼리 같은 I/O 작업이 포함된 경우에도 비동기 처리가 효과적입니다. 하지만 주의할 점도 있습니다.
모든 것을 비동기로 만들 필요는 없습니다. 단순한 연산이나 메모리 내 조회는 동기 함수가 더 효율적일 수 있습니다.
또한 비동기 코드는 디버깅이 어려울 수 있으므로, 로깅을 충분히 해두는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
비동기 처리를 적용하니 동시 접속자 500명도 거뜬히 처리할 수 있게 되었습니다. "같은 서버인데 이렇게 차이가 나다니!" 김개발 씨가 놀라워했습니다.
이제 예상치 못한 오류에 대비하는 방법을 알아봅시다.
실전 팁
💡 - I/O 작업(DB, 외부 API)에는 async/await를, CPU 작업에는 run_in_executor를 사용하세요
- ThreadPoolExecutor의 max_workers는 CPU 코어 수의 1-2배로 설정하는 것이 일반적입니다
- 비동기 라이브러리(httpx, asyncpg 등)를 활용하면 더 효율적인 코드를 작성할 수 있습니다
5. 에러 핸들링
서비스가 안정적으로 운영되던 어느 날, 갑자기 에러 알림이 쏟아지기 시작했습니다. 사용자가 잘못된 형식의 데이터를 보내거나, 모델이 처리할 수 없는 입력이 들어온 것입니다.
김개발 씨는 당황했습니다. 이런 예외 상황을 어떻게 우아하게 처리할 수 있을까요?
에러 핸들링은 예상치 못한 상황에서도 서비스가 안정적으로 동작하도록 만드는 기술입니다. 마치 건물의 비상구와 스프링클러처럼, 평소에는 눈에 띄지 않지만 문제가 생겼을 때 피해를 최소화합니다.
FastAPI는 HTTPException과 커스텀 예외 핸들러를 통해 체계적인 에러 처리를 지원합니다.
다음 코드를 살펴봅시다.
from fastapi import HTTPException, status
from fastapi.responses import JSONResponse
# 커스텀 예외 정의
class ModelNotLoadedError(Exception):
def __init__(self, model_name: str):
self.model_name = model_name
# 커스텀 예외 핸들러 등록
@app.exception_handler(ModelNotLoadedError)
async def model_not_loaded_handler(request, exc: ModelNotLoadedError):
return JSONResponse(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
content={"error": "model_not_loaded", "detail": f"Model '{exc.model_name}' is not available"}
)
@app.post("/predict")
def predict(request: PredictionRequest):
if "classifier" not in ml_models:
raise ModelNotLoadedError("classifier")
try:
result = ml_models["classifier"].predict([request.features])[0]
return PredictionResponse(prediction=result, confidence=0.95, model_version=request.model_version)
except ValueError as e:
raise HTTPException(status_code=400, detail=f"Invalid input: {str(e)}")
금요일 저녁, 김개발 씨는 퇴근 준비를 하고 있었습니다. 그때 슬랙 알림이 울렸습니다.
"500 Internal Server Error 급증 중!" 무슨 일인지 로그를 확인해보니, 일부 사용자가 빈 배열을 보내서 모델이 크래시된 것이었습니다. 박시니어 씨가 달려왔습니다.
"에러 핸들링이 제대로 안 되어 있네요. 사용자에게는 친절한 메시지를 보여주고, 서버는 안정적으로 유지해야 해요." 그렇다면 에러 핸들링이란 정확히 무엇일까요?
쉽게 비유하자면, 에러 핸들링은 마치 안전벨트와 에어백 같습니다. 사고가 나지 않으면 존재감이 없지만, 사고가 났을 때 생명을 구합니다.
잘 설계된 에러 핸들링은 서비스 장애를 예방하고, 사용자에게 명확한 피드백을 제공합니다. 에러 핸들링이 없으면 어떤 일이 벌어질까요?
예외가 발생하면 서버가 500 에러를 반환하고, 사용자는 "Internal Server Error"라는 무의미한 메시지만 봅니다. 더 심각한 경우 서버 프로세스가 죽어버릴 수도 있습니다.
개발자도 무엇이 잘못됐는지 파악하기 어렵습니다. 바로 이런 문제를 해결하기 위해 체계적인 예외 처리 전략이 필요합니다.
FastAPI는 HTTPException을 제공합니다. 이것을 raise하면 지정한 상태 코드와 메시지로 응답을 보냅니다.
400번대는 클라이언트 잘못, 500번대는 서버 잘못을 나타내는 것이 HTTP의 규약입니다. 더 나아가 커스텀 예외 핸들러를 등록할 수 있습니다.
위 코드에서 ModelNotLoadedError는 우리가 정의한 도메인 특화 예외입니다. @app.exception_handler로 등록하면, 이 예외가 발생했을 때 자동으로 지정한 응답을 반환합니다.
503 Service Unavailable은 "서비스가 일시적으로 이용 불가"를 의미하는 적절한 상태 코드입니다. 실제 현업에서는 어떻게 활용할까요?
에러 응답의 형식을 일관되게 유지하는 것이 중요합니다. {"error": "에러코드", "detail": "상세메시지"} 같은 표준 형식을 정해두면, 프론트엔드에서 에러를 파싱하고 처리하기 쉬워집니다.
또한 에러 발생 시 로깅을 남기면 디버깅에 큰 도움이 됩니다. 하지만 주의할 점도 있습니다.
에러 메시지에 내부 구현 세부사항이나 민감한 정보를 노출하면 안 됩니다. "Database connection failed: password incorrect"같은 메시지는 보안상 위험합니다.
사용자에게는 일반적인 메시지를 보여주고, 상세 정보는 서버 로그에만 남겨야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
에러 핸들링을 추가한 후, 같은 상황이 발생해도 서버는 안정적으로 유지되고 사용자에게는 "입력 데이터가 올바르지 않습니다"라는 친절한 메시지가 표시되었습니다. 이제 마지막으로 API 문서화를 통해 다른 개발자들과 소통하는 방법을 알아봅시다.
실전 팁
💡 - 400 에러는 클라이언트 잘못, 500 에러는 서버 잘못이라는 원칙을 지키세요
- 프로덕션에서는 상세한 에러 스택을 사용자에게 노출하지 마세요
- 모든 예외를 로깅하되, 민감 정보는 마스킹 처리하세요
6. API 문서화 Swagger
API 개발이 완료되었습니다. 이제 프론트엔드 팀에 전달해야 하는데, 김개발 씨는 문서를 어떻게 작성해야 할지 막막했습니다.
엔드포인트가 10개인데 일일이 위키에 정리해야 할까요? 그리고 코드가 바뀔 때마다 문서도 업데이트해야 하나요?
Swagger는 API를 시각적으로 보여주고 테스트할 수 있게 해주는 도구입니다. 마치 레스토랑 메뉴판처럼 어떤 음식(엔드포인트)이 있고, 어떤 재료(파라미터)가 필요하며, 무엇이 나오는지(응답)를 한눈에 보여줍니다.
FastAPI는 코드만 작성하면 Swagger 문서가 자동으로 생성됩니다.
다음 코드를 살펴봅시다.
from fastapi import FastAPI, Query
# 상세한 메타데이터로 앱 초기화
app = FastAPI(
title="ML Model Serving API",
description="머신러닝 모델 예측 서비스 API입니다.",
version="1.0.0",
docs_url="/docs", # Swagger UI 경로
redoc_url="/redoc" # ReDoc 경로
)
# 상세한 문서화가 포함된 엔드포인트
@app.post("/predict",
response_model=PredictionResponse,
summary="모델 예측 수행",
description="입력 특성을 받아 분류 모델의 예측 결과를 반환합니다.",
responses={
200: {"description": "예측 성공"},
400: {"description": "잘못된 입력 데이터"},
503: {"description": "모델 로딩 실패"}
}
)
def predict(request: PredictionRequest):
return PredictionResponse(prediction=0.85, confidence=0.92, model_version="v1")
프론트엔드 팀의 이프론트 씨가 김개발 씨에게 물었습니다. "API 문서 어디서 볼 수 있어요?
엔드포인트 목록이랑 파라미터 형식이 필요해요." 김개발 씨가 머뭇거리자 박시니어 씨가 대신 대답했습니다. "브라우저에서 /docs 경로로 가보세요.
거기 다 있어요." 이프론트 씨가 해당 주소로 접속하자 깔끔한 Swagger UI가 나타났습니다. "와, 여기서 바로 테스트도 되네요!" 그렇다면 Swagger 문서화란 정확히 무엇일까요?
쉽게 비유하자면, Swagger는 마치 이케아 가구의 조립 설명서와 같습니다. 어떤 부품(파라미터)이 필요하고, 어떤 순서로 조립(요청)해야 하며, 완성품(응답)은 어떤 모습인지 그림과 함께 설명합니다.
개발자 간의 소통을 훨씬 쉽게 만들어주는 표준화된 문서입니다. 예전에는 API 문서를 어떻게 관리했을까요?
개발자가 노션이나 위키에 일일이 작성해야 했습니다. 코드가 변경되면 문서도 수동으로 업데이트해야 했습니다.
문서와 실제 코드가 맞지 않는 일이 빈번했고, 이로 인한 혼란이 끊이지 않았습니다. 바로 이런 문제를 해결하기 위해 자동 문서화가 등장했습니다.
FastAPI는 코드의 타입 힌트와 Pydantic 스키마를 분석하여 Swagger 문서를 자동 생성합니다. 코드가 곧 문서이므로, 코드를 수정하면 문서도 자동으로 업데이트됩니다.
더 이상 "문서 업데이트하는 거 깜빡했다"는 일이 없습니다. 위의 코드를 살펴보겠습니다.
FastAPI() 생성자에 title, description, version을 넣으면 문서 상단에 표시됩니다. 엔드포인트 데코레이터의 summary와 description은 각 API의 설명이 됩니다.
responses 딕셔너리로 가능한 응답 상태와 설명을 정의할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?
팀 간 협업에서 Swagger 문서는 계약서 역할을 합니다. 백엔드가 "이 형식으로 보내면 이렇게 응답할게요"라고 약속하면, 프론트엔드는 그에 맞춰 개발합니다.
Swagger UI에서 바로 API를 테스트할 수 있어서 포스트맨 없이도 간단한 확인이 가능합니다. 하지만 주의할 점도 있습니다.
자동 생성된다고 해서 문서화를 소홀히 하면 안 됩니다. Field의 description, 엔드포인트의 summary 같은 메타데이터를 충실히 작성해야 좋은 문서가 됩니다.
또한 프로덕션 환경에서는 문서 경로를 비활성화하거나 인증을 추가하는 것이 보안상 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.
상세한 메타데이터를 추가하고 나니, 프론트엔드 팀에서 질문이 크게 줄었습니다. "문서가 정말 잘 되어 있네요!" 이프론트 씨의 칭찬에 김개발 씨가 뿌듯해했습니다.
이로써 FastAPI로 모델을 서빙하기 위한 핵심 개념들을 모두 살펴보았습니다. 기초 설정부터 데이터 검증, 모델 로딩, 비동기 처리, 에러 핸들링, 문서화까지, 실무에서 꼭 필요한 내용들입니다.
여러분도 오늘 배운 내용을 바탕으로 멋진 ML 서비스를 만들어보세요.
실전 팁
💡 - Field와 데코레이터의 description을 성실히 작성하면 문서 품질이 크게 올라갑니다
- 프로덕션에서는 docs_url=None으로 Swagger UI를 비활성화하는 것을 고려하세요
- OpenAPI 스키마는 /openapi.json 경로에서 JSON으로 다운로드할 수 있습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
OpenAPI로 API 문서화 완벽 가이드
Spring Boot 프로젝트에서 springdoc-openapi를 사용하여 Swagger UI로 자동 API 문서를 생성하는 방법을 단계별로 배웁니다. 실무에서 바로 활용할 수 있는 어노테이션과 스키마 정의 방법까지 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.