Skip to content
isdnetworks
Go back

스트림이 살아 있는지 아는 법

(수정됨)

체결 내역을 실시간으로 밀어 주는 SSE 스트림을 만들었는데 화면이 멈춘다는 제보가 왔다. 값이 안 바뀌는 상태로 계속 남아 있었다.

Table of contents

Open Table of contents

원인 — 데이터 없음과 연결 끊김

값이 안 오는 이유는 체결이 없는 것과 연결이 끊긴 것 둘이다. 앞은 정상이고 뒤는 고장인데 EventSource 입장에서는 둘 다 아무것도 안 오는 상태로 같다.

TCP 도 데이터를 안 보내는 동안에는 끊김을 감지하지 못한다. 중간의 nginx 가 proxy_read_timeout 으로 유휴 연결을 정리해도 양쪽이 한동안 모른다.

조치 — 의미 없는 바이트를 주기적으로

해결은 변화가 없어도 주기적으로 무언가를 보내는 것이었다. 클라이언트가 무시하는 : keep-alive 주석 줄을 보내면 화면에는 아무 영향이 없고 바이트만 흘러간다.

연결이 유지되고 그것마저 안 오면 끊겼다는 판단 근거가 된다. 주기는 nginx 공식 문서의 proxy_read_timeout 기본값 60초보다 짧게 잡아야 정리되기 전에 신호가 지나간다.

판단 기준 — 클라이언트도 판단하게 한다

두 가지 방법이 있었다. 하나는 EventSource 의 자동 재연결에 맡기는 것이다. 다른 하나는 받는 쪽이 마지막 수신 시각을 기록해 일정 시간 이상 아무것도 안 오면 스스로 close() 하고 다시 붙는 것이다. 뒤쪽을 골랐다. 이유는 EventSource 는 서버가 닫았을 때만 재연결해서다. 조용히 살아 있는 반쪽 연결은 끊긴 것으로 안 본다. 대신 화면마다 타이머 코드가 늘어나는 것을 감수했다.

보내는 쪽만 신호를 만들고 받는 쪽이 그것을 판단하지 않으면 신호가 있어도 아무 일도 안 일어난다. 주석 줄도 수신으로 치므로 조용한 시간에도 판단 근거가 갱신된다.

재연결이 몰리는 자리

서버를 재시작하면 모든 클라이언트가 동시에 재연결을 시도해서 서버가 다시 죽는 문제가 생겼다. 그러면 또 동시에 재연결하는 순환이 만들어진다.

시도할수록 간격이 늘어나는 방식에 Math.random() 무작위 지연을 함께 넣었다. 간격만 늘리면 모두가 같은 간격으로 늘어나 여전히 같은 순간에 몰리므로 무작위가 필요했다.

순번과 버퍼 지표

재연결하면 그동안의 체결을 놓친다. 시세처럼 최신값만 필요한 것은 상관없지만 체결 내역은 하나도 빠지면 안 됐다. 각 메시지에 id: 로 순번을 붙이고 재연결할 때 Last-Event-ID 로 마지막 순번을 보내 그다음부터 받게 했다.

서버가 최근 몇 개를 ArrayDeque 버퍼에 갖고 있어야 이것이 성립한다. 너무 오래 끊겼으면 전체 상태를 다시 받는다. 버퍼를 넘어 전체를 다시 보낸 횟수를 Micrometer 카운터로 쟀다. 감으로 정했던 버퍼 크기를 근거를 갖고 조정할 수 있었다.

정리


Share this post on:

Previous Post
다시 확인할 때는 다시 재었다
Next Post
건너뛴 조건의 전체 목록