[요즘 우아한 개발] 메시지 발송 이중화 여정기

이 글은 《우아한 요즘 개발》에서 발췌했습니다.

골든래빗 출판사

#스프링클라우드     #이중화      #공통시스템개발팀

이재훈 2022.03.16

우아한형제들 공통시스템개발팀에서 메시지 플랫폼을 개발하는 이재훈입니다. 여기서는 2021년에 진행한 SMS 발송 외부 시스템 이중화 프로젝트 이야기를 하려고 합니다. 어떤 이유로 이중화를 하게 되었고 스프링클라우드 Config를 이용해 어떻게 배포 없이 트래픽을 전환할 수 있었는지에 대한 여정이기도 합니다.

 

2021.06.18 SMS 발송 외부 시스템 장애 발생

장애는 언제나 소리 소문 없이 우리에게 찾아왔습니다. SMS를 발송하는 외부 시스템의 장애가 발생했고, 이로 인해 우아한형제들의 서비스 전역에서 SMS를 발송하지 못했습니다. 대표적인 영향으로 인증 문자 메시지를 발송할 수 없어 운행을 시작하고자 하는 라이더들이 앱에 로그인을 하지 못하는 상황으로 이어졌습니다.

장애에 대해 전혀 대비를 하지 않은 상황에서 저희가 할 수 있는 일이 별로 없었습니다. 팀은 외부 시스템이 빠르게 복구되기만을 기다릴 수밖에 없었습니다. 저희 팀은 장애가 해소되는 시간 동안 많은 무력함을 느꼈습니다. 장애는 저희에게 큰 동기부여가 되었고, 다른 어떤 업무보다 이중화 작업을 최우선으로 프로젝트를 시작하게 되었습니다.

 

이중화를 가로막는 요인

이중화라는 목표를 달성하려면 어떤 기능을 개발해야 할지 고민해보았습니다. 완벽한 이중화 구성을 위해서는 모든 SMS 요청에 대해 두 개의 외부 시스템에서 동일하게 요청을 수행할 수 있어야 합니다. 메시지 플랫폼에 요청되는 SMS 유형은 다음과 같습니다.

  • 일반 SMS/LMS
  • 인증 SMS
  • 해외 수신자에 대한 SMS
  • 이미지를 첨부한 MMS

추가로 투입하는 외부 시스템에 대해 모든 SMS 요청을 처리하는 기능을 개발하는 것은 시간이 조금 걸리는 일입니다. 따라서 가장 비즈니스 영향도가 가장 높은 인증 SMS 유형부터 기능을 개발하는 방향으로 결정했습니다. 그다음 차례대로 개발이 완료된 유형에 대해서는 두 개의 외부 시스템으로 트래픽을 분산해 발송하는 방향으로 결정했습니다.

 

어떻게 두 개의 외부 시스템에 트래픽을 분산할까?

메시지 플랫폼에는 라우터(Router)라는 모듈이 있습니다. 라우터 모듈은 요청받은 메시지가 어떤 외부 시스템에 발송 요청할지 결정하고 요청을 넘겨주는 역할을 담당합니다. 따라서 라우터 모듈에서 외부 시스템별 트래픽 비율 정보를 알고 있고, 비율에 따라 A와 B 외부 시스템으로 트래픽을 분산하도록 아키텍처를 결정했습니다. 라우터 모듈부터 외부 시스템까지 요청되는 전반적인 과정은 다음과 같습니다.

 

 

  • 인증 SMS에 대해서만 가중치 기반 라우팅 정책(Weighted Routing Policy)을 사용해 A 외부 시스템과 B 외부 시스템으로 트래픽을 분산할 수 있게 합니다.
  • 일반 SMS, 해외 SMS, MMS는 아직 B 외부 시스템에서 구현이 완료되지 않았으므로 단순 라우팅 정책을 사용해 모든 SMS 요청을 처리할 수 있는 A 외부 시스템으로만 발송하도록 합니다.

 

