본문 바로가기

Programming/프로그래밍 내용 정리

[내용정리] 토스 SLASH 22 - 토스뱅크의 완전히 새로운 대출 시스템

https://youtu.be/SLamxuykpnw

 

 

 

토스뱅크는 마이크로 서비스의 장점을 그대로 가져오면서

전통적인 금융시스템과의 연계를 새롭게 디자인했다.

 

마이크로서비스 아키텍처로 구성된 '서비스 서버'

모놀리틱으로 구성된 '코어뱅킹 서버'를 

각각 독립된 API 서비스로 정의했다.

 

전문방식이 아닌 HTTP API 통신방식이 필요할 경우

코어뱅킹 시스템과 통신하면서

독립된 마이크로 서비스가 비즈니스 로직을 직접 해결할 수 있도록 구성했다.

 

 


 

 

비즈니스 로직을 처리하는 시스템 구조 뿐 아니라

KCB, NICE와 같은 대외기관과의 통신구조도 새롭게 디자인 했다.

 

대출이 실행되기 위해서는 고객의 신용정보가 필요하다.

신용정보는 허가받은 기관과의 통신을 통해서 사용할 수 있다.

 

또한 현재 고객이 가지고 있는 대출정보와

안전한 대출 실행을 위해 필요한 부가정보 등은

대외기관과의 연동을 통해서 가져올 수 있다.

 

대출 심사를 고도화 하는 과정에서 관계된 기관들이 늘어났고

이와 함께 심사 응답속도도 함께 늘어나는 문제가 발생했다.

 

기존 금융권에서는 여러 기관을 동시에 호출하는 비동기 처리로 이 문제를 해결했다.

 

하지만 사용자수가 갑자기 증가하게 되면 

대외기간이 처리할 수 있는 트래픽의 양보다 많은 요청을 동시에 보내게 되어

결국 상대 대외기관의 시스템이 마비되는 현상이 자주 발생했다.

 

토스뱅크는 대외기관이 견딜 수 있을 만큼의 트래픽을 처리하고,

유입되는 사용자 수에 맞게 동적으로 요청을 처리할 수 있는 유량제어 시스템을 도입했다.

 

이 시스템은 갑자기 많은 사용자가 토스뱅크에 진입하더라도

사용자는 안정적으로 응답을 받아볼 수 있도록 설계했다.

 

 


 

비즈니스 로직을 처리하기 위한 관계형 데이터베이스로는 Mysql,

이벤트 처리를 위한 메세지 서비스로는 Kafka,

유량제어와 캐시처리는 Redis를 활용했다.

 

 


 

마이크로 서비스 아키텍처 기반으로 구성된 대출 시스템

고전적인 금융 시스템과는 다른 데이터 흐름을 가지고 있다.

 

마이크로 서비스 구성에 맞게

독립된 서비스마다 독립된 스키마를 가지게 되고

필요에 따라서 API로 데이터를 요청하며 비즈니스 문제를 해결한다.

 

코어뱅킹 시스템 역시 독립된 서비스로서

API 통신을 통해 데이터를 주고 받을 수 있다.

 

대출이 실행되기 까지는 다양한 정보가 필요하고 

각 비즈니스 로직에서 구축된 데이터베이스 정보와 더불어

코어뱅킹에서 제공하는 정보를 적절하게 활용할 수 있다.

 

 


 

대출이 실행되기까지는 사용자 서버, 약관 서버, 상품 서버 등이 필요하다. 

 

그리고 대출 서버마다 독립된 스키마를 가지게 되었기 때문에

관리의 문제가 발생하게 된다.

 

협업을 위해서는

- 서버에 구성된 스키마를 언제든지 복제하여 생성하고 

- 버전 컨트롤 시스템(VCS)과 연동되어

- DDL쿼리의 버전이 관리되어야 하며

- 비즈니스 로직과 스키마의 동기를 구현해야 한다.

 


 

 

비즈니스 로직을 독립된 마이크로 서비스 내에서 직접 구현하고 있기 때문에

토스와 같은 애자일 조직에서는 데이터 스키마가 끊임없이 변화해야 한다.

 

하지만 데이터베이스의 잦은 스키마 변화는 협업을 어렵게 하고 오류가 발생할 수 있는 작업이다.

 

토스는 협업을 위한 스키마 관리 문제를 어떻게 해결했을까?

 

해결을 위해 토스뱅크는 시스템 구축 초기부터

