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

메시지 저장과 지난 대화 조회

쓰기는 많고 조회는 최근 것만 본다

앞 단계에서 채널마다 증가하는 번호로 순서를 정했습니다. 이제 그 메시지를 어디에 어떻게 담아 두고 어떻게 다시 꺼내는지 정합니다. 저장 설계는 읽기와 쓰기 중 어느 쪽이 많은지부터 봅니다.

항목성격
쓰기하루 2억 건계속 들어온다. 수정은 거의 없다
읽기대화방을 열 때 최근 50건최근 것만 본다. 오래된 것은 거의 안 본다

쓰기가 많고 읽기는 최근 쪽에 몰립니다. 이 편중이 저장 방식을 정합니다.

무엇을 기준으로 나누고 무엇으로 정렬하나

조회는 늘 "이 채널의 최근 것부터" 입니다. 그래서 채널 번호로 나누고 그 안에서 순서 번호로 정렬해 둡니다.

나누는 기준: 채널 번호
정렬 기준: 채널 안 순서 번호 내림차순

이렇게 두면 대화방을 열 때 한 채널의 앞쪽 50건을 연속으로 읽어 오면 끝납니다. 흩어진 곳을 여러 번 찾아가지 않습니다. 순서 보장을 위해 고른 파티션 기준이 조회 성능까지 같이 해결해 준 것입니다.

위치로 자르면 같은 메시지를 두 번 본다

지난 대화를 더 불러올 때 "몇 번째부터 몇 개" 로 요청하면 문제가 생깁니다. 채팅은 보고 있는 동안에도 새 메시지가 들어오기 때문입니다.

위치로 자르는 페이징은 새 메시지가 들어오면 경계가 밀려 같은 메시지를 두 번 보게 된다 위치로 자른다. 3번째부터 2개 52 51 50 49 48 47 50, 49 를 봤다 새 메시지 53 이 들어온 뒤 53 52 51 50 49 48 50 을 또 본다 번호로 자른다. 49 보다 작은 것 2개 새 메시지가 몇 개 들어와도 경계가 밀리지 않는다

그래서 위치가 아니라 을 기준으로 자릅니다. "번호 49보다 작은 것 50개" 처럼 요청하면 새 메시지가 몇 개 들어와도 경계가 밀리지 않습니다. 마지막으로 받은 번호가 다음 요청의 기준이 됩니다.

오래된 메시지를 같은 곳에 두지 않는다

하루 {REQ['msgs_per_day']} 이면 한 달에 60억 건입니다. 그런데 조회는 최근 쪽에 몰립니다.

구간어디에
최근 며칠빠른 저장소대화방을 열 때마다 읽는다
그보다 오래된 것값싼 저장소거의 안 읽는다. 읽을 때 조금 느려도 된다

나누는 기준이 이미 채널과 번호이므로 오래된 구간만 떼어 옮기기 쉽습니다. 보관 주기를 요구사항에서 미리 정하지 않으면 저장 비용이 계속 늘어나는데 아무도 지우자고 말하지 않는 상태가 됩니다.

첨부 파일은 메시지에 담지 않는다

이미지와 파일은 따로 저장하고 메시지에는 그 위치와 크기, 이름만 담습니다. 큰 데이터가 메시지 안에 들어오면 최근 50건을 읽는 흔한 조회가 무거워집니다.

삭제는 지우는 것이 아니다

메시지를 실제로 지우면 번호에 구멍이 생기고, 상대 화면에는 이미 도착한 것을 되돌릴 수 없습니다. 삭제 표시만 남기고 내용을 비웁니다. 번호는 남겨야 앞뒤 순서와 페이징이 그대로 동작합니다.

면접에서 이렇게 나옵니다

Q.메시지를 어떤 기준으로 나누고 정렬해 저장하시겠습니까

채널 번호로 나누고, 채널 안 순서 번호 내림차순으로 정렬합니다.

조회 패턴이 늘 "이 채널의 최근 것부터" 이기 때문입니다. 이렇게 두면 대화방을 열 때 한 곳에서 앞쪽 50건을 연속으로 읽어 오면 끝납니다.

나누는 기준: 채널 번호
정렬 기준: 채널 안 순서 번호 내림차순

순서 보장을 위해 고른 파티션 기준이 조회 성능까지 함께 해결합니다. 두 요구가 같은 기준을 가리킬 때는 설계가 단순해집니다.

흔한 실수: 보낸 시각을 정렬 기준으로 잡는 것. 순서의 근거가 이미 번호인데 정렬 기준을 시각으로 두면 두 기준이 어긋나는 순간이 생깁니다. 그리고 전체 메시지를 시각으로만 정렬해 두면 특정 채널의 최근 것을 모으려고 여러 곳을 훑어야 합니다.

Q.지난 대화를 더 불러올 때 어떻게 페이징하나요

위치가 아니라 값을 기준으로 자릅니다. 마지막으로 받은 번호를 다음 요청에 실어 보냅니다.

채팅은 보고 있는 동안에도 새 메시지가 들어옵니다. "몇 번째부터 몇 개" 로 요청하면 새 메시지가 앞에 끼어 경계가 밀립니다.

방식새 메시지가 들어오면
몇 번째부터 몇 개경계가 밀려 같은 메시지를 두 번 본다
번호가 N보다 작은 것 몇 개밀리지 않는다. 결과가 그대로다

위쪽으로 거슬러 올라가는 조회라 값 기준이 자연스럽게 맞습니다.

흔한 실수: 두 번 보이는 것을 화면에서 걸러 덮는 것. 중복은 눈에 보이지만 밀린 반대 방향에서는 누락이 생기고 그것은 눈에 안 보입니다. 원인을 경계에서 고치지 않으면 사라진 메시지를 나중에 찾을 수 없습니다.

Q.하루 2억 건 이 쌓이면 저장을 어떻게 관리하나요

읽는 구간과 안 읽는 구간을 분리합니다. 하루 2억 건 이면 한 달에 60억 건이고, 조회는 최근 쪽에 몰립니다.

구간어디에근거
최근 며칠빠른 저장소대화방을 열 때마다 읽는다
그보다 오래된 것값싼 저장소거의 안 읽는다. 조금 느려도 된다

나누는 기준이 채널과 번호이므로 오래된 구간만 떼어 옮기기 쉽습니다. 함께 보관 주기도 정합니다.

흔한 실수: 보관 주기를 정하지 않고 넘어가는 것. 그러면 비용이 계속 늘어나는데 아무도 지우자고 말하지 않는 상태가 됩니다. 요구사항 단계에서 얼마나 오래 남기는지를 함께 정해 두면 저장 설계가 훨씬 단순해집니다.

Q.메시지 삭제는 어떻게 처리하나요

삭제 표시만 남기고 내용을 비웁니다. 행 자체를 지우지 않습니다.

실제로 지우면 번호에 구멍이 생깁니다. 번호는 순서의 근거이자 페이징 기준이므로 남겨 둬야 앞뒤 관계와 조회가 그대로 동작합니다.

번호는 남긴다
내용은 비운다
삭제됨 표시를 남긴다

이미 상대 화면에 도착한 메시지는 되돌릴 수 없다는 것도 함께 말합니다. 삭제는 그 뒤에 그 메시지를 다시 불러오는 사람에게만 적용됩니다.

흔한 실수: 삭제된 메시지를 조회 결과에서 아예 빼는 것. 그러면 50건을 요청했는데 47건이 오고, 클라이언트가 개수로 다음 요청을 계산하던 부분이 어긋납니다. 자리는 남기고 내용만 비우는 편이 앞뒤 로직을 건드리지 않습니다.

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

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

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