자물쇠가 달린 중앙 원장과 양쪽 거래 지점을 선으로 연결한 모바일 뱅킹 보안 설계 일러스트

카카오뱅크급 모바일 뱅킹 시스템 설계: 원장·이체·멱등성·감사·장애 격리

송금 버튼을 누른 뒤 화면이 멈췄다고 하자. 사용자가 알고 싶은 것은 다시 눌러도 되는지, 돈은 빠졌는지, 상대에게 들어갔는지다. 카카오뱅크급 모바일 뱅킹 시스템 설계는 이 불확실한 몇 초를 중복 출금 없이 하나의 설명 가능한 결과로 수렴시키는 문제다.

이 글은 2026년 8월 16일 기준 공개 표준과 공식 기술 문서를 대조한 가상 설계다. iOS·Android 앱의 개인 사용자와 이체를 추적해야 하는 고객센터·운영자를 대상으로 잔액 조회, 같은 은행 이체, 타행 이체와 결과 확인을 다룬다. 카카오뱅크의 비공개 내부 구조·실제 처리량·지연을 주장하지 않으며, 아래 수치는 선택을 비교하기 위한 설계 가정이다.

20초 핵심 요약

  • 무엇: 잔액을 보고 송금하고 결과를 다시 확인하는 모바일 은행의 원장·이체 설계다.
  • : 재전송은 중복 출금을 만들 수 있고, 타행망 타임아웃을 실패로 오판하면 같은 돈을 다시 보낼 수 있다.
  • 어떻게: 복식 원장과 이체 상태를 기준 기록으로 삼고 멱등 키, 잔액 조회본, 대사와 장애 격리를 연결한다.

문제 이해: 사용자는 돈의 상태를 설명받아야 한다

개인 사용자의 대표 행동은 세 가지다.

  1. 로그인한 뒤 자기 계좌의 장부 잔액, 출금 가능 금액과 최근 거래를 본다. 느린 네트워크에서도 다른 사람의 정보가 섞이지 않고, 화면이 언제 기준인지 알 수 있어야 한다.
  2. 수취인과 금액을 확인한 뒤 같은 은행이나 다른 은행으로 송금한다. 버튼을 두 번 누르거나 앱이 종료돼도 출금 효과는 한 번이어야 한다.
  3. 응답이 늦거나 앱을 다시 열었을 때 이체 결과를 조회한다. 아직 결과가 확정되지 않았다면 성공이나 실패를 지어내지 않고 다음 확인 방법을 제공해야 한다.

고객센터와 운영자도 사용자 여정에 참여한다. 이들은 하나의 이체 ID로 인증, 권한, 한도 판정, 돈의 기록, 외부망 응답과 정정 이력을 연결해야 한다. 운영자가 과거 금액 기록을 몰래 고치는 방식은 허용하지 않는다.

범위는 로그인·세션, 계좌와 거래 조회, 수취인 확인, 같은 은행·타행 이체, 한도, 중복 방지, 알림, 대사, 감사와 재해 복구까지다. 대출, 카드 승인, 외환, 투자와 현금자동입출금기는 제외한다. 모바일 화면 구현도 중심에 두지 않는다.

돈은 원화의 최소 단위인 정수로 저장한다. 계산 오차가 생길 수 있는 부동소수점 수는 쓰지 않는다. 개인정보는 앱·기기·네트워크·로그에 필요한 만큼만 남기며, 서버는 앱이 보낸 계좌 소유자나 인증 성공 표시를 그대로 믿지 않는다. OWASP 모바일 애플리케이션 보안 검증 표준(MASVS)은 저장, 암호, 인증·권한, 네트워크와 개인정보 등을 모바일 통제 항목으로 나눈다.

규모 가정은 조회와 이체의 다른 한계를 드러낸다

다음 값은 실제 서비스 통계나 부하 시험 결과가 아니다. 입력과 계산 결과, 가정이 달라졌을 때 바꿀 선택을 함께 적었다.

