핵심 목표: 성능과 안정성의 균형
본 프로젝트는 대규모 언어 모델의 추론 요청을 처리하는 HTTP 기반 게이트웨이를 2시간 내에 설계 및 구현하는 것을 목표로 했습니다. 핵심 과제는 KV 캐시 재사용률 증가와 단일 서버 과부하 방지 사이의 동적 균형을 유지하는 것입니다.
요청의 접두사(예: "질문: ")가 동일할 경우, 같은 백엔드 인스턴스로 라우팅하면 이전 계산 결과를 재활용하여 지연 시간을 줄일 수 있습니다. 그러나 모든 동일 접두사 요청이 한 인스턴스에 몰리면 과부하가 발생하고 대기 시간이 급증합니다. 이를 해결하기 위해 실시간 상태 기반의 스마트 라우팅 전략을 도입했습니다.
기술 스택 및 환경
- 언어: Java 17
- 프레임워크: Spring Boot 3.2
- 빌드 시스템: Maven 3.8+
- HTTP 클라이언트: Spring 6.1+ 내장 RestClient
- 개발 환경: 온라인 IDE + AI 코드 보조
시스템 아키텍처
클라이언트 요청 → 게이트웨이 진입점 → 접두사 분석 및 라우팅 → 동적 부하 분산 → 백엔드 인스턴스 풀
↓
실시간 메트릭 수집 (캐시 성공률, 지연, 부하 분포)
주요 구성 요소 설계
| 모듈 | 구현 방식 | 설계 목적 |
|---|---|---|
| 접두사 기반 라우팅 | 정규 표현식 매칭 + 실패 시 폴백 전략 | 유사한 입력은 동일 인스턴스로 집중시키며, 과부하 시 자동 분산 |
| 동적 부하 분산 | 실시간 가중치 산출 알고리즘 | 성능과 부하 상태를 반영해 최적 인스턴스 선택 |
| 건강 상태 점검 | 주기적 연결 검사 + 장애 감지 후 자동 제거 | 장애 인스턴스는 즉시 배제되고 복구 시 재가입 |
| 메트릭 통계 | 슬라이딩 윈도우 기반 실시간 집계 | P95 지연, 성공률, KV 캐시 미스율 등 실시간 모니터링 |
핵심 기술: 동적 가중치 알고리즘
간단한 정적 라우팅은 단일 인스턴스 과부하를 초래할 수 있으므로, 다음과 같은 조합 점수 기반 전략을 적용했습니다:
instance_score = (cache_weight × historical_hit_rate) + (load_weight × (1 - current_load_ratio))
- 가중치 조정 가능:
cache_weight,load_weight는 외부 설정 파일에서 수정 가능 - 과부하 감지: 동시 요청 수가 100 이상일 경우, 캐시 우선 전략의 영향력을 점진적으로 감소
- 전략 교체 지원: 순환 방식과 비교하여 성능 차이를 직접 측정 가능
실제 압력 테스트 결과
| 테스트 시나리오 | 방법 | 결과 |
|---|---|---|
| 정상 트래픽 | 랜덤 접두사 동시 요청 | 캐시 성공률 ~40%, 부하 균형 양호 |
| 핫 접두사 | 80% 요청이 동일 접두사 사용 | 동적 가중치로 2~3개 인스턴스에 분산, 단일 과부하 방지 |
| 과부하 시나리오 | 단일 인스턴스 동시 요청 120+ | 자동으로 가중치 감소, 다른 인스턴스로 전환 |
| 장애 주입 | 백엔드 인스턴스 수동 중지 | 즉시 제거 및 재시작 시 자동 복귀 |
코드 품질 관리 전략
단시간 개발에도 불구하고 다음의 공학적 원칙을 준수했습니다:
- 설정과 코드 분리: 백엔드 목록, 임계값, 전략 파라미터는
application.yml에 분리 - 예외 처리: 3초 초과 시 다른 인스턴스로 자동 재시도, 전체 실패 시 503 반환
- 관측성 확보: 요청 수, 성공률, P95 지연, 캐시 효율, 부하 분포 실시간 출력
- 코드 가독성: 핵심 클래스에 책임 설명 추가, 의존성 주입 사용으로 테스트 용이성 확보
AI 협업 경험
AI를 활용해 2시간 내에 완전한 시스템을 구현하면서 다음과 같은 통찰을 얻었습니다:
성공적인 접근 방법
- 모듈별 분해: 먼저 라우팅, 부하 분산, 건강 체크 등의 모듈 목록을 작성한 후 각각을 별도로 요청 → 전체 일괄 생성보다 훨씬 안정적
- 버전 관리 중요성: 첫 번째 버전 이후 디렉터리 구조 변경을 요청했지만, 중간 상태를 저장하지 않아 오류 발생. 이후부터는 매 모듈 완료 시마다 로컬 백업 필수
- 상황 기억 유지: 대화가 10만 토큰을 넘으면 상세 정보가 사라짐. 필요 시 "이전에 정의한 구조를 기억하고 있나요?"처럼 리셋 명령 수행
주의해야 할 함정
- 대규모 리팩토링 위험: AI에게 전체 코드를 한번에 변경하라고 하면 의도치 않은 결함 발생 가능성 높음
- 의존성 충돌: 먼저
pom.xml생성 후 검증 후 코드 생성 권장 - 과도한 설계 유도: 간단한 기능에 복잡한 설정을 추가하는 경향 있음 → 항상 "필요한 것만" 강조
핵심 통찰
AI는 개발 속도를 획기적으로 향상시켰지만, 최종 코드 품질의 결정적 요소는 여전히 인간의 판단입니다.
어떤 시점에 분리할지, 예외 처리 경계는 어디인지, 실패 시 어떻게 대응할지 — 이러한 전략적 결정은 AI가 스스로 하지 않습니다.
AI는 실행자이며, 인간은 의사결정자이자 책임자입니다.
향후 개선 방향
- 스트리밍 응답 지원: 현재는 동기식 전달.
WebClient의Flux를 활용해 실제 스트리밍 전달 구현 - 더 정교한 접두사 매칭: 문자열 접두사 기반 근사 매칭 → Trie 트리 도입으로 정확도 향상
- 분산 메트릭 집계: 현재 메트릭은 메모리 기반.
Redis를 사용해 다중 게이트웨이 인스턴스 간 통합 모니터링 가능
결론
이 경험을 통해 다음과 같은 가치를 확인했습니다:
- 기술적 깊이: 게이트웨이 설계, 부하 분산, 관측성 원칙에 대한 실질적 이해 심화
- 협업 모델: "모듈화 → 단계적 구현 → 버전 관리 → 피드백 회수"라는 사람-기계 협업 프로세스 정립
- 인식 변화: AI는 대체하지 않으며, 오히려 사용하는 개발자가 경쟁력 있는 존재가 됩니다.
핵심은 AI의 능력을 최대로 활용하면서도, 인간만이 할 수 있는 전략적 판단과 책임감을 유지하는 것입니다.
최종적으로 말하고 싶은 것은: AI는 엔지니어를 대체하지 않지만, AI를 활용하는 엔지니어는 그렇지 않은 사람을 대체합니다.