안녕하세요 LLM플랫폼팀 심정수입니다.
저는 현재 LLM 기반 서비스의 안정적인 백엔드 아키텍처와 인프라 구성을 고민하고 개발하는 역할을 맡고 있습니다. 이번 글에서는 Java와 Python이 공존하는 폴리글랏(Polyglot) 환경에서, 어떻게 하면 인증의 파편화를 막고 시스템의 안정성을 확보할 수 있었는지, 고민과 해결 과정을 공유하고자 합니다.
현재 저희 팀이 운용 중인 서비스는 각 도메인의 특성에 맞춰 최적의 언어를 선택한 폴리글랏(Polyglot) 환경입니다.
폴리글랏(Polyglot) 아키텍처란?
여러 언어를 사용하는이라는 사전적 의미처럼, 하나의 거대한 서비스를 구축할 때 특정 언어에 국한되지 않고 각 마이크로서비스의 목적과 비즈니스 요구사항에 가장 적합한 언어와 프레임워크를 선택하여 조합하는 설계를 의미합니다.- Java (Spring Boot): 데이터의 신뢰성이 중요한 원천 데이터 소스 관리, 메타데이터 거버넌스, 그리고 복잡한 권한 체계인 RBAC(Role-Based Access Control) 및 ABAC(Attribute-Based Access Control) 로직을 담당합니다.
- Python (FastAPI): 유연한 비즈니스 로직 대응과 핵심 기능인 AI 모델 서빙 연동을 담당하여 인공지능 서비스의 민첩성을 극대화합니다.
이처럼
데이터 거버넌스 및 보안은 Java가, 지능형 서비스 구현은 Python이 맡는 구조에서 가장 큰 숙제는 어떻게 서로 다른 두 환경에 일관된 인증/인가 정책을 적용할 것인가였습니다. 초기 단계에서는 각 마이크로서비스가 인증을 개별적으로 처리했으나, 서비스 규모가 확장됨에 따라 인증 로직의 파편화와 보안 키(Secret Key) 관리의 복잡성이라는 문제에 직면했습니다.본 글에서는 Nginx(OpenResty)를 단순 프록시가 아닌 Programmable Gateway로 전환하여 인증 구조를 일원화하고, LLM 서비스의 핵심인 SSE(Server-Sent Events) 성능을 최적화한 경험을 공유합니다.
1. 배경: 분산된 인증 구조의 한계
초기 아키텍처는 클라이언트 요청이 각 애플리케이션으로 직접 전달되어, 각 서버가 JWT를 개별 검증하는 구조였습니다. 하지만 서비스가 확장됨에 따라 다음과 같은 문제가 발생했습니다.
- 인증 로직의 파편화: Java의 Spring Security와 Python의 FastAPI Auth 로직을 각각 유지보수해야 했습니다. 또한 동일한 검증로직을 두 언어로 중복 구현하는 과정에서 정책 불일치 위험이 커졌습니다.
- 보안 키 관리의 어려움: 모든 백엔드 서버가 Secret Key를 가져야 하는
Secret Sprawl현상으로 인해 보안 취약점이 노출될 가능성이 높았습니다. 이는 단 하나의 서비스만 침해당해도 전체 인증 체계가 위협받는 구조적 취약점이었습니다.
이를 해결하기 위해 인증/인가라는 공통 관심사를 애플리케이션 계층에서 분리하여, 인프라 계층인 Gateway로 이관할 필요가 있었습니다.
2. 기술적 의사결정: Why OpenResty over OpenSource Gateway?
Spring 생태계에 익숙한 팀이라면
Spring Cloud Gateway(SCG)를, 더 강력한 기능을 원한다면 Kong이나 APISIX를 고려하는 것이 일반적입니다. 하지만 저희는 단순히 '익숙함'이나 '유명세'에 의존하지 않고, 플랫폼의 특수한 인프라 상황과 성능 요구사항을 면밀히 분석했습니다. 결론적으로, 시장에서 검증된 여러 오픈소스 솔루션들과의 비교를 통해, 플랫폼에 가장 적합한 Lean한 구조를 고민했습니다.
2-1. API Gateway 솔루션 비교 분석
비교 항목 | Pure OpenResty (최종 채택) | Apache APISIX | Kong Gateway | Spring Cloud Gateway |
기반 기술 | Nginx (C, Lua) | Nginx (C, Lua) | Nginx (C, Lua) | Java (Netty/WebFlux) |
설정 관리 | 정적 (Config File) | 동적 (etcd 기반) | 동적 (Postgres 기반) | 정적/동적 (Java Config) |
추가 인프라 | 없음 (Zero) | etcd 클러스터 필수 | RDBMS 필수 | 없음 (Java 런타임) |
네트워크 구조 | Single Hop (통합형) | Sidecar/Gateway 선택 | Gateway 중심 (Heavy) | Double Hop (Nginx 병행 시) |
리소스 효율 | 압도적 (수십 MB) | 낮음 (etcd 관리 부담) | 낮음 (DB 운영 부담) | 보통 (JVM 오버헤드) |
의사결정 결과 | 최종 채택 | 운영 비용 과다로 탈락 | 인프라 과적으로 탈락 | 성능 및 홉 증가로 탈락 |
2-2. 핵심 탈락 사유
- Kong & Apache APISIX
두 솔루션 모두 OpenResty를 기반으로 한 훌륭한 엔진이지만, 이를 운영하려면 별도의 데이터베이스나 코디네이터(etcd) 클러스터를 상시 관리해야 합니다. “인프라 복잡도를 최소화하면서 최대의 성능을 낸다”는 팀의 기조에는 배보다 배꼽이 더 큰 오버헤드라 판단했습니다.
- Spring Cloud Gateway
Java 생태계와 매우 친화적이지만, 이미 Nginx가 리버스 프록시로 존재하는 저희 환경에서는 Double Hop(Nginx → SCG → WAS) 문제가 필연적이었습니다. 네트워크 단계가 추가될 때마다 발생하는 직렬화 오버헤드와 레이턴시는 AI 스트리밍 서비스에 적합하지 않았습니다.
2-3. 최종 선택: Pure OpenResty 커스텀
저희는 불필요한 기능을 걷어낸 가장 순수한 형태의 OpenResty를 선택하여 다음과 같은 가치를 얻었습니다.
- Zero Infrastructure
추가적인 DB나 서버 없이 기존 Nginx 설정에 Lua 스크립트만 얹어 즉각적인 게이트웨이 기능을 확보했습니다.
- Tailored Optimization
범용적인 솔루션들이 제공하는 무거운 기능들을 걷어내고, 저희에게 꼭 필요한
lua_shared_dict 기반 캐싱과 SSE 버퍼링 제어에만 집중하여 성능을 극대화했습니다.- Single Hop Architecture
기존 Nginx 레이어에 로직을 통합하여 네트워크 홉을 최소화하고 레이턴시를 방어했습니다.
물론 Nginx 설정에 스크립트가 포함되면 유지보수가 어려워질 수 있다는 우려도 있습니다. 저희는 이를 방지하기 위해 "인증 및 인가"라는 명확한 공통 관심사에만 기능을 제한하고, 복잡한 비즈니스 정책은 백엔드(Java/Python)로 위임하여 아키텍처의 순수성을 유지했습니다.
📌 참고: 설계 단계에서 Netflix Zuul이나 Python 기반 Gateway 등도 검토했으나, 현대적인 비동기 처리 요구사항과 인프라 성능 기준에 부합하지 않아 최종 후보군에서 제외되었습니다.
3. 아키텍처 재설계: 보안과 효율의 중앙화
저희는 Nginx에 Lua 스크립트 엔진을 결합한 OpenResty를 도입하여
Programmable Gateway를 구축했습니다. 이를 통해 Nginx의 이벤트 루프 기반 고성능 처리를 유지하면서도, Lua 스크립트로 유연한 로직 제어가 가능해졌습니다.3-1. 아키텍처 다이어그램: Single Hop Infrastructure
가장 큰 변화는 모든 외부 요청이 게이트웨이를 통과하며 물리적인 Single Hop 구조를 완성했다는 점입니다.

