자체 채팅 시스템(플랫폼) 구축 및 마이그레이션 후기 | 퀘스트에 참여하세요

자체 채팅 시스템(플랫폼) 구축 및 마이그레이션 후기
인사이트/로그전체 대상

자체 채팅 시스템(플랫폼) 구축 및 마이그레이션 후기

#채팅플랫폼개발 #몽고DB #DB선택 #채팅시스템자체구축 #채팅마이그레이션 #몽고클라우드 #레디스클라우드 #도큐먼트설계 #소켓어댑터 #SocketIO

작성일 : 23.01.12 09:19

0

0

0

👉 본문을 50%이상을 읽으면 '여기까지다' 퀘스트가 완료됩니다(로그인 필수)

렛플에서는 1:1 커피챗 / 프로젝트(스터디) 그룹챗 / 오픈 채팅방 등을 자체 운영하고 있습니다.

채팅은 이제는 필수불가결한 기능으로 자리매김하고 있기 때문에, 왠만한 웹사이트에서는 이 채팅을 지원하고 있죠

이것이 챗이라고 불리기도 하고, 대화방이기도하고, DM이기도 하고 여러가지로 불리고 있지만, 중요한건 이제는 채팅 기능없으면 뭔가 허전하게 생각한다는 겁니다.

렛플에서도 프로젝트 지원시에 혹은, 다른 이유로 채팅을 자주 사용하고 있죠

채팅을 구현하는 방법은 역시나 "외부 솔루션"을 쓴다 vs "자체 구축"을 한다로 나눌수있습니다

서비스를 준비하는 단계라면, 무조건 경제성을 먼저 고민하셔야 합니다.

외부 솔루션

- 장점 : 구축 시간이 단축된다. 시스템 안정성이 어느정도 보장된다. 멀티채널을 운용하게 되면, 각 채널마다 sdk를 지원해주기 때문에 쉽게 대응이 가능하다.

- 단점 : 비용이 지속적으로 들어간다. db가 외부에 있다보니, 연계 포인트가 생각보다 많고 신기능에 대한 확장성이 떨어진다.

API 형태이다보니까, 내부가 어떻게 구성되는지 모르고, 통신속도가 느려도 대처할 수 있는 방법이 없다.

자체 구축

- 장점 : 확장성이 좋다. 내부 db와 연계하기가 좋다. 아무래도 내가 손에 쥐고 있기 때문에 속도나 성능향상에 있어서 컨트롤할 수 있는 방법이 많다.

- 단점 : 어려워보인다(ㅎㅎㅎㅎㅎㅎㅎㅎ) , 언제 이걸 다 개발하고 있어 할것도 많은데

카카오톡을 하나 만들어야 한다. 부담감이 엄청나고 잘 만들어도 본전 같은 느낌입니다.

2020년도에 렛플을 시작하면서 저희도 이런 고민을 했습니다.

렛플인 -> 프로젝트 -> 라운지로 이어지는 여러페이지의 기능과, 프로젝트내의 여러기능들로 인해 사경을 헤매고 있을 때,

채팅을 어떻게 구현해야할 지 엄청난 고민을 했습니다.

고민의 포인트는 크게는 두가지입니다.

(자체 구축) 우리가 개발을 할 수 있을까? 안정적이고 시스템 부하없게?

(외부 솔루션) 비용 부담을 견딜 수 있을까 ? 한달에 못해도 몇십만원씩 내야 하는데

결론적으로는 자체구축을 할 수 있지만, 지금은 시간상으로 빨리 프로젝트를 마무리하는 것이 좋겠다.

그렇기 때문에 외부 솔루션으로 구현하자, 다만 가격이 저렴한 것을 찾아보자입니다.

2020년 여러가지 외부 솔루션을 고려해봤었는데, 대부분의 솔루션은 1:1 고객센터같은 기능만 제공해주네요.

저희가 원하는 건 1:1 / 그룹챗/ 오픈 챗 같이 대다수가 들어올 수있는 다자간 채팅 솔루션이이었는데 말이죠

