devFancy BE Developer

토스 SLASH 24 Server '분산 트랜잭션과 지속 가능한 마이그레이션 전략' 정리

2025-09-24
devFancy

토스뱅크에서 Server Developer 개발자분들이 발표한 영상을 듣고 유익한 내용이 있어서 포스팅으로 정리하게 되었습니다.


보상 트랜잭션으로 분산 환경에서도 안전하게 환전하기

문제 정의 및 동기

  • 원화 계좌 서버와 외화 계좌 서버가 분리되어 있고, 서로 다른 DB를 바라보고 있다면 이 과정(커밋-롤백 처리)이 복잡해진다.

  • 환전 서버가 원화 및 외화 계좌 서버에 입출금 요청을 보내는데, 입금 또는 출금이 실패하게 된다면, 은행 또는 고객에게 큰 피해를 줄 수 있다.

  • 따라서 환전의 원자성을 보장하기 위해 분산 트랜잭션 처리가 이뤄져야 한다.

2PC vs SAGA 분산 트랜잭션 비교

  • 분산 트랜잭션 방법에는 두 가지 방법이 있다.

2PC vs SAGA 패턴

2PC의 경우 Voting -> Commit 의 2단계로 이루어진다.

  • Voting 단계에서는 코디네이터가 각 트레이션 참여자들에게 커밋 가능 여부를 질의한다.

    • 각 트랜잭션 참여자들은 트랜잭션을 열고 커밋 가능 여부를 응답하게 된다. (ack)
  • Commit 단계에서는 모든 참여자들이 커밋 가능 여부라고 응답할 경우에는 코디네이터가 커밋 요청을 보내 트랜잭션을 성공으로 종료한다. (commit)

    • 하지만 단 하나의 서비스라도 트랜잭션 커밋 불가능 응답을 했을 경우에는 코디네이터가 롤백 요청을 보내 트랜잭션을 실패로 종료한다. (rollback)

SAGA 패턴의 경우 각 서비스의 작은 트랜잭션들을 실행하면서 진행하고, 특정 단계에서 실패하면 보상 트랜잭션 이 실행된다.

SAGA 패턴을 도입한 이유는 다음과 같다.

  • 각 서비스들의 로컬 트랜잭션들만 진행한다는 점에서 높은 가용성과 확장성을 가진다. (장점)

  • 환전 서비스가 높은 트래픽을 견뎌야 하고 카드나 회계등 다양한 트랜잭션 참여자들이 추가될 수 있다는 점에서 SAGA 패턴을 선택했다.

사가 패턴의 종류는 코레오그래피 사가오케스트레이션 사가가 있다.

  • 코레오그래피 사가: 중앙제어자 없이 메시지 브로커를 통해 이벤트를 교환하며 진행하는 방식

    • 장점: 중앙 제어자가 없기 때문에, 단일 장애지점이 없고 각 서비스들이 느슨하게 결합된다는 장점이 있다.

    • 단점: 현재 진행중인 트랜잭션의 상태를 추적하거나 디버깅하기 어렵다는 단점이 있다.

  • 오케스트레이션 사가: 오케스트레이터가 각 서비스들에 트랜잭션과 보상 트랜잭션을 명령하며 진행하는 방식

    • 장점: 현재 진행중인 상태를 추적하기 쉽다는 장점이 있다.

    • 단점: 오케스트레이터가 단일장애지점이 되고 모든 서비스에게 결합된다는 단점이 있다.

  • 오케스트레이션 방식을 선택한 이유: 클라이언트 요청을 받아 환전을 시작하는 환전 서버가 필요했고, 현재 진행중인 환전 상태를 관리해 있기 때문이다.

  • 예를 들어, 환전 한도를 고려하려면 현재 진행중인 환전의 금액과 상태도 추적이 필요했다. 따라서 환전 서버가 오케스트라가 되는 것이 적합하다고 판단했다.

SAGA를 이용한 환전 구현

입출금 실패에는 크게 정상적인 실패와 비정상적인 실패(에러)로 구분된다.

  • 정상적인 실패: 잔액 부족, 잔액 증명서 출력, 계좌 해지 등

  • 비정상적인 실패(에러): 서버 에러(5XX 응답/타임아웃), 네트워크 에러, 메시지 프로듀서/컨슈머 실패

HTTP vs Messaging

입출금 요청에는 HTTP, 메시징 방식이 존재하는데, 이 두 가지 방식을 사용했다.

  • 메시징 방식은 비동기로 동작하고 서비스끼리 느슨하게 결합된다는 장점이 있다. 그리고 재시도와 같은 에러 핸들링을 메시지 브로커 레벨에서 지원하여 결과적 정합성을 보장하는데 유리하다.

  • 그래서 대부분 SAGA 패턴의 경우, 메시징 방식으로 구현된다.

