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을 가져오면 되기 때문에, 능동적인 알림 생성이 가능하다.