ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 왜 은행은 무한 스크롤이 안되나요 - 토스(이응준)
    세미나, 영상 요약정리 2022. 7. 4. 00:01
    728x90

    https://www.youtube.com/watch?v=v9rcKpUZw4o 

     

    토스 뱅크에서 송금 서버를 개발하고 있는 이응준님의 영상을 보고 요약한 내용입니다.

     

     

    은행 앱의 조회 기간 설정

    은행앱의 기간 설정 및 조회

    은행 앱에서는 거래내역을 스크롤해서 살피면 지정된 기간만 조회되고 그 이상 조회하기 위해서는 기간 내역을 변경해야 합니다.

     

    하지만 대부분의 SNS에서는 메시지 피드를 조회할때 무한 스크롤을 통해 십수년전의 글까지 볼 수 있습니다.

     

    그러면 유독 은행은 왜 무한 스크롤을 제공하지 않을까요?

     

    은행이 무한 스크롤을 제공하지 않는 이유

    은행 시스템의 경우 다음과 같은 구조를 가지고 있습니다.

    일반 은행의 시스템 구조

    계정계는 실제로 유저의 돈을 다루며 원본 데이터가 저장되는 영역입니다.

    장애나 오류가 발생하면 치명적이기 때문에 아주 높은 신뢰도가 요구됩니다.

     

    채널계는 유저의 요청을 받아 직접 처리하는 영역입니다.

    돈을 직접 다루지는 않으며 그런 요청은 계정계로 위임합니다.

     

     

     토스 뱅크도 마찬가지로 다음과 같은 구조를 가지고 있습니다.

    토스 뱅크의 시스템 구조

    채널계는 k8s 클러스터 위에 구축된 도메인별로 분리된 복수개의 서버와 복수개의 DB를 가지고 있습니다.

    네트워크 구조가 복잡하고 DB가 여러개로 나뉘었기 때문에 경우에 따라 트랜잭션 처리가 어려운 경우가 있습니다.

    하지만 특정 서버에 부하가 몰리게 되면 그 서버만 스케일 아웃하거나 DB의 부하가 오면 특정 DB만 분리하면 되기 때문에 큰 트래픽을 다루는데 유리합니다.

     

    반면 계정계는 하나의 코어뱅킹 서버로 이루어져 있으며 데이터베이스 서버도 하나입니다.

    네트워크 구조가 단순하고 트랜잭션 처리가 단순합니다.

    하지만 급증하는 트래픽을 소화하는데 불리합니다.

     

    하지만 계정계는 높은 신뢰도가 요구되기 때문에 성능을 희생하더라도 이러한 아키텍처를 택하게 됩니다.

     

    거래내역 조회방법

     

    일반적인 은행앱의 거래 조회 방법

    은행앱의 거래조회 방법

    거래내역은 은행에서 매우 핵심적인 기록이기 때문에 계정계에서 관리합니다.

    채널계는 고객의 계정조회 요청을 받아 계정계에 전달하고 그 결과를 다시 앱으로 전달합니다.

    성능보다 신뢰도가 우선이기 때문에 광범위한 거래내역 조회를 빠른 응답 시간에 적은 부하로 제공하기는 어렵습니다.

    이런 요청을 최소화 하기 위해 UI에서 디폴트 조회범위를 수개월 정도로 설정하는 것이 현명한 선택입니다.

     

    토스 뱅크의 거래 조회 방법

    토스뱅크의 거래조회 방법

    하지만 토스뱅크에서는 기간 설정이 존재하지 않고 무한 스크롤이 됩니다.

    토스 뱅크는 계정계의 코어뱅킹 서버에 요청을 보내지 않습니다.

    채널계의 송금서버가 거래내역을 반환하는 구조인데 이렇게 하기 위해서는 항상 코어뱅킹 서버와 거래내역이 동기화되어 있어야 합니다.

    조금만 어긋나면 거래내역이 누락되는 문제가 생기면서 은행의 신뢰성에 큰 타격을 입을 수 있습니다.

    하지만 너무 빈번하게 동기화를 시도하면 코어뱅킹 서버에 부하가 커져서 장애가 발생할 수 있습니다.

     

    토스 뱅크는 어떻게 항상 코어뱅킹 서버와 동기화를 유지할 수 있을까요?

     

    유저가 이체할때 마다 이체내역을 송금 DB에 저장하자

    이체할때 마다 송금 DB에 저장

    송금 내역이 전부 송금 DB에 저장되니까 이것으로 거래내역 조회를 하면 되지 않을까요?

    하지만 이렇게 하면 다른 은행을 통해 토스뱅크에 입금한 것은 조회되지 않습니다.

    다른 은행을 통해 받아온 내역을 동기화 해야 하는 문제 발생

    따라서 이런경우에 코어뱅킹 서버가 송금 서버에게 입금이 되었다는 사실을 알려주어야 합니다.

    이를 해결하기 위해서 토스 뱅크에서는 Kafka를 이용합니다.

     

    카프카에 대해 알고 싶으시다면 다음 글을 참고하시면 좋습니다.

    https://junuuu.tistory.com/335?category=1006817

     

    코어뱅킹 서버가 카프카 토픽에 메시지를 프로듀싱하고 송금 서버가 컨슘하여 송금 DB에 저장합니다.

    이제 토스 앱으로 이체한 경우든 타행앱으로 이체한 경우든 토스 뱅크의 모든 거래를 조회할 수 있게 되었습니다.

     

    이는 이상적인 경우의 예시이며 현실은 예외상황에 대한 대처를 해야 합니다.

     

    예외상황에 대처하기

    송금 이력 누락 문제

    코어뱅킹 서버의 지연으로 송금 이력이 누락될 수 있음

    코어뱅킹 서버의 응답이 늦어져서 타임아웃으로 송금 서버가 응답을 받지 못하는 경우가 발생할 수 있습니다.

    이 경우에는 송금서버는 거래내역을 DB에 저장하지 못합니다.

     

    유저는 송금에 성공했지만 해당 거래내역을 볼 수 없는 문제가 발생하게 됩니다.

     

    위의 문제는 카프카로 해결할 수 있습니다.

    카프카를 통해 해결하기

    송금 API 실행중 타임아웃이 발생했더라도 송금이 완료되었을 때 코어뱅킹 서버가 Kafka 토픽을 통해 송금 서버에게 이체 실행 결과를 알려준다면 송금 서버가 거래내역을 송금 DB에 저장하고 유저에게 알려줄 수 있습니다.

     

    유저는 송금 직후에 타임아웃으로 인한 에러를 만나겠지만 결국 최종적으로 송금이 완료되고 나면 거래내역에 송금건이 조회될 것입니다.

     

    타임아웃으로 에러를 만난 유저가 재송금 시도

    유저에게는 송금 실패로 나오기 때문에 유저가 실패한 줄 알고 다시 송금할 수 있습니다.

    이를 해결하기 위해서 송금 서버는 코어뱅킹 서버에 송금 요청을 보내기 전에 우선 송금 요청을 DB에 저장합니다.

    그리고 완료되지 않은 송금 요청이 있는 유저가 송금을 요청하면 거절합니다.

    나중에 송금이 완료되어 송금 서버가 송금 이력을 저장까지 끝내고 나면 그때부터 새로운 송금 요청을 수락하게 됩니다.

     

    송금이 영원히 지연될 수 있는 문제

     

    만약 네트워크 문제로 송금 요청이 코어뱅킹 서버에 도달하지 못한다면 송금은 실행되지 않고 여전히 진행중인 상태로 송금 DB에 남아있게 됩니다.

    즉, 유저는 영원히 송금 요청이 불가능합니다.

     

    이를 해결하기 위해 송금서버는 주기적으로 코어뱅킹 서버에게 송금 요청의 상태를 확인합니다.

    이때 코어뱅킹은 송금서버에게 송금 상태를 응답할 때 '없음'으로 응답하게 되고 송금 서버는 송금 요청이 코어뱅킹에 도달하는데 실패한 것으로 보고 송금 실패로 처리합니다.

     

    이제 진행중인 송금이 사라졌기 때문에 유저는 다시 송금을 할 수 있게 됩니다.

     

    성공했는데 실패로 처리되는 문제

    아주 희박한 가능성으로 송금 요청이 지연되고 송금 요청이 이후에 완료되었지만 코어뱅킹 서버가 없음으로 응답하여 송금 실패로 처리해버렸지만 성공적으로 송금이 성공하는 경우입니다.

     

     

    이를 해결하기 위해서 송금 서버는 요청 시각을 포함하여 보냅니다.

    타임아웃을 설정해놓고 이 시간 이후에 송금 완료 메시지가 오지 않으면 송금 서버는 코어뱅킹 서버에게 송금 상태 조회를 하고 없음으로 응답합니다.

    이후 코어뱅킹 서버는 요청 시각을 보고 타임아웃으로 보고 요청을 처리하지 않고 거절합니다.

     

     

     

    송금 이력 저장 실패

    순간적인 DB장애로 송금 이력 저장에 실패할 수 있습니다.

    이렇게 되면 유저에게는 이체내역이 보이지 않게 됩니다.

    따라서 송금 이력이 실패할 경우 송금 완료 메시지를 다시 컨슘 하고 이력을 저장합니다.

     

    이때 재시도해도 실패할 수 있습니다.

    이런 경우에는 무한으로 재시도하는 것이 아니라 실패로 간주하고 더 재시도하지 않습니다.

    대신 컨슈머 데드 레터라는 카프카 토픽에 실패한 메시지를 저장하고 개발자가 실패하는 원인을 확인하여 문제를 해소한 뒤 해당 토픽을 다시 컨슘해서 송금 이력을 저장하게 됩니다.

     

    송금 이력 누락 문제

    토스뱅크 통장에 500원이 입금된 후 100원이 출금되었고 이것이 카프카를 통해 송금 서버에 동기화되는 과정을 생각해보겠습니다.

     

    만약 500원 입금 동기화가 실패하는 경우에는 재동기화로 해결할 수 있습니다.

    만약 100원 출금 동기화 성공이 먼저 완료되더라도 데이터의 정렬을 통해 유저에게는 제대로 보이게 됩니다.

     

     

    하지만 문제는 500원 입금 동기화가 실패하고 100원 출금 동기화 성공한 뒤 500원 입금 재동기화가 성공하기 전에 거래내역을 조회한 경우입니다.

    유저에게는 -100원으로 보이는 이상한 상황이 벌어집니다.

    이를 해결하기 위해 송금이 완료되었다는 카프카 메시지를 받았을 때 그것만 동기화하는것이 아니라 이전에 다른 거래가 있는지 코어 뱅킹 서버에 조회하여 동기화합니다.

     

    모든 거래내역에는 거래 순서대로 증가하는 일련번호가 붙어있기 때문에 송금 서버에 저장된 가장 최근 거래 건과 카프카 메시지로 받은 메시지의 일련번호를 확인하여 누락 여부를 판단할 수 있습니다.

     

    이렇게 되면 송금 서버는 100원 출금이 동기화될 때 500원 입금이 누락되었음을 발견하고 이것을 먼저 동기화하고 100원 출금 건을 동기화합니다.

     

    하지만 여기서 500원 입금 동기화가 또 실패하면 어떻게 될까요?

    100원 출금건도 동기화하지 않아야 순간적으로 잔액이 -100원이 되는 문제를 해결할 수 있습니다.

     

    이때 유저가 조회하면 입금된 500원과 출금된 100원이 모두 보이지 않는 문제를 겪게 됩니다.

     

    송금 서버는 진행중인진행 중인 거래내역을 DB에 저장하는데 유저가 계좌의 거래내역을 조회할 때 아직 진행 중인 거래가 있다면 즉시 계정계와 거래내역을 동기화시킵니다.

     

    따라서 유저가 거래내역을 조회하면 진행중인 100원 출금을 발견하고 동기화합니다.

    이때 500원 입금도 아직 동기화 되지 않았기 때문에 100원 출금보다 먼저 동기화합니다.

     

     

    동기화가 지연되는 문제

    동기화 지연 문제

    한 번에 백만 건의 거래내역을 동기화해야 하는 일이 생긴다면 어떻게 될까요?

    예를 들어 돈 많은 회사가 전국민에게 백원씩 주는 이벤트를 생각해 보겠습니다.

    백만명의 토스 고객에게 한 시간에 걸쳐서 입금이 완료된다면 1초에 300건 정도의 입금이 실행됩니다.

     

    코어뱅킹 서버가 카프카를 통해 송급 서버에게 입금을 알리면 송금 서버는 순서대로 하나씩 동기화합니다.

    만약 한 건 처리에 100ms가 걸린다면 1초에 10건이 처리할 수 있게 되고 1초에 300건씩 들어오는 입금을 처리하지 못하고 점점 밀리게 됩니다.

     

    한 시간이 지나면 거의 백만건이 밀리게 되고 이것을 모두 처리하려면 28시간이 걸립니다.

    이 시간동안 누군가 토스뱅크로 입금을 하면 밀린 입금 거래의 동기화가 다 끝나기 전까지는 해당 입금 거래가 안 보이는 문제가 발생하게 됩니다.

     

    하지만 모든 카프카 메시지를 하나의 쓰레드에서 하나씩 처리하지 않습니다.

    카프카는 메시지들을 여러 파티션으로 나누어 여러 개의 컨슈머가 처리할 수 있게 해 줍니다.

     

    이때 유저가 지정한 키를 기준으로 파티션을 나누어줍니다.

    만약 계좌번호를 키로 삼으면 같은 계좌의 거래내역은 반드시 같은 파티션에 들어가게 되고 같은 파티션은 같은 컨슈머가 처리하기 때문에 동기화 문제가 발생하지 않습니다.

     

    초당 300건의 입금 요청을 처리하기 위해서 파티션이 30개라면 충분히 처리할 수 있습니다.

     

    하지만 유저가 계속 늘어나서 더 높은 처리량이 필요하면 어떻게 될까요?

    파티션을 계속 늘리기만 하면 될까요?

    파티션을 늘리는데 시간이 걸리기 때문에 그 사이에 유저가 동기화 지연 문제를 겪게 될 수 있습니다.

    피크 타임을 기준으로 파티션을 넉넉하게 잡을 수 있지만 늘어난 파티션들은 시스템 자원을 차지하게 되며 다시 줄일 수 없기 때문에 지속적인 자원 낭비가 될 수 있습니다.

     

     

    따라서 파티션수는 적절하게 유지하고 컨슈머별로 워크 스레드를 충분히 할당합니다.

    파티션을 10개로 하고 컨슈머당 워커 스레드를 100개로 한다면 총 1천 개의 스레드로 건당 100ms 속도로 동기화가 가능해지고 초당 1만건의 동기화가 가능해집니다.

     

    이때 각 워크 스레드가 같은 계좌에 대한 동기화를 동시에 실행하면 이미 동기화가 완료된 거래를 재동기화하려고 시도하는 불필요한 트래픽을 유발할 수 있습니다.

     

    워커 스레드를 선택할 때 계좌번호 기준으로 선택하게 하여 같은 계좌를 항상 같은 스레드가 동기화하도록 합니다.

     

    최후의 수단

    지금까지 거래내역 동기화가 누락될 수 있는 다양한 케이스들과 방어법에 대해 알아보았습니다.

    하지만 예상치 못한 문제는 항상 발생할 수 있습니다.

    따라서 최후의 수단으로 유저가 직접 동기화를 실행할 수 있는 버튼을 제공합니다.

     

    요약

    일반 은행의 시스템 구조

    1. 토스뱅크는 빠른 거래내역 조회를 위해 채널계에 거래내역을 적재

     

    2. 채널계에서 적재된 거래내역을 통해 중복 송금 방지

    결과가 확정되지 않은 송금건이 있다면 추가 송금을 막고 일정 시간이 지나도 확정되지 않으면 실패 처리를 합니다.

     

    3. 카프카를 이용해 계정계와 채널계 사이의 거래내역을 동기화

    누락을 피하기 위해 누락된 건을 체크하고 컨슘이 실패하면 컨슈머 데드 레터를 이용해 재시도합니다.

    컨슘이 지연되는 것을 피하기 위해 적절한 개수로 파티셔닝하고 컨슈머별로 워크 스레드를 사용합니다.

    이때 동시성 문제를 피하기 위해 메시지 키 별로 워커 스레드를 할당합니다.

     

    4. 최후의 수단으로 수동 동기화 제공

     

    댓글

Designed by Tistory.