프로젝트 완료 후 최종적으로 라우터 모듈에서 SMS를 처리하는 모습은 다음과 같습니다.

 

  • 일반 SMS, 해외 SMS, MMS를 순서대로 B 외부 시스템에서 처리하도록 구현한 후 구현되는 순서대로 가중치 기반 라우팅 정책을 사용하도록 합니다.

 

다음으로 요구되는 내용은 A 외부 시스템과 B 외부 시스템으로 원하는 만큼 트래픽을 분산할 수 있어야 한다는 것입니다.

  • A 외부 시스템 장애 상황에는 B 외부 시스템으로만 요청이 가도록 해야 합니다.
  • B 외부 시스템 장애 상황에는 A 외부 시스템으로만 요청이 가도록 해야 합니다.
  • A 외부 시스템에서 처리할 수 있는 동시 처리량을 넘어서는 때에는 B 외부 시스템에 트래픽을 50:50으로 분산할 수 있어야 합니다.

 

트래픽 분산 기능은 가중치 기반 라우팅 정책에 있는 트래픽 비율 정보를 바탕으로 이루어지게 됩니다. 어떻게 트래픽 비율 정보를 배포 없이 바꿀 수 있을까요?

 

배포 없이 트래픽 변경하기

스프링 부트(Spring Boot) 애플리케이션에서는 application.properties 혹은 application.yml 파일을 이용해 프로퍼티를 정의하고 사용할 수 있습니다.

가중치 기반 라우팅 정책에서 트래픽 비율을 알고 있고, 트래픽 비율에 따라 어떤 카프카* 토픽으로 보낼지만 정하면 발송 이중화를 구현할 수 있게 됩니다. 여기까지 구현이 완료되었다면 배포를 통해 프로퍼티를 다시 적용해 트래픽 비율을 변경할 수 있게 됩니다.

하지만 우리는 시스템 운영 중에 배포 없이 트래픽을 변경하는 방법이 필요했습니다. 이를 구현하기 위해 스프링 클라우드 Config를 사용해 런타임에 프로퍼티 정보를 변경할 수 있는 기술을 도입하게 되었습니다. 스프링 클라우드 Config를 사용하기 위해 두 가지 아키텍처를 고안해 팀에서 토론을 진행했습니다.

  1. 스프링 클라우드 Config + 스프링 클라우드 Bus + 스프링 클라우드 Config Monitor를 이용한 아키텍처
  2. 스프링 클라우드 Config + Scheduled Polling을 이용한 아키텍처

두 아키텍처를 소개하고 어떤 과정을 통해 1안과 2안 중 하나를 결정하게 되었는지 살펴보겠습니다.

  • Apache Kafka. 실시간 스트림을 게시, 구독, 저장 및 처리할 수 있는 분산형 데이터 스트리밍 플랫폼

 

1안. 스프링 클라우드 Config + 스프링 클라우드 Bus + 스프링 클라우드 Config Monitor를 이용한 아키텍처

공통적으로 스프링 클라우드 Config를 이용해 원격 저장소를 구성했고 배포 없이 메시지 플랫폼 라우터 내의 외부 시스템별 발송 트래픽 정보를 변경하고 반영하는 시스템을 구성했습니다. 또한 시스템에서 프로퍼티 정보를 쉽게 바꿀 수 있도록 스프링 클라우드 Config에서 제공하는 백엔드 저장소 중 RDB(MySQL)를 활용하는 JDBC Backend를 사용했습니다.

