안녕하세요, 결제서비스개발팀에서 서버 개발을 담당하고 있는 Jayden, Lani입니다.
저희는 2024년 연초부터 하나의 큰 덩어리였던 카카오뱅크 오픈뱅킹 시스템에서 ‘카드 청구금액 알림 서비스’와 관련된 API 및 배치 작업들을 별도의 시스템으로 분리하는 작업을 계획했고, 2024년 9월 26일에 신규 시스템으로의 이관을 성공적으로 마무리했습니다.
이번 글을 통해 저희가 카드 청구금액 알림 서비스를 기존의 오픈뱅킹 시스템으로부터 분리한 이유부터 신규 시스템으로 이관하는 과정에서 겪었던 어려움과 느낀 점들을 공유하고자 합니다. 기존의 레거시 시스템에서 여러 문제에 봉착하여 일정 부분을 떼어낸 신규 시스템을 만드는 작업을 고민하고 계신 분들에게 타산지석이 되기를 기대하며 시작해 보겠습니다.
오픈뱅킹 서비스 분리의 필요성
먼저 ‘오픈뱅킹 서비스’가 생소하신 분들을 위해 어떤 서비스인지부터 설명드리겠습니다. 오픈뱅킹 서비스는 하나의 앱에서 다양한 금융서비스 구현이 가능하도록 금융기관에서 REST API 형태로 제공하는 플랫폼입니다. 카카오뱅크는 ‘금융결제원 오픈뱅킹 서비스’를 활용해 다른 금융기관의 은행 계좌에 대한 잔액조회, 이체 및 카드 청구금액 알림 서비스 등 다양한 기능과 서비스를 제공하고 있습니다.
이전까지 카카오뱅크는 [그림 1]처럼 하나의 단위 시스템을 통해 오픈뱅킹과 관련된 모든 서비스를 제공해왔고, 이 시스템은 비동기(Asynchronous) 프로그래밍 모델을 이용한 고성능 리액티브 스택(Reactive Stack) 웹 프레임워크인 Spring WebFlux를 사용하고 있었습니다. WebFlux를 사용하면 스레드 자원을 효율적으로 관리하여 하드웨어 자원 대비 높은 처리량을 제공할 수 있고, 특히 I/O 작업이 많은 경우 그 성능 차이는 더욱 커집니다.
오픈뱅킹 시스템 구축 당시, 타행 금융계좌 잔액조회와 이체 등의 ‘계좌 관련 서비스’가 먼저 개발되었습니다. 이 서비스들은 트래픽이 가장 많은 카카오뱅크 앱의 홈 화면에서 호출되었고, 주로 오픈뱅킹 API와의 I/O 작업이 많아 WebFlux를 사용의 이점을 누릴 수 있었습니다.
반면, WebFlux는 학습 난이도가 높고 코드가 동기(Synchronous) 방식보다 복잡하고 직관적이지 않다는 단점이 있습니다. 카드 청구금액 알림 서비스의 경우, 모바일 API 요청이 많지 않고 배치 작업을 통한 데이터 수집, 가공 및 알림 발송 등 자체 비즈니스가 복잡했습니다. 특히 배치 작업 시 처리량이 증가하면 금융결제원과 각 카드사의 처리 한도를 초과해 서비스 장애로 이어질 수 있어, WebFlux의 성능 최적화가 큰 도움이 되지 않았습니다. 따라서, 카드 청구금액 알림 서비스는 WebFlux 사용의 단점이 더 큰 서비스였습니다.
그러나 카드 청구금액 알림 서비스를 개발할 당시에는 WebFlux로 개발된 기존 시스템을 활용할 수 밖에 없었습니다. ‘금융결제원 오픈뱅킹 연동’만을 위한 별도 시스템을 만드는 것이 부담이었고, 오픈뱅킹을 이용하는 서비스에서 공통으로 관리되어야 하는 일부 데이터들이 있기 때문이었습니다.
그런데 이처럼 서비스 특성에 적합하지 않은 환경에서 운영하다 보니 비즈니스 로직을 수정하기 까다로웠고, 특정 서비스에서 장애가 발생할 경우 오픈뱅킹 관련 모든 서비스가 영향을 받는 등 운영상의 난이도가 점차 높아졌습니다. 저희는 이러한 상황에서는 안정적인 서비스를 계속해서 제공하기 힘들다고 판단했고, 카드 청구금액 알림 서비스를 기존의 오픈뱅킹 시스템으로부터 분리해 별도의 시스템을 구축하기로 결정했습니다.
서비스 독립을 위한 큰 그림
본격적으로 오픈뱅킹 서비스 분리 작업에 참여하면서, 다시 한번 카드 청구금액 알림 서비스의 핵심이 되는 기능에 대해 [그림 2]와 같이 정리해 보았습니다. 카드 청구금액 알림 서비스에서 가장 중요한 것은 출금 예정일 하루 전(D-1)에 알림을 잘 발송해주는 것입니다. 이를 위해 고객의 청구서가 누락 없이 잘 수집되어야 하고, 특정 계좌에서 출금되는 청구서 목록을 만들어주는 작업이 가능한 빠르고 정확하게 이루어져야 합니다.
저희 시스템은 기존에도 최대한 많은 청구서를 수집하기 위해 새벽에 수집하지 못한 청구서를 낮에 다시 수집을 시도하는 일종의 재처리 로직이 존재했습니다. 이로 인해 재처리 건이 많을 경우 모바일 API 응답이 지연될 수 있다고 판단했고, 청구서 수집이나 알림 발송과 같은 배치 작업이 모바일 API와 독립적으로 잘 수행되게 하기 위해 신규 시스템의 서버를 분리했습니다.
[그림 3]과 같이 API 서버는 모바일 API를 처리하고, OPS 서버는 배치 컨테이너에서 생성한 작업만을 처리하도록 설계했습니다. 또한, 이들 모두를 서블릿 스택(Servlet Stack)인 Spring MVC와 코틀린(Kotlin)을 사용해 구성했습니다. 이와 함께, 기존에 한 번에 수행되던 수집 배치를 사용자별로 시간대를 나누어 수행하는 등 부하가 커지지 않도록 배치 작업의 일부 로직을 개선했습니다.
데이터 이관 및 검증
서비스 모니터링을 위한 관리성 테이블이 약간 추가되었지만, 비즈니스를 제공하는 주요 테이블에는 큰 수정이 없었기 때문에 기존 데이터에 대한 이관 및 검증은 그리 어렵지 않았습니다. 다만, 이후 생성되는 데이터를 기반으로 모바일 API와 배치 작업들이 기존 시스템과 동일하게 동작하는지에 대한 검증이 필요했습니다.
검증 대상 1. 모바일 API
모바일 API가 잘 이관되었는지 검증하기 위해 기존 시스템과 신규 시스템의 모바일 API 요청 값과 응답 값을 비교했습니다. 이를 위해 기존에 여러 가지 데이터 소스로부터 하나의 통합된 응답을 제공하는 어그리게이션 역할을 수행하던 ‘프레젠테이션 계층(Presentation Layer, 표현 계층)’의 시스템을 활용했습니다. 해당 시스템을 통해 카드 청구금액 알림 서비스 관련 요청이 발생하면 동일한 요청을 기존 시스템과 신규 시스템의 API에 모두 전달하고, 두 시스템의 응답 값을 비교해 검증 결과를 데이터베이스에 저장했습니다.
검증 대상 2. 배치성 작업
배치성 작업은 매일 발송되는 알림 건수를 비교하는 방식으로 검증 모델을 설계했습니다. 카드 청구금액 알림 서비스에서는 크게 ‘청구서 도착’과 ‘출금 예정’ 두 종류의 알림을 발송합니다. 기존 시스템으로부터 데이터 이관이 잘 되었다면, 매일 동일한 청구서를 수집해 ‘청구서 도착’ 알림을 발송하고, 시간이 지나 결제일 하루 전 ‘출금 예정’ 알림이 발송될 것이라 예상했습니다.
이를 위해 [그림 5]처럼 기존 시스템에서 알림이 발송될 때마다 신규 시스템 API를 호출해 모든 발송 내역을 신규 시스템에 저장하고, 두 시스템에서 발송된 알림을 비교하는 간단한 배치 작업을 개발했습니다. 이때 기존 시스템에 의해 신규 시스템에서도 알림을 발송한다면, 실제로 고객은 동일한 두 개의 알림을 받게 됩니다. 이를 방지하기 위해, 신규 시스템은 발송 내역을 저장하기만 하고 실제 발송 시스템에 API 요청은 보내지 않았습니다.
그러나 검증을 시작하자 모바일 API와 배치 작업 검증 모두에서 예상보다 낮은 일치율을 보였습니다. 이어서 그 이유를 구체적으로 설명드리겠습니다.
원인 1. 오픈뱅킹 의존성
먼저 카드 청구금액 알림 서비스의 ‘오픈뱅킹 API에 대한 의존성’을 간과한 영향이었습니다. 이 서비스는 오픈뱅킹 API를 통해 금융 정보를 수집하고 알림을 발송하는데, 이 과정에서 조금의 시차에 따라 오픈뱅킹 API의 응답이 달라지거나, 네트워크 지연 등으로 인해 요청이 실패할 수 있었습니다. 가령 카드사 등록 과정에서 신규 시스템에서만 등록에 실패한다면, 기존 시스템에서만 해당 카드사의 청구서가 수집됩니다. 이 경우 두 시스템 간 수집되는 데이터에 불일치가 발생하고, 결국 모바일 API 응답과 알림 내용이 일치하지 않는 문제가 발생합니다.
원인 2. 재처리 큐(Queue) 메시지 누락
다음으로 재처리 큐에 대해 설명드리겠습니다. 기존 카드 청구 금액 알림 서비스에서는 오픈뱅킹 API 요청이 실패할 경우, 재처리 큐를 통해 일정 기간 동안 성공할 때까지 요청을 반복했습니다. 이번 프로젝트에서는 메시지 큐를 RabbitMQ에서 Kafka로 전환했기 때문에, 기존 큐에 쌓여 있던 메시지는 이전하기 어려웠습니다. 따라서 데이터를 이관하기 전부터 쌓여 있던 재처리 메시지를 기존 시스템이 처리하면서 두 시스템 간 수집되는 청구서에 차이가 발생했습니다. 이로 인해 메인 화면을 조회하는 API의 응답에도 차이가 발생했고, 신규 시스템에서는 아직 발송되지 않은 청구서 도착 알림이 기존 시스템에서는 발송되는 문제가 발생하여 관련된 API와 배치 작업 모두 검증에 실패했습니다.
원인 3. 로직 개선에 따른 데이터 불일치
마지막은 앞서 잠깐 언급했던 로직 개선이었습니다. 재처리 영향 외에도 시스템을 분리하면서 청구서 수집 전략 등의 로직을 개선했기 때문에 데이터가 맞지 않는 경우가 많이 발생했습니다. 예를 들어, 기존 시스템은 오픈뱅킹에 청구서 조회를 오전 3시에 요청해서 실패했지만, 신규 시스템은 오전 8시에 요청해서 성공하는 식이었습니다. 이런 경우 재처리를 통해 고객이 수신하는 알림 건수에는 차이가 없었지만, 위의 사례와 마찬가지로 메인 화면을 조회하는 API 응답에는 일시적으로 차이가 발생하여 검증에 실패했습니다.
결국 오픈뱅킹 의존성과 재처리 등으로 인해 실시간으로 두 시스템 동작의 동일성을 검증하여 신규 시스템을 검증하기엔 어려움이 있다고 판단했고, 실시간 응답의 동일성을 확인하기보다는 시차가 있더라도 두 시스템이 결과적으로 동일하게 동작하는지 확인하는 방식으로 검증 전략을 수정했습니다.
우선 오픈뱅킹에 의존적인 모바일 API의 경우, 매일 모니터링이 가능한 정도의 표본에 대해서만 검증을 진행하기로 했습니다. 표본 고객들의 API 검증 결과를 매일 모니터링하여 하루나 이틀의 시차가 발생하더라도 결국 데이터가 잘 수집되었는지와 검증 결과가 일치하는지를 확인했습니다. 당장의 API 응답값은 불일치하더라도 결과적으로 동일한 응답이 내려갔다면 검증 결과는 ‘성공’으로 판단했습니다.
또한 배치성 작업의 경우, ‘청구서 도착’ 알림은 확인하지 않고 ‘결제 예정’ 알림만 확인하기로 했습니다. 청구서 수집 시기는 기존 시스템과 신규 시스템 간에 차이가 생길 수 있으므로, 청구서가 언제 수집되든 결제일 하루 전날까지 결제 예정 알림만 잘 발송되면 문제 없다고 판단했습니다.
이렇게 검증 전략을 수정한 이후 ‘표본 고객에 대한 모바일 API 응답값’과 고객별로 발송된 ‘결제 예정 알림 내역’을 다시 비교해 보았습니다. 그 결과 데이터 간 차이 발생 건수가 확연히 줄어들었고, 이를 토대로 오류를 수정하며 약 한 달여 간 최종 검증 작업을 진행했습니다.
롤백(Rollback) 가능성 검토
시스템 이관 작업에서 ‘롤백(Rollback)’은 신규 시스템으로 전환 후 장애 발생 시 기존 시스템으로 돌아가는 절차를 의미합니다. 만약 롤백을 진행해야 한다면, 온라인 트래픽을 기존 시스템으로 다시 전환하고 신규 시스템에서 변경된 데이터를 기존 시스템으로 덮어써야 합니다.
저희는 이관 작업을 앞두고 롤백 가능성을 검토했으나, 결과적으로 롤백이 불가능하다는 결론을 내렸습니다. 대표적인 API와 배치 사례를 통해 롤백이 어렵다는 판단에 이른 과정을 설명드리겠습니다.
롤백의 어려움 1. API
[그림 10]은 신규 시스템과 기존 시스템을 병행 운영할 경우 대표적인 CUD(Create Update Delete) API인 ‘카드사 연결 해지’가 어떤 응답을 받을지 나타냅니다. 시스템 전환 이후 고객이 특정 카드사의 연결을 해지할 경우 신규 시스템에서는 해당 요청이 성공하지만, 기존 시스템에서는 이미 해지한 카드사에 해당되어 업데이트가 실패하게 됩니다. 따라서 병행운영 중 기존시스템으로 롤백할 경우, 고객 입장에서는 해지한 카드사가 다시 등록된 것처럼 보일 수 있습니다.
롤백의 어려움 2. 배치
[그림 11]은 청구서별 고객의 결제계좌를 업데이트하는 배치를 나타냅니다. 시스템 전환 이후 배치 스케줄이 달라져 기존 오픈뱅킹 시스템에서는 먼저 결제계좌를 업데이트했지만, 신규 시스템은 아직 결제계좌를 업데이트하지 않은 경우가 발생할 수 있습니다. 이 상황에서 기존 시스템으로 롤백하고 데이터를 덮어쓰면 결제계좌가 이전 계좌로 돌아가게 되며, 다시 새로운 계좌정보로 업데이트되기까지 한 달을 기다려야 합니다.
저희는 이처럼 오픈뱅킹 의존성이 강한 서비스 특성상 섣불리 롤백을 진행할 경우 데이터 정합성이 어긋나거나 추가적인 장애를 초래할 수 있다고 판단했습니다. 따라서 시스템 전환 이후 발생하는 문제는 수정 배포를 통해 대응하기로 결정했습니다.
롤백이 불가한 상황에서 믿을 수 있는 것은 ‘신규 시스템의 신뢰성’뿐이었습니다. 저희는 신뢰성을 검증하기 위해 아래 항목들을 매일같이 확인했고, 신규 시스템에서 문제가 없을 것이라는 판단 하에 시스템 전환 작업을 진행했습니다.
✔️ 청구서 수집 스케줄에 맞춰 청구서가 잘 수집되는지
✔️ 알림 발송이 누락된 청구서가 있는지
✔️ 발송된 알림 중 결제 계좌와 청구서가 잘못 맵핑된 경우가 존재하는지
서비스 이관 경험을 통해 얻은 것
신규 시스템으로 전환된 이후, 카드 청구금액 알림 서비스는 독립적으로 배포가 가능해져 기존 오픈뱅킹 시스템의 영향에서 비교적 자유로워졌습니다. 또한, Spring MVC와 코틀린 언어를 사용함으로써 코드가 간결해지고 비즈니스 로직 파악이 쉬워져 시스템 운영 부담도 크게 줄었습니다.
시스템 이관 과정은 순탄치 않았지만 여러 어려움을 해결하며 두 가지 중요한 교훈을 얻게 되었습니다.
1. 외부 의존성에 대한 고려
이 프로젝트를 진행하기 전에 서비스 이관과 관련된 정보를 최대한 참고했습니다. 하지만 시스템 이관 작업 중 ‘오픈뱅킹 시스템에 대한 의존성’으로 인해 검증 단계에서 어려움을 겪었습니다. 이러한 외부 의존성은 개발 단계에서 작성한 테스트 코드에서도 Mock을 활용하기 때문에 그 동작을 직접 검증하기가 어렵습니다. 따라서 외부 의존성이 큰 서비스의 경우, 일반적인 검증 전략을 그대로 사용하기보다는 시스템 특성에 맞는 검증 전략을 설계하고, 이관 과정에서 해당 전략이 유효한지 꼼꼼하게 확인하는 것이 중요했습니다.
2. 시스템 이관과 서비스 로직 개선의 분리
시스템 이관이 성공적이었는지 확인하려면 새로운 시스템이 기존 시스템처럼 잘 작동하는지를 살펴봐야 합니다. 이 과정에서 비즈니스 효율화를 위한 수정이 부수적인 영향을 줄 수 있습니다. 시스템 이관 작업은 그 자체로도 큰 복잡도를 가집니다. 개발자 입장에서 많은 수정을 하고 싶은 욕심을 조금 줄이고, 가능한 한 두 단계를 분리하여 이관과 검증을 먼저 하고, 그 다음에 개선 작업을 하는 것이 복잡성을 줄이는 한 방법이 될 수 있습니다.
‘MSA 아키텍처’가 최근 몇 년 동안 소프트웨어 아키텍처의 주요 흐름 중 하나로 인기를 끌고 있습니다. 이에 따라 많은 기업이 대규모 모놀리식 시스템을 작은 단위의 독립 서비스로 분리하여 더 높은 유연성과 확장성을 고민하는 사례가 많아지고 있습니다. 저희의 경험이 마이크로 서비스로 분리하거나 대규모 시스템 이관 작업을 준비 중인 개발자분들께 조금이나마 도움이 되길 바랍니다. 긴 글 읽어주셔서 감사합니다.