위 다이어그램은 Single Hop Infrastructure 구조를 보여줍니다.
- Gateway(OpenResty)
최전방 Entry Point로서 모든 외부 요청(:80)을 일괄 수신합니다. 여기서 Lua 스크립트가 실행되어 JWT의 서명을 검증하고, 유효한 요청에 한해 내부 서비스로 라우팅합니다.
- Java Backend
/api/v1/metadata, /api/v1/auth 등 데이터 소스 관리 및 권한 체계 관리를 담당합니다. 게이트웨이가 전달한 유저 정보를 바탕으로, Java로 구현된 RBAC/ABAC 로직을 최종 수행합니다.- Python Backend
/ai/v1/generate 등 실시간 AI 추론 요청을 처리합니다. 인증 로직을 게이트웨이에 위임(Offloading)했기 때문에, Python 서버는 무거운 인증 라이브러리 없이 오직 모델 연동과 비즈니스 로직에만 리소스를 집중할 수 있습니다.- Static Files
별도의 웹 서버를 두지 않고, React 빌드 파일을 Nginx 내부에 탑재했습니다. 이를 통해 CORS 문제를 원천 차단하고, 정적 리소스에도 인증 로직을 손쉽게 적용할 수 있게 되었습니다.
3-2. 인증 흐름의 변화: Security Offloading
새로운 아키텍처의 핵심은 Security Offloading입니다. 보안의 핵심인 Secret Key를 오직 게이트웨이만 관리하게 함으로써 보안 경계선을 명확히 했습니다.