1안으로 제시된 스프링 클라우드 Config + 스프링 클라우드 Bus + 스프링 클라우드 Config Monitor를 이용한 아키텍처를 살펴보겠습니다.

 

 

  1. DB에 프로퍼티 데이터가 수정되면 Config 서버 애플리케이션에서는 스프링 클라우드 Config Monitor에서 제공하는 /monitor 엔드포인트를 호출합니다.
  2. /monitor 엔드포인트로 요청된 정보를 바탕으로 카프카에 클라이언트의 프로퍼티 정보를 갱신하도록 하는 RefreshRemoteApplicationEvent를 발행합니다.
  3. 클라이언트는 카프카 토픽을 구독하고 있고 이벤트를 수신해 클라이언트의 프로퍼티 정보를 갱신합니다.
  4. 클라이언트를 갱신 시, Config 서버에 자신의 application-name과 profile 정보를 기반으로 프로퍼티 정보를 조회합니다.
  5. Config 서버는 요청받은 application-name과 profile 정보를 기반으로 DB에 저장된 프로퍼티 정보를 조회해 API 응답으로 반환합니다.
  6. 조회된 프로퍼티 정보를 기반으로 클라이언트에 @RefreshScope로 등록된 Bean을 재생성합니다.

 

2안. 스프링 클라우드 Config + Scheduled Polling을 이용한 아키텍처

다음으로는 스프링 클라우드 Config 정보를 주기적으로 폴링하는 스케줄러를 사용한 방식입니다.

 

 

  1. 메시지 플랫폼 라우터 모듈에서 10초 주기로 Config 서버에 application-name과 profile 정보를 기반으로 프로퍼티 정보를 조회합니다.
  2. Config 서버는 요청받은 application-name과 profile 정보를 기반으로 DB에 저장된 프로퍼티 정보를 조회해 API 응답으로 반환합니다.
  3. 이 아키텍처에서는 state라는 필드를 사용해 state 값이 변경된 때에만 Context Refresh를 하도록 합니다. state 값이 변경되지 않은 때에는 Context Refresh를 실행하지 않도록 해 비효율적으로 Bean을 재생성하는 과정을 제거했습니다. state값에는 가장 최근에 변경된 프로퍼티 수정시간 timestamp 값을 사용했습니다.
  4. 응답 정보의 state 값이 변경된 때에는 조회된 프로퍼티 정보를 기반으로 클라이언트에 @RefreshScope로 등록된 Bean을 재생성합니다.

 

최종 결정

1안과 2안 모두 장단점이 있는 아키텍처입니다.

 

1안. 스프링 클라우드 Config + 스프링 클라우드 Bus + 스프링 클라우드 Config Monitor를 이용한 아키텍처

장점
  • 프로퍼티에 대한 수정이 발생했을 때 Config 서버의 /monitor 엔드포인트를 호출해 클라이언트별로 1회만 프로퍼티 조회 API를 호출해 클라이언트에 프로퍼티 변경 사항을 반영합니다.
  • 실시간에 가깝게 반영됩니다.
단점
  • 클라이언트에 이벤트를 전파하기 위해 카프카와 같은 메시지 브로커가 필요합니다.
  • 또한 카프카가 정상적이지 않은 경우 프로퍼티 설정을 변경할 수 없는 구조입니다.

 

2안. 스프링 클라우드 Config + Scheduled Polling을 이용한 아키텍처

장점
  • 카프카와 같은 메시지 브로커를 관리하는 비용이 없습니다.
단점
  • 클라이언트가 주기적으로 Config 서버에 프로퍼티 조회 API를 요청하는 구조입니다.
  • 클라이언트 개수가 늘어나게 되면 Config 서버로의 프로퍼티 조회 요청량이 선형적으로 증가하는 구조입니다. 따라서 클라이언트 개수가 작은 때에는 문제가 되지 않지만, 많아지는 경우 많은 트래픽이 발생합니다.

 

장단점만 놓고 보면 1안이 상당히 합리적인 아키텍처로 보입니다. 클라이언트 갱신에 드는 API 통신 비용이 저렴하고, Config 서버에 부담을 주는 구조가 아니기 때문입니다. 하지만 프로퍼티 설정을 변경하기 위해 카프카에 의존해야 하는 부분이 선택에 큰 요인으로 작용했습니다.

  • 1안의 경우 장애가 발생하면 Config 서버, 카프카, Config 클라이언트 모두 확인해야 합니다.
  • 2안의 경우 장애가 발생하면 Config 서버와 Config 클라이언트만 확인하면 되고 재시도도 1안에 비해 쉬운 편입니다.

 

