Julie의 Tech 블로그

주변 친구 찾기 - 웹소켓, Redis, Redis Pub/Sub 본문

Tech

주변 친구 찾기 - 웹소켓, Redis, Redis Pub/Sub

Julie's tech 2024. 2. 15. 20:24
728x90

본 글은 아래 책을 읽고 요약된 정보입니다.

https://product.kyobobook.co.kr/detail/S000211656186

  • 1장에서 보았던 근접성 서비스와는 살짝 다른 특징
    • 근처 사업장 주소는 정적인 정보이지만 주변 친구는 위치가 자주 바뀔 수 있음

개요

지원자: ‘주변에 있다’는 기준은 수치적으로 얼마나 가까운 것인지?

  • 5마일

지원자: 이를 직선거리로 가정해도 되는지?

  • OK

지원자: 얼마나 많은 사람들이 이 앱을 사용하는지? 10억명 중 10% 정도?

  • OK

지원자: 사용자의 이동 이력 보관 여부

  • Yes

지원자: 친구 관계에 있는 사용자가 10분 이상 비활성 상태면 사용자를 목록에서 사라지게?

  • Yes

지원자: GDPR, CCPA 같은 사생활 및 데이터 보호법 고민 필요?

  • 좋은 지적, 일단 생략

요구사항

  • 해당 친구까지의 거리
  • 해당 정보가 마지막으로 갱신된 시각(timestamp)
  • 친구 목록은 몇 초마다 갱신되어야함

비기능 요구사항

  • 낮은 지연시간
  • 안정성
  • 결과적 일관성: strong consistency가 아니어도 됨

개략적 규모 추정

  • ‘주변 친구’는 5마일(8km) 반경 이내 친구로 정의
  • 친구 위치 정보는 30초 주기로 갱신. 사람이 걷는 속도를 감안했을 때 주변 친구 검색 결과가 크게 달라지지 않을 것이기 때문
  • 동시 접속 사용자 수는 DAU의 10%로 추산
  • 평균적으로 한 사용자가 400명의 친구를 갖는다고 가정. 그 모두가 주변 친구 검색 기능을 활용
  • 페이지당 20명의 주변 친구를 표시, 사용자 요청이 있을 때 더 많은 친구들을 보여줌

개략적 설계안

  • API 설계
    • 위치 정보를 모든 친구에게 전송해야함. 이 때문에 클라이언트-서버 간 통신을 단순히 HTTP 프로토콜로 구성할 수는 없음
    • 사용자는 근방의 모든 활성 상태 친구의 새 위치 정보를 수신해야함
    1. P2P방식 - 가장 단순
    활성 상태인 근방 모든 친구들과 항구적 통신 상태를 유지하지만 대규모 시스템에서는 적합하지 않음. 엄청난 양의 갱신 요청과 수신을 해야함

       2. 소규모 백엔드 → 더 큰 규모로 확장 가능하도록 변경
    • 로드밸런서: RESTful API 서버와 웹소켓 서버 앞단에 위치
    • RESTful API 서버: 친구를 추가/삭제 혹은 사용자 정보를 갱신하는 등의 부가적인 작업을 처리
    • 웹소켓 서버: 친구 위치 정보 변경을 거의 실시간에 가깝게 처리. 클라이언트는 서버 한 대와 연결을 지속함
    • Redis 위치 정보 캐시: 활성 상태 사용자의 가장 최근 위치 정보를 캐시하는데 사용, TTL 필드를 활용할 것
    • 사용자 데이터베이스: 사용자 정보와 사용자의 친구 관계 정보를 저장, 관계형 혹은 NoSQL도 가능
    • 위치 이동 이력 데이터베이스: 사용자의 위치 변동 이력을 보관. 주변 친구 표시와 직접적으로 관계된 기능은 아님
    • Redis Pub/Sub 서버: 초경량 메시지 버스.
      • 웹소켓 서버에서 수신한 특정 사용자의 위치 정보 변경 event를 해당 사용자에게 배정된 pub/sub채널에 배정
      • Subscriber: 해당 사용자의 친구 각각과 연결된 웹소켓 연결 핸들러
      • Publisher: 위치 정보가 갱신되었다는 event를 발행하려는 사용자들
      1. 위치 정보가 갱신되었다는 event를 발행하는 사용자가 publisher로 등록됨
      2. 해당 publisher별로 구독하고 있는 subscriber (친구들 각각의 웹소캣 연결 핸들러)로 broadcast. 이 때 친구가 활성상태이면 거리를 재계산
      3. 재계산한 거리가 검색 반경 이내이면 갱신된 위치와 갱신 시각을 웹소켓 연결을 통해 해당 친구의 웹소켓 서버를 통해 클라이언트 앱으로 보냄
      한 사용자당 평균 400명의 친구, 그 중에서 활성상태 친구가 10%를 차지한다고 가정했으니 한 사용자의 위치가 바뀔 때마다 위치 정보는 총 40건이 발생할 것임
    • API 설계
      • 서버 API: 주기적인 위치 정보 갱신, 웹소켓 초기화 API
      • 클라이언트 API: 갱신된 친구 위치를 수신할 API, 새 친구 구독 API, 구독 해지 API
  • 두 사용자 사이의 거리가 특정 임계치보다 먼 경우에는 변경 내역을 전송하지 않음

