프로젝트 1차 기능 구현 완료 후에 트래픽 이슈에 대해 고민을 해보았다. 현재 소켓 통신으로 그룹에 대한 정보를 실시간으로 변경하고 있는데 만약 유저수가 많이 늘어나고 그룹도 많아진다면 ec2 서버 한대로는 트래픽 부하가 많이 생길것으로 예상된다.
이러한 경우에는 두가지 선택지가 주어진다.
scale-up : 서버 자체의 성능을 강화 시킴
scale-out : 서버의 수를 늘림
내가 scale-out을 선택한 이유는 다음과 같다.
위와 같은 이유로 scale-out을 선택했고, 그렇다면 현재의 socket 통신에 변화를 주어야 한다.
문제점
기존의 socket통신은 socket 정보를 서버의 in-memory에 저장하기 때문에 서버가 늘어나면 socket에 대한 정보를 서버끼리 공유할 수 없음
해결방법
기존의 adapter를 redis adapter로 바꿔서 socket 정보를 in-memory가 아닌 redis에 저장하도록 변경하면 서버끼리도 socket에 대한 정보를 공유할 수 있음
기존의 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를 사용해서 바로 연결해야 할 경우 공식문서에 나와있듯이 연결하는 로직을 추가해야 한다.
현재 프로젝트에서 하나의 redis 서버를 소켓, 그룹, 캐싱 총 3곳에서 사용하고 있는데 이들은 크게 서로 연결되어 있지 않고 따로 저장해도 되기 때문에 각각 다른 redis 서버를 줄 것으로 예상된다.
만약 그렇게 한다면 어댑터에서 redis를 바로 연결하도록 수정해야할 필요가 있어 보인다.