1. SSE(Server-Sent Events)의 선택 이유
AI 응답 스트리밍에는 WebSockets, Polling, SSE 등 다양한 방식이 있으나, AI 서비스의 특성상 서버에서 클라이언트로의 단방향 데이터 흐름이 주를 이루므로 SSE가 가장 적합합니다. - 단순성: 표준 HTTP 프로토콜을 사용하며 WebSockets보다 구현이 간단합니다. - 네이티브 지원: 브라우저의 ReadableStream API를 통해 별도의 라이브러리 없이 스트리밍 데이터를 처리할 수 있습니다. - 효율성: 지속적인 연결을 유지하면서도 오버헤드가 적어 AI 토큰 단위 전송에 최적화되어 있습니다.
2. Rails 백엔드 아키텍처 구현
Rails에서 스트리밍을 구현하기 위해서는 ActionController::Live 모듈의 포함이 필수적입니다.
- SSE 헬퍼 클래스: event와 data 형식을 규격에 맞게 작성하고 스트림을 관리하는 전용 클래스를 정의합니다.
- Controller 설정: 응답 헤더의 Content-Type을 text/event-stream으로 설정하고, 클라이언트의 연결 종료 상황을 IOError 예외 처리를 통해 관리합니다.
- AI 서비스 연동: ruby-openai 젬을 활용하여 stream: proc 블록 내부에서 각 토큰(chunk)이 생성될 때마다 SSE 헬퍼를 통해 프론트엔드로 데이터를 즉시 전송합니다.
3. React 프론트엔드 스트림 소비
프론트엔드에서는 fetch API와 ReadableStream을 사용하여 데이터를 점진적으로 읽어들입니다.
- AbortController 활용: 컴포넌트 언마운트 시 또는 새로운 요청 발생 시 이전 스트림 연결을 안전하게 종료하여 자원 낭비를 방지합니다.
- TextDecoder와 버퍼 처리: 이진 데이터로 들어오는 스트림을 텍스트로 변환하고, 줄 바꿈 단위로 파싱하여 JSON 데이터를 추출합니다.
- 상태 업데이트: 파싱된 토큰을 기존 문자열 상태에 누적시켜 사용자 화면에 실시간으로 텍스트가 출력되도록 합니다.
4. 실무 환경을 위한 고급 기능
운영 환경에서는 보안과 비용 관리가 중요합니다.
- Rate Limiting: Rails.cache를 활용하여 사용자별 요청 횟수를 제한함으로써 고비용 AI API의 남용을 방지합니다.
- 비용 추적(Cost Tracking): 요청 및 응답 토큰 수를 데이터베이스에 기록하여 예상 비용을 실시간으로 모니터링합니다.
- CORS 설정: 프로덕션 환경에서는 rack-cors 젬을 사용하여 프론트엔드 도메인에 대한 적절한 접근 권한을 설정해야 합니다.