설계 입력 계산과 결과 민감도와 선택 변화
등록 사용자 3,000만 명, 일간 활성 사용자 1,000만 명 사용자·계좌 분할 기준을 잡는 입력 가정 활성이 2배가 되면 조회 캐시와 잔액 조회본 처리기를 먼저 수평 확장한다.
활성 사용자당 하루 12회 계좌 조회 평균 약 1,389 QPS, 피크를 평균의 10배인 약 13,900 QPS로 가정 QPS(Queries Per Second, 초당 요청 수)는 조회량을 뜻한다. 값이 커져도 오래된 잔액 허용 범위를 먼저 정한다.
하루 이체 2,000만 건 평균 약 231 TPS, 피크 약 2,315 TPS로 가정 TPS(Transactions Per Second, 초당 거래 수)는 이체 처리량을 뜻한다. 평균보다 같은 계좌에 몰리는 요청을 따로 봐야 한다.
이체당 최소 2개 금액 기록, 기록당 인덱스 포함 1KB 하루 약 40GB, 연 약 14.6TB이며 복제 전 수치 실제 기록이 2배면 연 29.2TB다. 보존·압축·복제 비용을 다시 계산한다.
내부 이체 응답 p99 1초 이내, 외부망 호출 예산 2초 외부망 예산이 끝나면 처리 중으로 전환 p99는 요청 100개 중 99개가 끝나는 시간의 상한을 보는 지표다. 실제 계약이 다르면 목표를 바꾸되 타임아웃을 실패 판정으로 쓰지 않는다.

핫키는 요청이 한 계좌나 한 데이터 구역에 몰리는 현상이다. 이 글에서는 한 계좌의 거래 순서를 지키는 동안 생기는 대기를 찾기 위해 필요하다. 전체 이체량이 낮아도 급여 지급 계좌나 대형 가맹점 계좌의 잠금 대기와 충돌 재시도는 길 수 있다.

잔액을 보는 요청은 빠른 조회본에서 시작한다

여기서 프로젝션(projection)은 원래 금액 기록을 읽어 화면에 필요한 잔액만 미리 계산해 둔 조회본이다. 빠른 조회를 제공하기 위해 필요하지만, 돈의 최종 기준은 아니다.

  1. 앱이 POST /v1/sessions로 기기에 결합된 인증 결과와 위험 신호를 보낸다. 인증 서비스는 사용자가 누구인지 확인하고 짧게 쓰는 세션을 발급한다.
  2. 앱이 GET /v1/accounts/{account_id}/balance를 호출한다. 요청 접점은 세션 사용자에게 해당 계좌를 보거나 위임받은 권한이 있는지 서버에서 검사한다.
  3. 조회 서비스가 프로젝션에서 장부 잔액, 예약액을 제외한 출금 가능 금액, 반영 시각과 순번을 읽는다. 반영 시각은 오래된 상태를 숨기지 않기 위해 필요하다.
  4. 프로젝션 지연이 허용 상한을 넘으면 원장의 최신 스냅샷을 읽거나 새로고침 중이라고 응답한다. 실제 이체 가능 여부는 오래된 조회본이 아니라 최신 예약·확정 금액으로 다시 판정한다.

응답에는 다음처럼 조회 시점이 드러난다.

{
  "account_id": "acc_7Q...",
  "currency": "KRW",
  "ledger_balance": 1250000,
  "available_balance": 1170000,
  "as_of": "2026-08-16T09:00:01Z",
  "version": 88421
}

ledger_balance는 확정된 장부 잔액이고 available_balance는 아직 확정 전인 예약액까지 고려해 지금 보낼 수 있는 금액이다. 둘을 한 값으로 합치면 타행 이체 처리 중에 같은 돈을 다시 쓰게 할 수 있다.

이체 요청은 같은 의도를 한 번의 금전 효과로 만든다

