[FastAPI] 웹 프레임워크가 아니라 'AI 모델 서빙기'로 사용하기 (feat. Spring Boot)

2025. 12. 9. 04:29·ServerDev/FastAPI

 

 

 

저는 하나의 언어나 프레임워크를 고집하기보다, 프로젝트의 성격에 맞춰 Spring Boot, Django, FastAPI를 자유롭게 오가는 편입니다.

 

사실 저의 첫 웹 개발은 대학교 1학년 때 배운 Python과 Django로 시작되었습니다. 하지만 실무를 경험하며 복잡한 비즈니스 로직을 처리할 때는 Spring Boot가 주는 견고함에 매료되었죠. 강력한 트랜잭션 관리, 익숙한 JPA, 그리고 DI 컨테이너가 주는 안정감은 대규모 서비스를 구축할 때 대체 불가능한 도구였습니다. 하지만, 서비스에 'AI'와 'LLM'이 들어오면서 상황은 다시 바뀌었습니다.

 

최근 ChatGPT API(OpenAI)를 래핑(Wrapping)해서 함수 호출 서버를 만들거나, 직접 학습시킨 모델을 서빙해야 하는 일이 많아졌습니다. 이걸 Spring Boot 환경에서 구현하려니, Spring AI 같은 라이브러리가 있어도 Python 의 압도적인 라이브러리 지원과 민첩함을 따라가기엔 벅차더군요.  ( 함수 모듈화나 라이브러리화는 Python 이 더욱 익숙한 제 문제일 수도 있겠지만,,, )

 

특히 모델 서빙을 위해 KServe 같은 인프라를 구축하거나, 데이터 전처리 라이브러리(Pandas, PyTorch)를 체인으로 엮어야 할 때, 자바는 너무 무겁고 호환성 이슈가 많았습니다.

 

그래서 저는 '하이브리드 아키텍처'를 선택했습니다. "복잡한 비즈니스와 DB 관리는 잘하는 Spring Boot가 맡고, AI 추론과 외부 API 연동은 가볍고 빠른 FastAPI에게 맡기자."

 

 

오늘은 웹 프레임워크로서의 FastAPI가 아니라, 철저하게 Spring Boot의 파트너로서 'AI 모델 서빙 및 LLM 연동기' 역할을 수행하는 FastAPI 아키텍처를 어떻게 설계하는지 공유하려 합니다.

 

 

 

물론 그렇다고 FastAPI 의 단일 서버 구조와 , Spring 에서의 모델 호출이 잘못됐다는 것은 아닙니다.. 단지 제가 그렇게 사용하고 있을 뿐... 

 

 

 

2.  전체 아키텍처: 각자의 역할에 집중하기

제가 제안하는 구조는 전형적인 MSA(Microservices Architecture) 의 축소판입니다.

MSA 에 대한 자세한 설명은 다른 장에서 다루겠습니다.. (FastAPI 와는 크게 맞지 않은 내용이라 분리하고 싶음..ㅎㅎ)
  • Spring Boot (Main Server)
    • 사용자 인증/인가 (JWT, OAuth2)
    • DB CRUD 및 트랜잭션 관리
    • 클라이언트와의 직접적인 통신
    • FastAPI에게 "이 데이터로 예측 결과 좀 줘"라고 HTTP 요청 전송
  • FastAPI (Model Server)
    • DB 연결? 안 합니다. (필요하다면 읽기 전용으로만)
    • 복잡한 유저 관리? 안 합니다.
    • 오직 무거운 AI 모델을 메모리에 로딩하고, 들어온 데이터로 Inference(추론)하여 JSON으로 반환하는 일만 합니다.

이렇게 역할을 분리하면, FastAPI 프로젝트는 아주 가볍고 목적이 뚜렷해집니다.

 

 

 

 

 


3.  패키지 구조: Spring의 향기가 나는 구조 잡기

스프링 개발자라면 Controller - Service - Repository - DTO 구조가 익숙하실 겁니다. FastAPI에서도 이 패턴을 그대로 차용하면 아주 직관적인 구조를 만들 수 있습니다.

다만, 우리는 DB를 안 쓸 거니까 Repository 대신 ML(Machine Learning) 레이어가 들어갑니다.

 자주 쓰는 디렉터리 구조 (Directory Structure)

