-
Notifications
You must be signed in to change notification settings - Fork 2
3주차 릴리즈 노트
- 단일 Nest 서버 구조로 인한 확장성 한계
- 서비스 분리 및 상태 관리 필요성
- 객체지향 원칙
- 단일책임원칙(SRP): API/WebSocket 서버 분리
- 캡슐화: 각 서버의 독립적 기능 구현
- 낮은 결합도: Stateless 아키텍처 구현
- WebSocket & Redis
- Redis를 통한 상태 관리
- Load Balancer 도입으로 확장성 확보
기존 코드 : 하나의 Nest 서버에서 API 와 WebSocket 연결 역할을 모두 수행
문제 : 유지보수 어려움, 수평 확장이 어려움, 상태 관리 어려움
개선 후 : API 서버와 WebSocket 서버 분리 후 Load-Balancer 서버 구현으로 WebSocket 서버 확장 가능하도록 변경 및 Redis 연결 상태 관리를 통해서 다양한 문제 해결
리팩터링 전 서버의 모습입니다.
위와 같은 구조로 되어있는데요.
내부 구조를 살펴보면
위와 같은 구조로 코드가 작성되어있습니다.
이를 분리하기 위해 이러한 모습을 그려보았습니다.

WebSocket 서버와 API 서버를 분리해보았습니다.
실제로 이렇게 분리하기 위해 여러가지 방법을 고민해보았는데요.
일단 동일한 코드를 실행하고 Nginx를 통해 /ws /api 가 이미 나뉘어져있기 때문에 docker-container와 nginx를 다소 수정해서 실행해보았습니다.
이렇게 되면 중복되는 코드가 많은 문제가 발생하는데요.
필요한 부분만 남아있을 수 있도록 가지치기를 해보았습니다.
이렇게 불필요한 부분을 제거하고 보니 불필요한 모듈이 존재하는 것을 확인하였고
위와 같이 간소화해보았습니다.
두 모습을 비교하면 이러한 모습이 됩니다.
이제 WebSocket 서버를 확장하는 방법에 대해서 살펴보겠습니다.
다음과 같은 로직을 고민해보았습니다.
- 만약에 이미 연결된 방이 있다면 해당 방으로 접속해야한다.
- 연결된 방이 없다면 CPU 사용량이 가장 낮은 서버의 주소로 이동해서 새로운 접속을 해야한다.
이러한 로직을 구현하기 위해 중간에 Load-Balancer 서버가 필요해졌습니다.
그림으로 그려보면 위와 같은 모습이 됩니다.
이렇게 Load-Balancer 서버를 도입하고보니 WebSocKet의 상태를 알아야했었는데요.
중간에 미들웨어(Redis)를 통해 그러한 상태 관리 로직을 구현해보았습니다.
구성은 이렇게 되어있습니다.
각각의 서버는 각각의 책임과 역할을 수행할 수 있게 분리해보았습니다.
이러한 모습이 객체지향이 추구하는 것과 밀접하다는 생각이 들었는데요. 객체지향의 여러가지 원칙 중 단일책임원칙과 캡슐화를 고려하면서 작업을 해보았습니다.
객체 간 관심사가 분리되어 유지보수가 편리했었는데요.
유지보수 이야기를 마지막으로 해보겠습니다.
저희 프로젝트는 각 노드가 WebSocket의 Room으로 되어있습니다.
이때 하위에 연결된 커넥션이 존재하는 경우 삭제했을 때 데이터가 남아있거나, 저장되어서 고아 객체(orphan object)가 됩니다.
고아 객체(orphan object)문제를 해결하기 위해 트리를 삭제 하기전 하위 트리를 모두 조회해서 연결된 커넥션이 있는지 확인을 해야했었는데요.
이때 Redis에서 상태를 저장하고 있기 때문에 Redis를 먼저 조회하고 남아있는 커넥션이 존재하는 경우 삭제를 하지 못하도록 설정하였습니다.
그렇게 될 경우 위와 같은 형태로 서버 아키텍처가 구성됩니다.
만약 기존 아키텍처였다면 SpaceService Class에 의존하고 있는 Class가 많아서 관련 Class를 모두 확인해가면서 작업을 해야했었는데 이렇게 분리가 되고나니 코드의 복잡도가 많이 감소하여서 쉽게 유지보수를 할 수 있었습니다.