멱등성은 같은 의도의 요청이 여러 번 도착해도 금전 효과가 한 번만 생기는 성질이다. 모바일 재전송과 응답 유실이 곧 중복 송금으로 이어지지 않게 하려고 필요하다. HTTP(Hypertext Transfer Protocol, 앱과 서버가 요청·응답을 주고받는 규칙)의 표준인 RFC 9110도 멱등의 뜻을 설명하지만, 송금에 쓰는 POST가 자동으로 멱등해지는 것은 아니다. RFC 9110 §9.2.2

  1. 앱은 수취인·금액 확인 뒤 한 번의 사용자 의도를 나타내는 무작위 Idempotency-Key를 만든다. 같은 이체를 재시도할 때 새 키로 바꾸지 않는다.
  2. 앱이 POST /v1/transfers로 출금 계좌, 수취 경로, 최소 통화 단위의 금액과 메모를 보낸다. 서버는 형식과 계좌 권한을 검사한다.
  3. 위험·한도 기능이 사용자, 기기, 수취인과 일 누적액을 검사한다. 거절은 돈을 기록하기 전에 확정하고 이유를 감사 기록에 남긴다.
  4. 이체 조정 기능이 (owner_id, idempotency_key)를 유일 키로 저장하면서 요청 본문의 해시와 transfer_id를 함께 만든다. 해시는 본문을 짧은 고정 길이 값으로 바꾼 비교표시이며, 같은 키를 다른 송금에 재사용했는지 찾기 위해 필요하다.
  5. 같은 키와 같은 본문이 다시 오면 새 이체를 만들지 않고 첫 이체의 현재 상태를 돌려준다. 같은 키인데 본문이 다르면 409 Conflict로 거부한다.

멱등 기록과 이체 생성은 하나의 데이터베이스 거래로 확정해야 한다. 둘 사이가 갈라지면 돈의 처리는 시작됐는데 중복 방지 기록만 사라질 수 있다. 고객 요청 식별자와 상태 변경을 원자적이고 지속적인 작업으로 묶어야 한다는 공개 설계 지침도 이 선택을 뒷받침한다. AWS Builders’ Library의 멱등 API 설계

같은 은행과 타행 이체는 확정 시점이 다르다

원장은 돈이 어디에서 빠지고 어디로 들어갔는지를 기존 값을 덮지 않고 새 기록으로 쌓는 장부다. 이 설계에서는 한 이체의 차변과 대변을 같은 거래로 기록해 금액이 사라지거나 생기지 않았는지 검사하는 최종 기준이므로 필요하다. 복식 원장은 한 거래의 빠져나간 금액과 들어온 금액 합이 같아야 하는 기록 방식이다.

같은 은행 이체는 출금 계정 차변과 입금 계정 대변을 원장에 한 번에 추가한 뒤 POSTED로 확정한다. 둘 중 하나만 기록된 중간 상태는 밖으로 드러나지 않는다.

타행 이체는 외부 금융망을 같은 데이터베이스 거래에 묶을 수 없다. 먼저 금액을 RESERVED로 예약해 사용 가능 잔액에서 빼고 외부망에 지시한다. 외부 성공을 확인하면 POSTED, 명시적 거절이면 예약을 풀고 FAILED, 연결이 끊기거나 시간이 끝나면 결과 미확정인 UNKNOWN으로 둔다.

다음 그림은 앱의 이체 버튼에서 시작해 번호 순서대로 읽는다. 5번에서 같은 은행과 타행이 갈라지고, 타행 경로의 UNKNOWN에서 상태 조회와 대사가 다시 최종 상태로 이어지는지 본다.

앱 이체 요청부터 인증·멱등 키·한도 예약을 거쳐 같은 은행과 타행 결과로 나뉘는 흐름

이 그림에서 가장 중요한 부분은 UNKNOWN이 실패가 아니라는 점이다. 사용자는 이때 처리 중이며 다시 송금하지 않아도 됨과 이체 ID, 마지막 확인 시각을 본다. 서버는 새 송금을 권하지 않고 외부 상태 확인과 대사로 결과를 확정한다. ISO 20022 지급결제 자료에 처리 상태를 알리는 메시지가 별도로 정의돼 있다는 사실은 지급 지시와 상태 확인을 구분해야 한다는 일반 근거다. 특정 국내망이 같은 메시지를 쓴다는 뜻은 아니다. ISO 20022 지급결제 자료

커밋할 때는 outbox도 함께 쓴다. outbox는 확정된 원장 거래와 밖으로 보낼 이벤트를 같은 저장 거래에 적어 두는 표이며, 돈은 확정됐는데 잔액 갱신이나 알림 신호만 사라지는 틈을 막기 위해 필요하다. 별도 전달기가 이를 읽어 프로젝션과 알림으로 보낸다. 알림 전송이 실패해도 이미 확정된 송금을 취소하지 않는다.

