우리는 채팅을 왜 Long Polling으로 개발했는가

JeongYong Park·2023년 10월 14일
5
post-custom-banner

채팅을 구현해야하는 프로젝트의 요구사항이 존재해 채팅 기능을 개발해야 했습니다. 채팅이라고 하면 웹 소켓이 가장 먼저 떠올랐지만 다르게 구현할 수 있는 기술이 무엇이 있을까 하고 고민했습니다. 오늘은 그 과정을 포스팅 하고자 합니다.

문제

기능 요구사항 중 채팅 기능에 대한 요구사항이 있었습니다. 그런데 단순히 "채팅 기능이 있어야 한다." 정도로 요구사항이 써져있었기 때문에 문제에 대한 상황은 저희가 정해야 했습니다.

  • 1:1 채팅? 그룹 채팅?
    • 판매자와 구매자만이 채팅할 수 있기 때문에 1:1 채팅으로 정합니다.
  • 웹 애플리케이션? 모바일까지 고려?
    • 프론트엔드가 웹 개발자이기 때문에 웹으로 한정짓습니다.
  • 중요 기능은 무엇인가?
    • 채팅방의 회원들이 각각 어디까지 읽었는지
    • 읽지 않은 채팅의 개수를 어떻게 처리할 것 인가?
    • 채팅이 왔을 때 어떻게 notification 할 것인가?
    • 이렇게 문제와 상황의 범위를 결정 짓고 채팅을 어떤 방식으로 구현할 지 결정해야 했습니다.

채팅의 구현방식

채팅을 구현하는 방식에는 여러가지가 존재합니다. 그중에서 우리 팀이 생각한 방식은 아래 세 가지 입니다.

  • Polling
  • Long polling
  • Websocket

Polling

처음으로 폴링에 대해서 알아보겠습니다. 클라이언트가 일정 주기로 서버에게 필요한 데이터를 요청하는 방식입니다. 예를 들면, 이런 거죠!

클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: 없는데?

클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: 없다니까?

클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: ...없어

클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: 오 이번에는 줄게 있어.

가장 구현하기 쉬운 방법이지만 다음과 같은 단점이 있습니다.

서버에 변경사항이 없어도 클라이언트는 계속 요청을 보내게 됩니다.
이로 인해 서버에 부담을 주게 됩니다. 또한 HTTP Connection을 맺기 위한 비용이 계속 발생합니다.
폴링 방식은 서비스가 매우 작고 구현이 굉장히 급하다면 선택할 수 있겠지만 일반적으로 더 나은 방법을 필요로 합니다.

Long polling

롱 폴링은 이름답게 클라이언트의 요청에 대해서 서버가 일정시간동안 기다렸다가 클라이언트에게 응답을 주는 방식입니다.

이 방식도 한 번 예를 들어 보겠습니다.

클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: (잠시 기다렸다가)...없어

클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: (잠시 기다렸다가)...없는데

클라이언트 : 안녕, 나 채팅 메시지 좀 줄래?
서버: (잠시 기다리는 중에 채팅 메시지가 옴)여기있어!

즉 롱폴링의 흐름은 다음과 같습니다.

  1. 클라이언트가 서버에게 요청을 보낸다.
  2. 서버는 메시지를 보낼 때까지 커넥션을 끊지 않고 기다린다.
  3. 만약 서버에 메시지가 왔다면 1번 요청에 대한 응답을 보낸다.
  4. 이를 받은 클라이언트는 다시 서버에게 요청을 보낸다.

롱폴링 방식은 클라이언트와 연결을 유지하며 변경된 데이터가 발생하거나 정해진 타임아웃 시간이 지나면 데이터를 전송하게 됩니다. 하지만 다음과 같은 단점이 있습니다.

  • 데이터가 빈번히 바뀌게 되면 폴링 방식보다 많은 요청과 응답을 하게 됩니다.
  • 또한 다수의 클라이언트에게 동시에 이벤트가 발생하게 되면 클라이언트가 요청을 동시에 보내게 되기 때문에 서버의 부담이 급증하게 됩니다.

