제품 출시가 얼마 남지 않아 회사분들 대부분이 밤을 새가면서 일을 하고 있다.
서버를 개발하고있는 사수님도 그 중 하나인데, qa가 진행되면서 아주 바쁘시다.
qa팀이 새벽까지 근무하면서 피드백을 보내면 수정하고, 수정한 데이터를 리로드 해줄 사람이 필요하다.
리소스가 변경될때마다 dev, qa서버를 리로드 해줘야만 변경된 리소스가 적용되기 때문에
사수님도 함께 밤을 새야하는 상황이었다.
혹여나 사수님이 잠을 자기라도 하면 다른 분들의 작업도 멈춰버리기 때문에 잠을 잘 수가 없는 상황이었다.
사수님께선 나에게 원격으로 서버를 리로드하는 기능이 필요하다고 하셨고, Pub/Sub기능을 사용해봤냐고 하셨다.
Redis Pub/Sub (Publish/Subscribe)은 메시지 브로커 시스템의 일종으로,
데이터의 '출판자'(publishers)가 메시지를 보내고,
그 메시지를 구독하는 '구독자'(subscribers)가 이를 받아 처리할 수 있게 해주는 패턴이다.
Redis Pub/Sub은 실시간 애플리케이션, 채팅 애플리케이션, 실시간 통계, 실시간 알림 시스템 등 다양한 분야에서 사용된다.
Publisher:
메시지를 발행하고, 해당 메시지는 특정 '채널'을 통해 전송된다. Publisher 채널에 메시지를 보내지만, 누가 메시지를 받는지는 알지 못한다.
Channel:
메시지가 전송되는 경로이다. Subscriber는 하나 또는 여러 채널을 구독할 수 있다.
Subscriber:
Subscriber는 하나 이상의 채널을 구독하고, 해당 채널로부터 메시지를 받는다. 메시지가 채널에 도착하면, 그 메시지는 모든 구독자에게 전송된다.
비동기 통신: Redis Pub/Sub은 비동기적으로 메시지를 교환한다. 출판자는 메시지를 보내고 계속 다른 작업을 수행할 수 있으며, 구독자는 메시지를 수신하면 작성되어 있는 로직에 따라 작동한다.
다대다 통신: 한 출판자가 여러 구독자에게 메시지를 보낼 수 있고, 한 구독자는 여러 채널을 구독할 수 있다.
경량 메시징 시스템: Redis Pub/Sub은 간단하고 가벼운 메시징 시스템을 제공한다. 복잡한 기능이나 높은 오버헤드 없이 메시지를 효율적으로 교환할 수 있다.
실시간 처리: 실시간으로 메시지를 교환할 수 있기 때문에, 채팅 시스템이나 실시간 알림 시스템 등에 적합하다.
실시간으로 변경점을 확인하는 데에는 Polling의 방식도 있다. Polling은 클라이언트가 서버에 주기적으로 요청을 보내어 새로운 데이터나 업데이트가 있는지 확인하는 방식이다. 클라이언트는 설정된 간격마다 서버에 "새로운 정보가 있습니까?"라고 묻고, 서버는 이에 대한 응답을 보낸다.
Dev, Qa 서버에서 Data서버에 지속적으로 요청을 보내 Polling하는 방식도 데이터를 실시간으로 반영하는 효과가 있지만, Pub/Sub에 비해 불필요한 오버헤드가 들어간다는 차이점이 있다.
Redis Pub/Sub은 메시지가 있을 때만 데이터를 전송하므로 효율적이다. 반면, Polling은 정해진 간격으로 지속적으로 데이터를 요청하기 때문에 효율성이 낮을 수 있다.
Polling은 서버에 지속적인 부하를 주는 반면, Redis Pub/Sub은 새로운 메시지가 있을 때만 통신이 발생하기 때문에 서버에 부하를 줄일 수 있다.
Polling은 기본적인 HTTP 요청/응답 모델을 사용하기 때문에 구현이 간단하지만, Redis Pub/Sub은 메시징 시스템과의 통합이 필요하며, 발행/구독 로직을 관리해야 한다.
따라서, Redis Pub/Sub은 실시간 알림, 실시간 대시보드, 채팅 애플리케이션과 같이 빠른 데이터 전달과 높은 효율성이 요구되는 시스템에 적합하다.
Polling은 복잡도가 낮고, 빠른 실시간 처리가 필요하지 않은 시스템에서 사용하기 적합하다. 예를 들어, 새 이메일 확인, 일정 업데이트와 같이 몇 초 또는 몇 분의 지연이 큰 문제가 되지 않는 경우에 사용된다.
이번 기능에선 데이터가 변경되는대로 Qa분들께서 받아서 테스트를 해야하기 때문에 Redis Pub/Sub기능이 더 적합했다.
기능은 데이터를 등록, 수정할 수 있는 데이터 관리 페이지에 만들었다.
데이터 디자이너님께서 데이터를 등록, 수정한 후 사수님께 리로드를 요청하던 부분을
데이터 디자이너님께서 서버를 선택하고 리로드 버튼을 누르면 작동하게끔 했다.
방법은 간단했다.
// reload.controller.ts
@Post()
@ApiOperation({ summary: 'Reload Game Data' })
async reloadGameData(@Body('servers') servers) {
await this.reloadDataService.reloadGameData(servers);
}
servers는 서버의 타입으로 구성되어있는 배열이다.
// redis.service.ts
private async loadRedisPubSubManager(t: number, type: string): Promise<Redis> {
return await this.moduleRef.resolve(`REDIS_${type}_CLIENT_${t}`);
}
// ===================================================================
// Pub / Sub
// ===================================================================
public async publish(t: number, type: string, channel: string, message: string) {
const redis = await this.loadRedisPubSubManager(t, type);
const result = await redis.publish(channel, message);
this.logger.log(
`Published ${result} message(s) to Server ${t}, Channel ${channel}, Message ${message}`
);
}
public async subscribe(t: number, type: string, channel: string) {
const redis = await this.loadRedisPubSubManager(t, type);
await redis.subscribe(channel);
this.logger.warn(`Subscribe Channel: ${channel}`);
redis.on('message', (channel, message) => {
this.logger.log(channel, message);
});
}
redis.service에 redis를 사용할 수 있는 로직을 작성해두고
// reload.service.ts
@Injectable()
export class ReloadDataService {
constructor(private readonly redisService: RedisService) {}
async reloadGameData(servers: number[]) {
servers.map(async t => {
try {
await this.redisService.publish(t, 'PUB', `Message`, t.toString());
return { server: t, status: 'success' };
} catch (error) {
console.error(`Failed to publish for server ${t}:`, error);
return { server: t, status: 'failed', error };
}
});
}
}
reload.service에선 redisService에서 publish메서드를 받아 메시지를 publish한다.
이렇게 Publish한 메시지를 각 서버에서 받고 데이터를 리로드 해준다.
디자이너님도 만족하고, 사수님도 만족할만한 작은 자동화 기능을 만들고나니 뿌듯했다.
내가 공부해서 만든 기능이 누군가에게 도움이 될 수 있다는 점이 개발에 있어 가장 매력을 느끼는 부분인 것 같다.