[Multiple Database Banking Simulation] 4. 은행 프로세스

dbdbdeep·2025년 9월 12일

현재 필요한 데이터 처리

  1. 모든 유저의 잔액
  • Front End에서 조회( SQL로 한번에 가능 )
  1. 특정 유저의 잔액
  • Front End에서 생성( SQL로 한번에 가능 )
  1. 계좌 간 송금( 같은 은행 내, 타행 이체 )
  • RDG( Random Data Generator )로 생성
  • Front End에서 생성

문제는 "계좌 간 송금"이다.

DB를 쓰면 Transaction내에서 원자성을 유지해준다. 여러가지의 이유로 Client 쪽에서 중복으로 발생, 동시 발생의 이유로 계좌이체에서 "멱등키(Idempotent-Key)"를 이용한다.

쉽게 말하면 하나의 작업에 대해 여러번 요청이되어도 한번만 처리되는 루틴이다.

Flask에서 멱등키를 제어하여 DB쪽으로 넘겨줘야하는 상황이다. 이러한 작업은 처음해보는 것이라 조금 정리를 한 후 진행할 예정이다.

정리

기술적인 내용을 정리하기 어떻게 처리하는지, 어떤 구조를 가져야하는지 정리한다.

현재 계좌 간 송금을 할 때 일관성과 안정성을 보장해야한다. 돈에 관련된 것이라 굉장히 중요하다. 송금시 DB는 여러개의 테이블로 이것을 보장한다.

테이블 개념적 내용

1. 계좌 테이블

  • 고객의 계좌 정보를 저장한다.

2. 거래전표 테이블

  • 이체시 내역을 저장한다. 이것이 insert 되었다고 이체가 일어난 것이 아니다. 실제 이체 전에 어떤 내역이 일어날지 미리 저장해두는 테이블로 생각하면 된다.

3. 원장 테이블

  • 모든 계좌의 거래 내역을 기록하는 테이블이다. 같은 은행에서 계좌이체가 일어나면 2개의 데이터가 insert 된다.

4. 외부 송금 테이블

  • 외부송금시, 같은 DB가 아니니 많은 이유로 송금이 실패할 수 있다. 그래서 내가 송금을 할 때 보낸 금액을 hold 해두기 위한 테이블이다.

멱등키란

멱등키(Idempotency Key)는 한마디로 같은 요청이 여러 번 들어와도 결과는 한 번만 나게 하는 식별자로서 클라이언트가 직접 요청에 붙여 보내는 고유 키

현재 이체 프로세스는 RDG.py와 FE에서 이루어질 것이다. 각 프로그램에서 이체를 실행시키 전에 멱등키를 발급한다.

FE : crypto.randomUUID()
BE : uuid.uuid4()

함수를 이용한다. 이것으로 중복된 처리를 일관되고 문제없게 처리한다. 즉 하나의 흐름(하나의 이체)에 대해서 하나의 멱등키를 이용해 중복처리를 막아준다.
uuid는 랜덤값으로 겹치는 값이 나올 확률은 0에 가깝다.

테이블 상세 설계

accounts Table(계좌)

account_id: 계좌 번호
owner_id: 고객 식별자
currency: 통화(KRW 등) <- KRW만 관리할거라 제외
status: ACTIVE/FROZEN/CLOSED
balance: “기장까지 반영된” 잔액
hold_amount: 보류된 금액(외부송금 대기분)
available = balance − hold_amount (가용잔액; 뷰로 보거나 계산해서 씀) <- 사용할 때 계산식으로 유도해서 사용할 예정

transactions Table(거래 전표, 1줄로 발생)

txn_id: 거래 번호, PK

type: INTERNAL(내부이체) / OUT(외부로 보냄) / IN(외부에서 들어옴)

  • 추후 각 상황에 따라 숫자코드로 부여할 예정

status: PENDING/POSTED/SETTLED/REVERSED

  • PENDING : 생성되었으나 확정은 아직 x
  • POSTED(내부) : 원장 기장 완료
  • SETTLED(외부) : 정산 확정
  • REVERSED : 실패/취소 등의 상태
  • 추후 각 상황에 따라 숫자코드로 부여할 예정

src_account_id / dst_account_id: 출금/입금 계좌(내부이체면 둘 다 채움)

amount: 금액( 입/출금 다 양수로 관리 )

idempotency_key: 멱등키(중복 기장 방지)

created_at/updated_at

ledger_entries Table( 원장 = 기장 라인들, 실제 돈이 입출이 되었으면 적는다. )

