우리의 DeepSeek Spring Boot 스택을 더 빨리 구축했더라면 좋았을 것입니다 — 그 이유
요약
GPT-4o 사용으로 인한 막대한 AWS 비용 문제를 해결하기 위해 DeepSeek 기반의 Spring Boot 스택으로 마이그레이션한 사례를 다룹니다. 비용 절감과 품질 유지 사이의 균형을 맞추기 위한 아키텍처 결정 및 평가 과정을 설명합니다.
핵심 포인트
- GPT-4o의 높은 토큰 비용으로 인한 운영 부담 및 벤더 종속성 문제 지적
- 비용 절감을 위해 DeepSeek 모델로의 마이그레이션 결정
- 품질 저하를 방지하기 위한 데이터 기반의 평가 하네스 구축 중요성
- Global API를 활용한 OpenAI 호환 엔드포인트 기반의 효율적 전환
I Wish I'd Built Our DeepSeek Spring Boot Stack Sooner — Here's Why
3개월 전, 저는 단 한 달 동안의 LLM 추론 (inference) 비용으로 어떻게든 41,000달러까지 불어난 AWS 청구서를 바라보고 있었습니다. 우리는 문서 요약부터 채팅 완성 (chat completions), 임베딩 생성 (embedding generation)에 이르기까지 모든 작업에 GPT-4o를 사용하고 있었고, 계산 결과가 더 이상 말이 되지 않았습니다. Series A 스타트업의 CTO로서 손익 계산서 (P&L) 상의 모든 달러는 중요하며, 저는 다음 이사회 회의 전에 변화를 만들어야 한다는 것을 알고 있었습니다. 그 뒤로 6주 동안 아키텍처 결정, 벤치마킹, 그리고 제가 오늘날까지도 혜택을 보고 있는 완전한 마이그레이션 (migration) 과정이 이어졌습니다. 이것은 우리가 어떻게 Global API를 통해 Spring Boot 서비스를 DeepSeek으로 옮겼는지, 비용은 얼마였는지, 무엇을 배웠는지, 그리고 동일한 경로를 고려하는 누구에게나 추천할 만한 프로덕션 준비 완료 (production-ready) 패턴에 대한 전체 이야기입니다.
내가 거의 벗어나지 못할 뻔했던 벤더 종속 (Vendor Lock-In)의 함정
먼저 솔직하게 말씀드리고 싶은 것이 있습니다. 우리가 GPT-4o를 선택한 것은 그것이 최고의 모델이었기 때문이 아닙니다. 그것이 기본값(default)이었기 때문에 선택했습니다. 그것이 저의 실수였고, 제가 자문하는 거의 모든 스타트업에서 보이는 실수이기도 합니다. 제품을 출시하기 위해 전력 질주할 때는, 저항이 가장 적은 경로인 "잘 알려진 제공업체를 사용하고" 최적화는 나중에 고민하는 방식을 택하게 됩니다. 하지만 '나중'은 결코 오지 않으며, 3년이 지나면 실제 워크로드 (workload)에 대해 경제적으로 전혀 말이 되지 않는 가격에 종속되어 버립니다.
우리가 문제를 겪고 있다는 것을 깨달은 순간은 마이크로서비스 (microservices) 전반에 걸친 요청당 비용을 계산했을 때였습니다. 우리의 문서 처리 파이프라인 (pipeline)은 GPT-4o 가격(백만 토큰당 입력 $2.50 / 출력 $10.00) 기준으로 요청당 평균 0.038달러를 기록하고 있었습니다. 우리는 한 달에 약 280,000건의 요청을 처리하고 있었습니다. 이는 단 하나의 파이프라인에만 매달 10,640달러가 소모된다는 뜻이었으며, 심지어 그것은 우리의 가장 큰 비용 센터 (cost center)조차 아니었습니다. 고객 대상 채팅 서비스는 매달 또 다른 18,000달러를 잡아먹고 있었습니다. 토큰당 비용이 절반이라도 낮은 모델로 전환했을 때 우리가 절약할 수 있는 금액을 간단히 계산해 보았을 때, ROI (투자 대비 수익)는 너무나 명백해서 거의 당혹스러울 정도였습니다.
저를 망설이게 했던 것은 마이그레이션 (migration) 비용이 아니었습니다. 바로 품질 저하에 대한 두려움이었습니다. 더 저렴한 모델로 전환했다가 환각 (hallucination) 현상이나 이상한 포맷팅 문제로 고생했다는 팀들의 무시무시한 이야기를 들었기 때문입니다. 그래서 저는 주말을 할애하여 적절한 평가 하네스 (evaluation harness)를 구축했고, 우리의 프로덕션 프롬프트 (production prompts)를 대상으로 몇 가지 후보 모델들을 실행해 본 뒤 데이터에 기반한 결정을 내렸습니다. 그 결과가 바로 여러분과 공유하고자 하는 내용입니다.
내 마음을 돌린 비용 모델
구현 방법을 보여드리기 전에, 이것이 저에게 고민할 필요 없는 결정 (no-brainer)이 되게 만든 수치들을 공유하겠습니다. 이 모델들은 제가 Global API를 통해 평가한 것들인데, Global API는 현재 저의 기본 게이트웨이 (gateway)로 사용되고 있습니다. 왜냐하면 단일 OpenAI 호환 엔드포인트 (OpenAI-compatible endpoint)를 통해 184개의 모델을 노출하기 때문입니다. 이 부분이 매우 중요합니다. 벤더 종속 (vendor lock-in)은 단순히 가격에 관한 문제만이 아니라, 전환 비용 (switching costs)에 관한 문제입니다. 만약 설정 파일에서 문자열 하나를 바꾸는 것만으로 모델을 교체할 수 있다면, 여러분은 영원히 강력한 위치에서 협상할 수 있습니다.
| 모델 | 입력 ($/M) | 출력 ($/M) | 컨텍스트 윈도우 (Context Window) |
|---|---|---|---|
| DeepSeek V4 Flash | 0.27 | 1.10 | 128K |
| ... |
이 수치들을 잠시 살펴보십시오. DeepSeek V4 Flash는 GPT-4o와 비교했을 때 입력은 약 9배, 출력은 약 9배 더 저렴합니다. 이것은 10%의 최적화가 아니라, 유닛 이코노믹스 (unit economics)의 구조적 변화입니다. 규모를 키워가는 스타트업에게 이것은 벤처 자금을 지원받는 것과 수익을 내는 것 사이의 차이입니다. 200K 컨텍스트 윈도우를 갖춘 "프리미엄" 티어인 DeepSeek V4 Pro조차도 입력과 출력 모두에서 GPT-4o보다 4.5배 더 저렴합니다.
GLM-4 Plus의 가격 또한 주목할 만합니다. 128K 컨텍스트에 입력 $0.20, 출력 $0.80으로 목록에서 가장 저렴한 옵션입니다. 가장 똑똑한 모델이 아니라 그저 신뢰할 수 있는 모델만 있으면 되는 우리의 문서 분류 (document classification) 서비스에는 이것이 적절한 선택이었습니다. 그리고 Qwen3-32B는 32K 컨텍스트에 $0.30/$1.20의 가격으로, 품질이 우리가 허용할 수 있는 범위 내에 있음을 확인한 후 채팅 제품의 기본 모델이 되었습니다.
아키텍처 결정
여기서 저는 CTO가 이 문제를 어떻게 생각해야 하는지에 대해 이야기하고 싶습니다. 질문은 "DeepSeek를 사용할 것인가, GPT-4o를 사용할 것인가"가 아닙니다. 그것은 잘못된 프레임워크(framing)입니다. 질문은 "다시는 그런 질문을 하지 않으려면 어떤 적절한 추상화 계층 (abstraction layer)을 구축해야 하는가?"가 되어야 합니다.
제가 구축했고 현재 권장하는 방식은 우리 Spring Boot 서비스 내에 얇은 모델 라우팅 계층 (model-routing layer)을 두는 것입니다. 모든 LLM 호출은 문자열로 된 모델 식별자를 받는 ModelClient 인터페이스를 통해 전달됩니다. 내부적으로는 Global API의 OpenAI 호환 엔드포인트 (OpenAI-compatible endpoint)를 가리킵니다. 핵심은 단 하나의 애플리케이션 속성인 app.llm.default-model=deepseek-ai/DeepSeek-V4-Flash를 변경하는 것만으로, 몇 주가 아닌 몇 분 만에 다른 모델을 운영 환경 (production)에 배포할 수 있다는 점입니다.
이것이 바로 벤더 종속 (vendor lock-in)에 대한 보험 정책입니다. 다음 분기에 30% 더 저렴한 새로운 모델이 출시된다면, 저는 당일에 바로 A/B 테스트를 진행할 수 있습니다. DeepSeek가 가격을 인상할 때 (결국 모든 것은 더 비싸지기 마련이므로 언젠가는 일어날 일입니다), 저는 이미 준비된 대안을 가지고 있습니다. 이것이 바로 CTO로서 밤에 편히 잠들 수 있게 해주는 선택권 (optionality)입니다.
제가 10분 만에 끝낸 기본 설정
먼저 Python 버전을 보여드리겠습니다. 왜냐하면 이 패턴을 Spring Boot로 이식하기 전에 Python으로 프로토타입 (prototype)을 만들었기 때문입니다. Java/JS 코드는 구조적으로 동일합니다. 동일한 OpenAI 클라이언트, 동일한 베이스 URL (base URL), 동일한 모델 문자열을 사용합니다. 제가 이 기능을 어떤 언어로든 10분 이내에 구축할 수 있었다는 사실 자체가 핵심입니다.
import openai
import os
...
그게 전부입니다. 설정의 전부는 이것뿐입니다. api.openai.com에서 global-apis.com/v1으로 Base URL (기본 URL)을 교체하는 것이 유일하고 유의미한 변경 사항입니다. 그 외의 모든 것 — SDK, 요청 형태 (request shape), 응답 파싱 (response parsing), 스트리밍 (streaming) — 은 Global API가 OpenAI 스펙을 충실히 구현하고 있기 때문에 동일합니다. 이를 평가하는 CTO에게 있어, 이러한 호환성은 결정적인 기능 (killer feature)입니다. 저는 팀원들에게 새로운 SDK를 다시 교육할 필요도 없었고, 재시도 로직 (retry logic)을 다시 작성할 필요도 없었으며, 우리의 관측성 스택 (observability stack)에 있는 새로운 도구가 작동할지 고민할 필요도 없었습니다. 즉시 교체 가능한 호환성 (Drop-in compatibility)이야말로 빠른 반복 (fast iteration)을 가능하게 만드는 핵심입니다.
우리의 Spring Boot 서비스에서는 동일한 패턴이 다음과 같이 나타납니다. 저는 application.yml에서 Base URL을 설정했고, 나머지는 OpenAI Java 클라이언트가 처리하도록 두었습니다:
spring:
ai:
openai:
...
그리고 서비스 클래스에서의 실제 호출은 여러분이 예상하는 그대로입니다:
@Service
public class DocumentSummaryService {
private final ChatClient chatClient;
...
이것이 여러분이 본 가장 우아한 Spring Boot 코드는 아니라고 말하겠지만, 작동하며, 프로덕션 환경에 바로 적용 가능하고 (production-ready), 마이그레이션하는 데 15분밖에 걸리지 않았습니다. 빠른 반복의 핵심은 엔지니어링 시간을 인프라 배관 작업 (infrastructure plumbing)이 아닌 제품 차별화에 쏟을 수 있다는 점입니다.
첫날부터 사용했더라면 좋았을 프로덕션 준비 완료 패턴 (Production-Ready Patterns)
기본적인 연결이 작동하기 시작한 후, 저는 이러한 종류의 시스템을 대규모로 운영 가능하게 만드는 패턴에 집중했습니다. 이것들은 이론적인 내용이 아닙니다. 프로덕션 환경에서 저를 괴롭혔던 문제들이며, 이제는 타협할 수 없는 필수 요소라고 생각하는 것들입니다.
스트리밍 응답 (Streaming responses). 이것은 비용의 변화라기보다 UX(사용자 경험)의 변화에 가깝지만, 매우 중요합니다. 1,500개 토큰 분량의 요약을 반환할 때, 사용자는 스피너(spinner)를 바라보며 4.5초 동안 기다리고 싶어 하지 않습니다. 스트리밍은 체감 지연 시간(perceived latency)을 첫 토큰 생성까지 300ms로 단축시키며, 이는 "이 앱 정말 빠르다"와 "이 앱은 왜 이렇게 느리지?"의 차이를 만듭니다. OpenAI Python SDK와 Spring AI 클라이언트 모두 Global API를 통해 스트리밍을 지원하며, 저는 스트리밍 기능 없이 채팅 제품을 출시하는 것은 범죄라고 생각합니다.
공격적인 캐싱 (Aggressive caching). 우리의 문서 처리 서비스는 반복되는 콘텐츠를 많이 처리합니다. 사용자들이 동일한 PDF를 업로드하거나, 고객 지원 티켓이 동일한 문서를 참조하는 식입니다. 저는 LLM 호출 앞에 24시간 TTL(Time To Live)을 가진 Redis 캐시를 추가했습니다. 히트율(Hit rate)은 약 40%를 유지하고 있으며, 이는 40%의 직접적인 비용 절감으로 이어집니다. 128K 컨텍스트 모델의 경우, 동일한 긴 프롬프트를 반복해서 처리하지 않아도 되기 때문에 캐시는 꼬리 지연 시간(tail latency) 또한 줄여줍니다. Redis를 추가하는 데 들어간 ROI(투자 대비 효율)는 약 3일간의 엔지니어링 작업으로 월 4,200달러를 절감한 것이었습니다.
계층적 모델 선택 (Tiered model selection). 모든 요청에 가장 똑똑한 모델이 필요하지는 않습니다. 우리의 라우팅 로직은 현재 다음과 같은 형태를 띱니다. 단순 분류는 GLM-4 Plus ($0.20/$0.80)로, 일반적인 채팅은 DeepSeek V4 Flash ($0.27/$1.10)로 보내며, 복잡한 추론 작업만이 DeepSeek V4 Pro ($0.55/$2.20)로 격상됩니다. 우리 트래픽의 약 70%는 저가형 계층(cheap tier)에, 25%는 중간 계층(mid tier)에, 그리고 5%는 프리미엄 계층(premium tier)에 해당합니다. 이러한 계층적 아키텍처야말로 대규모 환경에서 진정한 비용 절감을 실현하는 열쇠입니다.
품질 모니터링 (Quality monitoring). 저렴한 모델에 대해 제가 두려워하는 점은 가격이 아니라, 소리 없는 품질 저하 (silent quality degradation)입니다. 저는 모든 LLM 응답의 2%를 캡처하여, 더 작은 LLM-as-judge 패턴을 통해 실행하고 몇 가지 차원에서 점수를 매기는 샘플링 레이어 (sampling layer)를 구축했습니다: 형식을 준수했는가, 질문에 답변했는가, 어조가 적절했는가 등입니다. 품질이 임계값 (threshold) 아래로 떨어지면 저에게 페이지 (paged) 알림이 옵니다. 이것이 바로 모델 마이그레이션 (model migration)이 "완료"되었다고 자신 있게 말하기 전에 필요한 관측성 (observability)의 종류입니다.
우아한 폴백 (Graceful fallback). 속도 제한 (Rate limits)은 발생합니다. 제공업체 중단 (Provider outages)도 발생합니다. 제 경험상 Global API는 놀라울 정도로 안정적이었지만, 만약 서비스가 중단되더라도 제 채팅 제품이 함께 중단되지 않는다는 사실을 알고 있으면 더 마음 편히 잠들 수 있습니다. 폴백 패턴 (fallback pattern)은 간단합니다: DeepSeek V4 Flash를 시도하고, 실패하면 Qwen3-32B를 시도하며, 그것도 실패하면 GLM-4 Plus를 시도하고, 모두 실패하면 정중한 에러 메시지를 반환합니다. 저하된 답변의 비용은 제품을 사용할 수 없는 비용보다 훨씬 낮습니다.
프로덕션 투입 3개월 후의 수치들
실제로 어떤 일이 일어났는지 공유하겠습니다. CTO들은 약속보다 실제 수치를 더 좋아하기 때문입니다. 새로운 스택을 도입한 첫 달의 결과는 다음과 같습니다:
- 총 LLM 지출: $11,400 (기존 $29,600에서 감소). 이는 61%의 절감 효과입니다.
- 평균 지연 시간 (Average latency): 비스트리밍 (non-streamed) 응답의 경우 1.2초, 스트리밍 (streamed)의 경우 첫 번째 토큰까지 320ms
- 품질 점수 (Quality score): 내부 벤치마크 (internal benchmark)에서 84.6% 기록, GPT-4o의 83.1%보다 상승 (저렴한 모델이 우리의 특정 워크로드에서 실제로 더 높은 점수를 기록했는데, 이는 제가 예상하지 못한 결과였습니다)
- 마이그레이션에 투입된 엔지니어링 시간: 총 약 80시간, 엔지니어 2명이 3주에 걸쳐 분담
- 회수 기간 (Payback period): 2주 미만
가장 놀라웠던 점은 품질 결과였습니다. 저는 품질이 다소 저하될 것을 예상하고 들어갔으며, 비용 절감을 위해 몇 퍼센트 정도의 품질 손실은 감수할 준비가 되어 있었습니다. 하지만 결과적으로 우리는 약간의 품질 향상을 얻었습니다. 제 생각에 그 이유는 DeepSeek V4 Flash가 더 최근에 학습되었고, 팀이 특히 지시 이행 (instruction-following) 능력에 공을 들였기 때문인 것 같습니다. 문서 요약 (document summarization) 및 구조화된 추출 (structured extraction)이 주를 이루는 우리의 워크로드 (workload)에서는 원시 추론 (raw reasoning) 능력보다 이 점이 더 중요합니다.
이 마이그레이션을 고려 중인 다른 CTO에게 해주고 싶은 말
중요도 순으로 세 가지를 말씀드리겠습니다. 첫째, 무엇인가를 마이그레이션하기 전에 평가 하네스 (evaluation harness)를 구축하십시오. 이 점은 아무리 강조해도 지나치지 않습니다. 저에게 적합했던 도구는 각 후보 모델에 대해 200개의 대표적인 운영 환경 프롬프트 (production prompts)를 실행하고, 우리 제품에 중요한 몇 가지 차원 (dimensions)에 따라 점수를 매기는 작은 Python 스크립트였습니다. 그러한 데이터 없이는 그저 추측만 할 뿐이며, AI 비용을 가지고 추측하는 것이 바로 41,000달러의 청구서를 받게 되는 지름길입니다.
둘째, 모든 것을 한꺼번에 마이그레이션하지 마십시오. 우리는 전환하기 전에 일주일 동안 섀도 배포 (shadow deployment)를 실시했습니다. 즉, 새 모델이 병렬로 실행되면서 응답을 채점하고 기존 모델과 비교하는 방식이었습니다. 실제로 스위치를 전환할 때는 2주에 걸쳐 한 번에 하나의 서비스씩 진행했습니다. 이러한 방식의 점진적 롤아웃 (incremental rollout)이 마이그레이션을 되돌릴 수 있게(reversible) 만들어 줍니다.
셋째, 라우팅 레이어 (routing layer)를 수용하십시오. 코드베이스 어디에도 모델 이름을 하드코딩하지 마십시오. Global API를 사용하는 핵심적인 이유는 선택적인
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기