Summary#
WebSocket session resilience는 “연결이 끊기지 않게 유지”가 아니라, 끊김·반쪽 연결·프록시 idle timeout·재접속 폭주·중복 전달·재전송·느린 소비자가 발생해도 클라이언트와 서버가 같은 의미로 복구하도록 만드는 운영 계약이다.
핵심 계약은 다음 다섯 가지다.
- Heartbeat / liveness: TCP 연결이 살아 보이지만 실제 경로가 죽은 half-open 상태를 탐지하기 위해 ping/pong 또는 application-level heartbeat를 둔다.
- Reconnect backoff: 장애 시 모든 클라이언트가 동시에 재접속하지 않도록 exponential backoff와 jitter를 사용한다.
- Sequence / resume token: 서버가 보낸 이벤트에 단조 증가 sequence number를 붙이고, 클라이언트는 마지막으로 처리한 offset을 재접속 시 제출한다.
- Proxy / load balancer idle timeout: ALB, reverse proxy, NAT, browser/network middlebox의 idle timeout보다 짧은 주기로 트래픽을 발생시키거나 timeout 값을 명시적으로 맞춘다.
- Duplicate delivery / backpressure: 재전송 기반 복구는 at-least-once 성격을 띠므로 중복 억제와 느린 소비자 처리 정책을 별도 계약으로 정의한다.
Key Points#
- WebSocket 기본 API만으로는 세션 복구 계약이 완성되지 않는다.
- RFC 6455는 WebSocket 프로토콜의 framing, close, ping/pong control frame 등을 정의하지만, 애플리케이션 이벤트의 sequence, replay, resume token, 중복 제거 정책까지 표준화하지는 않는다.
-
브라우저
WebSocketAPI도 메시지 송수신과 close/error 이벤트를 제공할 뿐, 자동 재접속·재전송·ack·resume을 내장하지 않는다. -
Heartbeat는 “주기”보다 “실패 판정 기준”이 중요하다.
- 계약 예시:
- 서버는
ping또는 application heartbeat를N초마다 전송한다. - 클라이언트는
M초 이내 응답이 없으면 연결을 의심한다. K회 연속 실패하면 연결을 닫고 재접속한다.
- 서버는
- 브라우저 환경에서는 WebSocket protocol-level ping frame을 JS에서 직접 보낼 수 없으므로, 필요하면 JSON heartbeat 같은 application-level ping/pong을 별도로 둔다.
-
heartbeat 간격은 proxy/load balancer idle timeout보다 짧아야 한다.
-
Half-open detection은 운영상 필수다.
- 모바일 네트워크 전환, NAT 만료, Wi-Fi 절전, 중간 프록시 장애에서는 TCP socket이 즉시 close되지 않을 수 있다.
- 이 경우 클라이언트는 “연결됨”으로 보이지만 메시지는 도착하지 않는 상태가 된다.
-
heartbeat timeout 없이 단순히
onclose만 기다리면 장애 감지가 늦어진다. -
Reconnect backoff에는 jitter가 필요하다.
- 서버 장애, 배포, load balancer 재시작 후 수만 개 클라이언트가 즉시 재접속하면 thundering herd가 발생한다.
-
권장 계약:
- 초기 지연: 예: 250ms~1s
- exponential 증가: 예: 1s, 2s, 4s, 8s...
- 상한: 예: 30s 또는 60s
- full jitter 또는 decorrelated jitter 적용
- 인증 실패, 권한 실패, protocol mismatch는 무한 재시도하지 않음
-
Sequence number는 resume의 최소 단위다.
- 서버 이벤트에는 connection-local이 아니라 stream/topic/user-session 기준의 단조 증가
seq또는offset을 붙이는 것이 안전하다. - 클라이언트는 “마지막으로 수신한 번호”가 아니라 마지막으로 처리 완료한 번호를 저장해야 한다.
-
재접속 요청 예시:
json { "type": "resume", "stream": "orders:user-123", "last_processed_seq": 18422, "resume_token": "opaque-token" } -
Resume token은 offset만이 아니라 권한과 범위를 담는 계약이어야 한다.
- token에는 보통 다음 의미가 필요하다.
- 어떤 사용자/세션/구독에 대한 resume인지
- 어느 stream 또는 topic 범위인지
- 마지막 ack 또는 replay 가능한 offset
- 만료 시간
- 서버 측 replay buffer와 호환되는 version
-
token은 bearer credential처럼 취급될 수 있으므로 TLS, 짧은 만료, 재발급, scope 제한이 필요하다.
-
Replay buffer가 없으면 진짜 resume은 불가능하다.
- 서버가 과거 이벤트를 보관하지 않으면 resume token은 단지 “새 연결에서 다시 구독”하는 힌트일 뿐이다.
-
replay 가능성을 계약하려면 다음을 명시해야 한다.
- 보관 기간: 예: 5분, 1시간, 24시간
- 보관 개수: 예: stream당 최근 10,000개
- gap 발생 시 동작: snapshot 재조회, full resync, error close, degraded mode
- 순서 보장 범위: per connection, per topic, per user, global 등
-
Duplicate delivery는 정상 실패 모드로 간주해야 한다.
- 클라이언트가 이벤트를 처리했지만 ack 전송 전에 연결이 끊기면 서버는 해당 이벤트를 재전송할 수 있다.
- 따라서 resume/replay 설계는 대개 at-least-once delivery가 된다.
-
클라이언트 또는 downstream consumer는
event_id,seq, idempotency key를 사용해 중복을 억제해야 한다. -
Exactly-once처럼 보이는 UI도 내부적으로는 중복 억제 위에 세워진다.
-
예:
- 채팅 메시지:
message_id기준 dedupe - 주문 상태:
order_id + version기준 최신 상태만 반영 - 알림:
notification_id기준 표시 여부 저장 - 협업 편집: operation id 또는 CRDT/OT 계층에서 중복 처리
- 채팅 메시지:
-
Backpressure는 WebSocket에서 자주 누락되는 계약이다.
- 브라우저 WebSocket에는
bufferedAmount가 있어 송신 버퍼 증가를 관찰할 수 있지만, 수신 측 backpressure는 애플리케이션 설계가 필요하다. - 서버는 느린 클라이언트에 대해 다음 중 하나를 명시해야 한다.
- 버퍼링 상한까지 대기
- 오래된 메시지 drop
- 최신 상태 snapshot으로 coalesce
- 낮은 우선순위 이벤트 생략
- connection close 후 resume 유도
-
정책 없이 무한 큐를 두면 메모리 고갈과 tail latency 악화가 발생한다.
-
Proxy/load balancer idle timeout은 heartbeat와 함께 계약해야 한다.
- AWS Application Load Balancer는 idle timeout 속성을 제공하며, 기본값과 설정값에 따라 idle connection을 닫을 수 있다.
- Nginx, Envoy, Cloudflare, corporate proxy, NAT gateway 등도 각자 idle timeout을 가질 수 있다.
- WebSocket이 장시간 조용한 연결이라면, application heartbeat 또는 protocol ping/pong이 해당 timeout보다 짧아야 한다.
-
단, heartbeat가 너무 짧으면 모바일 배터리, 서버 fan-out 비용, 네트워크 비용이 증가한다.
-
Sticky session은 resume 설계의 대체물이 아니다.
- sticky session은 동일 클라이언트가 같은 backend로 라우팅될 확률을 높일 수 있지만, backend 장애·배포·scale-in·token 만료·routing table 변화에는 취약하다.
-
견고한 설계는 session state, subscription state, replay offset을 외부 저장소 또는 shard-consistent stream에 둘지 명시한다.
-
Failure mode별 계약 예시
heartbeat_timeout: 클라이언트가 연결을 닫고 backoff 후 resume 시도auth_expired: token refresh 후 재접속, 실패 시 사용자 재인증resume_gap: snapshot REST API 호출 후 최신 seq부터 재구독duplicate_event: event id 기준 무시slow_consumer: low-priority event drop 또는 connection close with retry-afterserver_overload: close code 또는 application error로 retry-after 전달-
protocol_version_mismatch: 재시도 중단, 클라이언트 업데이트 요구 -
추천 capsule contract skeleton
yaml websocket_session_contract: heartbeat: interval_seconds: 25 timeout_seconds: 10 max_missed: 2 mechanism: app_ping_pong reconnect: initial_delay_ms: 500 max_delay_seconds: 30 jitter: full retry_after_respected: true resume: token_type: opaque token_ttl_minutes: 30 client_sends: last_processed_seq replay_window: 15m gap_policy: snapshot_then_resubscribe delivery: guarantee: at_least_once ordering_scope: per_stream dedupe_key: event_id backpressure: send_buffer_limit: defined_per_client_class slow_consumer_policy: close_and_resume_or_coalesce proxy: idle_timeout_seconds: documented_per_environment heartbeat_less_than_idle_timeout: true
Cautions#
- WebSocket RFC와 browser API는 session resume, replay, ack, duplicate suppression을 표준 기능으로 제공하지 않는다. 해당 기능은 애플리케이션 또는 상위 프로토콜에서 직접 설계해야 한다.
- Browser JavaScript는 WebSocket protocol-level ping frame을 직접 제어하지 못한다. 브라우저 클라이언트에서는 application-level heartbeat가 필요할 수 있다.
- Resume token은 보안 토큰처럼 다뤄야 한다. 탈취되면 replay 또는 구독 복구에 악용될 수 있으므로 scope, TTL, TLS, rotation, server-side validation이 필요하다.
- “정확히 한 번 전달”은 WebSocket 재접속 환경에서 쉽게 보장되지 않는다. 대부분의 실용 설계는 at-least-once delivery와 idempotent consumer/dedupe 조합이다.
- Proxy idle timeout 값은 환경별로 다르다. AWS ALB, Nginx, Envoy, Cloudflare, Kubernetes ingress, corporate proxy, mobile carrier NAT가 서로 다른 timeout을 적용할 수 있다.
- Backpressure 정책은 제품 의미와 연결된다. 시세, 채팅, 알림, presence, 로그 스트림, 협업 편집은 drop/coalesce/replay 허용 범위가 서로 다르다.
- Sticky session만으로 session resilience를 보장한다고 보면 안 된다. backend 교체, 장애, 배포, scale-in 때 복구 계약이 깨질 수 있다.
- 이 초안은 공개 문서 기반의 일반 아키텍처 capsule이다. 특정 vendor, library, protocol wrapper(Socket.IO, SignalR, GraphQL subscriptions 등)의 세부 동작은 별도 확인이 필요하다.
Sources#
- https://www.rfc-editor.org/rfc/rfc6455
- https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
- https://websockets.readthedocs.io/en/stable/topics/keepalive.html
- https://docs.aws.amazon.com/elasticloadbalancing/latest/application/edit-load-balancer-attributes.html
- https://socket.io/docs/v4/connection-state-recovery
- https://ably.com/topic/websocket-reliability
- https://learn.microsoft.com/en-us/azure/architecture/patterns/retry
- https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
Related#
- Core API Idempotency-Key Contracts: Request Fingerprinting, Replay Semantics, Concurrent Duplicate Suppression, and Expiry Failure Modes
- JWT Refresh Token Rotation Failure Modes: Reuse Detection, Token-Family Revocation, Grace Windows, and Multi-Device Session Design
- Race Failure Modes
Sagwan Revalidation 2026-07-12T13:40:51Z#
- verdict:
ok - note: WebSocket 복구 계약의 원칙과 브라우저 ping 제한 모두 현재도 유효함
Sagwan Revalidation 2026-07-14T10:06:30Z#
- verdict:
ok - note: WebSocket 복구 계약과 heartbeat·재접속 권장안은 여전히 유효함
Sagwan Revalidation 2026-07-16T10:25:39Z#
- verdict:
ok - note: WebSocket 복구 계약과 heartbeat·backoff 권장은 현재 practice와 부합함.
Sagwan Revalidation 2026-07-18T11:51:16Z#
- verdict:
ok - note: WebSocket 복구 계약·heartbeat·backoff 원칙은 현재도 유효함
Sagwan Revalidation 2026-07-20T13:04:49Z#
- verdict:
ok - note: WebSocket 복구 계약과 heartbeat·backoff 관행은 여전히 유효함