많이 찾다보니 한 두가지 솔루션으로 집약되었습니다.

(현재는 사실 더 많은 것 같습니다. 어떤 것을 써야할지 모를정도로 춘추전국시대입니다)

1. 센드버드 (SendBird)

https://www.sendbird.com

연쇄 창업가 한국인(유명하신 분)이 개발하고 운영중이신 채팅솔루션으로 , Built-In UI/UX를 제공해주는 센드버드라는 솔루션입니다.

투자도 많이 받고, 워낙 유명하신 분인지라 솔루션의 품질은 의심할 바없었고 저희가 하고자하는 기능은 모두 충족시켜줄 수 있었습니다.

다만 문제는 솔루션의 가격입니다. 인당 100원의 가격이 청구됩니다. (스타터의 기준)

처음 시작할때 이런 가격을 부담스러워하지 않을 수가 없죠

2. 트윌리오 (Twilio)

https://www.twilio.com/

미국 나스닥에 상장되어있는 기업으로 , 많은 기업에서 채택하고 있는 솔루션입니다.

UI/UX를 제공해주지 않습니다만, 인당 기준으로 약 10원 미만의 돈이 발생합니다. 거기에다가 어느정도 무료로 사용할 수 있는 사용자가 센드버드보다 많았고, 멀티플랫폼에 대한 대응도 쉬워보였습니다.

그래서 저희는 트윌리오를 선택했지만, 이내 직접 만들기 시작했습니다.

트윌리오 챗의 만족도는 초반에 상당히 높았습니다. 앱을 확장할때도 구성된 sdk로 쉽게 구현을 했기 때문입니다.

다만 만족도가 떨어지고 있습니다. 여러 api를 순차적으로 불러오기 때문에 시간이 느립니다.

그것뿐만 아니라, docs나 버전 업그레이드때문에 의미없는 시간을 버전 업하는데 쓰느라고 약간 짜증도 올라왔던 터입니다.

그래서 이럴바에는 어차피 성능에 문제도 있는데, 렛플만의 채팅 시스템을 만들자라고 결심을 하였습니다.

결심을 했다면, 사실 구현을 시작하는게 가장 현명한 방법입니다.


시작하자마자 아래와 같은 질문에 맞닥뜨리게 됩니다.


1. DB 선택과 설계는 어떻게?!

기존시스템이 관계형데이터베이스로 되어있기때문에 연장선상에서 관계형 DB(mysql)를 쓰려고했었는데 다들 추천하지않으시더라구요

솔직히 다들 왜 그렇게까지하냐라는 답변이셨어요

확실히 대세는 NoSQL인것 같습니다

채팅과 같이 인풋이 유동적인 서비스 뿐만 아니라 일반적인서비스도 NoSQL을 쓰면 편하겠다는 생각이 듭니다

그러나 데이터 중복이 일어나는 경우도 많아서 꼭 만능이라고 이야기하기도 민망합니다

서비스에 맞춰 구성하는게 가장 좋다는 원론적인 결론입니다

그렇기때문에 두개 중 하나만 쓰자라는 생각보다는 어떻게하면 혼용해서 쓸것인지가 중요합니다.

RDB의 조인을 걸어서 데이터를 추출했던 방식과 달리 몽고등은 유니크키값을 걸어서 연계 데이터를 추출하는방식이기 때문에

기존서비스와 주기적으로 기본값을 동기화해서 테이블을 이원화해서 쓰는게 좋습니다

기존 DB는 DB대로 쓰고 필요한부분을 주기적 업데이트하면서 독립적인 스키마를 구성하는게 좀 더 깔끔하게 구성할수 있는것 같습니다

예를 들면 렛플은 약 2-30개의 테이블이 있고 이는 RDB로 구성이 되어있습니다

기존 렛플디비를 쓰면 공통으로쓰는것이 있어 쉽게 구현할수있을것 같지만 RDB와 NoSQL을 혼용해서 쓰는것은 쿼리문만 복잡해집니다

아예 채팅시스템을 위해서 세개의 컬렉션을 만들고 주기적으로 동기화작업만 해주면 더 깔끔한 방식인거죠