입급과 출금은 HTTP 방식으로 이루어진다.

  • 첫 번째 이유는 출금 결과를 알고 입금으로 넘어가야 하기 때문에 동기 방식인 HTTP 를 적용했다.

  • 두 번째 이유는 유저가 환전이 즉시 완료되기를 기대하기 때문이다. 만약 예상외로 입출금이 너무 길어지면 환전이 지연됐다는 것을 유저에게 알려줘야 되는데, 이런 경우에는 타임아웃 기능이 필요하다. 타임아웃을 비동기로 메시징으로 구현하려면 입출금 결과를 다시 메시지로 받는 등 폴링하는 등 구현이 복잡해진다.

반면 출금 취소는 메시징 방식을 사용했다.

  • 출금 취소는 환전의 마지막 과정이며, 유저가 이 부분은 기다릴 필요가 없기 때문이다.

  • 출금 취소 또한 실패할 수 있다. → 이런 에러 핸들링을 메시지 브로커와 원화 계좌 컨슈머에 위임하여 결과적 정합성을 보장에 유리하기기 때문에, 출금 취소는 메시징 방식을 사용했다.

비정상적인 실패(에러 핸들링)

  • 환전 서버에서 원화 계좌 서버로 출금 요청을 보내는데 서버 에러나 타임아웃이 발생하게 되면 → 정상적인 실패로 보기 어렵다.

  • 이유는 입출금은 성공했는데 그 이후 동작에서 에러가 발생했거나 지연되어서 처리될 수 있기 때문이다.

  • 이런 경우에는 출금 결과를 다시 확인해서 그 결과에 따른 후속 처리를 하게 된다.

  • 예를 들어, 출금이 성공했더라면 보상 트랜잭션인 출금 취소 처리를 하고, 출금이 실패했더라면 환전을 실패처리할 수 있다.

  • 상대 계좌 서버나 네트워크에 문제가 생겨 입출금 요청에 에러가 발생한 경우에는 입출금 결과 확인 요청도 실패할 확률이 높다.

  • 이런 경우에는 어떻게 처리해야할까?

  • 메시지를 지연시켜 발행시켜줄 수 있는 카프카 메시지 스케줄러 라는 서버가 존재한다.

  • 그런데 지연 시간을 넣어 메시지를 발행하면 별도의 지연 토픽으로 메시지가 카프카 메시지 스케줄러로 전달된다.

  • 카프카 메시지 스케줄러는 지연 시간 만큼 보낸 후 원래의 토픽 메시지를 대신 발행하여 컨슈머가 지연 시간 뒤에 메시지를 가져갈 수 있도록 한다.

  • 이때 프로듀서와 컨슈머 모두 ‘환전’ 서버가 된다면 특정 동작을 지연 시간만큼 뒤로 예약하는 효과가 된다. 이 기능을 활용하여 상대 입출금 계좌 서버에 회복할 시간을 줄 수 있다.

  • 예를 들어 환전 서버원화 계좌 서버로 출금 결과 확인에 실패했을 때 그 즉시 재확인을 시도하는 것이 아니라 카프카 메시지 스케줄러를 통해 30초만큼 환전을 지연시킨 후출금 결과 확인을 재시도할 수 있다.

  • 만약 실패하는 경우, 이번에는 환전 지연 시간을 1분으로 늘려서 출금 결과 확인을 재시도할 수 있다.

  • 원화 계좌 서버에게 회복할 시간을 조금이라도 더 주는 효과가 된다.

  • 정해진 횟수를 모두 초과하는 경우에도 개발자가 문제 해결을 확인 후 수동으로 메시지를 다시 발행할 수 있어 출금 결과를 확인할 수 있다.

  • 만약 만약 환전 서버의 문제로 환전 지연 이벤트를 발행하지도 못하고 서버가 죽은 경우 → 배치 재처리를 한다.

  • 오케스트레이터된 환전 서버가 환전 트랜잭션의 마지막 상태를 저장하고 있기 때문에, 중단된 상태부터 환전 재시작이 가능하다.

    • 예를 들어 출금이 성공하고 멈춰버린 환전의 경우에는 배치가 출금 취소 메시지를 발행한 후에 환전을 실패 처리할 수 있다.
  • 정리하면, 출금 요청에서 에러가 발생했을 때 (1) 기본적으로 입출금 결과를 다시 확인하여 처리하고,

    • 입출금 결과 확인해도 실패했을 때는 (2) 환전 지연을 통해서 원화 계좌 서버에게 회복할 시간을 주게 된다.

    • 그런데 이런 환전 지연까지 못했을 경우에는 최종적으로 (3) 배치를 통해 환전을 재처리하게 된다.

  • 토스뱅크는 카프카 메시지의 결과적 정합성을 보장하고 있다.

  • 원화 계좌 서버가 출금 취소를 처리하다가 에러가 발생하게 되면 컨슈머 DL 메시지 브로커를 통해 메시지를 DL 서버로 전달한다.

  • 그리고 DL 서버는 정해진 재시도 횟수와 간격으로 서비스 메시지 브로커로 메시지를 다시 전달하여 원화 계좌의 출금 취소 재시도를 실행한다.

  • 만약 정해진 횟수를 모두 실패하는 경우에는 개발자가 코드 수정 등 원화 계좌 서버의 정상화를 확인한 후에 DL 서버를 통해 다시 메시지를 발행할 수 있다.

  • SAGA 패턴에서는 위와 같은 방식을 Trasnsactional Messaging 방식이라 부른다.

    • Local Transaction commit 과 Message 발행이 원자적으로 이루어져야 한다.

    • 즉, 입금 실패로 인한 환전 실패 처리와 출금 취소 메시지 발행은 항상 같이 이뤄저야 한다.

  • 그런데 서비스 메시지 브로커 장애 등으로 메시지 발행 자체가 안되면 이것을 어떻게 보장할 수 있을까?

  • 트랜잭셔널 메시징을 보장하는 방법에는 트랜잭셔널 아웃박스 패턴 등 다양한 방법들이 알려져 있지만, 토스뱅크에서는 프로듀서 데드 레터(PDL) 를 이용하고 있다.

  • 환전 서버가 서비스 메시지 브로커의 장애로 메시지 발행에 실패했을 경우에는 프로듀서 데드 레터로 메시지를 발행하여 DL 서버로 전달한다.

  • 그리고 DL 서버는 일정 시간이 흐른 후 회복된 서비스 메시지 브로커로 메시지를 다시 전달하여 원화계좌 컨슈머가 가져갈 수 있도록 한다.