단계별 인증 흐름 구조를 보여줍니다.
- Token Validation
클라이언트가 전송한 Bearer 토큰을 게이트웨이(Lua)가 가로채어 서명을 검증합니다. 이 과정에서 유효하지 않은 토큰은 백엔드까지 도달하지 못하고 즉시 차단됩니다.
- Claims Extraction
검증이 완료된 토큰의 Payload에서 유저 ID, 역할(Role) 등 비즈니스에 필요한 데이터를 추출합니다.
- Header Injection & Proxy
추출된 정보를
X-User-ID, X-User-Role과 같은 커스텀 헤더에 실어 백엔드로 전달합니다.- Trusted Zone Operation
백엔드 서비스(Java/Python)는 게이트웨이가 보낸 헤더를 전적으로 신뢰합니다. 별도의 인증 로직 없이 헤더값만으로 유저 컨텍스트를 구성하여 비즈니스 로직을 수행합니다.
이러한 신뢰 영역(Trusted Zone)의 구축은 백엔드 개발 생산성을 획기적으로 높여주었습니다. 더 이상 새로운 서비스를 만들 때마다 인증 라이브러리를 설정하거나 보안 키를 동기화하는 번거로운 작업이 필요 없기 때문입니다.
3-3. 네트워크 격리: 외부 노출 최소화
인증 로직의 중앙화와 더불어, 물리적인 네트워크 격리를 통해 보안성을 한 단계 더 강화했습니다. 저희는 Nginx를 유일한 접점으로 삼고, 백엔드 서버를 외부망에서 완전히 숨기는 DMZ/Private 구조를 적용했습니다.