Flyway라는 데이터베이스 마이그레이션 툴

Hibernate의 DDL validation을 적용하는 방법을 택했다.

 

Flyway데이터베이스 형상관리 툴이다.

버전 기반의 데이터베이스 마이그레이션 스크립트를 SQL파일로 관리할 수 있으며

Git과 같은 버전관리시스템에서 소스코드와 함께 SQL파일들이 관리된다.

 

버전관리시스템에서 관리된다는 것은 여러 장점을 갖는다.

장점 중 하나는 DDL, DML, SQL문을 서비스 로직과 함께 코드리뷰 할 수 있다는 점이다.

또한 서비스 로직과 데이터베이스 간의 동기화를 버전컨트롤시스템을 통해서 유지할 수 있다.

 


어떻게 버전컨트롤시스템과 연동하여 서비스 로직과 데이터베이스 간의 동기화가 유지될까?

 

예를들어, 재슬의 PC와 우진의 PC는 각각 로컬 데이터베이스를 가지고 있다고 해보겠다.

개발, 테스트 스테이지에도 각각 독립된 데이터 베이스를 가지고 있다.

 

현재 로컬에서 개발중이 피처 브랜치는 

아직 개발과 테스트에 반영되지 않은 버전 16, 17번의 데이터베이스 형상이 존재한다.

 

 

 

 

버전 1번부터 버전 17번까지 각각은 버전컨트롤시스템에 독립된 SQL 파일로 존재하고 있다.

 

테스트 스테이지에는 버전 1번부터 14번까지 SQL 파일이 데이터베이스에 적용되었으며, 

개발 스테이지에는 버전 1번부터 15번까지의 SQL 파일이 데이터베이스에 적용되었다.

 

재슬의 PC에 있는 피처브랜치가 개발브랜치에 머지된다면,

재슬 브랜치에 존재하는 버전 16번 SQL 파일이 개발 브랜치에 적용되며,

개발 데이터베이스는 flyway 동작을 통해서 개발 브랜치와 동일한 데이터베이스 형상을 가지게 된다. 

 

 

 

 

 

또한, 개발 브랜치가 테스트 브랜치에 머지가 된다면

아직 적용되지 않은 버전 15번, 16번 SQL 파일들이 flyway를 통해 마이그레이션 되며

테스트 브랜치 서비스 로직에 있는 데이터베이스 형상을 가지게 된다.

 

이렇게 flyway를 잘 세팅해 둔다면, 

협업을 하기 위해 새로운 개발자가 로컬에 데이터베이스를 만들어서 테스트 해보고 싶을 때

서버만 구동하면 해당 브랜치의 형상으로 데이터베이스 세팅이 완료된다.

 

또한 서비스의 배포와 함께 데이터베이스 마이그레이션이 함께 진행되기 때문에

실수로 데이터 베이스 형상을 반영하지 않고 서비스 로직이 배포되는 참사를 막을 수 있다.

 

 

 

 

 

각 스테이지 버전별 데이터베이스 형상관리 뿐 아니라 CI 상황에서도 flyway를 활용할 수 있다.

 

flyway와 같은 데이터베이스 마이그레이션 전략이 없다면

빌드 과정 integration 테스트 중 데이터베이스와 연계하는 부분을 mocking처리하거나 In-memory 방식으로 처리해야 한다.

 

하지만 flyway와 같이 데이터베이스 마이그레이션 전략이 존재한다면

직접 데이터베이스를 연계해서 처리할 수 있기 때문에

트랜잭션 혹은 데이터베이스 예외처리를 integration 테스트를 통해서 반복적으로 확인할 수 있다.

 

테스트가 시작되기 전 시스템 내에서 사용할 mysql을 실행하고, 

integration 테스트와 flyway가 동작할 일회용 테스트 스키마를 생성한다.

 

그러면 integration 테스트가 시작되기 전 spring context가 로딩될 때 

flyway를 통해서 자동으로 최신 데이터베이스 형상이 생성되며

레포지토리에 대한 mocking 처리 없이 직접 데이터베이스를 연계한 테스트를 진행할 수 있다.

 

 


 

 

토스뱅크 버전 컨트롤 시스템과 flyway를 활용해서 서비스 로직과 동기화할 수 있는

데이터베이스 마이그레이션 전략을 가지게 되었으나

이를 그대로 운영 시스템에 적용하지는 않았다.

 

서버 개발자가 작성한 DDL, DML 스크립트는 운영환경에서 치명적일 수 있기 때문에