모니터링

  • 오케스트레이션 SAGA에서 오케스트레이터는 각 트랜잭션 상태별 명령이 정해져 있기 때문에 State Machine 으로 나타내곤 한다.

  • 위와 같은 환전 플로우는 State Machine 이 된다.

  • 이때 데이터는 위와 같이 적용된다.

  • 환율, 환전 금액 등 환전 요청은 exchange_request 이라는 테이블에 스냅샷 형태로 저장된다.

  • 그리고 환전이 거쳐가는 상태들은 exchange_state_log 이라는 테이블에 변경이 아닌 추가 삽입으로만 저장된다.

  • 이렇게 저장했을 때 장점은 현재 상태 뿐만 아니라 환전이 거쳐가는 모든 상태를 확인 가능하다는 점이다.

  • 예를 들어, 환전 시작 후 출금 실패로 끝난 환전과 출금 성공 후 입금이 실패하여 출금이 취소된 환전을 구분하여 모니터링이 가능하다.

  • 중간에 멈춰버린 환전이 없다는 것도 모니터링이 필요하다.

  • 환전 서버는 오케스트레이터이기 때문에 모든 환전들의 상태를 관리하고 있다.

  • 따라서 일정 시간이 흐른 뒤에도 끝나지 않은 환전들을 탐지할 수 있다. 이는 개발자들에게 Alert(알림) 를 통해 알려주게 된다.

  • 계좌 서버들에는 입출금의 쌍이 맞는지를 비교할 수 있다.

  • 계좌 서버들은 각각 입출금 하나씩만 알 수 있기 때문에 이 두 개를 같이 보는 것이 필요하다. 원화계좌 입출금 내역과 외화계좌 입출금 내역은 ETL를 통해 분석 목적의 정보계 데이터베이스로 주기적으로 적재된다.

  • 그리고 이 정보계 DB를 바라보는 배치가 입출금을 묶어주는 키로 쌍이 맞는지를 지속적으로 체크할 수 있다.

  • 이것을 입금 또는 출금만 된 환전을 탐지할 수 있다.

결론 및 성과

  • 앞서 SAGA 패턴의 장점으로 확장성을 얘기했다. 토스뱅크에서는 매일 발생하는 입금과 출금을 기록하여 회계처리를 하게된다. 즉 회계 서버가 환전 트랜잭션에 참여자로 추가된다고 볼 수 있다.

  • 만약 이 부분을 2PC(투 페이지 커밋)으로 구현했다면 원화 계좌 서버와 외화 계좌 서버가 트랜잭션을 열고 회계 서버의 투표까지 기다리는 등 가용성 및 성능 문제가 있다. 또한 회계 서버에 장애가 났을 때 유저가 환전을 못하는 등에 서비스 간 강결합 문제도 존재한다.

  • 하지만 우리는 SAGA 패턴을 사용했기 때문에 원화 및 외화계좌 서버가 메시지를 발행하는 방식으로 쉽게 확장할 수 있었다.

