2023년 부스트캠프에서 방탈출 프로젝트를 진행할 때 단체 채팅방 기능을 만들기로 했다.
프로젝트 : 부스트캠프 8기 Lock Festival
채팅에 대한 상세 스펙을 정할 때 팀원 모두 완성도 있는 채팅을 구현하길 원했다.
즉, 카카오톡처럼 읽지 않은 인원 수 표시, 연속된 채팅에서 첫 번째 채팅에서만 프로필을 보여주기 등등
따라서 우리는 채팅방에서 다음과 같은 기능을 구현하기로 했다.
이번 포스팅에서는 첫 번째 상세 스펙인 각 채팅에 대해 읽지 않은 인원 수 계산하기에 대한 아이디어를 소개하고 다음 포스팅에 이어서 아이디어를 구현한 방법을 소개하겠다.
다른 기능들은 추후 다른 포스팅에서 소개하겠다.
나는 프론트엔드를 맡았기 때문에 서버와 DB 내용은 최소한으로 소개하겠다.
아래와 같이 실시간으로 사용자가 접속하면 채팅 옆의 읽지 않은 인원 수가 변경되는 것을 확인할 수 있다!
마땅한 방법이 떠오르지 않았을 때 우리는 구현하기 쉬운 가장 단순한 방법을 생각했다.
처음에 생각한 방법은 채팅 테이블에 대해 읽은 사람을 표시하는 속성을 추가하고 채팅을 읽을 때마다 읽은 채팅에 대해 전부 해당 속성을 업데이트하는 방법이다.
하지만 위의 방식은 엄청난 문제가 있는데 만약 유저 A가 1시간동안 채팅을 읽지 않았으며 1시간 동안 다른 사용자들은 100,000개의 채팅을 주고 받았다고 가정하자.
유저 A가 채팅방에 재접속라면 100,000개의 채팅 데이터에 대해 읽지 않은 인원 수를 계산해서 DB에 업데이트하고 접속하고 있는 클라이언트에게 알려줘야 한다.
logID | 채팅 내용 | 읽은 사람 |
---|---|---|
6578402a51a926a577b8cfa4 | 안녕하세요. | B,C |
6578402a51a926a577b8cfb0 | 새로왔어요! | B |
6578402a51a926a577b8cfcf | 잘 부탁해요 | B |
... 중략 | ||
6578402a51a926a577bfffff | 100,000번째 채팅! | B |
즉, 위와 같은 데이터가 있다고 가정했을 때 A가 채팅방에 들어와 100,000개의 채팅을 읽는다면 읽은 사람을 아래와 같이 업데이트해야한다.
logID | 채팅 내용 | 읽은 사람 |
---|---|---|
6578402a51a926a577b8cfa4 | 안녕하세요. | A,B,C |
6578402a51a926a577b8cfb0 | 새로왔어요! | A,B |
6578402a51a926a577b8cfcf | 잘 부탁해요 | A,B |
... 중략 | ||
6578402a51a926a577bfffff | 100,000번째 채팅! | A,B |
100,000개라면 문제가 없을 수도 있지만 만약 더 많은 채팅 데이터를 업데이트 해야하면 어떨까? 혹은 100,000개를 업데이트하는 단체 채팅방이 1,000개 라면??
우리는 DB에서 너무 많은 업데이트를 수행해야한다.
따라서 우리는 DB에서의 작업을 최소화하고 클라이언트 혹은 서버에서 계산할 수 있는 다른 방식을 생각해내야했다.
앞서 우리는 카카오톡 처럼 읽지 않은 인원 수를 표시하기로 했다. 잘 생각해보면 각각의 채팅 입장에서는 누가 읽었는지는 중요하지 않다. 단순히 몇 명이 읽지 않았는지가 중요하다.
아이디어를 소개하기 전에 로그ID에 대해 알고 가야한다. 보통 채팅 데이터는 주로 로그ID로 관리한다.
채팅 저장을 위해 데이터베이스는 Mongo DB를 사용했으며 Object ID를 log ID로 사용하였다.
Object ID는 16진수로 이루어져 있고 다음과 같이 3가지로 구성되어있는데 타임 스탬프와 생성 순서를 포함하고 있다.
즉, 17시 05분에 하나의 채팅 데이터를 생성하고 log ID로 Object ID를 부여하고 17시 15분에 하나의 채팅 데이터를 생성하면
17시 15분의 logID가 17시 05분의 logID보다 크다.
logID사이 대소 비교가 가능함을 알 수 있다.
만약 10분 간격으로 채팅을 친다면 아래와 같은 데이터가 생성될 것이다.
17:25의 로그ID가 나머지보다 큰 것을 확인할 수 있다.
logID | 채팅 내용 | 시각 |
---|---|---|
6578402a51a926a577b8cfa4 | 안녕하세요. | 17:05 |
6578402a51a926a577b8cfb0 | 새로왔어요! | 17:15 |
6578402a51a926a577b8cfcf | 잘 부탁해요 | 17:25 |
유저 A가 17시 25분의 채팅을 읽었다면 그 이전의 채팅은 반드시 읽었을 것이다.
즉 17시 05분의 채팅을 읽지 않고 17시 25분의 채팅을 읽을 수는 없다.
채팅 내용 | 시각 | 비고 |
---|---|---|
잘 부탁해요 | 17:25 | 유저 A가 얘를 읽었다면 |
새로왔어요! | 17:15 | 반드시 얘도 읽음 |
안녕하세요. | 17:05 | 반드시 얘도 읽음 |
즉, logID가 6578402a51a926a577b8cfaf인 채팅을 유저 A가 읽었다면
해당 채팅의 로그ID보다 작은 로그ID를 가지는 채팅(6578402a51a926a577b8cfaf보다 과거의 채팅)은 유저 A가 반드시 읽었을 것이다.
logID | 채팅 내용 | 비고 |
---|---|---|
6578402a51a926a577b8cfcf | 잘 부탁해요 | 얘를 읽었다면 |
6578402a51a926a577b8cfb0 | 새로왔어요! | 반드시 얘도 읽음 |
6578402a51a926a577b8cfa4 | 안녕하세요. | 반드시 얘도 읽음 |
확장하면 유저 A가 logID가 6578402a51a926a577b8cfaf인 채팅을 읽었다면 해당 logID보다 작은 값을 가지는 채팅은 반드시 읽었다.
앞서 말했듯이, 읽지 않은 인원 수를 표시할 때 각 채팅의 입장에서는 누가 읽었는지를 몰라도 된다. 단순히 몇 명이 읽지 않았는지가 필요하다.
몇 명이 읽지 않았는지 표시하기위해 각 유저가 마지막으로 읽은 채팅의 logID가 필요하다. 이 때, 현재 접속하고 있는 유저는 고려하지 않는다.
왜 각 유저가 마지막으로 읽은 채팅의 logID가 필요한지는 아래 예시를 통해 설명하겠다.
아래와 같은 채팅을 주고받았다고 가정하자.
채팅옆 주황색 숫자는 읽지 않은 인원 수를 표시한 것이다.
채팅방 참여 인원은 총 3명으로 탈출장인(나), 어그로꾼, 부산탈출러다.
우선 위의 채팅을 분석하면 21시 01분의 그때봐요 채팅까지는 모두가 읽었으므로 읽지 않은 숫자가 0이 된다. 따라서 표시하지 않는다.
그 이후 부산탈출러가 채팅방을 껐다. 따라서 그때봐요 채팅 이후로는 어그로군과 탈출장인(나)이 읽었으므로 읽지 않은 숫자가 1이 된다.
그 이후 어그로꾼이 채팅을 3개 보내고 채팅방을 껐다. 저도 14일밖에 안되는데.. 채팅부터는 탈출장인(나)만 읽었으므로 읽지 않은 숫자가 2가 된다.
이를 정리하면 아래와 같은 표가 된다.
채팅 logID(앞 6자리만 표시) | 채팅 내용 | 채팅 시각 | 읽은 사람 | 비고 |
---|---|---|---|---|
...중략 | ||||
657802 | 알겠습니다. | 21:01:02 | 탈출장인(나), 어그로꾼, 부산탈출러 | |
65781a | 그때봐요 | 21:01:07 | 탈출장인(나), 어그로꾼, 부산탈출러 | 부산탈출러 채팅방 off |
65781c | 저 그날 안될 것 같아요 | 21:01:16 | 탈출장인(나), 어그로꾼 | |
657921 | 일이 있어서... | 21:01:20 | 탈출장인(나), 어그로꾼 | |
657925 | 부산님 어디가셨징 | 21:01:25 | 탈출장인(나), 어그로꾼 | 어그로꾼 채팅방 off |
65807c | 저도 14일밖에 안되는데.. | 21:01:30 | 탈출장인(나) | |
658190 | 앗 | 21:01:42 | 탈출장인(나) | |
6582aa | 다들 어디가셨징 | 21:01:58 | 탈출장인(나) |
그럼 각 유저가 읽은 마지막 채팅의 logID는 다음과 같다.
유저 이름 | 마지막에 읽은 채팅의 logID | 비고 |
---|---|---|
부산탈출러 | 65781a | |
어그로꾼 | 657925 | |
탈출장인(나) | 아직 접속중이므로 마지막에 읽은 채팅의 logID는 없다. |
위의 자료를 활용해서 모든 채팅에 대한 읽지 않은 인원 수를 계산할 수 있다.
logID <= 65781a : 모두가 읽었으므로 읽지 않은 인원 수가 0이다.
65781a < logID <= 657925 : 탈출장인, 어그로꾼이 읽었으므로 읽지 않은 인원 수는 1(3-2)다.
657925 < logID : 탈출장인(나)만 읽었으므로 읽지 않은 인원 수는 2(3-1)다.
하지만 아직 고려하지 않은 것이 있는데 바로 유저들이 순차적으로 나가는 것이 아니라 동시에 나가는 경우다.
즉, 아래와 같이 부산탈출러의 그때봐요 채팅이후 부산탈출러와 어그로꾼이 동시에 채팅방을 나간 경우다.
이러면 한 명만 읽지 않은 채팅은 존재하지 않고 바로 2명이 읽지 않은 채팅이 된다.
다음 포스팅에서는 예외 상황 해결과 각 유저의 logID로 전체 채팅의 읽지 않은 인원 수를 구한 방법, 코드에 대해 포스팅하겠다.