데이터 모델은 식별자와 되돌림을 분리한다

최소 데이터 모델은 다음과 같다.

accounts(
  account_id PK, owner_id, currency, status, created_at
)

transfers(
  transfer_id PK, owner_id, source_account_id, destination_ref,
  amount_minor, currency, rail, status,
  idempotency_key, request_hash, external_ref,
  created_at, updated_at,
  UNIQUE(owner_id, idempotency_key)
)

ledger_transactions(
  ledger_tx_id PK, transfer_id UNIQUE, state, effective_at, created_at
)

ledger_entries(
  entry_id PK, ledger_tx_id, account_id, direction, amount_minor,
  sequence_no, created_at,
  UNIQUE(account_id, sequence_no)
)

balance_projection(
  account_id PK, ledger_balance, reserved_amount,
  available_balance, last_sequence_no, as_of
)

outbox_events(
  event_id PK, aggregate_id, event_type, payload, created_at, published_at
)

audit_events(
  audit_id PK, actor_type, actor_id_tokenized, action,
  object_type, object_id, decision, reason_code,
  correlation_id, occurred_at, integrity_hash
)

transfer_id는 사용자의 이체 흐름, ledger_tx_id는 돈의 기록, external_ref는 외부망 거래를 식별한다. 하나로 합치면 재전송, 반환과 정정의 관계를 표현하기 어렵다.

ledger_tx_id 안에서는 차변 합과 대변 합이 같고 통화가 섞이지 않으며 기표가 한 번만 확정돼야 한다. 승인된 행은 삭제하거나 덮어쓰지 않는다. 잘못된 이체는 원 거래를 참조하는 반대 방향의 새 기표로 상쇄한다.

last_sequence_no는 한 계좌에서 빠지거나 순서가 뒤집힌 프로젝션 갱신을 찾는 번호다. 프로젝션은 원장에서 다시 만들 수 있어야 한다. 둘이 다르면 원장이 우선한다.

감사 기록에는 전체 계좌번호, 인증 비밀과 불필요한 개인정보를 넣지 않는다. 토큰화는 원래 식별자를 바로 읽을 수 없는 대체값으로 바꾸는 방법이며, 조사 때 사건을 연결하면서 개인정보 노출을 줄이기 위해 쓴다. correlation_id는 한 이체의 인증·한도·원장·외부 응답·운영자 조치를 잇는 공통 추적 번호다.

상태표는 타임아웃과 정정을 실패 한 칸에 넣지 않는다

현재 상태 사건 다음 상태 사용자 문구 시스템 동작
RECEIVED 권한·한도 통과 VALIDATED 확인 중 아직 금액 기록 없음
VALIDATED 같은 은행 원장 커밋 POSTED 이체 완료 같은 멱등 키에 같은 결과 반환
VALIDATED 타행 금액 예약 RESERVED 처리 중 사용 가능 잔액에서 제외
RESERVED 외부 성공 확인 POSTED 이체 완료 확정 기표 추가
RESERVED 외부 명시적 거절 FAILED 이체 실패 예약 해제
RESERVED 외부 타임아웃·연결 단절 UNKNOWN 결과 확인 중 재송금 금지, 상태 조회·대사
UNKNOWN 외부 성공 확인 POSTED 이체 완료 외부 참조 저장
UNKNOWN 외부 미처리 확인 FAILED 이체 실패 예약 해제
POSTED 반환·업무상 정정 REVERSED 반환 완료 원 기록을 남기고 반대 기표 추가

대사는 외부 거래 명세와 내부 원장·이체 상태를 맞춰 차이를 찾고 바로잡는 작업이다. 결과 미확정 건을 성공 확인, 미처리 확인, 반환 또는 수동 검토로 보내기 위해 필요하다. 대사의 모든 결정도 원래 이체와 연결된 감사 이벤트로 남긴다.

