/////

WebSocket Session Resilience Contracts: Heartbeats, Reconnect Backoff, Resume Tokens, Proxy Idle Timeouts, and Duplicate-Delivery Failure Modes

WebSocket session resilience는 “연결이 끊기지 않게 유지”가 아니라, 끊김·반쪽 연결·프록시 idle timeout·재접속 폭주·중복 전달·재전송·느린 소비자 가 발생해도 클라이언트와 서버가 같은 의미로 복구하도록 만드는 운영 계약 이다. 핵심 계약은 다음 다섯 가지다. 1. Heartbeat / liveness : TCP 연결이 살아 보이지만 실제 경로가 죽은 half-open 상태를 탐지하기 위해 ping/pong 또는 applicat

/////

Summary#

WebSocket session resilience는 “연결이 끊기지 않게 유지”가 아니라, 끊김·반쪽 연결·프록시 idle timeout·재접속 폭주·중복 전달·재전송·느린 소비자가 발생해도 클라이언트와 서버가 같은 의미로 복구하도록 만드는 운영 계약이다.

핵심 계약은 다음 다섯 가지다.

  1. Heartbeat / liveness: TCP 연결이 살아 보이지만 실제 경로가 죽은 half-open 상태를 탐지하기 위해 ping/pong 또는 application-level heartbeat를 둔다.
  2. Reconnect backoff: 장애 시 모든 클라이언트가 동시에 재접속하지 않도록 exponential backoff와 jitter를 사용한다.
  3. Sequence / resume token: 서버가 보낸 이벤트에 단조 증가 sequence number를 붙이고, 클라이언트는 마지막으로 처리한 offset을 재접속 시 제출한다.
  4. Proxy / load balancer idle timeout: ALB, reverse proxy, NAT, browser/network middlebox의 idle timeout보다 짧은 주기로 트래픽을 발생시키거나 timeout 값을 명시적으로 맞춘다.
  5. Duplicate delivery / backpressure: 재전송 기반 복구는 at-least-once 성격을 띠므로 중복 억제와 느린 소비자 처리 정책을 별도 계약으로 정의한다.

Key Points#

  • WebSocket 기본 API만으로는 세션 복구 계약이 완성되지 않는다.
  • RFC 6455는 WebSocket 프로토콜의 framing, close, ping/pong control frame 등을 정의하지만, 애플리케이션 이벤트의 sequence, replay, resume token, 중복 제거 정책까지 표준화하지는 않는다.
  • 브라우저 WebSocket API도 메시지 송수신과 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-after
  • server_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/

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 관행은 여전히 유효함

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1