Websocket

웹소켓은 채널을 이용해 양방향 통신을 가능하게 합니다. 기본적으로 HTTP는 단방향 통신이기 때문에 서버가 먼저 클라이언트에게 응답할 수 없습니다. 하지만 양방향 통신을 가능하게 하는게 바로 웹소켓이라는 기술입니다.

이때문에 웹소켓은 HTTP 프로토콜을 사용하지 않고 WS 프로토콜을 사용합니다. 웹소켓의 동작과정은 TCP에 의존하며 동작하게 되는데 과정을 간단하게 적어보겠습니다.

  1. Opening Handshake
  2. Data transfer
  3. Closing Handshake

Opening handshake

최초 연결 요청시 HTTP를 통해 웹 서버에게 나 너랑 웹소켓 프로토콜로 통신하고 싶어라고 요청합니다. RFC 6455문서를 보면 아래와 같은 요청을 먼저 보낸다고 합니다.

        GET /chat HTTP/1.1
        Host: server.example.com
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
        Origin: http://example.com
        Sec-WebSocket-Protocol: chat, superchat
        Sec-WebSocket-Version: 13

눈여겨 볼점은 Upgrade 헤더를 함께 전송하는 것이겠네요.

이를 받은 서버는 이에 대한 응답을 아래와 같이 보내게 됩니다.

		HTTP/1.1 101 Switching Protocols
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
        Sec-WebSocket-Protocol: chat

이렇게 핸드쉐이크가 성공하면 프로토콜이 Websokcet으로 전환됩니다.

Data transfer

앞서 설명했듯이 웹소켓은 양방향 통신을 가능하게 합니다. 따라서 서로는 메시지를 주고 받으며 통신하는데 이때 전송되는 메시지를 웹소켓 프레임이라고 합니다. (자세한 부분은 이 블로그에 나와 있습니다.)

또한 연결 수립 이후에 서버와 클라이언트는 언제든 상대방에게 ping 패킷을 보낼 수 있습니다. ping을 받은 수신측은 pong을 빠르게 응답해야 합니다. 이런 과정으로 상대방과의 연결이 지속되고 있는지 확인하는 과정을 Heartbeat라고 합니다.

Close handshake

클라이언트 혹은 서버측 어느 누구나 연결을 종료할 수 있습니다. 연결 종료를 원하는 쪽이 상대방에게 close frame을 전송합니다.

그래서 우리는?

결론부터 말하자면 저희는 롱폴링 방식을 이용해 채팅을 구현하기로 했습니다. 이유는 다음과 같습니다.

  • 1:1 채팅만을 지원
    • 여러 사용자가 동시에 이벤트가 발생하는 일이 많지 않을 것이라 생각했습니다.
  • 채팅이 메인 서비스가 아니다.
    • 저희는 중고 거래 플랫폼이기 때문에 이 애플리케이션을 이용하는 사용자가 채팅 서비스를 이용하는 시간을 많지 않을 것이라 생각했습니다.
    • 그렇기 때문에 채팅 데이터 변경이 잦게 일어나지 않을 것이라 생각했습니다.
  • 구현이 쉽다.
    • 프로젝트 기간이 3주 정도 남은 시점에서 웹 소켓을 적용하는 것은 부담이 될 수 있다고 생각했습니다.

이러한 이유들로 저희는 롱폴링 방식을 결정하게 되었습니다!

참고 자료

https://ko.javascript.info/long-polling
https://developer.mozilla.org/ko/docs/Web/API/WebSockets_API
https://datatracker.ietf.org/doc/html/rfc6455#section-1.2

profile
다음 단계를 고민하려고 노력하는 사람입니다
post-custom-banner

0개의 댓글