그래서 기존DB와 무관하게 채팅은 세개의 컬렉션구조로 구성이 되었습니다

  • * 저희의 DB 구조는 다음과 같습니다. 세개의 컬렉션이 있습니다.

1) 유저(프로필) 컬렉션

유저와 관련한

  • - UID

    - 닉네임

  • - 이미지

  • - 소켓 연결 시간(채팅로그인)

    • - 현재 온오프라인여부

    • 위 내용을 도큐먼트로 관리하고 렛플RDB에서 닉네임 이미지등이 업데이트될때 같이 동기화를 합니다

2) 대화방(챗룸) 컬렉션

  • - UID

  • - 채팅유형(1:1/그룹챗/오픈챗)

    • - 현재 유효여부(true/false)

    • - 방장정보 (프로필과 연계)

    • - 게스트 정보 (프로필과 연계)

    • - 제목

    • - 메세지배열

    • - 최근메시지

    • - 최근 수정일

      • - 기타 참조정보값들

    • 들의 저장하고 있습니다

이 컬렉션도 기존 렛플이 가지고 있는 RBD 정산테이블과 같이 연동되어 업데이트됩니다

3)메시지 컬렉션

  • - 메세지별 타입(텍스트/이미지 등등)

    • - 방의 키값(어느방의 메세지인지)

      • - 누구의 메시지인지(프로필과 연계)

        • - 누가 읽었는지(배열)

          • - 유효성 여부

            • - 기타 참조정보값

    • 컬렉션을 두개로 할까 세개로할까 고민을 했습니다 왜냐하면 채팅방 컬렉션에서 메시지를 다 구겨넣어도 사실 큰 문제는 없을것같아서요

다만 세개로 분화한이유는 메세지타입이 상당히 다양해질것이고 채팅방에넣으면 계층이 상당히 복잡해질것 같다는 생각이 들더군요

개인적으로 JSON 계층이 복잡한 스타일을 좋아하지않아서요

그래서 세개의 컬렉션으로 현재운영이 되고있습니다

몽고DB를 직접 서버에 설치하시는경우도 있으신데 저희는 아틀라스에서 제공하는 클라우드DB를 사용하기로 했습니다

아직 익숙지가않아서 트래픽이 어느정도 발생할지 감이 않오기때문입니다

몽고자체는 상당히 안정된 DB라는 생각이 들고 다만 RDB를 모른채로 보시는게 더 공부하기엔 좋을것 같습니다



2. 어떤 기능을 채팅 시스템내 구현해야 하는가?

카카오톡/라인 및 왓츠앱 등 메신저 앱등이 나온지는 근 10년이 넘었습니다.

그만큼 상당히 안정적이고 다양한 기능등이 10년동안 상상할 수 없게 개발이 되었겠죠.

이제 막 채팅을 개발하더라고 하더라도, 비교가 될 수 밖에 없습니다. 비교대상이 카카오톡이 될 가능성이 높기 때문입니다.

이때부터 결정을 해야됩니다. 무엇을?????

"어디까지가 우리의 역할이고, 어디서부터 카카오톡으로 넘어가라고 말해야할지"

사실 오픈채팅이 나오기 전에는 그래도 각 서비스별 채팅 기능이 큰 의미가 있었습니다.

아이디를 교환하고 이런것들이 일회성 만남의 경우 , 좀 번거럽기도 하고 내 사생활이 노출되는 느낌을 받으니까요

그러나 오픈 채팅이후에는, 조금더 보안상으로 뛰어나진 것 같아서, 많은 분들이 오픈 채팅방으로 안내하시기도 합니다.

오픈 채팅방으로 안가고 , 모든 대화가 서비스안에서 체류하는 서비스는 "당근마켓"이 거의 유일하지 않을까 싶네요.

그렇다면 모든 채팅 서비스의 이상향은 당x마켓과 같은 형태의 서비스와 잘 융화되는 채팅 서비스일거예요

여기서의 함정은 "당x마켓"도 개발한지 10년이 다되가는 서비스라는 것입니다. ㅎㅎㅎㅎ