버전컨트롤 시스템에서 관리되는 sql 파일을 기반으로 DBA에게 작업을 요청하고 운영배포를 진행하고 있다.

 

flyway를 통해서 많은 이점을 얻었지만

정작 라이브환경에서 데이터베이스 형상과 서비스 로직의 차이로 인한 참사를 막을 수 없다면 적용에 의미가 없다.

 

 

 

 

그래서 토스뱅크는 Hibernate DDL 기능 중 Validate 옵션을 통해 문제를 해결했다.

Hibernate DDL은 정의된 entity를 데이터베이스로 생성해주는

flyway와 비슷한 데이터베이스 마이그레이션을 제공하는 전략하는 툴이다.

 

하지만 이미 더 강력한 마이그레이션 툴인 flyway를 통해서

정확한 sql문을 활용해 마이그레이션을 완료했기 때문에 실제 Hibernate의 마이그레이션 전략까지 적용하는 것은 무의미하다.

때문에 현재 정의된 서비스 로직에서의 data class 엔티티가 

데이터베이스의 스키마가 정의가 동일한지 확인하는 validate 옵션만 활용해서 문제를 해결했다.

 

만약 데이터베이스 스키마와 데이터 클래스 엔티티간의 동기화가 맞지 않다면 서비스가 시작되지 않을 것이고

잘못된 서버로 요청이 들어가는 것을 사전에 방지할 수 있다.

 

토스뱅크의 대출 서비스는 flyway와 hibernate DDL validate 옵션을 통해 안정적인 데이터베이스 마이그레이션 전략을 적용했다.

 

 


하지만 금융 도메인의 대출 시스템은

다른 서비스와 다르게 자체적인 서비스 구현만으로 제공할 수 있는 서비스가 아니다.

 

고객의 신용평가와 소득산출을 위해서는 여러 대외기관과의 협업이 필요하다.

특히 개인들의 신용정보를 제공하는 NICE, KCB와 같은 신용평가사와의 연동,

그리고 개인의 소득을 평가할 수 있는 데이터를 제공하는 신정원과 같은 많은 대외기관과의 연동이 필수이다.

 

과거 새로운 은행이 오픈하고 나면, 대외기관들이 신규 은행의 트래픽을 감당하지 못해

많은 트래픽이 대외기관에 유입되어 한동안 대출 신청이 동작하지 않는 등 문제가 생기는 일이 비일비재 했다.

 

토스뱅크는 구축 초기부터 대외기관들이 견딜 수 있을 만큼의 트래픽을 전달하는 파이프라인과 유량제어 시스템을 적용했다.

 

 

 

 

토스뱅크로 유입되는 요청들을 그대로 한꺼번에 대외기관에 보낸다면

대외기관의 성능에 과부하가 걸리게 된다.

 

대외기관의 성능과 토스뱅크의 신청시스템을 분리하기 위해

파이프라인 도입이 필수이다.

 

고객의 대외기관 요청을 파이프라인화 하기 위해 kafka를 도입했다.

kafka와 세팅된 파티션을 통해서 고객의 요청은 파이프라인에 배정된다.

 

각 파티션에 1대1로 대응되는 컨슈머를 배정했고,

각 컨슈머에는 메세지를 처리할 수 있는 독립된 스레드 풀을 배정했다.

 

스레드 풀 사이즈는 대외기관의 상태에 따라 dynamic하게 조절되며

기본적으로는 대외기관이 토스뱅크를 위해 알려준 기본성능으로 세팅되어 있다.

 

많은 트래픽이 한꺼번에 들어온다 할지라도

파이프라인과 정해진 스레드 풀을 통해서 요청이 나가게 된다면

대외기관이 견딜 수 있을 정도의 TPS만 전달된다.

 

 

컨슈머와 연동된 스레드풀은 

현재 서버의 상태에 맞게 동적으로 사이즈를 조절할 수 있는 풀이다.

대외기관의 TPS는 한정적이고

kafka로 구성된 파이프라인과 동적으로 구성가능한 스레드 풀로도 충분히 컨트롤 할 수 있는 IO구간이다.

 

정확한 TPS 측정을 위해서 API 호출은 동기호출로 구성했다.

스레드 풀 세팅은 Coroutine에서 제공하는 Fixed Thread Pool을 활용해서

각기 다른 사이즈를 가진 스레드풀을 array로 선언하여

