# Spring WebFlux와 Redis로 구축한 LLM 스트리밍 파이프라인
AI 기반 대화형 서비스에서 핵심은 응답 속도와 대화의 자연스러움이다. “AI 면접관” 프로젝트를 시작할 때 세운 원칙은 단 하나였다.
“AI와의 대화는 사람과의 대화처럼 자연스러워야 한다.”
이 목표는 단순히 빠른 응답을 반환하는 것을 넘어, AI가 ‘생각하며 말하는 듯한 실시간 스트리밍 경험’ 을 구현해야 한다는 기술적 과제로 이어졌다.
# 1️⃣ 아키텍처 설계 — 선택의 이유
# WebFlux: 비동기 I/O의 필연성
LLM 응답은 특성상 수 초 이상 지연되는 경우가 많다. 기존 Spring MVC의 스레드-퍼-리퀘스트 모델은 이런 장기 연결 요청에서 병목을 초래했다.
WebFlux는 소수의 논블로킹 스레드로 수천 개의 요청을 동시에 처리할 수 있다. 즉, 요청마다 스레드를 점유하지 않아 높은 동시성 환경에서도 안정적인 처리량을 확보할 수 있었다.
이 선택의 트레이드오프는 명확했다. 비동기 처리를 도입함으로써 디버깅이 복잡해지고, 코드 가독성이 떨어질 수 있다. 하지만, 응답 대기 시간이 긴 LLM 요청의 특성상 WebFlux는 필연적인 선택이었다.
# SSE: 단방향 스트리밍의 합리적 선택
AI 응답은 “서버 → 클라이언트”로만 흐른다. 따라서 양방향 통신(WebSocket)보다는 HTTP 기반의 단방향 스트리밍 프로토콜인 SSE(Server-Sent Events) 가 훨씬 단순하고 효율적이었다.
SSE는 별도의 프로토콜 업그레이드 없이 브라우저 기본 API(EventSource)로 바로 사용할 수 있으며,
자동 재연결 기능을 지원해 안정성이 높다.
이 선택은 구조를 단순화했지만, 동시에 바이너리 데이터 전송 불가라는 제약도 함께 가져왔다. 텍스트 기반 응답만 다루는 LLM 서비스이기에, 이는 충분히 감수할 수 있는 범위였다.
# LangChain4j: Gemini API와의 직접 연동
당시 Spring AI는 Google Gemini의 스트리밍 API를 직접 지원하지 않았다. 따라서 LLM 호출은 LangChain4j를 사용하여 처리했다.
LangChain4j는 Gemini의 토큰 단위 스트리밍(TokenStream)을 그대로 지원하며,
향후 다른 LLM과의 체이닝 구조로 확장하기에도 유리했다.
이를 통해 AI가 한 번에 문장을 내뱉는 대신, “조금씩 말하면서 생각하는 듯한” 자연스러운 사용자 경험을 구현할 수 있었다.
# 2️⃣ 문제와 해결
# 스트림 끊김 — 하트비트로 연결 유지
개발 초기, 약 30초 이상 응답이 지속되면 스트림이 조용히 끊어지는 문제가 발생했다. 원인은 로드밸런서의 idle timeout이었다.
이 문제를 해결하기 위해, 주기적으로 하트비트(:heartbeat) 이벤트를 전송하도록 했다. 하트비트는 클라이언트 화면에는 표시되지 않지만, TCP 연결을 유지시키는 역할을 수행했다.
이 단순한 개선으로 스트림 연결 안정성이 크게 높아졌고, 서버나 네트워크 환경에 관계없이 지속적인 스트리밍이 가능해졌다.
# Chunk 단위 저장으로 인한 DB 부하 — Redis 임시 버퍼로 완화
LLM 응답은 수백 개의 토큰으로 나뉘어 전송된다. 초기에는 각 토큰(Chunk)마다 DB에 저장하는 구조였는데, 이로 인해 지속적인 I/O 부하가 발생했다.
이를 해결하기 위해 Redis를 임시 버퍼로 사용했다.
응답이 생성되는 동안 Redis에 Chunk 데이터를 순차적으로 누적하고, 모든 응답이 완료된 시점에 한 번만 DB에 반영하도록 구조를 변경했다.
이 접근으로 DB I/O 부하를 대폭 줄였으며, 특히 다수의 동시 스트리밍 요청에서도 안정적인 처리 성능을 확보할 수 있었다.
# 3️⃣ 트레이드오프와 사이드이펙트
| 선택 | 장점 | 사이드이펙트 |
|---|---|---|
| WebFlux | 높은 동시성 처리 | 디버깅 및 스택 추적 난이도 증가 |
| SSE | 단순한 구조, 브라우저 호환성 | 바이너리 전송 불가 |
| Redis 버퍼 | I/O 부하 감소 | TTL 관리 실패 시 데이터 유실 가능성 |
| 하트비트 유지 | 연결 안정성 향상 | 네트워크 트래픽 소폭 증가 |
결국 모든 선택은 “대화의 자연스러움”이라는 목표를 달성하기 위한 트레이드오프였다. WebFlux의 복잡함, Redis TTL의 관리 리스크를 감수하더라도 사용자가 ‘생각하는 AI’와 대화하는 경험을 제공하는 것이 더 큰 가치였다.
# 4️⃣ 마무리하며
이 파이프라인을 통해 단순한 “응답 반환”을 넘어, AI가 실시간으로 사고하고 말하는 듯한 대화 경험을 구현할 수 있었다.
WebFlux는 비동기 처리의 복잡함을 동반하지만, Reactive Stream의 철학을 이해하고 Redis를 적절히 조합하면 확장성과 자연스러움을 모두 잡을 수 있다.