분하지만 초반에는 "오픈채팅"으로 넘어가게 될 수 밖에 없음을 인정해야합니다.

그리고 차츰 우리에 맞는 채팅 서비스를 기획해 나가는것이 중요할 것 같아요

렛플은 "커피챗"이라는 개념으로 이를 접근하고 있고, 운영자를 제외하고는 1포인트 이상을 지불해야지만 대화를 이어나갈 수 있습니다.

또한 대화의 진행여부에 따라 커피포인트를 환불해주거나 , 배분해주는 정책이 있습니다.

그러나 이런 컨셉 자체만으로도 사실 충분하지 않습니다.

저희가 2~3달전에 채팅에서 제공해드린 기능은 "단순 텍스트" 였습니다.

풍부한 정보를 나눌 수 없는 구조이기 때문에 , 이탈이 많이 발생할 수 밖에 없습니다.

그래서 업데이트를 통해 이탈을 줄이는 계획이 자연스럽게 수반됩니다.

컨셉뿐만 아니라 기능에서도 서비스와 잘 붙는 기능+ 안정성들이 추가되어야 합니다.

그래서 프로필카드 + 프로젝트 일감/마일스톤 공유 등 렛플의 기능들과 채팅이 좀 더 융화되는 모습으로 기획/개발을 해나가고 있습니다.

1) 초반에는 기본적인 기능(초대/방생성/알림)을 안정감있게 구현 + 텍스트위주의 대화

2) 중반은 다른 서비스에서 제공하고 있는 기능 중 정말 중요한 기능을 구현

(이미지(비디오) 전송, 상대방의 접속여부, 메시지 읽음 여부 등)

3) 후반으로 갈수록 서비스와 통합된 채팅기능 구현이 중요할 것 같습니다.

(프로필 카드, 일감/마일스톤 카드 등)


3. 서버 아키텍쳐의 구성

  1. 갑자기 서버 아키텍쳐라 말이 조금 이상할 수 있지만, 어차피 기존서버에 추가로 구현하던지, 아니면 새로운 서버에 구현을 하든지 결정해야합니다.

그래서 이를 어떻게 구성해야할지 또한 고민을 해야합니다.

이전에 솔루션 베이스로 개발을 진행했을 때는 미처 생각하지 못했던것들이 개발하다가 갑자기 생각이 나더군요

"멀티 서버"

렛플은 현재 서버가 이중화되어있기때문에, 실제 유저분들이 붙는 서버는 #1 혹은 #2 서버가 공통의 DB를 바라보는 방식이 됩니다.

소켓의 특성상, 클라이언트와 서버소켓이 커넥션을 맺는 방식이기 때문에 갑자기 클라이언트가 다른 서버로 붙어버리면 기존 소켓을 못 찾게되는 현상이 발생합니다.

그래서 스티키 세션(sticky session)이라고 해서 , 클라이언트가 특정 서버를 계속 붙을 수 있도록 라우터에서 해당 기능을 사용할 수 있습니다.

하지만 여기서 끝나지 않습니다.

클라이언트가 하나의 서버에 계속 붙더라도 다양한 클라이언트가 다양한 서버에 붙게 되는 경우, 클라이언트간의 통신을 매끄럽게 연결해줄 수 없습니다.

각 서버는 자기의 상태값을 가지고 있을 테니, 서버간의 통신을 해서 연결해줘야합니다.

그래서 소켓에서는 adapter(아답터)라는 개념이 있습니다.

https://socket.io/docs/v4/adapter/

어떤 서버에서 붙던지간에, 여러대의 서버가 db 상태를 참고하면서 서버 소켓가 알아서 자동적으로 대응하게 해주는 방식입니다.

어차피 몽고 db를 사용하려고 했었기 때문에, 구성도 어렵지 않았습니다.

제가 겪었던 가장 어려운 문제는 TEST 서버에서는 문제가 없는데, 운영서버만 올라가면 아답터가 연결이 안되는 버그가 발생하더라구요.