데이터 모델

  • 위치 정보 캐시
    • Redis와 같은 캐시로 구현
    • ‘주변 친구’ 기능은 사용자의 현재 위치만을 이용하기 때문
  • 위치 이동이력 데이터베이스
    • 스키마
      User_id | Latitude | Longtitude | Timestamp
    • 막대한 쓰기 연산 부하를 감당할 수 있어야함
    • 수평적 규모 확장이 가능해야함
    • 관계형 DB를 사용할 수도 있으나 이력 내용이 담기기 때문에 한 대에 보관하기에 너무 무거울 수 있어 샤딩이 필요함. 보통 사용자ID를 기준으로 샤딩
    • 카산드라 DB를 추천

규모 확장성

  • API서버, 특히 RESTful API 서버는 stateless 서버이기 때문에 손쉽게 확장 가능
  • 웹소켓 서버인 경우는 stateful 서버라 기존 연결이 종료될 수 있게끔 처리해야함
  • 사용자 DB의 경우 데이터를 샤딩하여 수평적 규모 확장이 가능함
  • 위치 정보 캐시의 경우 활성사용자의 수와 정보 보관에 필요한 바이트 수를 추산하여 필요한 서버 수를 예측한다. 이 역시 샤딩을 손쉽게 할 수 있어 수평적 규모 확장이 가능하다
  • 레디스 펍/섭은 채널을 만드는 비용이 매우 저렴하다. 채널 하나를 유지할 때 구독자 관계를 추적하기 위하여 해시테이블과 연결리스트가 필요한데 이는 매우 소량의 메모리만을 잡아먹는다.
  • 분산 레디스 펍/섭 서버 클러스터
    • 모든 채널이 서로 독립적이기 때문에 메시지를 발행할 사용자 ID를 기준으로 펍/섭 서버들을 샤딩하면 됨. 하지만 좀 더 매끄럽게 동작하게끔 하기 위해 서비스 탐색(service discovery) 컴포넌트를 도입
      • 키-값 저장소, 즉 해시 링으로 가능한 서버 목록을 유지하게 됨
      • 값은 레디스 펍/섭 서버가 됨. 예를 들어 총 4대의 서버가 있다고 했을 때 채널의 해시값을 구한 뒤 그 해시값이 속한 범주를 담당하는 레디스 펍/섭 서버를 찾음
  • 레디스 펍/섭 서버 클러스터는 stateful 서버 클러스터일까?
    • 특정한 채널을 담당하던 펍/섭 서버를 교체하거나 해시 링에서 제거하게 되는 경우 채널은 다른 서버로 이동시켜야하고, 해당 채널의 모든 구독자에게 그 사실을 알려야함. 그래야만 기존 채널에 대한 구독 관계를 해지하고 새 서버에 마련된 대체 채널을 다시 구독할 수 있기 때문. 이 관점에서는 stateful 서버
    • Stateful 서버 클러스터는 규모를 늘리거나 줄일 때 운영 부담과 위험이 크다. 그래서 일반적으로 오버 프로비저닝을 한다.

변형

  • 임의의 친구 정보
    • 만약 친구관계가 아니지만 정보제공에 동의하여 주변 친구들을 찾을 수 있는 기능이 있다면?
      • 지오해시에 따라 구축된 펍/섭 채널 풀을 두면 됨
      • 정보제공에 동의한 사용자들에 한해 위치정보를 지오해시에 매핑하여 그 값의 채널을 구독하고있게함
      • 이 때 격자 경계 부근에 있는 사용자를 처리하기 위해 본인이 위치한 지오해시 외에 주변 지오해시 격자 채널들에도 구독함
반응형