ai_serving_server/
├── app/
│   ├── main.py            # [Application] 앱 실행, 미들웨어, 수명주기 설정
│   │
│   ├── api/               # [Controller] 엔드포인트 라우팅
│   │   ├── __init__.py
│   │   └── v1/
│   │       └── prediction.py
│   │
│   ├── schemas/           # [DTO] 요청/응답 데이터 검증 (Pydantic)
│   │   ├── request.py
│   │   └── response.py
│   │
│   ├── services/          # [Service] 비즈니스 로직 (전처리 -> 추론 -> 후처리)
│   │   └── inference_service.py
│   │
│   ├── ml/                # [Repository 역할] AI 모델 로딩 및 관리
│   │   ├── model_loader.py # Singleton 패턴으로 무거운 모델 로딩
│   │   └── artifacts/      # .pt, .pkl, .h5 모델 파일 저장소
│   │
│   └── core/              # [Config] 설정 관리
│       └── config.py
│
├── tests/                 # 테스트 코드
├── requirements.txt       # 의존성 목록
├── Dockerfile             # 배포용 도커 파일
└── .env                   # 환경변수

 

어떤가요? 스프링 프로젝트 구조와 꽤 비슷하지 않나요? 이제 하나씩 뜯어보겠습니다.

 

 

 

 

 


 

 

 

4. 상세 구현: 계층별 코드 작성 가이드

1) Schemas (DTO): 데이터 규격 정의하기

스프링에서 RequestDto, ResponseDto를 만들듯, 파이썬에서는 Pydantic을 사용합니다. 이는 스프링 서버와 통신할 때 데이터 정합성을 맞추는 매우 중요한 단계입니다.

# app/schemas/request.py
from pydantic import BaseModel, Field

class PredictionRequest(BaseModel):
    text: str = Field(..., description="분석할 텍스트 본문", min_length=1)
    model_version: str = Field("v1", description="사용할 모델 버전")

# app/schemas/response.py
class PredictionResponse(BaseModel):
    label: str
    confidence: float

2) ML Layer: 모델 관리 (가장 중요!)

여기가 핵심입니다. 스프링에서 Bean을 싱글톤으로 띄우듯, AI 모델은 서버가 시작될 때 딱 한 번만 로딩해서 메모리에 올려두어야 합니다. 요청 들어올 때마다 torch.load() 하면 서버 터집니다.

 

저는 보통 싱글톤 패턴이나 전역 인스턴스를 활용합니다.
# app/ml/model_loader.py
import torch

class ModelLoader:
    _instance = None
    model = None

    def __new__(cls):
        if cls._instance is None:

            cls._instance = super(ModelLoader, cls).__new__(cls)
            
            # 실제 모델 로딩 로직 (경로에서 파일 읽기)
            cls.model = torch.load("app/ml/artifacts/best_model.pt")
            cls.model.eval() # 추론 모드 전환

        return cls._instance

    def predict(self, input_tensor):
        # 모델 추론 수행
        with torch.no_grad():
            return self.model(input_tensor)

# 전역 변수로 인스턴스화 (나중에 Service에서 갖다 씀)
model_loader = ModelLoader()

3) Service Layer: 비즈니스 로직

스프링의 @Service입니다. 여기서는 HTTP나 라우팅 같은 건 신경 쓰지 말고, "입력값 전처리 -> 모델 예측 -> 결과 가공"이라는 순수한 로직만 작성합니다.

# app/services/inference_service.py
from app.ml.model_loader import model_loader

class InferenceService:
    def __init__(self):
        # 필요하다면 여기서 모델 로더를 주입받을 수도 있음
        self.model = model_loader

    def predict_sentiment(self, text: str) -> dict:
        # 1. 전처리 (Preprocessing)
        tensor_data = self._preprocess(text)

        # 2. 추론 (Inference)
        # ML 레이어의 메서드 호출
        output = self.model.predict(tensor_data)

        # 3. 후처리 (Postprocessing)
        result = self._postprocess(output)
        return result

    def _preprocess(self, text):
        # 토크나이징, 정규화 등 로직
        return text.strip() 

    def _postprocess(self, output):
        # 텐서를 JSON으로 변환
        return {"label": "positive", "confidence": 0.98}

4) API Layer (Controller): 요청 받기

마지막으로 스프링의 @RestController 역할을 하는 APIRouter입니다. Depends를 사용해 Service를 주입받습니다.

# app/api/v1/prediction.py
from fastapi import APIRouter, Depends, HTTPException
from app.schemas.request import PredictionRequest
from app.schemas.response import PredictionResponse
from app.services.inference_service import InferenceService

router = APIRouter()

@router.post("/predict", response_model=PredictionResponse)
def predict(
    request: PredictionRequest,
    # 의존성 주입: 스프링의 @Autowired와 유사
    service: InferenceService = Depends() 
):
    try:
        result = service.predict_sentiment(request.text)
        return result
    except Exception as e:
        # 에러 발생 시 500 에러 반환
        raise HTTPException(status_code=500, detail=str(e))

 

 

