
AI 채팅의 연결은 재연결되지만 세션은 유지되지 않는 이유
요약
WebSockets는 AI 채팅에 적합한 양방향 프로토콜이지만, 연결이 끊기면 세션 상태가 유지되지 않는 stateless 특성을 가집니다. 프록시 타임아웃이나 네트워크 전환 시 발생하는 데이터 유실을 방지하기 위해 세션 레이어 구축이 필수적입니다.
핵심 포인트
- WebSockets는 양방향 통신을 지원하여 실시간 제어와 도구 호출에 적합함
- 연결 재연결 로직은 전송 계층만 복구할 뿐 세션 상태를 복구하지 못함
- 프록시 타임아웃, 페이지 새로고침 등으로 인한 데이터 유실 위험 존재
- 세션 ID 기반의 상태 유지 및 데이터 재생(replay) 인프라가 필요함
요약(TL;DR): WebSockets (웹소켓)는 프로덕션 AI 채팅을 위한 올바른 프로토콜입니다. 하지만 연결은 세션 레벨에서 상태가 없는(stateless) 특성을 가집니다. 연결이 끊어질 때 — AWS ALB는 기본 60초, Cloudflare의 Free 및 Pro 플랜은 100초 — 진행 중이던 모든 토큰(tokens), 도구 호출 결과(tool call results), 그리고 에이전트 컨텍스트(agent context)가 사라집니다. 재연결 로직은 소켓을 복구하지만, 세션을 복구하지는 않습니다. 이 포스트는 바로 그 간극을 다룹니다.
WebSockets (웹소켓)는 프로덕션 AI 채팅을 위한 올바른 프로토콜입니다. 하지만 이 사실이 대부분의 팀이 가장 먼저 맞닥뜨리는 실패를 막아주지는 못합니다. 엔터프라이즈 로드 밸런서(load balancer)는 도구 실행(tool execution)을 기다리는 동안 60초가 지나면 유휴 연결(idle connection)을 종료합니다. 재연결 로직은 1초 이내에 작동하고, 에이전트는 서버 측에서 계속 실행되지만, 클라이언트는 그 간극 동안 아무것도 받지 못합니다. 토큰도, 도구 호출 결과도, 컨텍스트도 없습니다.
재연결된 소켓은 연결이 끊겨 있는 동안 무슨 일이 일어났는지 알 수 없습니다. 세 가지 조건이 정기적으로 이 문제를 일으킵니다: 작업 중간의 프록시 타임아웃(proxy timeout), 생성 중간의 페이지 새로고침(page reload), 그리고 모바일 네트워크 핸드오프(mobile network handoff). 이들은 모두 동일한 근본적인 이유로 발생합니다: WebSocket (웹소켓) 프로토콜은 전송(transport)을 처리할 뿐 세션 상태(session state)를 처리하지 않으며, 재연결 로직도 이를 바꾸지 못합니다.
핵심 요약
- WebSockets (웹소켓)는 프로덕션 AI 채팅을 위한 올바른 프로토콜입니다: 양방향(bidirectional)이며, 지속적(persistent)이고, SSE (Server-Sent Events)가 할 수 없는 방식으로 실시간 제어(live steering) 및 도구 호출에 적합합니다.
- WebSocket (웹소켓) 연결은 세션 레벨에서 상태가 없습니다(stateless). 프록시 타임아웃, 페이지 새로고침 또는 기기 전환으로 인해 연결이 닫히면 모든 상태가 함께 사라집니다.
- 재연결 로직은 전송 계층을 재설정합니다. 연결이 끊겼을 때 진행 중이던 토큰, 도구 호출 또는 에이전트 컨텍스트를 복구하지는 않습니다.
- 이 간극을 채우는 것은 세션 레이어(session layer)입니다: 세션 ID(session ID)를 기준으로 대화 상태를 유지하고 재연결되는 클라이언트에 이를 다시 재생(replay)하는 인프라입니다.
AI 채팅을 위해 WebSockets (웹소켓)가 제대로 수행하는 것
프로토콜에 대한 문제는 초기에 정리할 가치가 있습니다. 왜냐하면 이 글의 나머지 부분은 그 상위의 인프라 계층(infrastructure layer)에 대해 논하기 때문입니다. 프로덕션(production) 환경의 AI 채팅을 위해서는 WebSockets (웹소켓) 또는 SSE (Server-Sent Events) 중 하나를 선택해야 합니다. 두 방식 모두 클라이언트로 토큰을 스트리밍하지만, 신호가 반대 방향으로 흐를 수 있게 해주는 것은 오직 WebSockets 뿐입니다.
WebSockets는 양방향(bidirectional)입니다. 사용자가 스트리밍 도중에 취소를 하면, 그 신호는 동일한 채널을 통해 다시 전달됩니다. 도구 호출(tool call) 확인이나 워크플로우 승인도 같은 방식으로 작동합니다. 실행 도중 인간의 입력(human input)을 위해 워크플로우가 일시 중지될 때, 해당 입력은 폴링 엔드포인트(polling endpoint)를 통해서가 아니라 인밴드(in-band) 방식으로 도착해야 합니다.
SSE는 단방향 스트림입니다. 안정적인 네트워크 환경의 단순한 챗봇이라면 이는 문제가 되지 않습니다. 하지만 도구 호출, 스트리밍 중 취소, 또는 멀티 디바이스 연속성(multi-device continuity)을 추가하게 되면 문제가 됩니다.
프로덕션 AI 연결이 실제로 실패하는 지점
모든 연결 끊김이 나쁜 네트워크 상태 때문에 발생하는 것은 아닙니다. 프로덕션 환경에서 더 흔한 원인은 AI 채팅이 아닌 HTTP 요청을 위해 설계된 인프라 기본 설정(infrastructure defaults)입니다. 응답이 생성되는 도중 수십 초 동안 지속될 수 있는데, 대부분의 기본 설정은 이를 위해 구축되지 않았습니다.
AWS Application Load Balancer (ALB) 유휴 시간 제한(idle timeout). AWS Application Load Balancer 문서에 따르면, AWS ALB는 기본적으로 60초 동안 유휴 상태인 연결을 종료합니다. 표준 HTTP 환경에서는 관대한 수치입니다. 하지만 다운스트림 API(downstream API)의 응답을 기다리는 에이전트에게 60초의 침묵은 일상적인 일이며, 연결은 경고 없이 종료됩니다. 사용자의 응답은 아무런 설명 없이 문장 중간에 멈춰버립니다.
Cloudflare 프록시 시간 제한(proxy timeout). Cloudflare의 WebSocket 트러블슈팅 가이드에 명시된 바와 같이, Cloudflare Free 및 Pro 플랜에서는 WebSocket 연결이 100초 동안 활동이 없으면 종료됩니다. Enterprise 플랜에서는 이 제한을 높일 수 있지만, Free 및 Pro 플랜에서는 상한선이 고정되어 있습니다.
모바일 네트워크 핸드오프 (Mobile network handoffs). WiFi에서 셀룰러로 전환되면 기저의 TCP 연결이 즉시 끊어지며, WebSocket도 함께 종료됩니다. 모바일 환경에서는 커버리지 영역 사이를 이동하거나, 탭을 백그라운드로 전환하거나, 건물 내부로 들어가는 등 일반적인 사용 과정에서 이러한 현상이 발생합니다.
페이지 새로고침 및 탭 충돌 (Page reload and tab crash). 사용자가 생성 도중에 페이지를 새로고침하거나 브라우저가 충돌하는 일은 매우 흔합니다. 이 경우 연결이 닫히며, 별도로 저장된 것이 없다면 해당 연결에 종속된 모든 세션 상태 (Session state)는 사라집니다.
재연결 로직이 세션 문제를 해결하지 못하는 이유
표준적인 재연결 패턴은 소켓 (Socket)을 다시 설정합니다. 전송 계층 (Transport)은 밀리초 단위로 복구됩니다. 하지만 연결이 끊겼을 때 전송 중이었던 상태 (State)를 복구할 수는 없습니다.
토큰 스트림 위치 (Token stream position). 연결이 끊긴 동안에도 응답은 계속 생성되었습니다. 그 토큰들은 갈 곳을 잃었습니다. 클라이언트가 재연결되었을 때, 문장 중간부터 시작하거나 아예 아무것도 찾지 못하게 됩니다.
도구 호출 결과 (Tool call results). 일부 채팅 응답은 조회, 검색, 또는 사용자가 트리거한 작업과 같은 실시간 데이터에 의존합니다. 에이전트 (Agent)가 해당 결과를 기다리는 동안 연결이 끊어지면, 응답이 영영 오지 않거나 정보를 사용하기도 전에 종료되어 버립니다.
에이전트 컨텍스트 (Agent context). 멀티턴 (Multi-turn) 대화에서 에이전트는 무엇을 물었는지, 무엇을 답했는지, 그리고 무엇이 진행 중인지와 같은 컨텍스트 (Context)를 축적합니다. 세션이 끊겼다가 상태 복구 없이 재연결되면, 에이전트와 클라이언트는 동일한 대화 내에서 서로 다른 지점에 있게 됩니다. 사용자는 이를 대화 흐름의 상실로 경험하게 됩니다. 즉, 이전 내용을 무시하는 응답을 받거나 이미 답변된 내용을 반복하는 식입니다.
대부분의 팀이 선택하는 패턴은 Redis 버퍼 (Redis buffer)를 사용하는 것입니다. 에이전트와 클라이언트 사이에서 시퀀스 번호 추적 (Sequence number tracking), 오프셋 저장 (Offset storage), 중복 제거 키 (Deduplication keys)를 관리하는 방식입니다. 이는 전체 페이지 새로고침은 처리할 수 있습니다. 하지만 배포로 인해 발생하는 재연결, 재연결 윈도우(Reconnect window)에 두 번 걸리는 모바일 핸드오프, 그리고 버퍼가 소진되는 속도보다 메시지가 더 빠르게 생성되는 모든 상황에서는 무너지기 쉽습니다.
심지어 Vercel의 AI SDK 리드조차 이 간극을 메우기 위해 플러그형 인터페이스 (pluggable interface)를 구축했습니다. 이 단계에 도달하는 모든 팀은 동일한 인프라를 처음부터 구축하며, 이를 무기한 직접 관리하기로 선택합니다. 재연결 (Reconnection)은 프로토콜 계층 (protocol layer)을 다루지만, 세션 상태 (session state)는 그보다 한 계층 위에 위치하며 이는 완전히 별개의 문제입니다.
프로덕션 AI 채팅이 전송 계층 (transport layer)에 요구하는 것
프로덕션 AI 세션을 위한 실행 가능한 모든 접근 방식은 네 가지 요구 사항을 충족해야 합니다. 이는 구현 방식에 중립적입니다. 즉, 벤더(vendor)와 관계없이 어떤 인프라 옵션이든 반드시 제공해야 하는 기능입니다.
지속적인 상태 저장 (Persistent state storage). 대화 기록 (conversation history), 토큰 위치 (token positions), 도구 호출 (tool call)의 입력 및 출력, 그리고 에이전트 상태 (agent state)는 안정적인 세션 ID (session ID)에 따라 저장되어야 하며 연결 끊김 현상에서도 유지되어야 합니다. 세션 ID는 닻 (anchor) 역할을 합니다. 즉, 어떤 재연결 후에도, 어떤 기기에서든 동일한 세션에 접근할 수 있어야 합니다.
오프셋 기반 재생 (Offset-based replay). 다시 접속한 클라이언트는 마지막으로 수신한 일련번호 (serial) 이후의 메시지를 요청합니다. 인프라는 누락된 모든 메시지를 중복 없이 순서대로 전달합니다. 클라이언트가 자신의 오프셋 (offset)을 제공하면, 인프라가 그 간극을 채웁니다.
프로토콜 폴백 (Protocol fallback). 프록시 (proxy)나 방화벽 (firewall)에 의해 WebSocket 업그레이드가 차단될 때, 전송 방식은 자동으로 HTTP 스트리밍 (HTTP streaming)이나 롱 폴링 (long-polling)으로 저하 (degrade)됩니다. 이는 배포마다 별도의 설정을 요구해서는 안 됩니다.
다중 기기 전달 (Multi-device delivery). 세션 ID를 구독하는 인증된 모든 기기는 현재 상태와 기록을 모두 받습니다. 세션은 이를 연 탭 (tab), 브라우저 (browser) 또는 기기 (device)에 종속되지 않습니다.
Ably AI Transport가 세션 계층 문제를 해결하는 방법
다행히도, 여러분이 직접 인프라를 구축할 필요는 없습니다. Ably AI Transport는 내구성이 있는 세션 계층 (session layer)입니다. 즉, WebSocket 프로토콜이 처리할 수 없는 상황에서도 사용자 경험이 유지되도록 만드는 요소입니다. 세션은 Ably에 존재하며, 여러분의 애플리케이션은 Ably와 통신합니다.
이 포스트에서 제기된 다섯 가지 실패 사례는 각각 특정 기능과 직접적으로 매칭됩니다:
프록시 타임아웃(proxy timeouts), 모바일 핸드오프(mobile handoffs), 페이지 새로고침으로 인한 연결 끊김. 전송 방식(transport)은 WebSocket 우선, 그다음 HTTP 스트리밍(HTTP streaming), 그다음 롱 폴링(long-polling) 순으로 자동으로 저하(degrade)되므로, 표준 WebSocket 연결을 끊어버리는 인프라 기본 설정 상황에서도 세션이 유지됩니다. 배포 시 별도의 설정이 필요하지 않습니다.
→ 재연결 및 복구 (Reconnection and recovery)
클라이언트가 연결되지 않은 동안 생성된 토큰. 토큰 스트림은 세션에 귀속되어 저장됩니다. 재연결 시 클라이언트는 놓친 모든 내용을 중복 없이 순서대로 전달받습니다. 개발자가 오프셋(offset)을 추적하거나 캐치업(catch-up) 로직을 구현할 필요가 없습니다.
→ 토큰 스트리밍 (Token streaming)
작업 도중 도구 호출(tool call) 결과 및 에이전트 컨텍스트(agent context) 유실. 에이전트 상태(agent state), 도구 호출의 입력 및 출력, 그리고 대화 기록은 생성되는 즉시 세션으로 발행(publish)됩니다. 재연결된 클라이언트는 토큰뿐만 아니라 전체 컨텍스트를 복구합니다.
→ 재연결 및 복구 (Reconnection and recovery)
스트림 중간의 스티어링(steering) 및 인간 참여(human-in-the-loop) 신호. 취소(cancellation), 승인(approval), 그리고 인간의 입력은 동일한 세션 채널을 통해 에이전트로 전달됩니다. SSE(Server-Sent Events)를 배제하게 만드는 양방향(bidirectional) 요구 사항이 별도의 시그널링 메커니즘 없이 해결됩니다.
→ 인간 참여 (Human in the loop)
단일 탭 또는 장치에 종속된 세션. 세션 ID를 구독하는 인증된 모든 장치는 현재 상태와 히스토리를 전달받습니다. 데스크톱에서 시작된 대화는 재시작 없이 모바일에서 계속됩니다.
→ 멀티 디바이스 세션 (Multi-device sessions)
시작하기: Vercel AI SDK · Core SDK
자주 묻는 질문 (Frequently asked questions)
AI 채팅에서 SSE가 여전히 적절한 선택인 경우는 언제인가요?
사용자가 메시지를 제출하고 서버가 토큰을 스트리밍하며, 중단이 필요 없는 단순한 요청-응답 (request-response) 패턴을 따르는 챗봇의 경우, SSE는 합리적인 시작점입니다. WebSockets보다 배포가 더 쉽고, 지속적인 연결 오버헤드가 없으며, 안정적인 네트워크에서 잘 작동합니다.
제약 사항은 애플리케이션에 에이전트적 동작 (agentic behaviour)이 추가되기 시작할 때 나타납니다. 즉, 도구 호출 (tool calls), 스트림 중간 취소 (mid-stream cancellation), 다중 장치 연속성 (multi-device continuity), 그리고 사용자가 오프라인인 동안 완료되는 백그라운드 작업 등이 포함될 때입니다. 그 시점에서 SSE의 단방향 아키텍처 (unidirectional architecture)는 더 이상 절충안이 아니라 차단 요소가 됩니다.
프로덕션 환경에서 AI 연결 끊김을 방지하려면 어떤 타임아웃 (timeout) 값을 설정해야 하나요?
WebSocket 연결의 경우 AWS ALB의 유휴 타임아웃 (idle timeout)을 최소 3,600초로 설정하십시오. 기본값인 60초는 장시간 실행되는 에이전트 작업이 아닌 HTTP 요청을 위해 설계된 것입니다. Cloudflare의 Free 및 Pro 플랜에서는 WebSocket 타임아웃이 100초로 고정되어 있습니다. 해당 임계값보다 훨씬 낮은 수준을 유지하기 위해 약 25초 간격으로 하트비트 핑 (heartbeat pings)을 전송하십시오.
Nginx의 경우, 이에 상응하는 설정은 proxy_read_timeout입니다. 이 세 가지 변경 사항은 AI 채팅 배포 시 발생하는 대부분의 프로덕션 타임아웃 실패를 해결합니다.
재연결 로직 (reconnection logic)이 세션 복구 문제를 해결할 수 있나요?
재연결 로직은 전송 (transport) 문제를 해결합니다. 상태 (state) 문제를 해결하지는 못합니다. 지수 백오프 (Exponential backoff)와 하트비트 (heartbeats)는 소켓을 재설정합니다.
하지만 이들은 공백 기간 동안 생성된 토큰, 클라이언트가 연결 해제된 동안 도착한 도구 호출 (tool call) 결과, 또는 여러 단계에 걸쳐 축적된 컨텍스트 (context)를 복구할 수는 없습니다. 재연결 시 중복 메시지를 방지하려면 WebSocket 계층이 아닌 세션 계층 (session layer)에서 시퀀스 번호 (sequence numbers) 또는 멱등성 키 (idempotency keys)가 필요합니다. 세션 계층 없이 재연결하는 클라이언트는 빈 컨텍스트에 도달하게 되며, 대화 내용을 잃거나 대화를 처음부터 다시 시작하게 됩니다.
Ably는 WebSocket 재연결 후 누락된 메시지를 어떻게 재생 (replay) 하나요?
Ably는 발행된 모든 메시지에 일련번호 (serial number)를 할당합니다. 클라이언트가 재연결될 때, 전송 계층 (transport layer)은 내부의 untilAttach 메커니즘을 사용하여 공백 기간 동안 발행된 메시지들을 가져옵니다. 이를 통해 히스토리 쿼리 (history query) 범위를 정확한 재연결 시점으로 제한할 수 있습니다.
Ably는 누락된 모든 메시지를 순서대로 전달하며, 과거 메시지와 실시간 메시지 간의 중복은 발생하지 않습니다. 클라이언트는 자체적인 오프셋 (offset)을 추적하거나 캐치업 로직 (catch-up logic)을 구현할 필요가 없습니다. 모든 플랜에는 기본적으로 2분간의 휘발성 히스토리 (ephemeral history)가 포함됩니다. 퍼시스턴트 채널 (Persisted channels)은 이를 확장하여 Standard 플랜에서는 72시간까지, Pro 및 Enterprise 플랜에서는 최대 365일까지 지원합니다.
운영 환경 (production)에서 이러한 현상을 겪으신 적이 있나요? 장애가 어떤 모습이었는지 궁금합니다. 프록시 타임아웃 (proxy timeout)이었나요, 페이지 새로고침이었나요, 아니면 이 문제가 처음 나타나게 된 다른 원인이 있었나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기