당근 채팅 시스템은 어떻게 만들까? | 2024 당근 테크 밋업

แชร์
ฝัง
  • เผยแพร่เมื่อ 11 ม.ค. 2025

ความคิดเห็น • 32

  • @hsh-sh3xh
    @hsh-sh3xh 2 วันที่ผ่านมา

    32:28 고루틴을 사용해서 서버들과 데이터베이스에 접근하는 과정은 논블로킹으로 처리하셨나요?

  • @overthinker-tk1zn
    @overthinker-tk1zn หลายเดือนก่อน +1

    좋은 내용 감사합니다 👍 잘듣고가요 고퍼콘에 이어 좋은 내용이네요

  • @jjamlee
    @jjamlee หลายเดือนก่อน +1

    꿀 정보 공유 감사합니다.

  • @ygpark8611
    @ygpark8611 หลายเดือนก่อน +1

    잘봤습니다 감사합니다~

  • @kohahn21
    @kohahn21 หลายเดือนก่อน +6

    푸시서버에서 유저한테 푸시를 보내려면 key store에서 없단걸알아야하는데 없는이벤트발생을 위해 사용자가 앱 나갓을시 이벤트전송을 서버까지해줘야될거같은데 이때 제대로요청을 서버에서 못받아서 계속온라인상태일수도 잇는거 아닌가요?
    아니라면 유저 온라인오프라인 관리를 어떻게하는지 궁금합니다

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk หลายเดือนก่อน +12

      제가 질문을 잘 이해한지 모르겠네요.
      사용자가 앱을 종료했지만 서버에서 클라이언트의 종료 요청을 받지 못한 상황에 대해 말씀하시는것 같네요.
      먼저 푸시요청 2번째 케이스 key value store가 잘못된 경우에 해당할 것 같아요.
      추가로, 로컬메모리에 연결되어있는 세션을 주기적으로 ping pong 을 주고 받으며 pong을 보내지 않는 클라이언트는 끊어내는 로직을 돌리고 있는데요.
      이 때 클라이언트가 실제 접속중이 아닌경우에 대해 보정이 됩니다.

    • @BO-nn9up
      @BO-nn9up หลายเดือนก่อน

      푸시 알림 요청 직전 사용자가 온라인인지 오프라인인지 체크하면 해결될 일 아닌가요?

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk หลายเดือนก่อน

      ​@@BO-nn9up
      맞아요
      지금도 푸시 알림 요청 전 온라인인지 오프라인인지 먼저 체크 후
      온라인 유저에게는 웹소켓 이벤트 발송을, 오프라인 유저에게는 푸시 알림을 요청하는 방식이에요.
      그래서 해당 유저가 온라인인지 오프라인인지 유무를 판단하는 방법이 중요하다고 생각했어요.
      오프라인 유저 판단은 아래와 같이 해요.
      1. Redis에 유저가 서버에 접속한 정보가 저장 되어있지 않은 경우
      2. Redis에서는 접속한 유저라고 판단했지만 실제 서버에는 붙어있지 않은 경우
      3. Redis에 유저의 서버 접속 정보가 있지만 해당 서버 IP가 존재하지 않는 pod인 경우
      4. 실제 서버 메모리에 붙어 있지만 클라이언트가 pong을 날리지 않는 경우
      여기서 1,2의 경우에는 11:10 영상에서 설명하였구요.
      3의 경우 처음 컨슈머에서 서버로 연결을 맺을때 서버의 kubernetes pod DNS로 서버가 존재하는 pod인지 lookup으로 체크하는데요.
      이 때 "no such host" 에러가 발생한다면, 이미 내려간 서버 pod라고 판단하고 해당 유저의 서버 접속 정보를 Redis에서 제거하고
      그 유저에게 푸시요청을 보내요.
      4의 경우 클라이언트와 일정기간 ping/pong을 주고 받으며 연결이 끊겼다면 세션을 메모리에서 삭제하고
      Redis에서도 제거해서 해당 유저에게 푸시 알림을 보내도록 했어요.

  • @jjll1512
    @jjll1512 23 วันที่ผ่านมา +1

    컨슈머 같은 경우에는 queue의 메시지를 구독하고 key-value 스토어를 조회한 다음에 메시지를 전달받을 상대방이 온라인인 경우에 chat server로 메시지를 전달해달라는 요청을 보내고 그렇지 않으면 알림 서버에서 전달하여 push 하는 방식인 것인가요? 이 땐 그냥 API 호출을 수행하나요?

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk 15 วันที่ผ่านมา +1

      말씀하신 방식이 맞습니다.
      11:12 에서 미접속 유저에대한 푸시 설명을 하고있어요.
      > 이 땐 그냥 API 호출을 수행하나요?
      그냥 API 호출이 무엇을 의미하는지 잘 모르겠지만, 푸시알림을 담당하는 서비스가 있고 채팅서버는 이쪽으로 gRPC 요청을 보내는 방식이에요.
      푸시 알림 담당 서비스는 Android는 FCM, iOS는 APNS을 통해 알림을 모바일 디바이스로 보내고 있어요.
      이 때 채팅서버에서 받은 데이터를 적절히 조합해 모바일로 보내기 위한 페이로드를 OS별로 만들어서 보내야해요. 여기에는 title, subtitle, body, badge count, profile image 등이 해당돼요.

    • @jjll1512
      @jjll1512 15 วันที่ผ่านมา

      @@Andrewkwon-ec7rk 답변 정말 감사드립니다. 추가적인 질문을 드려도 될까요?
      1. 컨슈머는 분리된 서버인가요? 만약 그렇다면 채팅 서버와의 웹소켓 세션관리 등을 관리하고 요청과 세션 정보를 토대로 채팅 메시지를 처리할거 같은데, 채팅서버에서 이를 처리해도 되지 않은지 궁금합니다.
      2. 그리고 컨슈머가 채팅서버로 메시지를 전달할 때 서버 IP기반으로 Rest API를 호출하나요?
      3. 쿠버네티스 환경이라면, 동적으로 늘어나는 파드의 IP(채팅서버의 IP)를 어떻게 불러오셨는지 궁금합니다.

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk 15 วันที่ผ่านมา +1

      1. 컨슈머는 분리된 컴포넌트로 따로 pod로 띄우고 있어요. 컨슈머에서는 웹소켓 세션관리는 따로 하지 않고 유저가 어느 서버에 접속되어있는지 key value store에서 확인만 해요.
      2. 컨슈머가 채팅서버로 전달할때는 key value store에서 조회한 유저가 붙어있는 서버의 IP 기반으로 gRPC 연결을 맺고 요청을 해요.
      3. 동적으로 pod가 늘어나도 pod의 IP를 key value store에서 조회해서 알게되는 방식이기 때문에 미리 IP들을 알아내서 연결을 맺을 필요는 없어요. 처음 서버가 연결될때는 커넥션을 맺겠지만 이후 요청은 맺어진 커넥션에서 요청하는 정도일거에요.
      추가로) pod의 DNS를 보고 현재 pod가 유효한 pod인지 lookup 하는 단계를 거쳐요, 이 때 유효하지 않은 pod라고 판단이 되면 key value store에 저장된 해당 값을 삭제해서 보정해요. 만약 미리 연결이 필요한 상황이라면 headless service를 이용해 IP를 가져올수 있을거에요.

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk 15 วันที่ผ่านมา +1

      위에 대한 내용은 7:18, 9:37에서 설명하고 있어요.

    • @jjll1512
      @jjll1512 15 วันที่ผ่านมา +1

      @@Andrewkwon-ec7rk 답변 정말 감사드립니다!

  • @hsh-sh3xh
    @hsh-sh3xh หลายเดือนก่อน +1

    8:42 에서 유저B가 어느 서버에 있는지에 대한 정보도 k-value 스토어에 저장되어 있는건가요??

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk หลายเดือนก่อน +1

      네 맞아요! 그림을 자세히 보시면
      key-value store에 B.UserID : Server2 IP 라고 되어있는데요, 유저B가 연결된 서버IP를 저장하고 있는 상태를 표시한것이에요.
      유저B의 세션정보를 물으신것이라면 ChatServer 2 로컬 메모리 영역에 저장되어있고 이건 표시에서는 생략했어요.

    • @hsh-sh3xh
      @hsh-sh3xh หลายเดือนก่อน +1

      @@Andrewkwon-ec7rk 넵 감사합니다. 발표듣고 많이 배웠습니다 🙇🏻‍♂

  • @nallwhy
    @nallwhy หลายเดือนก่อน +2

    최신 메시지 순 채팅방 정렬을 서버 쪽에서 관리해야할 필요성은 어떤 것이 있나요?

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk หลายเดือนก่อน +1

      클라이언트에서 채팅방 리스트 조회 요청을 할때 최신순으로 보여주기 위함인데요, 만약 최신순으로 정렬을 하지 않고 서버에서 보내줘야 한다면 페이징 없이 해당 유저의 모든 채팅방을 다 보내줘야하는 문제가 생겨요.
      간단히 말하면 유저의 채팅방을 최신메시지 페이징 단위로 조회하기 위함이라고 보시면 됩니다.

  • @Jay-zr8kx
    @Jay-zr8kx หลายเดือนก่อน +1

    예시에서 나오는 서버 1,2 등 여러대가 나오는데 여기서 서버들은 동일한 작업을 처리하는 서버의 인스턴스들인건가요?

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk หลายเดือนก่อน +1

      넵 맞아요. 데이터베이스에 메시지 저장요청을 하고, 클라이언트와 웹소켓 연결을 맺고, 릴레이 요청을 처리하는 등의 작업을 해요

    • @Jay-zr8kx
      @Jay-zr8kx หลายเดือนก่อน

      @@Andrewkwon-ec7rk 감사합니다!

  • @가사나다
    @가사나다 หลายเดือนก่อน +2

    많은 유저를 배치로 처리할때 pub/sub이 불리한 이유는 무엇이었는지 구체적으로 설명 하실수 있을까요? 겉으로 봐서는 pub 또한 배치로 하고 각자의 subscriber가 메시지를 들고 가는 형식이면 될 것 같아서요

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk หลายเดือนก่อน +2

      좋은 질문입니다.!
      시간관계상 Pub/Sub을 사용하지 않은 이유를 짧게 말씀드렸는데요. 조금더 자세히 말씀드려볼게요.
      먼저 Pub/Sub을 사용하게되면 어떤 토픽으로 구독을 해야할지 고민해봐야하는데요.
      1. 채팅방 아이디 구독
      2. 단일토픽 구독
      3. 유저 아이디 구독
      4. 서버 인덱스 구독
      네가지 정도 생각해 볼수 있어요
      첫번째 채팅방 아이디로 구독하는 방식은 유저가 서버1로 접속할때 서버1은 해당 유저의 모든 채팅방 아이디를 가져와서 구독해야하는 상황인데 모든유저가 접속할때마다 유저의 모든 채팅방을 가져오는게 부담이고 토픽수가 너무많아지는 상황이라 성능이슈 문제도 있어서 적합하지 않다고 보았구요.
      두번째로 단일 토픽을 구독하는 방식은 메시지가 발생할때마다 모든 채팅서버가 해당 메시지를 처리해야하는 상황이 오고, 이때 접속한 유저가 있다면 해당 서버에서 처리하면되지만 미접속 유저들에게 푸시를 보낼때는 어떤 서버에서 책임지고 푸시를 보내게 할지 설정하기가 어렵다고 판단했어요.
      세번째로 유저아이디 기반으로 구독하는 방식은 예를들어 100명 채팅방에서 메시지를 발송한다면 각각의 유저 아이디를 기반으로 100번 이벤트를 발행해야 하고 구독하는 쪽에서 해당유저에게 보낼 메시지이벤트 비즈니스로직을 처리해야하는데요, 이때 배치를 사용해 효율적으로 서비스들에게 요청을 하지 못하고 단건으로 요청하게 되서 비효율적이 될거라고 판단했어요.
      이벤트를 발행하기전에 먼저 서비스에 호출해서 데이터를 먼저 패치하는 방법이 있는데 이러면 Pub 메시지가 비대해져서 비효율적이 될거라고 생각했어요.
      또한 유저아이디 기반 구독방식의 경우 미접속 유저를 체크하기가 어려워요. Pub/Sub을 사용하는 이점은 유저의 세션정보를 레디스에 따로 저장하지 않는 장점이 있는데 구독은 유저가 접속할때만 될거라 어느 유저가 미접속했는지 판단하는 단계가 또 필요하게 될거라고 보았어요.
      네번째로 statefulset으로 생성한 서버 인덱스를 구독하는 방식을 사용할 수 있을텐데요, 유저id를 서버 대수로 샤딩해서 유저 아이디만 보더라도 어느 서버에 붙을지 미리 규칙을 정한뒤 100명 채팅방에 메시지가 발송되면 샤딩기준으로 유저아이디를 묶어서 이벤트를 발행하는 방법이에요. 이 방법을 적용하려면 현재 deployment로 되어있는 서버를 statefulset으로 변경해야하고 웹소켓 서버 앞단에서 유저 아이디 기반으로 서버에 배정하는 프록시 서버를 추가 구현해야하는 상황이에요. 이건 이후에 추가 개선안으로 생각해보고 있는데 릴레이 시스템을 전반적으로 많이 변경해야하는 상황이라 부담이 되어 선택하지 않았어요.
      AS-IS 구조는 메시지큐로 이벤트를 발행하는 구조였기에 최소한의 수정으로 효율을 낼수있는 방안을 적용하여 개선했다고 보시면 됩니다.
      제가 Pub/Sub을 많이 다뤄보지는 않아서, 만약 유저아이디 기반으로 배치를 효율적으로 처리할수있는 방법이 있다면 알려주시면 감사해요~

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk หลายเดือนก่อน +1

      GPT에 물어보니 PUBSUB NUMSUB [user ids…] 을 사용하면 구독 개수를 확인할 수 있다고하네요. 메시지 요청에 위 연산을 추가하면 0인 값들을 모아서 미접속유저로 판단하고 푸시를 보낼수 있겠군요.

    • @가사나다
      @가사나다 หลายเดือนก่อน

      @@Andrewkwon-ec7rk publish를 배치성으로 할수 있지 않나 나이브하게 생각했는데, 제 기억이 틀렸었군요:)
      Stateful 이용한 구현 또한 오프라인인 유저에게 푸시를 발송하는 로직 + 지금의 key value 와 consumer를 활용한 방법에 비해 큰 이점이 없음(지금 제가 생각하기에는 그런데, 있다면 말씀 부탁드립니다) + 말씀하신대러 추가적인 프록시 서버 구현 필요로 인해 포기할수밖에 없을 것 같아요
      다만 지금의 구현에서 consumer가 scale in/out 될시에 rebalance time 때문에 메시지 발송이 상당히 밀리거나 그런 현상은 없었을까요? 만약 mq로 카프카를 사용하고 있었다면 필연적인 일이라서요

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk หลายเดือนก่อน

      @@가사나다
      "Stateful 이용한 구현 또한 오프라인인 유저에게 푸시를 발송하는 로직 + 지금의 key value 와 consumer를 활용한 방법에 비해 이점"

