우리 서비스는 웹소켓을 사용한 실시간 정보 교환이 중요하다. 서버와 클라이언트가 각각 이벤트를 발생시키고, 이에 따라 모의 면접이 진행되기 때문에 웹소켓 없이는 상상하기도 싫어싫어~
그런데 모의 면접 진행 중 연결이 끊어질 수도 있겠다는 발칙한 상상을 해보았다.
서버에서는 뭘 어떻게 해줘야 진행중이던 면접을 무사히 계속해서 끝을 볼 수 있을까?
먼저 연결이 끊어지는 경우를 정의해보았다.
네트워크 불안정으로 웹소켓 연결이 끊어지는 경우는 예방이 어렵다. CDN 등을 활용해서 경로를 안정적으로 가져가는 방법은 우리의 소규모 프로젝트에서 진행할 수 없고, 끊어진 이후 대처를 잘 하는 것이 더 중요해 보인다.
서버 리소스 부족의 경우, 웹소켓 연결 수를 제한하는 방법을 쓸 수 있다. 일정 threshold 이상의 연결이 생기면 그때 서버를 수평 확장하여 자원을 마련할 수 있다. 하지만 이 방법도 마찬가지로, 우리의 소규모 프로젝트에서는 고려 대상이 아니다.
수평 확장
동일한 어플리케이션의 인스턴스를 여러 개의 서버에 운용하고, 로드 밸런싱을 통해 요청을 적절히 분산시킨다.
서버 간 상태 공유를 위해 Redis 등의 추가적인 저장소가 필요하다.
사실상 우리 프로젝트에서는 연결이 끊기더라도 사후 대처를 잘 하는것이 가장 중요해 보인다.(사실 예방을 했다고 하더라도 중요하다)
우리 프로젝트에서 클라이언트는 많은 정보를 가지고 있지 않다. 웹소켓 특성 상, 이벤트 기반으로 모의 면접이 진행되기 때문에 그때그때 상황에 따라 필요한 정보를 주고 받을 뿐이다.
다만, 모의 면접에는 흐름(우리가 정의한 순서)이 있기 때문에 다음에 어떤 이벤트가 발생할지 어느정도 예상은 가능하다.
클라이언트가 아무 정보도 저장하지 않고 있다고 했을 때, 재연결 요청에 서버는 어떤 처리를 하고 어떤 정보를 주어야 할까?
위 두 가지만 잘 전달해주면 모의 면접을 이어서 진행하는 건 어렵지 않아보인다!
사실 우리 프로젝트의 모의면접세션 관리는 엄청난 구조를 가지고있다. 샤라웃 투 수한이형
너무나도 유연하고 완벽한 구조를 가지고 있기 때문에, 현재 상태는 빈틈없이 관리되고 있다.
다만, 이 중에 어떤 정보를 클라이언트에게 전달해야 하는가는 고민해볼 문제이다.
서버에서 발생시키는 이벤트는 room에 broadcast하는 것이 대부분이고, WebRTC연결을 위한 signaling이나 room 생성, 참여, 상태 등만 개인 queue로 전송한다.
서버에서 웹소켓 세션 연결이 끊겼다는 것을 바로 알아채기 어렵다는 것도 고민 대상이다.
웹소켓 세션 연결 상태를 모니터링 하기 위해서는 보통
등의 기술을 사용한다.
하지만 1, 2번은 일정 간격으로 클라이언트-서버 간 메시지를 주고받는 것이기 때문에 오버헤드가 발생할 수밖에 없다.
3번의 경우에는 감지가 매우 오래걸리기 때문에 우리 서비스에서 사용하기는 적합하지 않다.
우리 서비스의 경우, queue로 개인 메시지를 보내는 상황에서는 exception이 발생하기 때문에 끊김을 바로 감지할 수 있지만, 서버에서 발생시키는 대부분의 이벤트는 broatcast된다는 점을 생각했을 때, 즉시 감지는 쉽지 않은 것 같다.
서버가 굳이 연결이 끊긴 것을 감지해야 할까?
클라이언트가 즉시 재연결 요청을 보내면, 연결이 끊겼었다는 것을 파악할 수 있지 않나?
재연결 요청이 오면, 그 user에게 마지막으로 발생했던 이벤트를 동일하게 다시 발생시키면 되지 않을까?
라는 생각이 들었다.
broadcast가 되었건 queue가 되었건, 각 user에게 마지막으로 발생했던 이벤트를 기억해두었다가 어떤 user에게 재연결 요청이 들어오면 해당 user에게 보냈던 마지막 이벤트만 다시 보내는 방법이다.
물론, room 입장 시 제공하는 정보도 다시 주어야하겠다.(어떤 room을 구독해야 하는지, nickname 등)
이 방식으로는, 서버와 클라이언트에서 연결 유지 확인을 위한 오버헤드를 줄이고, 단순한 구현으로 재연결을 할 수 있을 것 같다고 생각했다.
이 방법이 가능한 이유는, 우리가 userId를 웹소켓 세션의 일부로써 관리하기 때문이다.
웹소켓 세션에 UUID나 ConnectionToken을 발급해서 관리하는 경우에는 재연결 시 이전 세션을 특정할 수 없는데, 우리의 경우 userId를 직접 사용함으로써 user와 포함된 세션을 특정할 수 있다.
user가 어떤 방에 속해있었는지와 마지막으로 발생시켰던 이벤트만 기억하면, 재연결은 어렵지 않아 보인다.
이게 다 기가막힌 InterviewSession의 구조와, JWT를 이용한 사용자 검증 덕분인 것 같다.
이 방법으로는 전통적인 연결 상태 모니터링(heartbeat, ping/pong)을 하지 않음으로써 오버헤드를 줄이고, 마지막 이벤트를 재발생시켜 최대한 효율적이고 실용적으로 재연결을 처리하도록 구현할 수 있다.
물론 모든 서비스에 다 적합한 솔루션은 아니지만, 짧은 시간 진행되는 면접에서 빠른 재연결이 가능하다는 장점이 우리 서비스에 적용하기 좋다고 생각한다.