TTL(Time To Live, 자동 만료까지의 시간)은 멱등 기록을 언제 정리할지 정하는 보존 값이다. 저장비를 제한하기 위해 필요하지만, 이체·민원·재전송 가능 기간보다 짧으면 오래된 같은 키가 새 송금으로 처리될 수 있다. Stripe의 24시간 정책은 공개 결제 API의 구현 사례일 뿐 은행 이체 보존 기간의 근거가 아니다. Stripe 멱등 요청

사용자가 보는 네 가지 실패에서 복구를 시작한다

오래 기다린다

  • 감지: 요청 구간별 지연, 외부기관별 타임아웃 비율과 큐에서 가장 오래 기다린 시간을 측정한다.
  • 완화: 내부 이체와 외부망 호출의 연결 풀과 작업 큐를 분리한다. 외부 예산이 끝나면 연결을 붙들지 않고 202 Acceptedtransfer_id를 반환한다.
  • 복구: 상태 조회 작업이 외부 참조를 확인해 UNKNOWN을 전진시킨다.
  • 정합성 확인: 예약액, 외부 결과와 최종 상태가 같은 이체 ID로 이어지는지 대사한다.

같은 버튼을 다시 누른다

  • 감지: (owner_id, idempotency_key) 유일 조건 충돌과 요청 해시 비교로 재시도를 찾는다.
  • 완화: 새 이체 대신 기존 transfer_id와 현재 상태를 반환한다.
  • 복구: 첫 응답이 유실돼도 같은 키나 이체 조회로 결과를 찾는다.
  • 정합성 확인: 멱등 기록, 이체와 원장 거래의 1:1 연결, 중복 기표 0건을 검사한다.

잔액이 이전 값으로 보인다

  • 감지: 프로젝션의 as_of, 마지막 순번과 이벤트 처리 지연을 측정한다.
  • 완화: 갱신 시점을 화면에 보이고, 출금·한도 판정에는 오래된 조회본을 쓰지 않는다.
  • 복구: 누락된 순번부터 원장 이벤트를 다시 적용하거나 조회본 전체를 재생성한다.
  • 정합성 확인: 원장 기표 합과 프로젝션 잔액을 표본·전체 검사로 비교한다.

돈은 빠졌지만 알림이 없다

  • 감지: 원장은 POSTED인데 outbox 이벤트가 발행·전달되지 않은 건을 찾는다.
  • 완화: 앱의 이체 내역을 결과 확인의 기본 채널로 두고 알림은 보조로 취급한다.
  • 복구: 알림만 다시 보낸다. 송금을 재실행하지 않는다.
  • 정합성 확인: 알림 횟수가 아니라 이체와 원장의 최종 상태를 확인한다.

병목은 평균 처리량보다 한 계좌와 외부망에서 먼저 보인다

병목 사용자 증상 관측할 값 완화 남는 비용·한계
한 계좌의 동시 이체 확인 시간이 길어짐 계좌별 잠금 대기, 충돌 재시도 계좌별 순번, 짧은 원장 거래, 예약 분리 계좌 순서를 지키면 무한 병렬화할 수 없음
프로젝션 처리 지연 이전 잔액이 보임 이벤트 지연, as_of 나이, 순번 누락 계좌별 순서 적용, 재생, 상한 초과 시 원장 조회 직접 조회가 늘면 쓰기 경로와 경쟁
외부망 지연 타행 이체가 오래 처리 중 외부별 p95·p99, 타임아웃률, UNKNOWN 체류 별도 큐·연결 풀·회로 차단기, 제한된 재시도 너무 빠른 차단은 정상 요청도 늦춤
재시도 폭주 앱 전체가 느려짐 요청당 시도 수, 큐 깊이, 429·5xx 응답 지수적으로 간격을 늘리고 무작위 지연과 재시도 예산 적용 결과 확정이 더 늦어질 수 있음
대사 적체 처리 중이 오래 지속 미대사 건수·금액·최고 연령 금액·연령 우선순위, 자동·수동 큐 분리 인력과 증빙 보존 비용 발생

회로 차단기는 외부 실패가 일정 수준을 넘으면 새 호출을 잠시 막는 장치다. 한 외부기관의 느린 응답이 내부 잔액 조회와 같은 은행 이체의 연결까지 차지하지 못하게 하는 역할이므로 필요하다.