에 대해 먼저 말씀드릴게요,

      생각한 아이디어는 다음과 같아요.
      1. 유저가 어느 서버에 붙을지 규칙을 정한다.
      2. 규칙에 맞게 유저들이 접속할때 해당 서버에 연결시킨다.
      3. 메시지 요청시 이벤트를 받아야 할 유저들을 해당 규칙에 따라 어느 서버에 보낼지 나눈다.
      4. 서버마다 한번의 요청에 유저 아이디를 묶어서 보낸다.
      5. 릴레이 요청을 받은 서버는 유저아이디를 순회하며 접속한 유저에게 이벤트를 전달한다.
      6. 유저아이디를 순회했을때 접속하지 않은 유저들에게는 푸시를 보낸다.
      위 방법을 적용하면 가장큰 장점은 Redis를 사용하지 않고도 메시지 전달을 할 수 있게된다.
라고 생각해요.
      Redis에 유저가 접속할때마다 or 배포할때마다 세션정보를 관리하는게 부담이 되서요.
      Redis 정보를 100% 신뢰 못할때도 있구요

    • @Andrewkwon-ec7rk
      @Andrewkwon-ec7rk หลายเดือนก่อน

      ​@@가사나다 "다만 지금의 구현에서 consumer가 scale in/out 될시에 rebalance time 때문에 메시지 발송이 상당히 밀리거나 그런 현상은 없었을까요? "
      이건 히스토리를 잘 몰라 팀원분들에게 여쭤봤는데요,
      저희 경험을 공유하자면, 현재 저희 시스템에서는 컨슈머가 scale in/out될 때의 리밸런스가 메시지 발송에 큰 지연을 일으키지는 않았어요. 그 이유는 파티션 수가 많지 않고, 컨슈머와 적절히 분배되어 있어 리밸런스에 걸리는 시간이 비교적 짧기 때문이에요. 또한, 운영 중에는 장애 대응을 제외하고는 빈번한 스케일 조정이 없었기 때문에, 리밸런스로 인한 영향을 크게 체감하지 못했어요.
      평상시에는 lag이 높은 수준이 아니고 배포 시에는 전체 파드를 한꺼번에 내리지 않고 점진적으로 배포하는 방식을 사용하고 있어 lag를 줄이는 데도 도움이 되고 있어요. scale out 시에는 메시지 처리량이 늘어나 오히려 발송이 밀리는 염려를 줄일 수 있어요.
      만약 순간적으로 피크를 치는게 빈번한 환경에서 리밸런스 시간이 걱정된다면 이를 최소화할 수 있는 여러 전략을 고려해 보시는 것도 좋을 것 같아요.