
Projects
BizKit AI 서버 아키텍처: 도메인과 모델 실행의 책임 분리
Spring, FastAPI, Redis Streams, RunPod, 임베딩 서비스의 책임을 나눈 설계와 비동기 작업 구조, 기능별 처리 경로를 정리한다.
문제와 담당 범위
BizKit은 팀 CARO의 디지털 명함 서비스이다. 명함 생성·OCR·직무 기반 자기소개·GitHub 활동 분석 등 서로 다른 AI 작업을 도메인 서버와 연결해야 했다. 모델마다 실행 시간과 자원 사용이 다르고, 모델이나 프롬프트 변경이 도메인 로직과 함께 움직이지 않도록 경계를 나눌 필요가 있었다.
나는 AI 서버의 설계와 구현, 이미지 생성·LLM·임베딩 모델 비교, GPU 모델 서버 배포를 맡았다. VLM OCR 모델 비교는 내 담당이 아니었으며, FastAPI 서버 배포는 클라우드 팀원이 담당했다.
이 글은 기능이 확장된 뒤의 책임 경계를 다룬다. 최초 큐 설계의 이유는 초기 설계 기록에 분리했다. 계획했던 리뷰 연동과 최종 제품에서 제외한 범위도 구분한다.
구현 구조
이 그림은 비동기 작업의 책임과 데이터 흐름을 요약한 개념도이다. 현재 구현에는 HEX, 직무 분석, 명함 생성, OCR 각각에 작업을 접수하고 조회하는 경로와 즉시 결과를 기다리는 동기 경로가 있다. 그림은 전자를 설명하며, 모든 기능의 인증 경로나 배포 인스턴스 수를 나타낸 것은 아니다.
| 구성요소 | 책임 | 별도로 확인할 사항 |
|---|---|---|
| Spring | 사용자·명함 등 도메인 데이터와 정책 | AI 모델의 생성 품질 |
| FastAPI | 요청 검증, 기능별 접수, 상태·결과 조회 | 기능별 동기·비동기 경로 |
| Redis Streams·Worker | 작업 전달과 실행, 상태·결과 기록 | 자동으로 보장되지 않는 중복 실행 방지·비용 상한 |
| RunPod 모델 서버 | GPU가 필요한 모델 추론 | GPU 할당 지연과 cold start 시간의 변동 |
| 임베딩 서비스·Milvus | 벡터 생성·저장·검색 | 캐시 응답 재사용의 안전성 전체 |
AI 저장소의 비동기 경로는 task_id를 반환하고 상태·결과 조회 API를 제공한다. 모델 실행과 HTTP 접수를 분리한 계약이다.
그림의 상태·결과 저장은 작업 전달용 Redis Streams와 구분된다. 현재 구현은 Redis Hash에 작업 레코드를 저장하고, Worker가 상태와 결과를 갱신하면 FastAPI의 조회 API가 같은 레코드를 읽는다. 명함 이미지 데이터의 보관은 작업 상태·결과 메타데이터와 별도로 처리한다.
작업 상태와 진행 단계
작업 상태는 pending, running, completed, failed이다. 세부 진행 상황은 별도 progress 필드에 담는다. 정상 완료와 실패는 서로 다른 종료 상태이다.
위 그림은 처리 흐름을 설명하는 개념도이다. 접수 오류, Worker 종료, 상태 저장 실패까지 포함한 완전한 복구 명세는 아니다. 작업 상태를 저장하는 것과 외부 모델 서버의 실행을 취소하는 것도 구분해야 한다.
기능별 경계
명함 생성
명함 생성은 스타일 입력을 모델용 프롬프트로 바꾸고 배경을 생성한 뒤, 이름과 연락처를 별도 렌더러로 배치하는 구조이다. 비동기 경로에서는 명함 Worker가 호출하는 파이프라인 안에서 렌더링한다. 이미지 모델과 텍스트 처리 단계를 나눴으며, 렌더러를 독립 서비스로 배포한 구조는 아니다.
이미지 모델에게 연락처의 정확한 철자까지 맡기지 않으려는 선택이었다. 다만 최종 이미지의 가독성을 확인하려면 렌더러의 잘림·줄바꿈과 입력 오타를 따로 점검해야 한다.
명함 OCR
OCR은 업로드한 명함 이미지에서 연락처 후보를 추출하고 정규화하는 작업이다. Spring을 단순 프록시로 두지 않고 프론트엔드가 FastAPI를 호출하는 경로를 설계했다. 이는 Spring의 도메인 데이터 조회를 생략하려는 설계이며, 실제 서비스의 모든 중간 경유지와 인증 처리를 생략했다는 뜻은 아니다.
인증·인가는 여전히 필요하다. 요청 주체 확인, 업로드 크기·형식 제한, 호출량 제한, 원본 이미지와 추출 개인정보의 보관 정책은 별도로 다뤄야 한다. 이 처리 경로만으로 전체 접근 통제가 검증됐다고 볼 수는 없다.
직무 분석
직무 분석은 회사·부서·직무·프로젝트 정보를 바탕으로 자기소개를 생성하는 기능이다. 현재 Worker는 먼저 입력 직무가 개발과 관련 있는지 임베딩 필터로 판단하고, 차단되지 않은 요청의 시맨틱 캐시를 조회한다. 캐시에서 응답을 찾으면 반환하고, 없으면 회사·부서의 웹 검색 결과를 프롬프트 재료로 사용해 LLM을 호출한다. 생성한 소개문과 검색 신뢰도는 캐시에 저장한다.
필터와 캐시는 불필요한 모델 호출을 줄이려고 넣었다. 하지만 유사도가 높아도 사용자 이름이나 프로젝트 내용이 다르면 같은 소개문을 반환해서는 안 된다. 비용 절감이나 정확도 개선을 확인하려면 검색 품질, 차단 정책, 개인화된 응답의 재사용 조건을 각각 평가해야 한다.
HEX와 리뷰의 범위
HEX는 협업·소통·기술·문서화·신뢰성·재협업 선호도를 여섯 축으로 표현하려 한 분석 기능이다. GitHub 활동을 수집하고 지표를 점수로 환산하는 부분을 구현했다. LLM의 점수 보정도 계획했지만, 보정의 정확한 근거를 제시하기 어려워 점수는 정형 지표로 한정했다.
이후 LLM은 서비스에서 동료가 남기는 리뷰를 분석하는 방향으로 잡았다. 여기서 리뷰는 GitHub의 코드 리뷰와 구분된다. AI 서버 배포 현황에는 리뷰를 포함해 보고했지만, 이후에도 서비스 연결은 목표로 남아 있었다. 팀은 최종적으로 리뷰 기능을 제외했고, 이 기능은 최종 사용자에게 제공되지 않았다. 자세한 과정은 개발 회고에 정리했다.
모델과 인덱스 설정
LLM 비교에서는 Qwen2.5 7B Instruct를 권장 모델로 정했다. 후속 README의 LLM 설정은 Qwen3-8B-AWQ이다. 비교 당시의 선택과 후속 구현 설정을 구분해야 한다.
임베딩 비교에서는 BGE-m3-ko와 IVF_FLAT 조합을 선택했다. 컬렉션 생성 코드의 인덱스와 검색 metric은 AUTOINDEX와 COSINE이다. 비교 조건과 구현 구조는 모델 비교 기록과 임베딩 서비스에 나눠 정리했다.
트레이드오프
서버와 모델을 분리하면 도메인 서버가 모델 로딩이나 프롬프트 구현을 직접 관리하지 않아도 된다. 대신 배포 대상과 네트워크 호출, 상태 저장, 실패 처리 지점이 늘어난다. API 형태를 유지해도 출력 의미나 임베딩 공간이 달라질 수 있으므로, 모델 교체에 따른 영향이 없다고 볼 수는 없다.
GPU 모델 서버를 직접 구성한 것도 관리 부담이 있는 선택이다. 라이브러리 호환성, GPU 할당, 실행 비용을 직접 다뤄야 했다. RunPod 역시 외부 인프라이므로 직접 모델을 배포했다는 이유만으로 개인정보가 외부로 나가지 않는다고 볼 수 없다. 텍스트 렌더링 분리와 외부 전송 정책도 별개이다.
검증과 한계
팀 서비스는 사용자에게 제공했다. 나는 AI 서버 구현과 GPU 모델 배포를 맡았고, FastAPI 배포는 클라우드 팀원이 담당했다. 서비스 연동은 팀에서 진행했다.
모델 비교의 추론 시간이나 검색 지표만으로 비동기 작업 구조 전체의 안정성을 알 수는 없다. 실제로 GPU 할당 문제 때 두 계층이 각각 실패한 호출을 재시도하면서 작업이 누적됐고, GPU가 할당된 뒤 불필요한 작업까지 실행돼 비용을 소모했다.
장기 작업 이력, 재처리 기준, 큐와 외부 모델 실행의 취소·만료는 남은 설계 과제이다.
배운 점
성공했을 때의 처리 경로를 나누는 데서 그치지 않고, 실패했을 때의 책임도 정해야 했다. 누가 작업을 접수하고, 어디서 재시도하며, 응답이 끊긴 뒤 어떤 실행과 비용이 남는지가 운영에서 중요했다.