필요에 따라서 사이즈가 크거나 작은 스레드 풀을 동적으로 선택할 수 있도록 했다.

 

사용자의 유입이 증가할 경우, 고객의 신청 대기시간을 최소화 하기 위해

동적으로 사이즈를 조정하여 대외기관이 받을 수 있는 최대의 요청 수를 활용했다.

 

 

 

 

 

이러한 구성은 대외기관마다 다르게 세팅된다.

각 기관마다 최적화된 요청수가 다르기 때문이다.

그에 따라 사전 세팅에 기관별로 연동을 최적화할 수 있다.

 

하지만 대외기관과의 통신이 파이프라인화 되었다고 할지라도 

사용자가 급격하게 증가하게 되면 사용자는 여전히 오랜시간동안 심사가 완료되기까지 기다려야 한다.

 

또한 대외기관이 장애를 일으킬 수 있기 때문에

고객이 대출을 실행하기까지 문제에 직면하지 않도록

해당기관의 대체 기관을 유동적으로 활용할 수 있어야 한다.

 

 


그래서 토스뱅크는 오픈 초기부터 유량제어 시스템을 적용했다. 

 

 

일반적인 서킷의 동작방식은 

API 실패율과 요청 수에 기반하여 요청을 전달하지 않도록 서킷을 오픈하거나

일부만 요청을 흘려서 서킷의 상태를 확인할 수 있도록 half open 처리하면 된다. 

 

하지만 대출신청 시스템은 대출이 실행되기까지 여러 단계를 거쳐야 하므로

중간 단계에서의 서킷의 동작으로부터 앞 단계의 서킷이 서로 연동하면서 동작하여

고객이 대출을 실행하기까지의 불필요한 과정을 거치지 않도록 처리 했다.

 

 

이런 상호연동 가능한 유량제어 시스템은 

각 단계별 정확한 통계치를 측정해야 동작할 수 있다.

 

 


 

그러면 토스뱅크는 어떤 방식으로 지표를 수집하고 고객의 유입을 제어했을까?

 

토스뱅크의 몯든 서비스 서버는 100% 코틀린으로 작성 되었으며,

코루틴과 웹클라이언트 그리고 레디스를 활용한

non-blocking 지표 수집 시스템을 구축했다.

 

지표 수집 자체가 서비스에 영향을 주면 안되고

IO 기반의 작업이기 때문에, IO 시간동안 request 스레드를 점유하지 않는 non-blocking한 아키텍처를 적용하게 되면

스레드의 불필요한 점유없이 시스템을 처리할 수 있다.

 

Redis 역시 in-memory 기반으로 빠르게 응답성을 보장하고

1분 단위의 지표를 적재해서 처리하기에 최적의 데이터 저장소이다.

 

대외기관의 처리시간을 측정하기 위해 서버는

대외기관의 파이프라인 적재 직전 이벤트의 시작 시간을 Redis에 기록하고

대외기관과 연동이 끝난 뒤 응답완료 시간과 성공실패 여부를 Redis에 기록한다.

 

분 단위로 처리된 Redis의 키 값에는 누적된 요청 수와 누적된 응답시간

그리고 성공여부가 기록되고 이러한 데이터는 Redis에서 mysql에 분 단위 통계적 지표로 쌓이게 된다.

쌓인 데이터를 통해 대외기관의 심사시간, TPS의 증가여부, 대외기관의 실패율을 판단할 수 있게 된다.

 

만약 신용정보원, KCB, NICE와 같은 대외기관이

약속된 TPS 보다 성능이 떨어지게되고  실패율이 점차 증가하게 된다면

대외기관 파이프라인에 있는 dynamic 풀의 사이즈를 조정하여

대외기관이 견딜 수 있을 수준의 요청수로 조정한다.

 

요청수를 조정했음에도 신용정보원에서 제공하는 마이데이터 API의 실패율이 일정비율로 증가하게 된다면

계속해서 마이데이터 API를 활용하는 것은 좋지 않은 고객 경험으로 이어진다.

 

만약 고객이 앱 스크래핑 가능한 인증서를 보유했다면

상호보완이 가능한 대체 기관에서 정보를 가져올 수 있도록 마이데이터 서킷을 half open 처리해서 앱 스크래핑으로 전환한다.

 

마이데이터가 온전히 제 기능을 하지 못한다면

마이데이터 서킷을 open하고 모든 고객이 인증서를 통한 앱 스크래핑으로 강제 전환하게 된다.

 

 


 