아무리 환경을 맞춰보거나 여러가지 해결책을 도입해도 해결이 안되더라구요

그래서 약 3~4개월을 1개의 서버로만 운영하였습니다.

이렇게 되면 , 가장 큰 문제는 서비스의 응답속도가 현격히 떨어지기 때문에 검색순위 등에 모두 악영향을 미치게 됩니다.

그래서 렛플이 지금 구글의 검색순위에서 상당히 하단으로 밀리게된 원인입니다.

해결책은 뭐냐?

1) 더 잘하는 사람이 고쳐준다.

2) 채팅 서버를 어플리케이션 서버와 분리하고 하드웨어 스펙을 높인다.

그러나 2번을 하다보니 또 다시 문제가 발생했습니다.

하드웨어의 CPU 사용량이 끊임없이 올라가서 , 채팅 서버가 응답을 하지 않거나 먹통이 된다. 몽고 DB의 Transaction이 미친듯이 올라갑니다.

기존 어플리케이션 서버에 같이 구동했을때는 전혀 문제되지 않았던 것이었는데

다른 도메인으로 채팅서버를 변경하고나서부터 문제가 발생합니다.

여러가지 개선을 통해서 해당 상황을 안정화해야 하는데요 안정화 대상으로 삼은것들은 아래와 같습니다.

  1. 1) DB 인덱싱 및 최적화

많이 사용하면서도 부하를 주는 ObjectJson Type의 데이터에 대한 인덱싱

조회시 해당 필요가 정말 필요한지를 확인 후 사용하도록 최적화.

2) Reconnection 제거 및 UI상 재연결가능하도록 수정

끊어지면 계속 붙도록 세팅해놓은 Reconnection 설정이 아무래도 서버에 계속 부하를 주는 것으로 짐작되었습니다.

같은 도메인이면 이를 계속 호출하지 않을텐데, 다른 도메인이여서 그런지 끊임없이 호출해서

서버의 CPU에 부담을 주는 것 같다는 생각이었습니다. 이러다보니 몽고DB에도 과부하를 주고 있는 것 같구요.

그래서 자동연결설정은 꺼버리니 앱의 경우 백그라운드에 가면, 커넥션이 끊어지고, 웹도 브라우저탭이 비활성화되면 안들어오면 커넥션이 끊어집니다.

끊어지는 경우를 대비해 이를 재연결할 수있도록 안내문구 및 동선을 변경합니다.

3) 불필요한 전송량을 줄인다.

채팅 참가자의 경우 , 쓸일이 있을 것 같아서, 참가자의 전체 목록을 서버에서 내려줬는데

이게 오픈채팅방의 경우 꽤나 데이터량이 됩니다.(600명이상이니까요)

이를 전체목록을 내리지 않고 , 참여자 수만 내려주는 방식 또한 메시지를 읽은 사람도, 읽은 사람의 전체 목록을 내려줬었는데,

오픈채팅방도 이에 대한 부하가 있을 것 같아, 읽은사람의 숫자만 내려주는 방식으로 변경했습니다.

4) 연산이 비싼 쿼리를 쓰지 않는다.

몽고DB에서는 "Ensure Selectivity"라고 해서 , 효율성이 좋은 쿼리가 있습니다.

a=2인 항목을 뽑는게 a!=2보다는 훨씬 선택의 폭이 좁고 좋다는 이야기입니다.

저희도 그런 비효율적인 쿼리를 사용하고 있어, 이를 선택적인 쿼리로 변경하였습니다.

https://www.mongodb.com/docs/manual/tutorial/create-queries-that-ensure-selectivity/

실제 참고한 개발후기등은 다음과 같습니다. 상세하게 잘 정리되어있습니다.

https://techblog.woowahan.com/2547/

4. 그러나 역시 좀 에러는 계속 난다. 레디스가 답인가?

독립된 서버와 렛플 어플리케이션서버가 채팅 서버를 품었을 때를 비교하면,

몽고 db 커넥션 수나, reading/writing수가 엄청나게 올라갑니다.

뭔지는 모르겠는데 이런방식으로는 유지가 안됩니다.