Trade Off

  • 트랜잭션 구현의 복잡도는 증가하는 단점이 있지만, MSA로 전환하면서 기존 계정계 시스템이 가지는 강결합 문제를 해결하여 다른 계정계 시스템과의 느슨한 결합과 확장 가능한 구조로 전환하는 장점이 있다.

토스뱅크가 차세대를 하지 않는 이유: 지속 가능한 마이그레이션 전략

마이그레이션 사이클은 아래와 같은 순서로 진행된다.

  • (1) 대상 선정 -> (2) 분석 -> (3) 설계 -> (4) 구현 -> (5) 검증

1단계. 대상 선정

  • 1단계인 대상 선정에서 대출 상환 도메인을 선정했고, 해당 도메인에서 복잡도가 낮은 UseCase 부터 순차적으로 전환하기로 결정했다.

2단계. 분석

2단계인 분석에서는 연역적 분석과 귀납적 분석이 있다.

  • 연역적 분석은 일반적인 원칙이나 이론을 바탕으로 구체적인 사례나 결론을 도출하는 분석 방법이다.

    • 도메인 분석: 도메인 지식 학습 및 관련 회사 규정, 상품 약관, 법률 등 분석

    • 대전제 설정: 학습한 도메인 지식을 기반으로 대전제 설정

  • 귀납적 분석은 구체적인 사례나 데이터를 통해 일반적인 원칙이나 이론을 도출하는 분석 방법이다.

    • 기존 시스템 분석: 소스코드와 데이터 흐름 분석, 함수 호출 및 I/O 추적

    • 검증 및 보완: 분석한 정보를 바탕으로 대전제를 검증하고 추가 전제를 설정

  • 귀납적 분석은 정적 분석과 동적 분석으로 나뉜다.

  • 정적 분석으로는 함수 호출 그래프 분석이 있다. 함수간의 호출을 시각화하여 전체 시스템 구조를 쉽게 파악할 수 있다.

  • 동적 분석으로는 Kafka를 통해 메서드별 I/O 수집을 통한 기존 시스템 분석 및 활용이 있다.

3단계. 설계

3단계 설계 과정에서는 분석 결과를 바탕으로 설계 문서를 작성하여 마이그레이션의 핵심 가이드로 활용한다.

대표적으로 도메인 캡슐화를 통해 데이터를 표준화하는 방법, 테스트 케이스 작성, 도메인 문서화가 있다.

  • 도메인 캡슐화를 통해 데이터를 표준화하는 방법: 다양한 형태의 데이터 타입을 도메인 캡슐화를 통해 효율적으로 데이터를 관리하며, 중요한 도메인은 별도로 캡슐화하여 가독성 및 데이터 관리 용이성을 높인다.

  • 테스트 케이스 작성: 분석 과정을 통해 도출한 도메인을 기반으로 예상 가능한 모든 테스트 케이스와 시나리오를 작성한다. 그 후에 단위 테스트코드를 구현하여 도메인 로직을 더욱 견고하게 만들어 도메인의 안정성과 신뢰성을 확보하게 된다. 대출 상환 도메인 에서는 테스트 커버리지를 90% 를 유지하고 있다.

  • 도메인 문서화: 문서를 잘 작성하고 꾸준히 관리해야하는 이유는 마이그레이션이 일회성 작업이 아니라 지속적으로 관리되고 유지되어야 한다. 담당자가 바뀌더라도 빠르게 파악하고 최적화된 로직을 꾸준히 유지될 수 있도록 도와준다. 효과적으로 문서화를 하기 위해서는 글 뿐만 아니라 다이어그램을 활용하면 좋다. 이는 팀원들이 쉽게 이해할 수 있고, 의사소통이 원활해지며 개발속도와 완성도가 높아진다. 또한 잘 작성된 문서화는 비즈니스 로직을 한 눈에 파악할 수 있게 되었다.

Review

이번 토스 SLASH 24 영상들을 통해 분산 트랜잭션의 복잡성과 대규모 시스템 마이그레이션 전략의 중요성에 대해 이해할 수 있었다.

나머지 영상도 여유 있을 때 찾아보고 유익한 내용이 있다면, 추가로 업로드할 예정이다.

Reference


Recommend

Index