Foundry
실시간 채팅 시스템 설계
심화
핵심

접속하지 않은 사용자에게 전달하기

동시 접속 20만은 전체 사용자가 아니다

여기까지는 접속해 있는 사용자에게 밀어 주는 이야기였습니다. 그런데 동시 접속 20만 은 지금 붙어 있는 수일 뿐입니다. 전체 사용자는 그보다 훨씬 많고, 대부분은 지금 앱을 보고 있지 않습니다.

상태무엇이 필요한가
접속 중게이트웨이로 밀어 준다. 앞 단계에서 다뤘다
앱을 내려 둔 상태푸시 알림. 그리고 다시 열 때 놓친 것 따라잡기
오래 접속하지 않음알림도 쌓이지 않게. 열 때 조회로 충분하다

안 읽은 것을 저장하지 말고 계산한다

가장 먼저 떠오르는 방법이 사람마다 미확인 목록을 만드는 것입니다. 이 방법은 큰 채널에서 무너집니다.

미확인 메시지를 사람마다 저장하면 행이 폭발하고 마지막 읽은 번호만 두면 한 행으로 끝난다 사람마다 미확인 목록 사용자 1 미확인 52 사용자 2 미확인 52 사용자 3 미확인 52 그리고 49,997 행 더 메시지 1건에 5만 행 마지막 읽은 번호만 사용자 1 채널 X 47 미확인 = 최신 52 - 읽은 47 = 5건 사람마다 채널마다 한 행 안 읽은 개수는 저장하지 않고 계산한다 5만 명 채널이어도 행이 늘지 않는다

최대 5만 명 채널에 공지 하나를 올리면 미확인 행 5만 명 개가 생깁니다. 대신 사람마다 채널마다 마지막 읽은 번호 하나만 두면 안 읽은 개수는 뺄셈으로 나옵니다.

안 읽은 개수 = 채널의 최신 번호 - 내가 읽은 번호

앞 단계에서 채널마다 증가하는 번호를 만들어 둔 것이 여기서 두 번째로 쓰입니다. 순서를 위해 도입한 번호가 미확인 계산까지 해결합니다.

다시 열었을 때 따라잡기

앱을 다시 열면 클라이언트가 마지막으로 받은 번호를 보냅니다. 서버는 그 뒤의 것을 보냅니다.

문제는 오래 접속하지 않은 경우입니다. 놓친 것이 수천 건이면 그것을 다 밀어 주는 것은 낭비입니다. 화면에는 최근 50건만 보이기 때문입니다.

놓친 양어떻게
적을 때그 뒤의 것을 그대로 보낸다
많을 때개수를 제한해 보내고 나머지는 지난 대화 조회로 넘긴다

밀어 주기와 조회는 경쟁 관계가 아닙니다. 실시간은 밀어 주기가, 따라잡기는 조회가 맡는 편이 양쪽 모두 단순해집니다.

푸시 알림은 도착을 보장하지 않는다

푸시는 다른 회사의 서비스를 거쳐 갑니다. 지연될 수 있고 사라질 수도 있습니다. 그래서 푸시를 전달 수단으로 믿지 않습니다. 푸시는 "열어 보라" 는 신호이고, 실제 내용은 앱이 열린 뒤 서버에서 다시 가져옵니다.

원칙이유
푸시에 내용을 다 담지 않는다도착이 보장되지 않고 잠금 화면에 남는다
알림은 채널 단위로 묶는다활발한 채널이 알림 수십 개를 만든다
안 읽은 수는 서버가 계산해 함께 보낸다클라이언트가 세면 놓친 만큼 어긋난다

접속 중인지 보고 보내면 그 사이에 바뀐다

접속 중이면 푸시를 보내지 않는 것이 자연스러워 보입니다. 그런데 확인하는 순간과 보내는 순간 사이에 상태가 바뀝니다. 사용자가 방금 앱을 내렸다면 아무 알림도 받지 못합니다.

그래서 푸시를 조금 미뤄 둡니다. 몇 초 안에 그 사용자가 읽었다는 신호가 오면 취소하고, 오지 않으면 보냅니다. 상태를 물어보는 대신 결과를 기다리는 쪽이 확실합니다.

조용하게 만드는 것도 요구사항이다

알림을 끈 채널, 방해 금지 시간, 언급된 경우만 받기 같은 설정이 없으면 사용자는 앱을 지웁니다. 이 판단은 보내는 순간에 필요하므로 알림 설정은 자주 읽는 데이터입니다. 캐시에 두고 전달 경로에서 바로 확인합니다.

면접에서 이렇게 나옵니다

