모든 탭에 퍼지는 실시간 이벤트, SSE 1개로 끝내는 전략

지난 flexible 세션에서는 프로덕트 그룹 플랫폼 디비전 소속의 백엔드, 프론트엔드 구성원이 함께 구축하고 운영한 실시간 이벤트 전송 서비스에 대해 공유했습니다. 이 세션은 서버부터 클라이언트까지 유기적으로 구성된 실시간 이벤트 전송 서비스의 필요, 여러 탭을 단 하나의 tcp 연결로 통합하는 방법, 메시지의 유실 가능성과 이를 타파하기 위한 전달 보장 설계, 그리고 타입 안전한 메시지 흐름을 위한 백엔드와 프론트엔드 간의 협업 등 다양한 주제를 다루었습니다.
실시간 지속 연결의 한계, 단일 파이프라인의 필요성
flex 제품의 성숙도가 증가할수록 제품 내의 여러 정보들, 이를테면 결재 문서에 대한 승인 여부, 출퇴근 및 휴가 등 구성원 근무 기록, 동료와의 원온원 노트 등 조회 중인 자원의 실시간성이 중요해지고 있었습니다. 임시적인 해결책으로서 구현한 숏폴링 기법은 불필요한 서버 부하 및 네트워크 오버헤드, 비효율적인 클라이언트 자원 사용 등의 문제를 야기했습니다. 또한 B2B 제품의 특성상 고객사 프록시 네트워크는 WebSocket이나 HTTP/2 프로토콜을 지원하지 않는 경우도 있었습니다. 우리는 이러한 문제에 대응하기 위해 하나의 Server-sent events; SSE 연결로 모든 탭에 이벤트를 전파하는 구조를 설계하기로 결정했습니다. 이 구조를 통해 클라이언트 자원 사용의 비효율성을 해소하고 실시간 전달 품질을 높이며 실제 제품을 사용하는 고객사 환경에서의 높은 호환성을 달성하는 것을 목표로 했습니다.
Service Worker API가 만든 단일 파이프라인
클라이언트측 핵심 기술은 Service Worker API입니다. 우리는 Service Worker를 사용자 인터페이스 어플리케이션과 SSE 서버의 사이에 위치하는 프록시 역할로 활용하여, 브라우저의 여러 탭이 flex 어플리케이션을 사용하더라도 하나의 tcp 연결만 사용하도록 설계했습니다. 서버로부터 수신된 메시지는 ServiceWorkerGlobalScope의 Clients#matchAll 메서드로 Service Worker가 제어하는 모든 클라이언트를 향해 그대로 전파됩니다. 유사한 역할이 가능한 SharedWorker 대비 클라이언트의 생애주기를 직접 관리하지 않을 수 있다는 점, 클라이언트 탐색 메소드가 빌트인으로 제공된다는 점, Service Worker가 백그라운드에서도 동작 가능하다는 점 등이 선택의 주요한 이유였습니다.
안정성과 신뢰성을 위한 설계 및 트레이드오프
우리는 단순히 SSE 연결의 수를 줄이는 것을 넘어 서비스의 안정성과 확장성을 확보하는 데 집중했습니다.
- 권한 및 인증의 실시간성 확보: 메시지 전송 시점에 실시간으로 권한을 확인하는 로직을 적용하여 연결 시간을 기존 1분에서 5분으로 연장할 수 있도록 했습니다. 이를 통해 권한 업데이트 지연을 없애고 보안을 강화할 수 있었습니다.
- 복구 및 안정화 로직: Service Worker에 네트워크 연결 상태를 확인하는 하트비트 감지기를 두고, 메시지를 수신하지 못하는 것으로 판단 시 재연결을 시도, 지속적 실패 시에는 지수 백오프 재시도를 적용하여 네트워크 불안정 상황에서도 안정적인 연결을 유지합니다. exactly-once 전달 보장, 고가용성, 메시지 순서 보장, 그리고 권한 확인을 최소 요구 사항으로 정의하고 이를 충족하는 시스템을 구축했습니다.
- 기술적 트레이드오프: 우리는 양방향 통신이 가능한 WebSocket 대신 단방향 SSE를 선택했습니다. 이는 서버 측 푸시에 집중하고, SSE 프로토콜에 내장된 재연결 사양을 최대한 활용하여 네트워크 호환성 커버리지를 높이고 운영 비용을 절감하려는 실용적인 전략적 판단이었습니다.
재연결에도 유실 없는 이벤트 보장
연결 지연이나 일시적인 네트워크 불안정 상황에서도 이벤트 유실이 발생하지 않도록 설계했습니다. SSE 프로토콜의 id 속성을 클라이언트측 서비스에서 마지막 이벤트 식별자로 저장하여 주제 구독 시점에 서버에 전달, 클라이언트와 서버 사이에 유실된 메시지를 관측하고 다시 전송할 수 있도록 했습니다. 덕분에 사용자는 탭을 새로 열거나 네트워크가 불안정한 상황에서도 동일한 이벤트 스트림을 놓치지 않고 이어갈 수 있으며, 이는 실시간성뿐만 아니라 서비스 신뢰성을 높이는 핵심 요소가 되었습니다.
스키마의 약속으로 시작하는 타입 안전한 이벤트 흐름
이러한 서버측 메시지 푸시 서비스의 데이터 흐름은 견고한 스키마 약속을 전제로 합니다. 서버와 클라이언트 엔지니어는 모든 이벤트를 CloudEvents 인터페이스를 구현한 Avro 스키마로 정의한 뒤 AWS Glue Schema registry에 등록했습니다. 클라이언트측 어플리케이션에서는 자동 생성된 TypeScript 인터페이스를 활용할 수 있었고 예측 가능한 형태의 타입 안전한 이벤트를 다루게 되었습니다. 이로 인해 서비스 전반의 유지보수성과 개발 효율성 또한 크게 향상되었습니다. 서버에서는 NATS와 같은 메시지 브로커를 통해 이벤트 스트림을 발행하고 타입 안전성을 보장하는 구조를 구축했습니다.
마치며
실시간 이벤트 전송 서비스는 안정성과 확장성을 담보해야 합니다. 플렉스팀은 Service Worker API, NATS, 중앙 인가 시스템 등을 활용해 단일 SSE 연결, 메시지 전달 보장, 권한 검증 등 실시간 이벤트 전송 서비스의 제공을 위한 제반 요구 사항을 식별하고 이를 충족하는 시스템을 구축했습니다. 앞으로도 flexible에서는 다양한 문제 해결 여정을 계속 공유해 나가도록 하겠습니다.
지금 플렉스팀과 함께해주세요!
