[Nest.js] socket 통신에서 redis adapter를 적용해 트래픽 개선

김지엽·2024년 1월 18일
0
post-thumbnail

1. 개요

프로젝트 1차 기능 구현 완료 후에 트래픽 이슈에 대해 고민을 해보았다. 현재 소켓 통신으로 그룹에 대한 정보를 실시간으로 변경하고 있는데 만약 유저수가 많이 늘어나고 그룹도 많아진다면 ec2 서버 한대로는 트래픽 부하가 많이 생길것으로 예상된다.

이러한 경우에는 두가지 선택지가 주어진다.

  1. scale-up : 서버 자체의 성능을 강화 시킴

  2. scale-out : 서버의 수를 늘림

2. scale-out을 선택한 이유

내가 scale-out을 선택한 이유는 다음과 같다.

  • 내 프로젝트의 경우 그룹이 유저에 비례해서 증가한다. 따라서 유저가 계속 늘어난다고 가정하면 확장에 한계를 가지면 안되기 때문에 선택했다.
  • scale-out의 경우 확장 후에 불필요하다고 느끼면 다시 어느정도 줄이는 scale-in등 유연한 대처가 가능하다.
  • 프로젝트에서 이미 redis를 사용하고 있어 데이터 정합성을 관리하기 어렵지 않다.

위와 같은 이유로 scale-out을 선택했고, 그렇다면 현재의 socket 통신에 변화를 주어야 한다.

3. scale-out시에 socket.io의 문제점 및 해결방법

문제점
기존의 socket통신은 socket 정보를 서버의 in-memory에 저장하기 때문에 서버가 늘어나면 socket에 대한 정보를 서버끼리 공유할 수 없음

해결방법
기존의 adapter를 redis adapter로 바꿔서 socket 정보를 in-memory가 아닌 redis에 저장하도록 변경하면 서버끼리도 socket에 대한 정보를 공유할 수 있음

4. socket.io에 redis adapter 적용

기존의 socket통신 코드가 들어간 gateway는 수정할 필요가 없다.

코드를 살펴보자

redis-io.adapter.ts

export class RedisIoAdapter extends IoAdapter {
    private adapterConstructor: ReturnType<typeof createAdapter>;
                                           
	// 기존에 이미 redis를 이용하고 있기에 새로 연결이 아닌 기존의 redisService를 매개변수로 받음
    constructor(
        app: any,
        private readonly redisService: RedisService,
    ) {
        super(app);
    }

    async connectToRedis(): Promise<void> {
      	// 이미 연결되어 있는 redis 클라이언트를 받아옴
      	// pub은 메시지 발행에 사용
        const pubClient = this.redisService.getRedisClient();
		// sub은 채널을 구독해 메시지 수신에 사용
        const subClient = pubClient.duplicate();

        this.adapterConstructor = createAdapter(pubClient, subClient);
    }

    // main.ts에서 redis 어댑터 생성시 호출됨
    createIOServer(port: number, options?: ServerOptions): any {
        const server = super.createIOServer(port, options);
        server.adapter(this.adapterConstructor);
        return server;
    }
}

main.ts

...

const redisService = app.get(RedisService);
const redisIoAdapter = new RedisIoAdapter(app, redisService);
await redisIoAdapter.connectToRedis();

// redis 어댑터를 websocket에 적용
app.useWebSocketAdapter(redisIoAdapter);
...

위와 같이 많이 추가되는건 없다. 기존의 소켓 어댑터에서 새로 정의한 redisAdapter로 갈아끼우면 끝이다. 내 경우에는 이미 redis를 사용하고 있이에 redis 모듈을 정의한 상태이고, redis 모듈에서 연결이 완료되었기 때문에 클라이언트만 가져온 것이다.

만약 어댑터에서만 redis를 사용해서 바로 연결해야 할 경우 공식문서에 나와있듯이 연결하는 로직을 추가해야 한다.

5. 추후 발전시킬점

현재 프로젝트에서 하나의 redis 서버를 소켓, 그룹, 캐싱 총 3곳에서 사용하고 있는데 이들은 크게 서로 연결되어 있지 않고 따로 저장해도 되기 때문에 각각 다른 redis 서버를 줄 것으로 예상된다.

만약 그렇게 한다면 어댑터에서 redis를 바로 연결하도록 수정해야할 필요가 있어 보인다.

참고

nestJs 공식 문서
redis 어댑터 트래픽 개선

profile
욕심 많은 개발자

0개의 댓글