서버마다 웹 페이지를 호출해 메모리를 갱신하던 방식은 배치 서버에 쓸 수 없고 수평 확장에도 맞지 않아, 빈을 Redis 채널에 구독시켜 한 번의 메시지로 모든 서버가 갱신하도록 바꿨다.
배경
데이터베이스는 느리고 메모리는 빠르다. 실제 데이터 접근 방식의 차이 때문이며, 메모리 계층 구조에 따라 자주 쓰는 데이터는 메모리에 올릴 필요가 있다.
비즈하우스는 스프링 빈 생명주기를 이용해 빈을 초기화할 때 데이터베이스의 데이터를 메모리로 끌어올린다. 실제 데이터 접근은 메모리에서 이루어진다.
문제는 서버 간 데이터가 동기화되지 않는다는 것이다.
기존 방식의 문제
데이터베이스의 변화를 메모리에 반영하기 위해 다음 페이지에 갱신 요청을 보내고 있었다.
https://www.bizhows.com/common/clear_static_value.jsphttps://intra.bizhows.com/common/clear_static_value.jsp
요청을 받으면 데이터베이스에서 최신 데이터를 가져와 메모리에 다시 올린다. 즉 각 서버에 올려 둔 페이지에 각각 요청을 보내 각각 메모리를 갱신하는 형태다.
| 문제 | 내용 |
|---|---|
| 배치 서버 | 배치 서버는 웹 페이지를 올리지 않는다. 현 구조로는 갱신할 수 없다 |
| 수평 확장 | 서버를 늘리면 모든 서버의 웹 페이지를 일일이 들러야 한다. 서버가 100대가 되면, 그리고 www-1·www-2 처럼 특정 서버를 구분할 방법이 없으면 불가능하다 |
수평 확장에 적절하지 않은 구조로 판단했다.
검토한 방법
처음에는 레플리카를 만드는 작업과 유사하다고 보았다. 데이터베이스가 트랜잭션 변화를 감지해 각 서버에 트랜잭션 로그를 뿌리고 각 서버가 그 증분을 반영하는 single leader 방식을 생각했으나, 기술적으로 쉬워 보이지 않았다.
따라서 자동 갱신은 나중으로 미루고 수동 갱신 방식을 수평적으로 개선하기로 했다.
개선의 착상
원문은 문제를 현실에 빗대어 설명한다.
정보가 필요한 사람에게 같은 메시지를 각각 보내는 방식이 지금 구조다. 보통 이렇게 하지 않는다. 정보 공유가 필요한 사람을 모아 채널(단체방)을 만들고 채널에 정보를 공유하는 것이 일반적이다.
이러면 대상이 100명이든 10,000명이든 상관없고 메시지는 한 번만 보낸다. 궁극적인 차이는 메시지를 각 개인이 아니라 채널에 보낸다는 데 있다.
개선
Redis pub/sub 을 활용한 구조로 바꿨다.
- 빈을 등록할 때 빈을 채널에 구독시킨다. 단체방에 입장시키는 것이다.
- 빈이 채널에 오는 메시지를 받을 수 있는 상태가 된다.
- 채널에 오는 메시지를 받아 처리하는 로직을 작성한다. 여기서는 데이터베이스의 데이터를 메모리로 올리는 작업이다.
채널 관리와 메시지를 구독한 서버에 뿌리는 작업은 Redis 가 맡는다.
구현
빈을 등록할 때 wowpress_config 채널을 구독하게 하고, 메시지가 왔을 때 처리할 함수 이름을 receiveMessage 로 지정한다.
private void addTopicAndSubscribe(RedisMessageListenerContainer container) {
final MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(this, "receiveMessage");
messageListenerAdapter.afterPropertiesSet();
container.addMessageListener(messageListenerAdapter, ChannelTopic.of("wowpress_config"));
}
wowpress_config 채널로 WOWPRESS_CONFIG_INIT_MESSAGE 가 오면 프로퍼티를 갱신한다.
public void receiveMessage(String message) {
if (WOWPRESS_CONFIG_INIT_MESSAGE.equals(message)) {
this.wowpressApiConfigDetail = wowpressApiConfigDetailFactory.makeWowpressApiConfigDetail();
log.info("{} 수신 및 와우프레스 설정 초기화 완료", message);
} else {
log.info("정의되지 않은 와우프레스 설정 메시지: {}", message);
}
}
갱신 페이지에서 제출을 누르면 wowpress_config 채널에 WOWPRESS_CONFIG_INIT_MESSAGE 가 발행된다.
결과
하나의 Redis 에 연결된 ab-www.bizhows.com, ab6-www.bizhows.com, ab10-www.bizhows.com 이 동시에 메시지를 받아 데이터를 갱신하는 것을 확인했다.
남은 것
자동 갱신은 구현하지 않았다. 데이터베이스의 트랜잭션 변화를 감지해 증분을 반영하는 방식은 기술적 난이도 때문에 미뤘다.
참고 자료
- memory hierarchy:
https://ko.wikipedia.org/wiki/메모리_계층_구조 - redis pub/sub:
https://redis.io/docs/manual/pubsub/ - spring with redis:
https://spring.io/guides/gs/messaging-redis/
이 기록에서 확인할 수 없는 것
| 항목 | 내용 |
|---|---|
| 개선 시점 | 원문에 날짜가 없다. 첨부 이미지 파일명이 2023년 6월과 2024년 3월로 나뉘어 있다 |
| 배치 서버 문제의 해결 여부 | 개선 구조가 배치 서버에도 적용됐는지 원문이 확인해 주지 않는다 |
관련 문서
| 대상 | 경로 |
|---|---|
| 리눅스 커널·systemd 로그로 장애를 조사하는 방법 | technology-data/infrastructure/linux-log-inspection.md |
| PodDisruptionBudget 적용 기준과 현황 | technology-data/infrastructure/k8s-pod-disruption-budget.md |