팬아웃(fan-out)은 한 사건을 여러 후속 처리기로 퍼뜨리는 구조다. 원장 커밋을 프로젝션, 알림과 감사 분석으로 보내 원장 쓰기에서 보조 기능 장애를 떼어내기 위해 사용한다. 대신 각 처리기가 같은 이벤트를 중복 수신할 수 있으므로 이벤트 ID와 마지막 처리 위치를 기록한다.

인증·한도·감사는 돈을 기록하기 전후를 모두 설명한다

인증은 사용자가 누구인지 확인하고, 권한 검사는 그 사용자가 지금 이 계좌에서 이 금액을 보낼 수 있는지 요청마다 판정한다. 앱이 보낸 owner_id, 한도 통과 값이나 생체인증 성공 문자열은 근거로 삼지 않는다.

NIST SP 800-63B-4는 피싱 저항성을 사용자의 주의에 기대지 않고 가짜 인증 상대에게 유효한 인증 비밀이나 결과가 노출되지 않게 하는 성질로 설명한다. 수동 입력 일회용 비밀번호 자체는 피싱 저항 인증이 아니다. 새 수취인이나 고액 이체에서 기기에 결합된 암호 인증과 거래 내용 확인을 강화할 근거가 된다. NIST SP 800-63B-4의 피싱 저항성

한도는 회차, 하루 누적액, 수취인과 위험 신호를 서버에서 계산한다. 두 이체가 동시에 같은 남은 한도를 읽고 모두 통과하지 않도록 한도 예약과 이체 예약을 같은 순서 처리 구역이나 원자적 조건부 갱신으로 묶는다.

감사 저장소는 운영 검색용 사본과 무결성을 보호한 장기 보관본으로 나눌 수 있다. 앱과 서버 사이를 오가는 인증 토큰·계좌 정보는 전송 중 암호화하고 기기에는 최소한만 저장한다. OWASP MASVS-NETWORK는 네트워크 트래픽 보호와 개발자가 통제하는 원격 상대의 신원 확인을 요구한다. OWASP MASVS-NETWORK

주요 비용은 원장·감사 데이터의 장기 보존과 복제, 여러 지역 사이 네트워크, 보안 키 관리와 24시간 대사·사고 대응 인력이다. 캐시와 비동기 처리를 늘리면 읽기 비용을 줄일 수 있지만 오래된 데이터, 재처리 도구와 운영 복잡도가 늘어난다. 국내 보존 기간과 인증 강도는 이 글에서 임의 수치로 정하지 않는다.

재해 복구는 기존 이체의 확정부터 시작한다

RPO(Recovery Point Objective, 복구 시 잃을 수 있는 데이터 시점)는 장애 뒤 어느 시점까지의 데이터를 보존할지 정하는 목표다. 이 설계는 원장 RPO 0을 지향한다고 가정하지만, 동기 복제의 쓰기 지연과 비용이 커진다. RTO(Recovery Time Objective, 복구 완료 목표 시간)는 핵심 이체를 30분 안에 복구한다고 가정하며 실제 값은 업무 영향과 규제·계약을 반영해 승인해야 한다.

NIST SP 800-34는 중단 뒤 시스템·운영·데이터를 회복할 계획, 절차와 기술 조치를 함께 준비하고 업무 영향에 따라 우선순위를 정하라고 안내한다. 은행 규정의 대체물은 아니지만 다음 복구 순서를 세우는 공식 근거다. NIST SP 800-34 Rev. 1

  1. 원장 복제본의 마지막 확정 위치와 무결성을 검사한다. 두 지역이 동시에 원장 쓰기 주체가 되는 일을 막고 하나만 승격한다.
  2. 인증·권한과 읽기 전용 계좌 조회를 복구한다. 불확실한 이체를 성공으로 보이지 않는다.
  3. 내부 이체를 제한된 용량으로 연다. 원장의 차변·대변 합계와 멱등 저장소가 함께 정상인지 확인한다.
  4. 외부기관별 연결을 하나씩 열고 새 이체보다 기존 UNKNOWN 대사를 먼저 처리한다.
  5. 프로젝션과 알림처럼 원장에서 다시 만들 수 있는 처리기를 마지막 위치부터 재생한다.
  6. 원장 합계, 멱등 키 중복, 외부 명세와 프로젝션 차이를 검사한 뒤 정상 용량으로 돌아간다.

