본문 바로가기

개발

브라우저 최소화 상태에서 웹소켓 끊김 현상

업무 중 겪었던 문제와 해결방법 기록.

현상

회사에서 진행 중인 프로젝트에서 mqtt.js를 사용하는데, 브라우저를 최소화한 상태로 일정 시간이 지나면 웹소켓 연결이 끊기는 현상이 발견됐다. 검색해본 결과 socket.io 등 웹소켓을 사용하는 다른 라이브러리들에서도 같은 증상이 발생하는 것으로 보였다.

 

관련 링크

- https://github.com/socketio/socket.io/issues/4040

 

Disconnect after 5 minutes if tab minimized on Chromium · Issue #4040 · socketio/socket.io

Describe the bug Using a Chromium browser, when the tab through which the connection is established is minimized for more than 5 minutes, the client gets disconnected by the server due to a ping ti...

github.com

- https://solace.community/discussion/694/client-websocket-disconnects-when-browser-is-minimized-or-not-in-focus-chrome

 

Client WebSocket disconnects when browser is minimized or not in focus - Chrome

Here's a tip for anyone using websockets and chrome to establish a connection to their PubSub+ broker. The issue:

solace.community

원인

원인은 각 라이브러리에서 웹소켓 연결을 유지하는 방식과 브라우저(크롬)의 정책 때문이었다.

- https://developer.chrome.com/blog/timer-throttling-in-chrome-88/

 

Heavy throttling of chained JS timers beginning in Chrome 88 - Chrome Developers

Intensive throttling takes effect when the page has been hidden for more than 5 minutes, the page has been silent for at least 30 seconds, WebRTC is not in use, and the chain of timers is 100 or greater.

developer.chrome.com

웹소켓 서버(브로커)에서는 보통 특정 클라이언트와 일정 시간(keepalive 설정) 동안 아무 메시지를 주고받지 않으면 연결을 끊는다. 그래서 클라이언트에서는 일정 주기(서버의 keepalive 설정보다 짧은)로 ping을 보내고, 서버로부터 그에 대한 응답을 받아 연결이 유지되고 있음을 확인한다. 간단히 말해 서로 메시지가 없는 채로 시간이 지나면 연결이 끊기기 때문에 주기적으로 생사확인을 하는 것이다. mqtt.js를 예로 들면 연결을 맺을 때 다양한 옵션을 설정할 수 있는데, 그 중 keepalive가 해당 주기를 설정하는 값이다.

 

이와 별개로 브라우저에서는 최소화 상태로 일정시간이 지나면 해당 페이지가 '비활성화' 상태라고 판단하고 해당 페이지에서 실행되는 'setTimeout', 'setInterval' 등에 쓰로틀을 적용한다. 이 때 위에서 설명한 '주기적으로 ping을 보내는 것'이 라이브러리에서 'setInterval'로 구현되어 있다면? 우리가 설정한 주기대로 ping을 보내지 않게 될 것이고 그 결과 서버에서 연결을 끊는 것이다.

 

연결 끊김이 발생하는 과정을 순서대로 적어보면 다음과 같다. (keepalive 설정: 클라이언트 30초 / 서버 60초)

 

1. 브라우저창 최소화 또는 화면 꺼짐 상태 일정시간(5분 이상) 지속

2. 브라우저(크롬)에서 해당 페이지 setTimeout, setInterval 등에 쓰로틀(1분) 적용

3. 원래 30초 주기로 보내던 연결확인 ping이 쓰로틀로 인해 보내지지 않음

4. 60초 후 서버에서 연결 끊음

5. 연결이 끊겼으므로 클라이언트에서는 재연결 시도

6-1. 연결 실패 -> 5번

6-2. 연결 성공 -> 3번

 

*6단계에서 거의 대부분 재연결에 실패하는데, 연결에 성공했다는 메시지를 브로커로부터 받더라도 처리를 라이브러리에서 'setTimeout'으로 스케쥴링하고, 'setInterval'과 마찬가지로 쓰로틀이 걸려있기 때문으로 추정됨.

해결 방법

웹소켓 관련 코드가 Web Worker에서 실행되도록 했다. 웹 워커는 브라우저 메인 스레드와 별개 스레드에서 코드가 실행되며, 워커 스레드와 메인 스레드 간에 메시지를 주고받는 방식으로 작동한다.

 

결정적으로 워커 스레드에서 실행되는 코드는 위에서 언급한 쓰로틀의 영향을 받지 않는다. 또 프로젝트 구조상 모든 웹소켓 메시지가 아닌 특정한 메시지들만 메인 스레드에서 처리하면 되었기 때문에, 그런 점에서도 적절한 해결책이었던 것 같다.

 

즉 웹소켓 연결, 재연결, 주기적으로 ping 보내기 등은 워커 스레드에서 수행하되 특정 조건의 메시지만 메인 스레드로 넘겨주는 식으로 코드를 수정했다. 그 결과 브라우저 비활성화 상태에서도 웹소켓 연결은 지속적으로 유지되었고, 메인 스레드에서 처리해야 할 메시지도 줄어드는 효과가 있었다. *성능에 직접 영향을 줄만큼 애초에 양적인 차이가 나지는 않았지만, 프로젝트 규모가 커지면 유의미한 구조 변화라고 생각된다. (관련 읽을거리)

 

그렇지만 브라우저의 쓰로틀 정책의 적용을 받으면서도 웹소켓 연결이 유지되도록 한 것은 아니기 때문에 엄밀히 말해 온전한 해결책은 아니었던 것 같다. 상황에 따라 여러가지 다른 방법으로 문제를 해결할 필요가 있을 듯하다.

'개발' 카테고리의 다른 글

모바일 크로스 브라우징 경험기  (0) 2023.02.21
2022년 회고  (1) 2023.01.06
React Query에서 데이터 reference를 관리하는 방법  (0) 2022.11.30
React Query 사용할 때 주의할 점  (0) 2022.11.30
React에서의 Authorization  (0) 2022.11.30