그리미
socket 을 이용한 데이터 처리 본문
환경은 다음과 같습니다
- 단일 CPU 위에 백엔드와 프론트 엔드 서버 돌아가고 있음
- 실시간으로 오는 각종 센서 데이터
- Node.js 기반 백엔드
- 프론트엔드
시간 당 수 테라바이트의 데이터가 오고 가는 환경입니다.
백엔드는 실시간 데이터를 센서로 부터 받기에 이를 효율적으로 전달하기 위해선 익숙하던 HTTP 가 아닌 WebSocket 프로토콜을 이용하기로 했습니다.
자원을 효율적 그리고 안정적으로 사용하기 위해선 센서 데이터의 경우 백엔드 서버에서 모두 받아서 처리하기 보단, 적절하게 구독하여 필요한 것이 좋다고 판단 했습니다.
이를 위해 했던 첫 번째 방법은 하나의 소켓으로 관리되던 프로젝트를 구분하기로했습니다. (아래는 예시이며, 관리자랑 유저를 위한 용도로 나누었다 보시면 됩니다)
@WebSocketGateway({
namespace : '/admin',
}
export class AdminGateway ...
@WebSocketGateway({
namespace : '/user',
}
export class UserGateway ...
두 번째로 한 것은 특정 센서 데이터만 구독하게 이벤트를 만드는 일이였습니다
@SubscribeMessage('subscribe')
async handleSubscribe(@ConnectedSocket() client: Socket, @MessageBody() data: any) {
userService.subscribe(data);
}
이벤트 구독의 경우 Map 을 통해 관리를 하기로 했습니다. 우선 당장는 DB 를 활용하기 어려운 환경이기도 하였고, 구독 해지와 같이 구독 관리를 편하게 하기 위해서 였습니다
private subscriptions: Map<string, sensorId> = new Map();
물론 구독은 해지하는 이벤트도 만들었으며, 클라이언트 단의 이슈(특정 소켓 데이터가 필요 없는 페이지로 이동한다는 등의)로 구독이 해지될 때는 모든 구독을 취소하는 unsubscribeAll 이라는 메서드도 구현 했습니다.
@SubscribeMessage('unSubscribe')
async handleSubscribe(@ConnectedSocket() client: Socket, @MessageBody() data: any) {
userService.unSubscribe(data);
}
async unsubscribeAll() {
for (const namespace of this.subscriptions.keys()) {
await this.unsubscribe(namespace);
console.log(nameapce + " was deleted");
}
}
이렇게 하니 단순히 백엔드와 프론트엔드에서 센서를 처리할 때는 CPU 사용량이 25%에 도달하였는 데, 12% 로 줄일 수 있었습니다.
CPU 를 백엔드와 프론트엔드 서버만 돌아간다면 이쯤으로 마무리 지어도 좋았겠지만... 다른 여러 서버들도 올라오기에 보다 CPU 사용량을 줄여야 했습니다.
그리하여 Json을 protobuf로 변경하기로 했습니다.
protobuf는 바이너리로 이루어진 데이터 포멧이며 역직렬화와/직렬화를 효율적으로 합니다
그러기에 텍스트 기반의 Json 보단 효율적으로 동작합니다.
message Person {
optional string user = 1;
optional int32 id = 2;
optional string act = 3;
}
이를 통해 CPU 사용량 9% 까지 줄일 수있었습니다.