채팅 서비스가 기본적으로 제공해야하는 기능:

  • 클라이언트로부터 메시지 수신
  • 메시지 수신자를 결정하고 해당 메시지를 전달
  • 수신자가 접속 (Online) 상태가 아닌 경우에는 해당 메시지를 보관해야한다.

 

채팅 서버는 상태 유지가 필요할 수 밖에 없음. 즉 클라이언트와 채팅 서버는 독립적인 네트워크 연결을 유지해야함. 그리고 채팅 서버가 살아있는 한 클라이언트는 연결을 바꾸지 않겠지.

 

채팅 시스템에서는 푸시 서비스 기능은 필요하다. 앱을 실행중이지 않는 사용자가 메시지가 도착했다는 알림을 받기 위해서.

 

실시간 서버로 채팅 서버 뿐 아니라 접속 상태 서버도 필요하다. 이는 사용자의 접속 여부를 판단해서 알림을 보낼지 말지 판단할 때 쓰임.

 

채팅 이력의 데이터는 특이하다:

  • 데이터의 양은 일단 엄청남. 페이스북 메신저나 왓츠앱을 매일 600억개의 메시지를 받는다고 함.
  • 빈번하게 사용되는 건 최근에 주고받은 메시지임. 대부분의 오래된 메시지는 사용되지 않음.
  • 하지만 메시지를 검색하는 기능은 필요하다.
  • 1:1 채팅앱의 기준으로는 읽기 쓰기가 50:50 이라고 계산하면 된다.

 

채팅 이력의 데이터를 봤을 때 Key-Value 데이터베이스가 유력하다:

  • Scale Out 이 쉽기 때문에.
  • Latency 도 낮고
  • 관계형 데이터베이스는 Long Tail 데이터는 잘 처리하지 못하는 경우가 있음.
  • 페이스북 메신저는 HBase 를 이용하고 있고, 디스코드는 Cassandra 를 이용하고 있음.

 

메시지 id 는 유니크하면서도, 순서를 보장할 수 있어야한다.

  • 관계형 데이터베이스에서는 Auto Increment Column 을 쓰면 되지만 Key-Value 스토어에서는 이게 힘들다.
  • Snowflake 를 쓴다면 전역적 64bit 순서 번호 (sequence number) 기능을 쓰면 됨.

 

그룹 채팅을 위한 메시지 테이블의 기본키는 (channel_id, message_id) 를 쓴다:

  • 무조건 Auto Increment Column 을 쓰지 않는 이유.
  • 이게 아니라면 지역적이라도 메시지 순서를 보장할 수 있으면 된다. 즉 같은 channel 에서만 메시지 id 순서만 보장하면 됨.

 

채팅 메시지 흐름 (1:1 기준: 사용자 A,B. 채팅 서버 1,2)

    1. 사용자 A 가 채팅 서버 1로 메시지 전송
    1. 채팅 서버 1 은 ID 생성기를 통해 메시지 ID 를 생성해줌.
    1. 채팅 서버 1은 해당 메시지를 메시지 동기화 큐로 전송
    1. 메시지가 키-값 저장소에 보관됨.
    1. 사용자가 접속 중인 경우에는 메시지는 사용자가 있는 채팅 서버로 전달되서, 사용자 B 는 메시지를 읽을 수 있음. 사용자가 접속 중이 아니라면 푸시 알림 메시지를 통해서 사용자한테 전달됨.
    1. 사용자 B 와 연결되어 있는 채팅 서버 2는 메세지를 사용자 B 에게 전달됨.

 

여러 단말 사이의 메시지 동기화:

  • 사용자 A 가 휴대폰만 쓰는게 아니라, 노트북을 쓸 수도 있음. 이 경우에 메시지 동기화는 어떻게 하는지에 대한 거임.
  • 디바이스마다 각각의 세션이 있다고 했을 때 모든 디바이스에서 메시지를 전달 받는건 어렵지 않음. 독립적인 세션이 있으면 되니까.
  • 문제는 이제 각 디바이스마다 읽기 시작해야하는 메시지 위치가 있을건데, 이를 결정하기가 어렵다는거임. 이건 그냥 각 디바이스마다 cur_max_message_id 를 유지하면 된다. 이 아이디 값을 바탕으로 읽어야 할 총 메시지를 가지고 오면 됨.

 

소규모 그룹 채팅에서의 메시지 처리 흐름:

  • 먼저 해당 그룹에 사용자 3명이 있다고 가정해보자. (사용자 A, B, C)
  • 사용자 A 가 보낸 메시지는 각각의 사용자에 맞는 메시지 동기화 큐로 복사된다. (그러니까 사용자 별로 메시지 동기화 큐가 있는 개념임.)
    • 만약 이렇게 안한다면 하나의 메시지를 이용해서 모든 사용자들에게 알려줘야할거다. 메시지 처리 지연 시간이 훨씬 길어질듯.
    • 이 방법의 문제는 메시지가 사용자별로 “복사” 되니까 대규모 그룹방에서는 적절하지 않음.
    • 위챗에서는 이 방법을 쓰고 그룹의 크기를 500개로 제한한다고 함.
  • 사용자마다 자신의 메시지 동기화 큐가 있는 개념이니, 여러 사용자가 보낸 메시지라도 이 큐에 차곡차곡 쌓여 있을 것.
  • 메시지 동기화 큐에서 읽고나서 각각의 사용자에게 메시지를 전달해주면 된다.

 

접속 상태 표시:

  • 사용자가 채팅 앱에 접속 중인 상태라는 건 어떻게 알까? 사용자가 로그인을 할 때 접속상태 서버 (presense server) 를 통해 사용자의 상태를 관리해주면 된다.
  • 그리고 사용자가 로그아웃하면 접속상태 서버는 online 에서 offline 으로 변경해주면 됨.
  • 문제는 접속 장애가 있을 때 어떻게 할거냐인데, “접속은 되었다 안되었다 할 수 있음” 그러니 이를 엄격하게 검사해서 상태를 변경하는 방법은 좋지 않다.
  • 대체적으로는 heartbeat event 를 바탕으로 접속 유지를 판단한다.
    • 5초동안 heartbeat 를 보내도록 하고, 30초 동안 heartbeat 가 오지 않는다면 오프라인으로 상태를 변경하도록 하는 매커니즘임.

 

상태 정보의 전송:

  • Pub-Sub 모델을 써서 알려주면 된다. 사용자 A 가 있고 이의 접속 상태를 알고 싶은 사용자 B, C, D 가 있다면 A 가 오프라인으로 될 때 B, C, D 의 Subscriber 에 상태가 오프라인으로 변경되었다고 써주는거임.
  • 이런 Pub-Sub 모델은 소규모 그룹일 때 효과적이다. 규모가 크면 어렵긴 함. 사용자 A 가 오프라인으로 변경되었는데 이를 100,000명에게 알려준다고 생각해봐라.
    • 대규모 사용자인 경우에서는 다른 방법을 해야할거임, 처음에만 상태 정보를 읽어가게 만들거나, 수동으로 조회하도록 하거나, 파티셔닝을 하거나 이런 방법들에서 고민해봐야할듯.

+ Recent posts