이 내용은 일반적인 chat app의 시스템 디자인이라고 볼 수 있다.
<기능>
<메시지 앱의 트래픽 특성>
-> 어떤 데이터베이스 사용할지 결정하는데 중요함
유저 A가 유저 B에게 메시지를 보내는 경우
'A -> backend -> B'
A가 메시지 보내고싶을 때 백엔드로 HTTP연결해서 보낼수는 있다.
백엔드가 B한테 HTTP리퀘스트를 못한다.
그럼 A가 B에게 보내고싶을 때, 어떻게 B가 그 사실을 알아차릴 수 있을까?
해결하기 위한 몇가지 옵션이 있다.
B가 백엔드한테 새로운 메시지가 있는지 계속 물어보고, 백엔드는 바로 답을 준다.
간격은 마음대로 정할 수 있음
<단점>
대부분이 답이 "아니요"일텐데, 리퀘스트를 계속 보내야하므로 리퀘스트 수가 많아진다.
-> 백엔드 인프라스트럭처에 코스트를 높일 수 있다.
메시지 Latency
-> Polling을 1초마다 한다고 가정하면, B는 A가 보낸 메시지를 1초후에 알 수 있다.
새로운 메시지가 있는지 계속 서버에게 물어보며, 백엔드는 메시지가 있거나 타임아웃 할 때까지 리퀘스트를 잡고있는다.
예를 들어, 백엔드에게 물어봤는데 만약 메시지가 아직 없다면 백엔드가 없다고 응답하지않고 계속 기다린다.
타임아웃을 정해놓고 타임아웃이 될 때까지 기다렸는데도 아무것도 없으면 백엔드가 타임아웃이라고 리스폰스를 보내준다. 이를 받으면 다시 바로 서버에게 새로운 메시지가 있는지 물어본다.
만약 타임아웃되기전에 메시지가 오면 메시지를 리스폰스로 보내준다.
<단점>
HTTP는 리퀘스트가 있으면 리스폰스를 받고 끝인데, Web Socket은 오픈 커넥션을 유지한다.
클라이언트와 서버 사이에 Web Socket으로 Connect를 하고나면 오픈 커넥션이 유지된다.
오픈 커넥션이 유지되면 양방향 소통이 가능하다.
Web Socket으로 커넥션한다고 가정하고 시스템 디자인을 해보자.
모든것을 다 Web Socket으로 할 필요는 없다.
잠시 메시지 큐에 대해 알아보자!
메시지 큐에는 2가지 Entity가 있다.
아래 예시를 보자.
위 예시에서는 Service A가 Publisher이며, Service B가 Subscriber이다.
Service A가 지금 이 이벤트가 일어났다며 메시지 큐에게 알려줌
메시지 큐가 해당 이벤트에 Subscribe하고 있었던 모든 서비스들에게 알려줌.
"지금 Topic1에 관련된 이벤트가 새로 들어왔으니까 처리해"
<장점>
시스템에 마이크로 서비스들이 많아지다보면 Service A에서 어떤 이벤트가 일어났을 때, 거기에 Depend하는 서비스가 많을 수 있다.
예를 들어,유저가 메시지를 보냈다고 가정해보자.
새로운 메시지가 왔을 때, 이걸 데이터베이스에 저장해야하고 이 메시지를 받는 유저한테 메시지를 포워드 해줘야하고, 만약 받는 유저가 지금 로그인하고있지 않다면 푸쉬 알림을 보내줘야하고, 어떤 경우에는 이메일을 보내줘야하고 ...
여러 의존이 있을 수 있는데, 이걸 Rest API나 RPC 콜로 Synchronous하게 하다보면 그 모든 디펜던시 관련된 코드를 Service A에 넣어야한다.
그러면 Service A가 점점 복잡해지고 테스트하기도 어려워지고, 디펜던시가 많아진다. ('커플링이 많다'라고 이야기하기도 한다) -> 시스템 디자인할 때 좋지않은것이다.
이럴 때 메시지큐를 이 사이에 집어넣으면 디펜던시가 적어진다.
왜냐하면 Service A는 Service B, C, D가 존재한다는 사실을 알 필요가 전혀 없다.
그냥 새로운 유저가 메시지를 새로 보내려하면, 메시지 큐에 알려주고 Service A는 끝이다.
-> Service A는 디펜던시가 하나밖에 없는것이다.
Service B, C, D는 그 이벤트들에 Listen(Subscribe)을 하고있으면 메시지 큐가 알려준다.
-> "유저 A가 유저 B한테 메시지 보내고 싶대 너희들 할거 해"
-> 예를 들어, Service B가 DB에 저장하고,
-> Service C가 푸시 알림을 보내고,
-> Service D가 유저한테 메시지 포워드를 해주는 등...
똑같은 일을 해도 이 경우에는 Service A가 Service B, C, D에 대한 디펜던시가 없다.
따라서 디커플링을 하는데 좋다.
자, 다시 1:1 채팅으로 돌아와서 계속 알아보자.
-초록색 유저 : 로그인 한 유저
-빨간색 유저 : 로그아웃 되어있는 유저
로그인 되어있는 유저들은 Chat Server하고 각각 Web Socket으로 오픈 커넥션이 있는 것.
유저A가 유저B한테 메시지를 보내는 상황을 가정해보자.
DB를 그냥 DB로 보고 넘어왔는데, 어떤 DB를 사용하는것이 굉장히 중요하다.
DB를 잘 선택하기 위해서는 트래픽 특성이 중요하다.
<트래픽 특성>
DB를 잘 선택하기 위해 다른 정보들도 살펴보자.
따라서 Key Value Store을 사용하자!
주의해야할 점!
메시지의 Key를 만들 때, Range Scan하기 쉽게 디자인해야한다.
유저 A가 유저 A,B,C가 있는 그룹챗에서 메시지를 보낸다는 걸 가정해보자.