Q.5만 명 채널에서 안 읽은 메시지 수를 어떻게 관리하나요

저장하지 않고 계산합니다. 사람마다 채널마다 마지막 읽은 번호 하나만 둡니다.

안 읽은 개수 = 채널의 최신 번호 - 내가 읽은 번호

미확인 목록을 사람마다 만들면 공지 하나에 행 5만 명 개가 생깁니다. 번호 하나만 두면 채널 크기와 무관하게 사람당 한 행입니다.

방식메시지 1건의 비용
사람마다 미확인 행5만 명 개의 쓰기
마지막 읽은 번호0. 읽을 때만 갱신한다

순서를 위해 도입한 번호가 여기서 두 번째로 쓰입니다.

흔한 실수: 안 읽은 수를 100 이상은 표시하지 않게 제한하는 것으로 답하는 것. 화면 표시를 바꿀 뿐 저장과 쓰기 비용은 그대로입니다. 문제가 어디서 생기는지를 짚지 못한 답입니다.

Q.오랫동안 접속하지 않은 사용자가 앱을 열면 놓친 메시지를 어떻게 주나요

클라이언트가 마지막으로 받은 번호를 보내고, 서버가 그 뒤의 것을 보냅니다. 다만 양이 많으면 개수를 제한합니다.

놓친 것이 수천 건이어도 화면에는 최근 50건만 보입니다. 전부 밀어 주는 것은 낭비입니다.

놓친 양어떻게
적을 때그 뒤의 것을 그대로 보낸다
많을 때개수를 제한하고 나머지는 지난 대화 조회로 넘긴다

실시간은 밀어 주기가, 따라잡기는 조회가 맡습니다. 두 경로를 나누면 양쪽 모두 단순해집니다.

흔한 실수: 접속하지 않은 동안의 메시지를 사용자별 대기 목록에 쌓아 두는 것. 오래 안 오는 사용자의 목록이 무한히 자라고, 큰 채널에서는 사람 수만큼 복사됩니다. 메시지는 채널에 한 벌만 두고 어디까지 봤는지만 사람별로 두는 편이 낫습니다.

Q.푸시 알림으로 메시지를 전달해도 되나요

전달 수단으로 믿지 않습니다. 푸시는 다른 회사의 서비스를 거치므로 지연되거나 사라질 수 있습니다.

푸시는 "열어 보라" 는 신호로 쓰고, 실제 내용은 앱이 열린 뒤 서버에서 다시 가져옵니다.

원칙이유
내용을 다 담지 않는다도착이 보장되지 않고 잠금 화면에 남는다
채널 단위로 묶는다활발한 채널이 알림 수십 개를 만든다
안 읽은 수는 서버가 계산해 보낸다클라이언트가 세면 놓친 만큼 어긋난다

흔한 실수: 푸시가 도착한 것을 전달 완료로 기록하는 것. 도착 보고가 오지 않는 경우가 많아 실제보다 낮게 집계되고, 그 숫자를 보고 전달 실패를 의심하며 엉뚱한 곳을 고치게 됩니다.

Q.접속 중인 사용자에게 푸시가 중복으로 가는 것을 어떻게 막나요

푸시를 몇 초 미뤄 두고, 그 사이 읽었다는 신호가 오면 취소합니다.

접속 상태를 확인한 뒤 보내는 방식은 확인과 전달 사이에 상태가 바뀝니다. 사용자가 방금 앱을 내렸다면 접속 중으로 보여 아무 알림도 받지 못합니다.

전달을 시도한다
몇 초 안에 읽음 신호가 오면 푸시를 취소한다
오지 않으면 푸시를 보낸다

상태를 물어보는 대신 결과를 기다립니다. 확인 시점의 정보는 이미 과거이지만 읽음 신호는 사실입니다.

흔한 실수: 확인과 전달 사이 간격을 짧게 줄여 해결하려는 것. 간격을 줄이면 어긋나는 창이 좁아질 뿐 없어지지는 않습니다. 경쟁 구간을 좁히는 것과 제거하는 것은 다릅니다. 반대로 늘 푸시를 보내고 클라이언트가 지우게 하면, 잠금 화면에 잠깐 떴다 사라지는 알림이 남습니다.

먼저 스스로 답해보고 아래 답변과 견줘보세요. 막히는 부분은 문제로 확인할 수 있어요.

읽었으면 문제로 확인해보세요

실시간 채팅 시스템 설계 문제를 풀면 틀린 문제가 자동으로 노트에 쌓입니다. 가입 없이 5문제를 먼저 풀어볼 수도 있어요.