보시다시피 데이터베이스(DB)가 머신러닝 모델(ML)로 바뀌었을 뿐,
데이터가 흐르는 구조는 Spring의 MVC 패턴과 완전히 동일합니다.
  1. Client (Request)
    • 사용자(Spring Boot 서버)가 FastAPI에게 POST /predict 요청을 보냅니다.
    • 이때 JSON 데이터(PredictionRequest)가 함께 전송됩니다.
  2. Step 1: Router (Controller)
    • Spring의 Controller 역할입니다. (api/v1/prediction.py)
    • 가장 먼저 요청을 받아 Pydantic(DTO)을 이용해 데이터 유효성을 검사합니다.
    • 검증이 통과되면 Service 계층을 호출합니다.
  3. Step 2: Service Layer
    • Spring의 Service 역할입니다. (services/inference_service.py)
    • HTTP 통신과는 무관한 순수 비즈니스 로직을 담당합니다.
    • 입력된 텍스트를 정제하거나(전처리), 추론 결과를 포맷팅하는(후처리) 작업이 여기서 일어납니다.
  4. Step 3: ML Layer (Repository)
    • Spring의 Repository 역할입니다. (ml/model_loader.py)
    • DB에 접근하는 대신, 메모리에 로드된 AI 모델 객체에 접근합니다.
    • 실질적인 연산(Inference)을 수행하고 결과값을 Service로 반환합니다.

 

 

5. Spring 개발자를 위한 아키텍처 꿀팁

FastAPI를 서빙기로 운영하면서 얻은 몇 가지 팁을 전합니다.

 

 

1. Lifespan Events 사용하기 (앱 시작/종료 시점 제어)

Spring의 @PostConstruct나 ApplicationRunner처럼, FastAPI 앱이 시작될 때 모델을 미리 로드해야 합니다. FastAPI의 lifespan 기능을 쓰면 됩니다.

# app/main.py
from fastapi import FastAPI
from contextlib import asynccontextmanager
from app.ml.model_loader import ModelLoader

@asynccontextmanager
async def lifespan(app: FastAPI):
    # 시작 시점: 모델 로딩 트리거
    ModelLoader() 
    yield
    # 종료 시점: 리소스 정리 (필요하다면)
    print("서버 종료")

app = FastAPI(lifespan=lifespan)

 

 

2. Health Check 엔드포인트 만들기

스프링 서버가 "FastAPI야, 너 살아있니?"라고 찔러볼 수 있는 구멍을 만들어줘야 합니다. 보통 /health 경로를 사용합니다.

Spring Cloud(Eureka)나 AWS ALB, 혹은 K8s가 FastAPI의 상태를 알 수 있도록 헬스 체크 API는 필수입니다.
만약 , OCR 모델을 따로 사용한다면 해당 모델에 대해서도 status 확인을 해야겠죠
@app.get("/health")
def health_check():
    # 모델이 정상적으로 로드되었는지 확인하는 로직 추가 가능
    return {"status": "ok", "model_loaded": True}

 

 

3. 동기(Sync) vs 비동기(Async) 주의사항

이게 제일 중요합니다. 대부분의 AI 라이브러리(PyTorch, Numpy 등)는 CPU를 점유하는 동기(Blocking) 작업입니다.
이런 작업을 async def로 선언하면, 이벤트 루프가 멈춰버려서 다른 요청을 못 받게 됩니다.

AI 추론 함수는 그냥 def (동기 함수)로 선언하세요. 그러면 FastAPI가 알아서 별도의 스레드 풀(Thread Pool)에서 실행시켜 줘서, 메인 스레드가 막히는 걸 방지해 줍니다.

 

 

 

 

 

 


 

 

마치며: 도구는 도구일 뿐

 

 

"망치를 든 사람에게는 모든 것이 못으로 보인다"라는 말이 있습니다.
자바 개발자라고 해서 억지로 자바로 AI를 할 필요는 없습니다. 반대로, 파이썬을 쓴다고 해서 굳이 익숙하지 않은 파이썬으로 복잡한 웹 서비스를 다 짤 필요도 없죠.

  • Spring Boot: 복잡한 비즈니스 로직, 트랜잭션, 안정성 담당.
  • FastAPI: 빠르고 유연한 AI 모델 서빙 담당.

이 두 프레임워크를 Controller(Router) - Service - Repository(ML) 라는 공통된 아키텍처 언어로 연결한다면, 유지보수하기도 쉽고 각 언어의 장점만 쏙쏙 뽑아먹는 최상의 서비스를 만들 수 있을 것입니다.

 

물론 모든 모델 서빙 구조를 front - backend(spring) - backend(fastAPI) 로 나누는 것은 아닙니다.

 

