HowAreYou 프로젝트 소개 - 아키텍처 설계와 번역 API 병목 개선
실시간 언어교환 플랫폼 HowAreYou에서 아키텍처를 설계하고 번역 API 병목을 수평 확장으로 해결한 과정을 정리했습니다.
1. 프로젝트 개요
- 프로젝트명:
HowAreYou - 한 줄 소개:
실시간 채팅,번역,단어 추출,퀴즈,추천을 결합한 언어교환 플랫폼 - 핵심 기능:
WebSocket기반 실시간 채팅, 번역문 저장, 단어 추출, 개인 단어장, 퀴즈, 사용자 맞춤 추천 - 주요 기술:
Spring Boot,FastAPI,Flask,PostgreSQL,MongoDB,Redis,AWS ECS,ALB,Route 53,Gemini API,LibreTranslate,k6
이 프로젝트는 채팅 응답성과 번역 처리 속도가 곧 서비스 품질로 이어지는 구조였다. 그래서 기능 구현뿐 아니라 배포 구조와 운영 안정성까지 함께 설계해야 했다.
2. 인원
- 개발 참여 인원 :
5명
3. 협업 방식
협업에서는 문서 기준과 작업 추적 기준을 분리해 운영했다. Notion은 팀이 같은 설계를 보도록 만드는 공간으로 쓰고, GitHub Issue는 실제 구현 단위와 우선순위를 관리하는 용도로 사용했다.
Notion에는 요구사항, ERD, Flow Chart, 시스템 아키텍처, API 문서를 정리해 팀이 동일한 설계 기준을 공유하도록 했다.
GitHub Issue로 기능 개발, 리팩터링, 부하 테스트 같은 작업을 단위별로 관리하고 우선순위와 진행 상태를 추적했다.
4. 내 역할
LibreTranslate와Gemini API를 활용한 번역 서버 구조 설계AWS ECS기반 인프라와CI/CD구성Spring Boot,FastAPI,Flask서버를 하나의 배포 구조로 통합- 서비스 아키텍처 설계
k6부하 테스트 설계 및 번역 API 병목 분석- 병목 해소를 위한
Scale-out구조 적용
내 핵심 기여는 아키텍처 구성과 성능 개선이었다. 단순히 기능을 구현하는 수준이 아니라 어떤 서버를 어디에 배치하고, 어떤 요청이 어디를 거쳐 흐르는지까지 직접 설계했다.
5. 아키텍처
아래 아키텍처는 내가 직접 구성한 배포 및 서비스 구조다.
사용자 요청은 Route 53과 ALB를 거쳐 ECS 내부 서비스로 들어간다. 메인 서버는 Spring Boot로 구성했고, 분석 서버는 Flask와 KoNLPy, 태깅 서버는 FastAPI와 Hugging Face 기반으로 분리했다. 번역 서버는 별도 ECS 서비스로 두어 메인 서버와 독립적으로 확장할 수 있게 설계했다.
핵심 설계 의도는 세 가지였다.
- 역할이 다른 서버를 분리해 배포와 장애 영향을 국소화한다.
- 번역 서버를 메인 서버와 분리해 병목이 발생할 때 독립적으로 확장할 수 있게 한다.
- PostgreSQL, MongoDB, Redis를 용도별로 나눠 데이터 저장과 조회 비용을 관리한다.
이 구조의 핵심은 메인 애플리케이션 로직과 번역 처리 로직을 분리한 점이다. 이후 성능 병목이 발생했을 때 메인 서버를 건드리지 않고 번역 서버 계층만 조정할 수 있었다.
6. 번역 API 이중화
번역 기능은 처음부터 비용과 품질을 함께 고려한 이중화 구조로 설계했다.
- 기본 번역:
LibreTranslate기반NLP번역 서버 - 보조 번역:
Gemini API기반LLM번역
대화 번역은 호출량이 많아질 가능성이 높았다. 모든 요청을 LLM으로 처리하면 품질은 높일 수 있지만 비용 부담이 커진다. 반대로 모든 요청을 로컬 번역 서버만으로 처리하면 비용은 통제할 수 있지만 문맥 품질이 부족할 수 있다.
기본 경로는 자체 번역 서버, 보조 경로는 LLM 번역으로 두는 이중 경로를 채택했다. 이 선택은 단순한 기술 추가가 아니라 서비스 운영 비용을 고려한 구조적 결정이었다.
번역 방식은 누가 결정하는가
이중화 구조를 잡은 뒤, 실제 API 설계 단계에서 한 가지 결정이 더 필요했다. NLP 번역과 LLM 번역 중 어떤 방식을 쓸지, 그 결정을 서버가 할 것인가 클라이언트가 할 것인가. 두 가지 방안을 놓고 비교했다.
1) 서버 사이드에서 번역 방식 결정
- 클라이언트는 번역 요청 시
option(NLP, LLM)을 요청 바디에 담아 보낸다. - 서버는
option값을 판별해(if문 혹은switch) 그에 맞는 번역 결과를 반환한다.
| 장점 | 단점 |
|---|---|
| API를 한 개만 만들면 된다 | API의 책임 범위가 이분화된다 (하나의 API가 NLP 번역과 LLM 번역을 모두 담당) |
2) 클라이언트 사이드에서 번역 방식 결정
- 클라이언트에서 옵션을 미리 정하고, 번역 요청 시 두 API 중 하나를 선택해 호출한다.
| 장점 | 단점 |
|---|---|
API의 책임 범위가 좁아진다 (NLP 번역 API는 NLP 번역만, LLM 번역 API는 LLM 번역만 담당) | API를 두 개 만들어야 한다 |
최종적으로 2) 클라이언트 사이드 결정 방식을 선택했다. API를 하나 더 만드는 비용보다, API의 책임 범위가 좁은 것이 운영 안정성에 더 이롭다고 판단했기 때문이다. 각 API가 하나의 번역 경로만 책임지면 장애가 발생했을 때 원인 범위가 명확해지고, 한쪽 번역 경로의 수정이나 장애가 다른 경로에 영향을 주지 않는다. 실제로 이 구조 덕분에 이후 번역 서버 Scale-out 작업도 NLP 번역 경로만 독립적으로 조정할 수 있었다.
7. 트러블슈팅과 성능 개선
가장 큰 문제는 번역 API 병목이었다. 사용자가 몰리면 전체 서비스 체감 속도가 급격히 느려졌다. 원인을 감으로 판단하지 않기 위해 먼저 부하 테스트 시나리오를 만들었다.
- 테스트 조건:
200 VUs,5분간 반복 요청 - 테스트 대상:
/api/translate/basic - 비교 대상:
단일 번역 서버와5대 Scale-out적용 후 구조
단일 서버 테스트에서는 번역 요청이 몰릴 때 처리량이 빠르게 한계에 도달했다.
| 항목 | 단일 서버 | Scale-out 5대 |
|---|---|---|
HTTP Request Rate | 14.1/s | 61.5/s |
Iteration Rate | 13.3/s | 61.5/s |
평균 응답 시간 | 14s | 2s |
p95 응답 시간 | 17s | 8s |
평균 대기 시간 | 14s | 2s |
요청 실패율 | 0.0% | 0.5% |
병목은 네트워크가 아니라 번역 서버 내부 처리였다. http_req_waiting이 전체 응답 시간 대부분을 차지했고, 이는 요청이 서버 안에서 오래 대기하고 있다는 뜻이었다. 즉, 메인 서버의 비즈니스 로직보다 번역 엔진 자체가 병목이라는 결론에 도달했다.
처음에는 코드 레벨 최적화와 비동기 처리를 시도했지만 실패했다. 번역 요청은 I/O 대기보다 CPU 연산 비중이 큰 작업이었고, 비동기 전환만으로는 추론 처리량 한계를 넘을 수 없었다. 내부 테스트에서는 실패율이 오히려 증가해 이 방법을 유지하지 않기로 판단했다.
그래서 해결 전략을 비동기화에서 물리적 확장으로 전환했다. 병목이 CPU-bound 연산이라면 코드 구조보다 물리적인 처리 자원을 늘리는 편이 더 맞다고 봤다. 번역 서버를 1대에서 5대로 늘리고, 앞단에서 요청을 분산하도록 구성했다.
결과는 수치로 확인됐다.
- 처리량은
14.1/s에서61.5/s로 증가했다. - 평균 응답 시간은
14초에서2초로 감소했다. - 평균 대기 시간도
14초에서2초로 줄었다. - 실패율은
0.5%수준으로 낮게 유지됐다.
이 경험을 통해 모든 병목이 코드 레벨 최적화로 해결되지는 않는다는 점을 배웠다. 특히 CPU-bound 작업은 비동기화만으로는 처리 한계를 넘기 어렵다. 병목의 성격을 먼저 구분하고, 논리적 최적화와 물리적 확장 중 무엇이 맞는지 판단해야 한다.
마무리
HowAreYou는 내가 아키텍처를 설계하고 운영 문제를 해결하는 백엔드 개발자에 가깝다는 점을 가장 분명하게 보여준 프로젝트였다. 이 프로젝트에서 나는 서버를 구현하는 데서 멈추지 않고, 비용을 고려한 번역 구조를 설계했고, 실제 병목을 측정한 뒤 수평 확장으로 성능을 개선했다.





