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

읽음 표시와 갱신 비용

읽음 갱신이 메시지보다 많아진다

앞 단계에서 사람마다 채널마다 마지막 읽은 번호를 두었습니다. 읽음 표시는 그 번호를 상대에게 보여주는 기능입니다. 새 저장소가 필요하지 않습니다.

그런데 이 기능은 조용히 시스템에서 가장 무거운 쓰기가 됩니다.

읽음 갱신이 메시지보다 많다

메시지는 보낼 때 한 번 생깁니다. 읽음은 보는 사람마다, 스크롤할 때마다 생깁니다. 채널에 12명이 있으면 메시지 한 건에 읽음 갱신 12건이 따라옵니다.

항목하루
메시지 쓰기2억 건
읽음 갱신그보다 몇 배

그래서 갱신 횟수를 줄이는 것이 이 기능의 핵심 설계입니다.

읽음 갱신을 스크롤마다 보내면 저장이 여섯 번이고 모아서 마지막 번호만 보내면 한 번이다 스크롤할 때마다 보낸다 44 45 47 49 50 52 저장 6번 1초 동안 모아 마지막 것만 44 45 47 49 50 52 저장 1번 번호는 늘 커지므로 중간 값은 버려도 결과가 같다 늦게 도착한 작은 번호는 무시한다

읽은 번호는 늘 커집니다. 44에서 52까지 올라가는 동안의 중간 값은 아무 의미가 없습니다. 클라이언트가 1초쯤 모아서 마지막 번호만 보내면 결과가 같습니다.

뒤로 가지 않게 만든다

재시도나 순서 뒤바뀜으로 작은 번호가 늦게 도착할 수 있습니다. 그때 그대로 덮으면 읽음 표시가 거꾸로 갑니다.

지금 저장된 번호보다 큰 값만 받는다
작은 값은 조용히 버린다

이 규칙 하나로 같은 요청을 여러 번 보내도 안전해집니다. 번호가 늘 커진다는 성질이 재시도를 안전하게 만들어 줍니다.

채널이 커지면 기능을 줄인다

읽음 표시의 비용은 채널 크기에 따라 제곱으로 늘어납니다. {REQ['max_channel']} 채널에서 각자 읽으면 갱신 {REQ['max_channel']} 건이 생기고, 그것을 전원에게 알리면 전달이 25억 건이 됩니다.

채널 크기보여주는 것
1대1읽음과 안 읽음을 정확히
평균 {REQ['avg_channel']} 수준읽은 사람 수, 또는 누가 읽었는지 목록
아주 큰 채널표시하지 않는다

기능을 줄이는 것이 최적화보다 먼저입니다. {REQ['max_channel']} 채널에서 누가 읽었는지 아는 것은 사용자에게도 쓸모가 적습니다. 요구사항을 자를 수 있는지 먼저 보고, 자를 수 없을 때 기술로 풉니다.

읽음 전파는 팬아웃을 한 번 더 만든다

읽음 표시를 실시간으로 보여주려면 상대에게도 밀어 줘야 합니다. 이것은 메시지와 별개의 팬아웃입니다. 작은 채널에서는 그대로 밀어 주고, 큰 채널에서는 밀지 않고 화면을 열 때 조회로 가져옵니다.

끄는 사람은 남의 것도 못 본다

읽음 표시를 끄는 설정을 주면, 그 사람은 남의 읽음 표시도 볼 수 없게 만듭니다. 한쪽만 보는 것이 가능하면 설정을 끄는 것이 이득이 되어 모두가 끄게 됩니다.

면접에서 이렇게 나옵니다

Q.읽음 표시를 위해 어떤 데이터를 저장하시겠습니까

사람마다 채널마다 마지막 읽은 번호 하나입니다. 메시지마다 읽은 사람 목록을 두지 않습니다.

읽은 번호는 늘 커지므로 그 값 하나로 "어디까지 읽었는지" 가 다 표현됩니다. 안 읽은 개수도 채널 최신 번호와 빼서 구합니다.

사용자, 채널, 마지막 읽은 번호

메시지마다 읽은 사람을 기록하면 메시지 수와 사람 수를 곱한 만큼 행이 생깁니다. 번호 하나면 사람과 채널의 조합만큼입니다.

흔한 실수: 읽음 표시를 위해 새 저장소를 설계하는 것. 순서를 위해 만든 번호와 미확인 계산에 쓰던 값이 이미 그 일을 합니다. 면접에서는 이미 있는 것으로 새 요구사항을 푸는 것이 설계를 이해했다는 신호가 됩니다.

Q.읽음 갱신 요청이 너무 많으면 어떻게 줄이나요

클라이언트가 1초쯤 모아서 마지막 번호만 보냅니다. 요청 자체를 줄이는 것이 유일하게 확실한 방법입니다.

읽은 번호는 단조 증가하므로 중간 값을 버려도 결과가 같습니다. 44에서 52로 올라가는 동안의 값은 아무 의미가 없습니다.

방식저장 횟수
스크롤할 때마다6번
1초 모아서 마지막 것만1번

서버 안쪽을 바꾸는 방법은 근본이 아닙니다. 큐에 넣어도 처리할 건수는 그대로입니다.

흔한 실수: 대화방을 닫을 때 한 번만 보내는 것. 요청은 최소가 되지만 상대는 그동안 계속 안 읽음으로 보고, 앱이 강제 종료되면 읽은 기록이 사라집니다. 모아 보내기는 주기가 있어야 하고 그 주기가 사용자 경험과 비용의 균형점입니다.

Q.읽음 표시가 거꾸로 가는 경우를 어떻게 막나요

지금 저장된 번호보다 큰 값만 받고 작은 값은 버립니다.

재시도나 네트워크 순서 뒤바뀜으로 오래된 갱신이 늦게 도착할 수 있습니다. 그대로 덮으면 읽음 표시가 뒤로 갑니다.

새 번호 > 저장된 번호 이면 갱신한다
그렇지 않으면 조용히 버린다

이 규칙 하나로 같은 요청을 몇 번 보내도 결과가 같아집니다. 별도의 중복 방지 장치가 필요하지 않습니다. 값이 늘 커진다는 성질이 재시도를 안전하게 만듭니다.

흔한 실수: 요청에 시각을 실어 최신 여부를 판단하는 것. 클라이언트 시계는 틀릴 수 있고 기기가 여러 대면 더 어긋납니다. 이미 순서를 정하는 번호가 있으므로 그 번호를 비교 기준으로 쓰는 편이 확실합니다.

Q.5만 명 채널에서도 누가 읽었는지 보여줄 수 있나요

보여주지 않습니다. 채널 크기에 따라 기능을 줄입니다.

5만 명 채널에서 각자 읽으면 갱신이 5만 명 건이고, 그것을 전원에게 알리면 전달이 25억 건이 됩니다. 비용이 채널 크기의 제곱으로 늘어납니다.

채널 크기보여주는 것
1대1읽음과 안 읽음을 정확히
평균 12명 수준읽은 사람 수
아주 큰 채널표시하지 않는다

큰 채널에서 누가 읽었는지는 사용자에게도 쓸모가 적습니다. 요구사항을 자를 수 있는지 먼저 보고, 자를 수 없을 때 기술로 풉니다.

흔한 실수: 전파를 큐로 넘겨 나눠 보내겠다고 답하는 것. 나눠 보내도 총량 25억 건은 그대로입니다. 큐는 몰림을 고르게 펴 줄 뿐 총량을 줄이지 못합니다.

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

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

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