Public Zone (DMZ)
- 오직 Nginx Gateway만이 외부 인터넷(Public Internet)과 연결됩니다.
- 외부 공격 표면(Attack Surface)을 Nginx 한 곳으로 좁혀 보안 관제와 방화벽 정책을 집중할 수 있습니다.
Private Zone
- 데이터와 비즈니스 로직을 다루는 Java와 Python 백엔드 서버는 외부에서 직접 접근할 수 없는 사설 네트워크(Private Subnet)에 배치했습니다.
- 이들은 오직 DMZ에 위치한 Nginx로부터 오는 트래픽만 수신(Allow only from DMZ)하며, 외부의 악의적인 접근이나 포트 스캔으로부터 원천 격리됩니다.
이로써 클라이언트가 게이트웨이를 거치지 않고 백엔드 API를 직접 호출하는 우회 공격을 물리적으로 불가능하게 만들었습니다. 설령 애플리케이션 레벨 취약점이 발견되더라도, 게이트웨이라는 “단일 관문”을 통하지 않고는 접근 자체가 불가능하므로 전체 인프라의 안정성을 확보했습니다.
4. Performance: Shared Dictionary를 활용한 캐싱
클라우드 환경이라면 트래픽이 늘어날 때 서버를 늘리는(Scale-out) 선택지가 있습니다. 하지만 저희가 제공하는 온프레미스 솔루션 환경은 고객사의 한정된 하드웨어 리소스 내에서 운영되어야 합니다.
비록 저희가 사용하는 HS256 알고리즘이 가볍다고 하더라도, 채팅 서비스 특성상 한 명의 사용자가 짧은 시간 내에 수백 번의 요청(SSE, Polling 등)을 보내게 됩니다. 이때 동일한 토큰(Immutable Token)에 대해 매번 똑같은 해시 연산을 반복하는 것은 공학적으로 비효율적인 구조입니다.
"인프라가 자원을 낭비하지 않아야 AI가 빨라진다"는 원칙 아래, 연산을 조회로 치환하는 Nginx의 공유 메모리 캐싱 전략을 도입했습니다. 이는 성능 수치를 떠나 시스템의 구조적 효율성을 높이기 위함입니다.
4-1. 최적화 설계: 왜 lua_shared_dict인가?
저희는 JWT 캐싱을 위해 Redis 같은 외부 인메모리 DB를 도입하는 대신, Nginx 워커 프로세스들이 메모리를 직접 공유하는
lua_shared_dict를 선택했습니다. 이는 "인프라 복잡도 최소화”와 "성능 극대화"라는 두 가지 목표를 모두 달성하기 위함입니다.- 네트워크 I/O 제로
Redis 호출 시 발생하는 네트워크 레이턴시마저 제거하여 L1 캐시 수준의 속도를 구현했습니다.
- 프로세스 간 데이터 동기화
Nginx는 멀티 워커 구조이지만, Shared Memory를 통해 모든 워커가 동일한 검증 결과값을 참조할 수 있어 메모리 효율을 극대화했습니다.
4-2. 최적화 흐름도 및 상세 로직
요청 처리 과정을 초기화(Init)와 런타임(Runtime) 단계로 나누어, OS 시스템 콜 최적화 설계를 시각화했습니다.