entry_id: 원장 라인 번호

txn_id: 전표 번호

account_id: 계좌번호

amount: 증감 금액(입금은 양수, 출금은 음수)

created_at/updated_at

규칙: 전표 한 건 ↔ 원장 최소 2줄(이중부기).
같은 txn_id의 amount 합계는 항상 0이 되어야 함.

holds Table( 외부송금 보류 전용, 내부이체에는 안 씀 )

hold_id

account_id : 상대 계좌

amount

status: OPEN(대기)/CAPTURED(확정)/RELEASED(해제)

  • 추후 각 상황에 따라 숫자코드로 부여할 예정

idempotency_key

created_at : 보류가 걸린 시점

각 DBMS의 테이블은 위와 같이 설계할 예정이다.

A 계좌 -> B 계좌로 10,000원 송금

1. 내부이체

1.1 성공시
  1. 요청 시 데이터 확인
  • 존재하는 계좌인지, 돈은 있는지 등
  1. 전표 생성
  • 전표(transactions) 테이블에 status : PENDING 로 데이터 생성
  • 여기서 idempotency_key의 값을 넣어준다. <- FE에선 React에서 ,RDG에선 Flask에서 생성해서 넣는다.
  1. 계좌 락을 건다.
  • 이체를 수행하는 중 이 계좌를 다른 사람이 건들이면 문제가 생길 수 있다.
  1. 원장 기장
  • A : -10,000 , B : +10,000
  1. 잔액 반영
  2. 전표 반영
  • status : posted로 수정
1.2 실패시
  1. 계좌가 없거나 금액이 없을 경우 전표 미생성 후 바로 에러출력
  2. 동시요청 및 중복요청 -> 멱등키가 하나발급이 되어 멱등키가 중복 시 첫건에 대해서만 성공

2. 외부이체

출금은행 프로세스

  1. 요청 시 데이터 확인
  • 계좌 존재/상태 확인, 금액 > 0, 가용잔액 ≥ 금액 확인
  • (선행) FE/RDG가 생성한 Idempotency-Key(K_out) 헤더 유입 여부 확인
  1. 전표 생성
  • 전표(transactions) 테이블에 type: OUT, status: PENDING 으로 생성
  • idempotency_key = K_out 저장(멱등 선점)
  • createId(list_id_out) 발급(확정 단계의 멱등 기준)
  1. holds 데이터 생성
  • holds 레코드 status: OPEN, idempotency_key: K_out 으로 생성
  • accounts.hold_amount += amount → 가용잔액 감소
  • 이 단계에서 원장 기장은 대기(없음)
  1. 멱등키 발생
  • FE: crypto.randomUUID() / RDG: uuid.uuid4() 로 요청 전 생성된 K_out을 사용
  • 같은 K_out 재요청 시: 기존 결과 재사용(중복 기장 없음)
  • 같은 K_out + 다른 바디(금액/계좌 변경) 시: 409/422 에러
  1. 외부 지시/내부 호출
  • 우리 환경에선 수신은행 프로세스의 “수금 전표 생성”을 내부 호출로 대체
  • X-Correlation-Id 등 추적 값은 선택
  1. 확정(성공)
  • holds: OPEN → CAPTURED
  • 원장 기장(권장 2줄): 출금계좌 −금액, 출금은행_정산계정 +금액
  • transactions.status = SETTLED
  • 확정 멱등 기준: list_id_out(같은 ID로 여러 번 와도 1회만 전이)
  1. 실패(거절/시간초과)
  • holds: OPEN → RELEASED
  • 원장 기장 없음
  • transactions.status = REVERSED
  • 같은 list_id_out 재호출 시 상태 재전이 없음(멱등)

수신은행 프로세스

  1. 수금 전표 생성
  • 전표(transactions) 테이블에 type: IN, status: PENDING 으로 생성
  • idempotency_key = K_in 저장(출금 쪽과 별도 키)
  • createId(list_id_in) 발급
  1. 확정(성공)
  • 원장 기장(권장 2줄): 수신은행_정산계정 −금액, 입금계좌 +금액
  • transactions.status = SETTLED
  • 확정 멱등 기준: list_id_in
  1. 실패
  • 전표 REVERSED
  • 원장 기장 없음
  1. 멱등키 처리
  • 같은 K_in 재요청 시 기존 결과 재사용
  • 같은 K_in + 다른 바디면 409/422 에러
profile
DB관련 공부를 합니다.

0개의 댓글