이를 효율화하는 작업을 했다고 말씀드렸었는데 조금 효율화되었으나 이는 해결책이 아니었습니다.

위와 개선작업을 했음에도 불구하고 여전히 높은 비용이 계속 청구되고 있었습니다.

😱😱😱😱😱😱😱😱

채팅서버로 한달에 500달러라니, 지금 렛플 어플리케이션 서비비용이 한달에 50달러가 안되는데요

10배가 넘는 비용이 채팅서버로 청구되고 있었습니다.

😱😱😱😱😱😱😱😱

그래서 서버리스에서 고정적인 비용 30달러로 나갈 수 있는 고정서버(고정 cpu/ram)로 변경하고,

어플리케이션내로 채팅서버를 품는 것으로 원복처리를 했습니다.

사실상 해결하지 못한채로 굴복하고 말았습니다.

정신을 차리고 다시 이중화를 테스트하고 있었는데 갑자기 잘 되는 겁니다.

그러나 서버리스 몽고DB 방향으로 해서 몽고 어댑터(Mongo Adapter)를 구현하면 구현이 안됩니다.

(이중화 서버간 메시지가 가지 않는 현상이 계속 발생합니다)

그냥 서버리스가 아닌 고정 비용이 나가는 DB를 방향으로 테스트하면 성공하는 거였는데요. ㅋㅋㅋㅋㅋ

사실 문제점은 어플리케이션에 있기보다는, 몽고 클라우드 서비스쪽에 있었습니다. 저희쪽 이슈가 아니었던거죠.

서버리스에서는 몽고 어댑터로는 문제가 있으니, 레디스 서버를 별도로 구축합니다.

레디스 서버를 방향으로 해서 , 레디스 어댑터를 연결해주니 문제 끝 . 메시지가 문제 없이 나가게 됩니다.

🥵🥵🥵🥵🥵🥵🥵🥵🥵🥵

아틀라스 이 xx들 , 열받는다.

🥵🥵🥵🥵🥵🥵🥵🥵🥵🥵

참고. 마이그레이션은 어떻게?!

마이그레이션은 사실 못했습니다.

기존의 채팅방을 닫는다는 언제부터 공지를 드리고, 채팅방에서 쓴 비용을 환불해드리는 방향으로 진행했습니다.

가장 트래픽이 적은 일요일 4시부터 마이그레이션 작업을 하고 연결을 하였습니다.

오픈 채팅방도 사용할 수 있게 사용자를 모두 이관해주는 작업을 진행했습니다.

마이그레이션이라고 할것도 없었네요. 그냥 시스템 닫아버리는 것이 저희가 한 마이그레이션입니다.

5. 마무리

채팅이 가장 자존감을 갏아먹은 기능 개발이었던 것 같습니다. 최장 시간을 쓴 기능일것 같아요.

한 개선과정 및 업그레이드까지 한 6개월정도 소모됬고, 개선할때마다 자존감이 뚝뚝 떨어집니다.

레디스는 한번도 해본적이 없던터라, 사실 하기 싫었는데 하다보니 레디스까지 만지게 되네요. 신기합니다.

지금은 렛플 어플리케이션이 각각의 채팅 소켓을 품고 있고, 레디스를 통해 멀티서버간의 통신을 지원하고 있습니다.

비용도 상당히 안정적입니다. 한 30달러/월에 비용이 청구될 것 같습니다.

레디스 클라우드는 공짜로 쓰고 있습니다.

하루에 1,000원 꼴이니 이정도는 낼 수 있는 범위안에 들어왔습니다.

싱가포르 서버를 쓰기때문에 약간 느릴수는 있으나 아마 체감이 되는정도로 느리진 않을 것 같습니다.

처음에는 두렵고 하기 싫었던것들을 직접 해보니 또 막상 별게 아니다라는 생각이 듭니다만,

여전히 카카오톡 라인같은 메신저들이 더 대단해보이는건 어쩔수가 없네요. ^^

다들 자신만의 온전한 채팅 시스템 가질때까지 화이팅입니다.