제가 오늘 소개한 Front - Backend(Spring) - AI Server(FastAPI) 구조는 가장 보편적이고 관리가 쉬운 패턴일 뿐, 모든 상황에 적용되는 만능열쇠는 아닙니다. 상황에 따라 더 효율적인 아키텍처가 있을 수 있습니다. 이부분은 아키텍처 장에서 다루겠습니다.

 

 

오늘 제가 소개한 Spring(메인) + FastAPI(서빙) 구조가 모든 프로젝트의 '정답'은 아닙니다.

  • FastAPI의 잠재력: FastAPI는 단순히 모델만 돌리기엔 아까울 정도로 훌륭한 비동기 웹 프레임워크입니다. Python에 능숙한 팀이라면 굳이 Spring을 섞지 않고 FastAPI 단일 아키텍처로 전체 백엔드를 구축하는 것이 생산성 측면에서 훨씬 유리할 수 있습니다.
  • Java의 진화: 또한, Java 생태계도 놀라운 속도로 발전하고 있습니다. Spring AI나 DJL(Deep Java Library) 등을 활용하면 별도의 파이썬 서버 없이도 자바 환경 내에서 AI 기능을 구현할 수 있는 길이 열리고 있습니다.

단지, 제가 이 구조를 선택한 이유는 '효율성'과 '현실적인 타협' 때문입니다.

 

 

저에게는 Spring의 익숙한 안정성과 Python의 방대한 AI 생태계를 가장 적은 비용으로 동시에 누릴 수 있는 방법이 바로 이 하이브리드 구조였을 뿐입니다. 

 

아키텍처 설계는 항상 즐거운 거 같습니다. 

'ServerDev > FastAPI' 카테고리의 다른 글

[FastAPI - CORS 마스터 2부] 쿠키 인증(Credential) 이슈 해결과 보안을 위한 정교한 허용 전략  (0) 2025.12.11
FastAPI - CORS 마스터 1부] 프론트엔드 연동 첫걸음, CORS 에러  (0) 2025.12.10
[FastAPI - DI 마스터 3부] 의존성 오버라이드(Override)와 고급 패턴  (0) 2025.12.09
[FastAPI - DI 마스터 2부] Depends 사용법 기초부터 yield를 이용한 DB 연동까지 실전 정복  (0) 2025.12.06
[FastAPI - DI 마스터 01부] 복잡성 증가를 해결하는 의존성 주입(DI) 개념과 필요성  (0) 2025.12.04
'ServerDev/FastAPI' 카테고리의 다른 글
  • [FastAPI - CORS 마스터 2부] 쿠키 인증(Credential) 이슈 해결과 보안을 위한 정교한 허용 전략
  • FastAPI - CORS 마스터 1부] 프론트엔드 연동 첫걸음, CORS 에러
  • [FastAPI - DI 마스터 3부] 의존성 오버라이드(Override)와 고급 패턴
  • [FastAPI - DI 마스터 2부] Depends 사용법 기초부터 yield를 이용한 DB 연동까지 실전 정복
yeseul-kim01
yeseul-kim01
  • yeseul-kim01
    슬 개발일지
    yeseul-kim01
  • 전체
    오늘
    어제
    • 분류 전체보기 (79)
      • 자격증 (1)
        • 정보보안기사 (0)
      • DevOps (17)
        • Docker (6)
        • Kubernetes (1)
        • GitHub Actions (0)
        • AWS (4)
        • Monitoring (1)
        • Nginx (1)
        • GCP (3)
      • ServerDev (34)
        • SpringBoot (13)
        • DJango (5)
        • FastAPI (14)
        • Next (0)
        • Flask (0)
        • Database (2)
      • Algorithm (2)
        • BFS (0)
        • DFS (1)
        • 다익스트라 (0)
      • CS (8)
      • Data Engineering (1)
      • AI&MLOps (2)
      • Architecture (6)
      • Software Engineering (0)
        • Library Packaging (0)
      • Project (5)
        • docx-generator (0)
        • speak-note (2)
        • ms-serving (1)
        • keyshield (2)
      • ProgrammingLanguages (3)
        • Python (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    실무일기-백엔드편
    멀티모듈
    STT
    실무일기-인프라편
    KServe
    동시성제어
    grpc
    Kubernetes
    프로젝트기록-speaknote
    트러블슈팅
    FastAPI
    KeyShield
    rag
    SpringBoot
    아키텍처설계
    depends
    NLP부트캠프
    multipartfile
    docker
    FastAPI - CORS 마스터
    SpeakNote
    하이브리드아키텍처
    프로젝트기록-KeyShield
    실시간시스템
    di
    비동기처리
    MLops
    아키텍처
    Django
    백엔드
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
yeseul-kim01
[FastAPI] 웹 프레임워크가 아니라 'AI 모델 서빙기'로 사용하기 (feat. Spring Boot)
상단으로

티스토리툴바