상호보완 가능한 기관가는 다르게 KCB와 NICE처럼 신용평가에 반드시 필요한 기관에서 문제가 발생한다면 어떻게 할까?

 

해당 정보 없이는 대출을 진행할 수 없고, 보완 가능한 대외기관이 존재하지 않기 때문에

대외기관의 서킷을 open하고 고객의 대출상품 전체의 진입 서킷도 연동해서 같이 open해서

고객의 대출상품 진입을 차단하게 된다.

 

대외기관의 응답속도 뿐만 아니라, 응답에 대한 실패율 등을 종합적으로 판단하여

고객이 경험이 저하되는 순간에

각 대외기관의 서킷이 open / half open / close 여부를 판단하게 되고

대출상품 전체의 유량 제어 서킷과 연동할 수 있다.

 

적절한 TPS로 대외기관을 향해서 요청을 보내고 있기 때문에

토스뱅크로 인해서 대외기관의 문제가 발생하는 일은 거의 없다.

 

대부분의 경우는 굉장히 많은 사용자가 진입해서

사용자 수의 유량을 제어하는데 쓰이거나

대외기관의 자체적인 문제가 발생했을 시 서킷을 차단하는데 사용하게 된다.

 


 

특히 사용자가 많이 진입할 경우, 고객의 경험이 저하되기 때문에

이를 보완하기 위한 유량제어 시스템과 함께 대기열 시스템을 도입했다.

 

대기열 시스템은 대출 신청 시스템에 진입하기 위해서 반드시 입장 티켓을 발급받아야만 진입할 수 있는 시스템이다.

 

크게 대기열과 입장열 2개의 Queue로 구성할 수 있다.

대기열 시스템이 동작하게 되면, 모든 고객은 대기열에 입장하게 되며

일정한 주기에 따라 정해진 티켓 수만 발급하여 고객을 입장여로 이동시킨다. 

 

입장열에 들어온 고객만이 대출신청 시스템에 진입할 수 있다.

일정한 주기에 따라 정해진 수만 입장열로 입장하기 때문에

시스템에 진입할 수 있는 사용자 수의 속도를 제한할 수 있다.

 

대외기관에 처리되어있는 파이프라인의 Lag의 임계치가 증가하게 되거나

고객이 심사 결과를 받게 되는 시간이 임계시간을 넘어가게 되었을 때 대기열 시스템이 동작하게 된다.

 

대기열 시스템은 Redis에서 제공하는 ZSet을 활용하여 해결했다.

 

대기열에 입장하는 수는 현재 진행 중인 서킷의 지표에 따라

배압으로 다이나믹하게 관리된다.

대기열에 입장된 고객은 현재 배압으로 정해진 수에 따라 n명씩 입장열로 이동하게 되고

한 번 입장열에 진입한 고객은 지속해서 심사 시스템을 사용할 수 있다.

 

 


2022년 1월 1일 토스뱅크의 대출 재개는 많은 고객들의 호응 속에 이뤄졌다.

유량제어 시스템도 이 때 작동했다.

 

실제로 굉장히 많은 사용자가 1월 1일 오픈 시간에 맞춰 진입했다.

대외기관에 파이프라인을 구축해뒀던 토스뱅크는

많은 고객의 진입에 따른 트래픽을 정해진 TPS에 맞게 대외기관으로 전달했다.

 

그럼에도 이미 진입해있던 고객들로 인해 

신규 진입하는 고객들은 대출 심사 결과를 받기까지 오랜 시간을 기다려야만 했다.

 

앱 스크래핑을 통해 결과를 받아볼 수 있는 고객은 곧바로 이 방식으로 전환했다.

사용자수는 계속해서 늘어났고 최종적으로 대기열 시스템까지 동작했다.

 

대기열 시스템이 동작하기 전에 진입한 경우

인증서 미보유 고객들도 최대한 앱스크래핑으로 전환하여 많은 고객이 일정 시간 내에 대출 실행결과를 받아볼 수 있도록 동작했다.

 

흔히들 은행 시스템은 변화없고 재미없는 시스템으로 생각하고 있다.

하지만 토스뱅크의 구축 시스템은 초기부터 변화에 빠르게 대응하고 안정적인 시스템을 구축하기 위해 노력했다.

토스뱅크 대출 시스템은 서비스 간에 강결합 되어있지 않고, 유연하게 분리가 가능하며

앞으로의 기술 발전에 빠르게 대응할 수 있는 시스템이다.