소켓 응용 4/5 · SCALE
서버가 두 대면
방도 둘로 갈라집니다.
각 서버는 자신에게 연결된 사용자만 압니다. 브로커로 실시간 이벤트를 퍼뜨리고, 저장소로 놓친 이벤트를 복구하는 역할 분리를 봅니다.
저장소로 복구
나쁜 결과와 안전한 결과를 비교하세요
한 채팅방을 서버 A와 B에 걸쳐 연결합니다
보는 순서① 서버 분리 선택→② A에서 발행→③ B 수신 확인
- 1분산 연결
- 2발행
- 3단절
- 4결론
room-7A ┆ 끊어진 방 ┆ BLOCAL ONLY
서버끼리 분리 시나리오입니다. 실험 시작을 눌러 네 장면을 따라가세요.
화면에서 코드로 옮길 때
이 편의 원리 세 가지
서버 A의 메모리는 서버 B에 연결된 사용자를 알지 못합니다.
온라인 서버 인스턴스에 토픽 이벤트를 fan-out합니다.
끊긴 사용자가 마지막 offset 이후 이벤트를 다시 읽게 합니다.
구현 체크리스트
최소 네 조각으로 시작하세요.
데모의 시각 요소를 실제 프로토콜 필드와 런타임 동작으로 옮기면 됩니다.
- 1공통 room ID모든 서버가 같은 규칙으로 만드는 토픽 이름
- 2Pub/Sub 브로커현재 살아 있는 서버에 빠르게 이벤트 전달
- 3내구성 저장소고유 event ID와 순서를 유지하는 로그
- 4중복 허용 소비자재생 이벤트가 겹쳐도 한 번만 화면에 반영
이 데모에 대하여
한 채팅방을 서버 두 대에 걸쳐 놓습니다
서버가 한 대일 때는 방에 있는 사람 목록이 그 서버의 메모리에 다 있습니다. 그런데 서버를 한 대 더 늘리는 순간, 각 서버는 자기에게 붙은 사람만 알게 됩니다. 이 화면은 그 상태에서 한쪽 서버에 붙은 사람이 보낸 메시지가 다른 쪽 서버에 붙은 사람에게 닿지 않는 상황을 재현하고, 그것을 잇는 두 가지 장치가 각각 무엇을 담당하는지 나눠 보여줍니다.
- 서버가 분리된 설계를 고르고 A 쪽에서 메시지를 보냅니다.
- A 에 붙은 사람은 받고 B 에 붙은 사람은 못 받는지 확인합니다.
- 메시지가 어디까지 갔다가 멈추는지 경로 표시를 봅니다.
- 브로커를 켠 설계로 바꿔 같은 메시지를 보냅니다.
- 메시지가 브로커를 거쳐 반대쪽 서버로 퍼지는 단계를 따라갑니다.
- 이번에는 한쪽 사용자를 끊어 두고 메시지를 보냅니다.
- 다시 접속했을 때 브로커만으로 복구되는지 확인합니다.
- 저장소가 관여하는 단계가 따로 있는지 봅니다.
- 브로커만 켰을 때와 저장소까지 켰을 때의 결과를 비교합니다.
- 메시지가 지나가는 지점을 순서대로 적어 봅니다.
- 두 서버가 서로를 직접 부르는 단계가 있는지 확인합니다.
두 장치의 역할이 다릅니다
01
연결 목록은 서버마다 따로입니다.
각 서버는 자기에게 연결된 사용자만 알고 있습니다. 이것은 설계 실수가 아니라 연결이라는 것이 특정 서버와 맺어지는 관계이기 때문에 생기는 당연한 결과입니다. 서버를 늘리는 순간 방이 갈라지는 문제는 규모를 키울 때 반드시 만나게 되며, 서버 한 대로 잘 돌던 코드가 두 대에서 깨지는 대표적인 이유입니다.
02
브로커는 지금 있는 사람에게 퍼뜨립니다.
브로커를 켜면 한쪽 서버가 받은 메시지를 다른 서버들에게 전달하고, 각 서버가 자기에게 붙은 사람에게 내보냅니다. 이 방식은 빠르지만 그 순간 연결돼 있는 대상에게만 닿습니다. 지금 듣고 있지 않은 사람을 위해 메시지를 들고 있어 주지는 않는다는 점이 이 장치의 성격입니다.
03
저장소는 나중에 온 사람을 담당합니다.
끊겨 있던 사용자가 다시 붙었을 때 그동안의 메시지를 받으려면 어딘가에 남아 있어야 합니다. 그 역할은 브로커가 아니라 저장소가 맡습니다. 화면에서 두 장치를 따로 켜고 끌 수 있게 만든 이유는, 하나만으로는 해결되지 않는다는 점을 직접 확인하게 하기 위해서입니다.
04
실시간 전달과 나중 복구는 다른 문제입니다.
많이 쓰는 브로커를 붙여 놓고 메시지 유실이 해결됐다고 생각하기 쉬운데, 그 둘은 다루는 시점이 다릅니다. 지금 퍼뜨리는 일과 나중에 다시 읽을 수 있게 하는 일을 같은 장치에 맡기려 하면 어느 쪽도 제대로 되지 않습니다. 이 구분이 이 화면에서 가장 중요한 내용입니다.
05
같은 방 이름을 모든 서버가 알아야 합니다.
서버들이 메시지를 주고받으려면 어느 방에 대한 이야기인지 같은 규칙으로 이름을 만들어야 합니다. 서버마다 다른 방식으로 이름을 지으면 브로커를 붙여도 서로의 메시지를 알아보지 못합니다. 화면에서는 이 부분을 이미 맞춰진 것으로 두고 있습니다.
06
다시 읽으면 겹칠 수 있습니다.
저장소에서 놓친 메시지를 다시 읽어 올 때 이미 받은 것이 섞여 들어올 수 있습니다. 그래서 받는 쪽은 같은 메시지가 두 번 와도 화면에 한 번만 반영되도록 만들어야 합니다. 메시지마다 고유한 번호가 필요한 이유이고, 그 번호는 전달을 다룬 첫 편에서 본 것과 같은 역할을 합니다.
07
경로를 위치로 표시합니다.
메시지가 지금 어느 지점에 있는지를 값으로 들고 있어서, 실패한 경우에도 어디까지 갔다가 멈췄는지 화면에 남습니다. 전달 실패를 결과로만 표시하면 무엇을 고쳐야 할지 알 수 없는데, 멈춘 지점이 곧 빠진 장치를 가리킵니다.
08
서버 한 대에서는 드러나지 않습니다.
이 문제의 성가신 점은 개발 환경에서 재현되지 않는다는 것입니다. 서버를 한 대만 띄우고 시험하면 모든 사용자가 같은 메모리를 공유하므로 아무 장치 없이도 잘 동작합니다. 실제로 서버를 늘린 뒤에야 드러나고, 그 시점에는 이미 코드가 그 전제 위에 쌓여 있습니다. 규모를 키울 계획이 있다면 처음부터 서버 간 전달을 가정하고 짜는 편이 나은 이유입니다.
09
붙는 서버를 고정하는 방법도 있습니다.
같은 방의 사용자를 항상 같은 서버로 보내면 이 문제를 피할 수 있습니다. 다만 그러면 특정 서버에 부하가 몰리고, 그 서버가 죽었을 때 그 방 전체가 영향을 받습니다. 문제를 없애는 대신 다른 제약을 얻는 선택이라, 방의 크기와 서버 구성에 따라 어느 쪽이 나은지 달라집니다. 이 화면은 고정하지 않는 쪽을 전제로 합니다.
구조를 보여주는 최소 모델입니다.
브라우저는 어떤 서버나 브로커에도 연결하지 않고 정해 둔 장면을 재생합니다. 서버 두 대와 사용자 둘은 문제가 드러나는 최소 크기이며 실제 규모와는 무관합니다. 실제 설계에서는 여기 없는 것이 많은데, 저장소에 메시지를 얼마나 오래 둘지, 사용자마다 어디까지 읽었는지를 어떻게 기록할지, 브로커 자체가 죽었을 때 어떻게 할지, 서버가 늘어나고 줄어들 때 연결을 어떻게 재배치할지, 방 하나에 사람이 아주 많을 때 퍼뜨리는 비용을 어떻게 감당할지가 모두 별도의 문제입니다. 또한 브로커와 저장소를 각각 무엇으로 고를지에 따라 보장되는 성질이 크게 다르므로, 이 화면의 두 상자를 특정 제품과 바로 대응시켜 생각하면 안 됩니다. 같은 제품이 상황에 따라 두 역할을 모두 할 수도 있고 어느 쪽도 제대로 못 할 수도 있어서, 도구 이름보다 그 도구가 무엇을 보장하는지를 확인하는 편이 안전합니다. 특히 메시지를 잃지 않는다는 설명이 어느 조건에서 성립하는지, 서버가 갑자기 죽었을 때도 유지되는지를 따로 확인해야 합니다.
실험을 마쳤다면