또한 2안의 단점인 Config 서버에 발생하는 지속적인 트래픽도 현재 기준으로는 주기적으로 폴링을 하더라도 Config 서버에 무리가 가는 트래픽이 발생하지 않는다고 판단했습니다. 요구사항 또한 실시간으로 반영되는 것이 아닌 수 초 내에 변경되는 것으로 목적으로 하고 있어 트레이드오프를 따졌을 때 운영 비용이 상대적으로 낮은 2안을 선택하게 되었습니다.

위와 같은 선택을 통해 메시지 플랫폼에서 빠르게 SMS 발송 이중화를 구현할 수 있습니다. 이후 내부 어드민에 프로퍼티를 변경할 수 있는 기능을 추가해 외부 시스템별 트래픽 설정을 빠르게 변경할 수 있어 장애가 발생하더라도 빠르게 대응할 수 있는 시스템을 구성하게 되었습니다.

 

2021.10.19 다시 놈이 나타났다

2021.10.19 22:30에 한 외부 시스템의 장애가 발생해 외부 시스템으로 요청하는 SMS를 발송할 수 없는 상황이 발생했습니다.

 

 

하지만 저희는 장애를 극복할 수 있는 모든 준비가 되어 있고, 장애를 인지함과 동시에 안정적으로 SMS를 발송할 수 있는 외부 시스템으로 트래픽을 100% 전환했습니다. 더 이상 장애가 발생해도 좌절과 무력함을 겪지 않고 차분하게 대응할 수 있게 되었습니다. 또한 외부 시스템 장애시에도 자신 있게 장애 공유를 할 수 있습니다.

SMS 채널 장애를 완벽하게 이중화가 구현되어 SMS 채널 장애에 대해서는 극복할 수 있는 시스템을 갖췄습니다. 4월에는 알림톡 채널에 대해 이중화를 지원합니다. 가장 핵심적인 채널 2개에 대해 장애를 극복할 수 있게 되었습니다.

 

◆◆◆

 

다음 단계로 저희가 해보고자 하는 과제는 자동화입니다. 현재는 알람이나 제보를 통해 사람이 장애를 인지하고 판단해 트래픽을 전환합니다. 수동으로 사용하게 된 이유는 장애라고 판단할 수 있는 기준을 정하기 어렵기 때문이었습니다. 하지만 이제는 그간의 운영 경험을 바탕으로 장애라고 판단할 수 있는 몇 가지 기준을 정할 수 있게 되었습니다. 이런 상황에서 시스템이 자동으로 트래픽을 전환하도록 자동화하는 기능을 개발해 볼 예정입니다.

 

JPA + OAuth2 + JWT + AWS와 배우는 스프링 부트 3

Java 백엔드 입문자를 위한 풀 패키지

실력을 갖춘 개발자로 성장하려면 시작이 중요합니다.

그래서 이 책은 무엇부터 익혀야 하는지 막막한 입문자에게 백엔드 개발의 필수 지식을 학습 로드맵 중심으로 설명합니다.

이어서 스프링 부트 3 개발에 꼭 필요한 4대장인 JPA ORM, OAuth2 인증, AWS 배포, CI/CD를 최신 트렌드에 맞게 그리고 실무에 유용하게 알려줍니다.

모든 장 끝에는 연습문제가 수록되어 있어 배운 내용을 점검할 수 있습니다. 이 책이 여러분의 백엔드 개발자 여정에 든든한 나침반이 되어 줄 겁니다.

책 상세보기: https://goldenrabbit.co.kr/product/springboot3java

 

저자 우아한 형제들

우아한형제들은 배달이 일상을 조금 더 행복하게 하도록 오늘도 달리고 있습니다. 평범한 사람들이 모여 비범한 성과를 만들어 내는 곳이될 수 있도록 건강한 조직문화를 만드는 일에 진심을 다합니다. 2016년부터 ‘우아한형제들 기술블로그’를 운영하며 개발 조직의 성장 과정을 기록하고 있습니다.

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내