- Phase 1. Init Phase (워커 시작 시)
os.getenv와 같은 OS 시스템 콜은 비용이 듭니다. Nginx 워커가 뜨는 시점에 환경 변수(Secret Key)를 읽어 Lua의 지역 변수(Upvalue)에 할당 합니다. 덕분에 실제 요청 처리 중에는 시스템 콜 없이 메모리에서 즉시 키를 꺼내 쓸 수 있습니다.- Phase 2. Runtime Phase (요청 처리 시)
- Shared Memory 조회
- Lazy Verification
- Dynamic TTL
들어온 토큰을 Key로 메모리를 조회합니다. → O(1)
캐시 미스 발생 시에만 실제 CPU 암호화 연산(HMAC/RSA)을 수행합니다.
캐시 저장 시 JWT의
exp(만료 시간) 필드를 계산하여, 토큰이 만료되는 즉시 캐시에서도 제거되도록 설계하여 보안성을 유지했습니다.4-3. Lua 구현 디테일
저희의 목표는 단순한 CPU 절감이 아니라 결정론적인 응답 속도를 보장하는 것입니다. 암호화 연산은 입력 길이에 따라 시간이 변하는
O(N) 작업이지만, 메모리 조회는 항상 일정한 O(1) 작업입니다. 저희는 이 로직을 통해 트래픽 스파이크 상황에서도 게이트웨이가 항상 균일한 Latency를 보장하도록 만들었습니다.다음은 실제 프로덕션 코드의 핵심 로직을 단순화한 예시입니다.
5. Troubleshooting: SSE 스트리밍 지연 해결
LLM(대규모 언어 모델) 챗봇 기능의 핵심은 답변이 생성되는 대로 즉시 사용자에게 보여주는 실시간성입니다.
하지만 초기 구축 시 답변이 한꺼번에 쏟아지거나, 5~10초간 멈췄다가 출력되는 응답 지연 현상이 발견되었습니다.
5-1. 원인 분석: Nginx의 기본 버퍼링 메커니즘
Nginx는 기본적으로 효율적인 리소스 사용을 위해 백엔드의 응답을 일정 크기(
proxy_buffer_size)만큼 모았다가 클라이언트에 전달합니다. 또한, Gzip 압축 역시 데이터 블록이 쌓여야 압축을 시작합니다. 이는 일반적인 API 요청에는 유리하지만, 한 토큰씩 끊임없이 전달해야 하는 SSE(Server-Sent Events) 환경에서는 오히려 독이 되었습니다.5-2. 해결 방안: 스트리밍 전용 튜닝
실시간 스트리밍이 필요한 특정 경로(
/ai/v1/generate)에 대해 다음과 같은 전용 설정을 적용했습니다.proxy_buffering off;
백엔드(Python FastAPI)에서 오는 응답을 Nginx 메모리에 가두지 않고, 즉시 클라이언트로 Flush 합니다. 이를 통해 모델이 생성한 첫 번째 토큰이 유저에게 도달하는 시간(TTFT, Time To First Token)을 최소화했습니다.
gzip off;
스트리밍 응답에 대한 압축 지연을 제거했습니다. (대신 HTTP 응답 헤더에
X-Accel-Buffering: no를 실어 보내는 방식으로 보완할 수도 있습니다.)proxy_read_timeout 300s;
LLM의 추론 시간이 길어질 경우 커넥션이 중단되는 것을 방지하기 위해 타임아웃 임계값을 상향 조정했습니다.
- TCP 최적화
tcp_nodelay on; 설정을 통해 작은 크기의 패킷이라도 즉시 전송하도록 하여 체감 속도를 높였습니다.6. 결론 및 성과
단순한 프록시 도입을 넘어, Programmable Gateway를 통한 아키텍처 재설계는 보안과 성능, 그리고 생산성이라는 상충하기 쉬운 목표들을 동시에 달성하는 열쇠가 되었습니다.
✅ Performance: Latency & UX Optimization
- Algorithmic Efficiency
매 요청마다 반복되던 암호화 연산 O(N) 을 메모리 조회 O(1)로 치환하여, 트래픽 스파이크 상황에서도 균일한 응답 속도를 보장했습니다.
- UX Improvement
SSE 버퍼링 제거를 통해 LLM 답변의 TTFT(Time To First Token)를 0.5초 미만으로 단축, 끊김 없는 실시간 대화 경험을 완성했습니다.
✅ Security & Governance: 제로 트러스트 실현
- Attack Surface 최소화
모든 백엔드 서버를 Private Subnet으로 격리하고 공인 IP를 제거함으로써, 외부 공격 가능성을 차단했습니다.
- Policy Centralization
Java에서 관리하는 복잡한 권한 정책을 게이트웨이 단계에서 일관성 있게 적용했습니다.
✅ Productivity: 개발 집중도 향상
- Boilerplate 제거
Python 개발자들은 더 이상 프로젝트마다
AuthMiddleware를 구현하거나 Secret Key를 관리할 필요가 없습니다.- Focus on Core
인증 관련 커뮤니케이션 비용이 사라지고, 오직 AI 모델 서빙과 비즈니스 로직 고도화에만 전념할 수 있는 환경이 마련되었습니다.
마치며
폴리글랏 환경에서 Nginx는 더 이상 단순한 웹 서버가 아닙니다. 적절한 프로그래밍(OpenResty)을 곁들인 Nginx는 복잡하게 얽힌 분산 환경을 하나로 묶어주는
가장 가볍고 강력한 접착제가 될 수 있습니다. 저희는 기성 솔루션의 화려함 대신, 플랫폼의 요구사항에 가장 부합하는 Lean한 아키텍처 를 선택했고, 결과적으로 운영 복잡도를 낮추면서도 성능은 극대화하는 실용적인 엔지니어링을 실현했습니다.함께 보면 좋은 아티클
