토스뱅크에서 데이터는 어떤 방식으로 설계하고 있을까?
How is Toss Bank designing data?
테이블의 통합과 분리
테이블의 통합과 분리는 어떤 것이 맞는지 틀린 지 판단하기 어렵다. 잘못된 판단은 프로그램 본 수량, 소스량, 개발 시간 등을 증가시킨다. 업무에 따라서 통합 혹은 분리를 판단할 필요가 있다.
몇 가지 예시들을 보자
대출/카드
- 대출기본, 카드기본은 심사내역 분리
- 심사는 평가항목이 대출/카드가 상이한 항목이 많음
- 테이블을 심사내역 하나로 통합했을 때 불리한 점이 많음
- 연체 이후는 통합
- 대출이나 카드나 연체금액, 연체일 등 공통적인 항목이 많다.
- 또한 이후에 발생하는 법적 절차 테이블들은 고객별 데이터 → 통합이 더 효율적
보증
- 개별적으로 테이블 구성하면 3*6*10 하면 200여 개 까지 테이블 증가
- 어느 정도 통합 필요
대출/예금
과연 분리가 좋을까 통합이 좋을까?
테이블 통합과 분리: 이론
테이블 통합과 분리는 다음과 같이 3가지 방법이 있다.
- OneToOne 타입은 슈퍼 테이블/서브 테이블로 구성하는 방식
- 고객 테이블을 부모 테이블, 법인 테이블/개인 테이블을 자식 테이블로 구성
- 플러스 타입은 각각 개별 테이블로 구성하는 방법으로 법인 고객 테이블/개인 고객 테이블로 구성
- 싱글타입은 통합 테이블로 고객 테이블 한 개 구성
앞에 설명한 대출을 예시로 한번 분리해보자
대출을 개별 테이블로 분리한 경우
- 각 테이블에서 컬럼을 추가하면 되니까 확장성이 괜찮은 편이고 업무 디펜던시가 제거된다는 장점이 있다.
- 그러나 단점으로는 거래내역 테이블을 개별로 만들어야 하고 SQL 조인 시에도 각각 조인 해야하기 때문에 관리 용이성이 좋지 않음
대출을 슈퍼 + 서브 테이블로 분리
- 대출기본에 공통 속성
- 여신대출기본 수신대출기본에 개별 속성
- 테이블 조인 시에 대출기본 하나의 테이블과 조인을 해도 공통 속성인 계좌 상태 코드와 실행 일자를 조회할 수 있음
- 거래내역 테이블도 대출거래내역 한 개로 구성할 수 있다.
- 여신대출기본, 수신대출기본 각각 개별 속성인 실행금액과 한도 금액을 조회할 때는 여전히 여신대출기본, 수신대출기본 각각의 개별 테이블과 조인해야 하는 단점이 있음
대출을 통합 테이블로 구성
- 여수신대출기본 하나의 테이블과 조인을 해도 원장의 모든 속성 조회 가능 + 거래내역 테이블도 하나의 테이블로 구성 → 조인 성능이 우수하고 관리 용이성도 좋음
- but 여러 프로그램에서 하나의 테이블만 바라보게 되고 테이블 ALTER가 빈번(프로그램 수정 자주 발생)
- 대량 데이터 발생하면 성능 저하 → 인덱스 파티션 전략 필요
이번엔 보증으로 예시를 들어보자
개별 테이블로 분리
- 운영하다 보면 테이블이 수백 개로 늘어날 수 있기 때문에 어느 정도 통합을 고민해야 함
- 중금리 대출은 심사 시 연간 소득 금액 필요 마이너스 대출 필요 x
- 마이너스대출은 연장이 되고 중금리는 연장이 안됨
- 중금리 대출은 신용대출이고 주택금융 대출은 부동산 정보 필요
- 기관별/상품별/업무별로 전문과 필요 항목이 상이하기 때문에 통합이 쉽지 않음.
슈퍼 + 서브 테이블로 분리
- 공통 속성인 보증서 번호나 보험료율을 조회할 때는 통합보증기본 테이블 하나와 조인
- but 개별 속성을 조회할 때는 여러 번 조인 발생
통합 테이블 구성
- 아까 말했듯이 상이한 속성들이 너무 많아서 현실적인 설계는 아님(통합이 불가능한 건 아님)
해법은?
- 최대한 공통적인 것들은 모으자
- 데이터량/Transaction의 유형 고려
- 데이터량 없으면 성능 이슈가 없기 때문에 되도록 통합하는 것이 좋음
- 데이터량 많으면 슈퍼/서브 테이블 고려
- 사용자 경험을 해치지 않는다는 가정하에
- 트랜잭션을 분리해서 공통적인 속성을 먼저 입력하고
- 개별적인 속성은 팝업으로 입력하도록 담당자에게 업무 변경을 유도하는 것도 좋은 방법
통합사례들을 한번 알아보자
- 상품, 금리, 수수료는 명확하기 때문에 은행권에서 테이블 통합이 잘되고 있음
- 고객 정보는 고객에 통합되어 관리되고 있어 개인정보가 효율적으로 관리
순환참조의 활용
- 자기 자신 테이블의 PK를 일반 속성으로 넣는 방식으로 설계
- CONNECT BY START WITH으로 선행 상태 추적 가능(신규 -> 금리인하 -> 감면 -> 증액 -> 연장)
비대면 대량거래 설계
주요 키인 계좌번호, 대출신청번호, 금리산출번호 등의 컬럼 자릿수 설계는 어떻게 해야될까?
- 계좌번호는 기본적으로 고객이 기억해야 하는 번호이기 때문에 자릿수가 되도록 짧아야 한다.
- 비대면 은행 특성상 고객이 계좌를 빈번히 발생시킬 수 있기 때문에 계좌번호 자체가 모두 FULL이 찰 수 있다.
- 그렇게 되면 안 쓰는 계좌를 DELETE 해야 되는데 은행권에서 데이터를 날리는 것은 명확히 준칙 하에 이루어져야 하므로 어렵다.
- 다른 방법으로 컬럼 자릿수를 늘려야 되는데 자릿수를 늘리려면 관련 계좌 테이블과 자식 테이블 수십 개를 ALTER 해야 하고 관련 프로그램 수백 개를 수정해야 함
- 그러므로 처음 데이터 설계 시 많은 고민해야 됨.
현재 토스 계좌번호 체계는 앞 3자리 계정과목 코드와 마지막 자리 체크 디지트를 빼고 8자리를 활용한다.(ex.800-00000466-2)
- 이렇게 하면 총 채번 가능 건수는 일억 건이 가능
- 만약 하루에 대출이 만 건 정도 발생한다고 가정하면
- 1년 발생은 365만 건이 되고
- 사용 가능 년수는 27년이 됨. (1억 % 3,650,000)
- 27년 후인 2048년에 FULL이 차면 그때 계정과목코드를 바꾸면 된다.
성능 최적화 설계: 채번(id)
채번 엔터티
- 장점
- 순차적으로 엄격하게 채번.
- 체계적인 채번 가능
- 단점
- 객체(엔터티) 증가
- Lock 사용으로 느린 채번
시퀀스
- 장점
- nextval로 사용하기 편함
- 가장 빠른 성능
- 최소한의 Lock
- 장점
- 체계 부여가 불편
- 빈 번호 발생 가능
- 객체(시퀀스) 증가
Max+1
- 장점
- 별도의 객체 필요x
- 순차적으로 엄격하게 채번.
- 체계적인 채번 가능
- 단점
- Lock 사용으로 느린 채번
- 예외 상황 발생가능
- 최대값 관리 부담 존재
채번엔터티 같은 경우 락은 채번 테이블에서 발생하고 MAX+1은 INSERT 할 테이블에서 락이 발생. 토스 뱅크는 결번 발생을 허용하지 않는 업무를 제외하고는 성능 측면에서 뛰어난 오라클 시퀀스를 대부분 사용하는 방향으로 결정
후기
역시 실무는 토이 프로젝트들과는 달리 엄청나게 복잡한 것 같다. 도메인도 엄청나게 많고 그 안에서 분리된 것도 많다. 용어도 비슷한 게 많은 거 같고. 그리고 아무래도 실무다 보니깐 하나의 도메인이 있으면 추적하기 위한 이력 테이블도 존재해야 되니 더욱 고려할게 많은 것 같다.
계좌번호 자릿수 설계를 하는 걸 보고 아 설계는 저런 식으로 하는 거구나 하고 많이 배운 것 같다. 항상 답이 정해져 있는 게 아니라 분석을 통해 이렇게 나올 거 같고(하루 만 건) 그걸 기반으로 이렇게까지 할 수 있을 거(1억 % 365만건 = 27년) 같아서 이렇게 설계를 한다.
최근에 이론적인 것들을 공부하다 이렇게 현업에서 어떻게 하고 있는지 보니깐 너무 재밌는 것 같다. 매일 조금씩 공부해놓으면 나중에 현업에서 많은 도움이 될 것 같다.
참고:
*오타가 있거나 피드백 주실 부분이 있으면 편하게 말씀해 주세요.