본문 바로가기
카테고리 없음

[SSE, Redis Pub/Sub, RDB] 확장성 있는 알림 기능 구현[SSE, Redis Pub/Sub, RDB] 확장성 있는 알림 기능 구현

by hbIncoding 2025. 1. 15.

0. 확장성이란 무엇인가?

  • 확장성(Scalability)이란 소프트웨어가 증가하는 부하나 수요를 처리할 수 있는 능력을 의미한다. 즉, 사용자가 늘어나거나, 데이터가 증가하거나, 새로운 기능이 추가되더라도 시스템이 성능 저하 없이 원활하게 작동할 수 있는지를 나타내는 개념이다.
  • 확장성이라고 하면 수평적 확장 혹은 수직적 확장 등 주로 DB나 서버 인스턴스에서 많이 들어봤을 개념이다.

1. 알림 기능에서 내가 정의한 확장성

  • 다음과 같은 상황에서 코드 수정이 필요없거나 최소화 하도록 하자.
    • 다중서버로 확장
      • 사용자 A가 a 서버와 연결되어 있지만, b 서버에서 A 사용자와 관련된 이벤트가 발생했을 때 알림이 잘 전송되어야 한다.
    • 다양한 형태의 알림 기능 증가
      • 매도, 매수, 서버 공지, 이벤트 당첨 등 다양한 알림 형태에 대응 가능해야 한다.
      • 혹은 그 외의 서비스에 추가될 알림들에 대해 쉽게 대응 가능해야한다.
    • 알림 내용이 바뀌었을 때 유연하게 대응 할 수 있어야 한다.
      • "페이스북을(를) 50000원에 20개 매수했습니다." 에서 어느날 페이스북이 Meta 로 상호를 변경했다고 하면 DB에 작성된 알림 내용들을 다 수정하는게 아니라, 정규화된 데이터를 사용해야한다.

2. 클라이언트와 서버 통신에서 왜 SSE를 사용했는가?

특징 폴링 (Polling) 롱 폴링 (Long Polling) SSE WebSocket
통신 방식 주기적 요청 요청 후 응답 지연 서버 → 클라이언트 (단방향) 양방향 통신
실시간성 낮음 중간 높음 매우 높음
리소스 효율성 비효율적 중간 효율적 매우 효율적
사용 사례 간단한 상태 확인 알림, 데이터 푸시 실시간 알림, 이벤트 스트림 채팅, 게임, 실시간 협업 도구
설치/설정 간단 비교적 간단 간단 복잡 (특히 서버 설정)
방화벽 호환성 우수 우수 우수 제한적 (프록시/방화벽 문제)
HTTP 지원 완전 지원 완전 지원 HTTP 기반 초기 연결 이후 WebSocket으로 변경
  • 우선 왜 폴링 롱 폴링은 제외 했는가?
    • 폴링은 주기적인 확인 요청으로 비 효율적이다.
    • 롱 폴링이 응답을 무한정 기다리지 않는다. 타임아웃 시간을 관리해줘야하며, 재요청 로직까지 고려해줘야 한다.
  • 표를 봤을 때 WebSocket이 더 효과적으로 보이는데 왜 SSE를 사용했는가?
    • 알림은 서버에서 일방적으로 주는 단방향 통신이다. 양방향 통신이 필요없는 상황에서는 SSE가 WebSocket보다 리소스를 더 효율적으로 사용한다.
    • 자동 재연결, 단간한 설정 그리고 HTTP 기반 인프라 활용 등의 장점으로 SSE가 더 적합하다.

3. Redis Pub/Sub을 이용해서 다중 서버 상황에서 사용자에게 알림 보내기

 

  • 위 그림을 보면 Redis Pub/Sub의 역할을 쉽게 알 수 있다.
  • Redis Pub/Sub은 Redis를 사용하는 모든 서버에 구독과 발행을 시행한다.
    • 'Check' 라는 이벤트를 구독하면, 누군가 'Check'라는 이벤트를 발행했을 때 해당 이벤트 내의 데이터를 읽고 처리하도록 로직을 구성하게 되는 것이다. 즉, Redis에 연결된 특정 서버한테만 이벤트를 발생시킬 수는 없다. 
    • 쉽게 생각하면 차 빼달라는 아파트 방송은 모두가 듣지만, 차주만 행동에 옮긴다. (차주가 행동에 안옮길 수 도 있다.....)
  • Redis를 활용한 처리 방식을 선택
    • 서버를 연결하는 방법에는 다양한 네트워크 토폴로지가 있다. 
      • 중앙 집중형, 버스형, 링형, 메쉬형, 트리형 등등 
    • Redis Pub/Sub은 중앙 집중형 토폴로지에 가깝다.
      • 어쨌든 여러 서버들의 공통 자원을 저장하는 어딘가가 필요한데, Redis 만한게 없긴하다.
      • 중앙 관리의 편의성은 한곳에서만 관리하면 돼서, 편리하다는 점이 있다.
      • 다만 Redis 서버의 성능과 용량이 네트워크 통신량보다 많아지만 병목이 될 수 있다.
    • 결론은 가진 인프라에서 가장 쉽게 구현할 수 있기 때문이었다.

3. 다양한 형태의 알림은 DB에 어떻게 저장하고 꺼내 써야할까?

  • 결과부터 말하자면, 위와 같이 알림 기능에 대한 ERD를 작성했다.
    • 행동을 매수, 매도, 공지 사항 등을 Enum 처럼 사용한다고 할 수 있다.
    • ex) action=1 일 때 "(param1)을 (param2)원에 (param3)개를 매수했습니다." 라고 알림이 생성된다.
    • 이때 param들은 경우에 따라서 다른 테이블의 ID와 연결될 수 있다.
  • 그렇다면 왜 param은 3개 까지 밖에 안만들었는가?
    • 프로젝트 내에서 필요한 알림의 종류를 생각했을 때 param이 들어가도 많아봤자 3개 였다.
    • 하지만 이건 확장성을 높였지만 반대로 떨어진것 이기도 하다.
      • 여기서 확장성을 좀더 높이려면 행동부터 param3까지 JSON 형태로 한 Column에 저장해는게 더 좋았을 수 도 있다.
      • 특히 프로젝트에 사용한 PostgreSQL은 JSON 타입을 다루는데 MySQL보다 뛰어나기 때문이다.
  • FaceBook이 Meta로 바뀌는 것은 어떻게 처리하는가?
    • 각각의 action에 따라서 param을 저장할 때 FaceBook이 아닌 기업 정보가 저장된 테이블의 ID로 저장해둔다.
    • 예를들어 Facebook의 ID가 10245라면 기업 정보가 저장된 테이블의 name만 바꿔준다면, 알림 문자열을 생성할 때 id를 통해 name을 가져오면 되기 때문에, 능동적인 알림 생성이 가능하다.