백업 파일이 열린다고 복구가 끝나는 것은 아니다. 훈련에서는 원장 RPO·RTO, 키 접근, 멱등 기록의 동시 복구, 외부망 재연결과 대사 완료 시간까지 측정해야 한다.

대안은 단순함과 실패 설명 가능성을 맞바꾼다

선택 지점 단순한 대안 채택안 채택 이유와 대가
돈의 기록 계좌 잔액 행을 직접 갱신하면 구현과 조회가 단순하다. 추가 전용 복식 원장과 프로젝션을 분리한다. 금액 보존·정정·감사·조회본 재생이 쉬워지지만 저장량과 운영 복잡도가 늘어난다.
잔액 조회 모든 요청을 원장에 보내면 최신성이 좋다. 일반 화면은 as_of가 있는 프로젝션, 출금 판정은 최신 원장을 쓴다. 조회 피크를 격리하지만 잠시 오래된 화면과 재생 도구를 운영해야 한다.
타행 확정 외부망까지 하나의 거래처럼 보면 성공 모델이 단순해 보인다. 예약→성공·실패·미확정 상태와 대사를 둔다. 통제할 수 없는 외부 지연을 표현하지만 UNKNOWN 운영과 수동 검토가 필요하다.
데이터 배치 단일 데이터베이스는 거래와 운영이 쉽다. 처음에는 단일 원장 클러스터를 쓰고 실제 한계 뒤 계좌 그룹별로 나눈다. 조기 분할의 복잡도를 피하지만 초기 장애 범위와 용량 상한이 더 크다.

공개 원장 구현인 TigerBeetle도 금액을 예약한 뒤 확정·취소·만료로 해소하는 두 단계 이체를 문서화한다. 이는 상태 모델을 비교할 구현 사례일 뿐 국내 은행 적합성이나 특정 제품 채택을 보증하지 않는다. TigerBeetle 두 단계 이체

확장은 관측된 경계만 한 단계씩 분리한다

  1. 초기: 한 지역의 여러 장애 구역, 단일 원장 데이터베이스, 같은 저장 거래 안의 이체·멱등·원장·outbox와 비동기 프로젝션으로 시작한다.
  2. 조회 성장: 계좌별 프로젝션과 읽기 캐시, 독립 이벤트 처리기를 둔다. as_of와 순번 누락 경보가 없는 캐시는 쓰지 않는다.
  3. 외부망 성장: 기관별 어댑터, 큐, 연결 풀, 타임아웃·재시도 예산, 회로 차단기와 대사 작업대를 나눈다.
  4. 원장 한계 도달: 실제 계좌별 충돌률, 기록 쓰기량과 복구 시간을 근거로 계좌 그룹이나 법인 장부 단위로 나눈다. 서로 다른 구역의 이체는 중간 결제 계정과 상태 흐름을 명시한다.
  5. 여러 지역 재해 복구: 원장 쓰기 주체는 한 곳으로 유지하고 동기 또는 준동기 복제의 지연·가용성 비용을 고른다. 읽기와 알림은 먼저 여러 지역에서 동시에 서비스할 수 있지만 원장 다중 쓰기는 별도의 금액 불변 조건 검증 없이 열지 않는다.

이 설계가 선택한 기준은 명확하다. 돈은 추가 전용 복식 원장에 남기고, 빠른 잔액 화면은 다시 만들 수 있는 조회본으로 분리한다. 내부 이체만 한 번에 확정하며, 외부망 타임아웃은 실패가 아니라 대사가 필요한 미확정 상태로 보존한다.

남은 한계도 크다. 실제 은행망, 카카오뱅크 내부 시스템, 국내 규제 준수와 가정한 처리량·지연·복구 시간을 시험하지 않았다. 구현 전에는 외부 API timeout 예산과 상태 조회 계약부터 문서로 고정하고, 최신 전자금융 법령·감독규정, 금융결제원 계약과 기관별 보존 기준을 법무·준법·보안 담당자와 확인해야 한다.

참고 링크

비슷한 글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다