오류는 update가 아닌 reversal과 provider/은행 대사 근거로 해결합니다.
02 · HIGH-LEVEL DESIGN
정책 결정, 원장 commit, 외부 결과를 분리한다
# 아키텍처
risk/AML/제재 screening은 allow·review·block workflow의 입력입니다. 원장은 정책 결과와 provider outcome을 증명 가능한 state transition으로 기록하며, projection은 빠른 잔액 읽기를 위한 파생 데이터입니다.
digital wallet transfer: authorize → reserve → post/release → reconcile SVG DIAGRAM · ledger is source of truth
원장 경계:available → reserved와 reserved → destination/clearing은 상태가 다른 균형 분개입니다. risk review나 PSP timeout 동안 destination balance에 먼저 credit하지 않으며, projection의 숫자보다 ledger transaction의 invariant가 우선합니다.
policy/risk가 block, review, allow 중 하나를 선택하고 decision version을 기록합니다.
03Reserve
source available을 줄이고 reserved를 늘리는 ledger transaction과 outbox를 함께 commit합니다.
04Post / Release
verified outcome만 수취/clearing으로 post하고 실패·만료는 release 또는 reversal합니다.
04 · TRADEOFFS
빠른 balance read와 검증 가능한 value movement를 섞지 않는다
# 대안 비교
mutable balance만 두면 읽기는 빠르지만 감사·정정·재생 근거가 약합니다. 불변 ledger를 정본으로 유지하고 balance는 다시 만들 수 있는 projection으로 둡니다.
선택
강점
제약
설계 판단
mutable balance만
단순 read
감사·재생·정정 취약
금전 시스템의 유일 정본으로 쓰지 않음
immutable ledger + projection
균형·복구
schema·replay 운영
실제 value movement 기본
즉시 posted
짧은 UX
review/external outcome 불명
저위험 내부 전송만 제한
reserve → post/release
이중 지출 차단
expiry·case 운영
외부 rail·고위험 전송
05 · FAILURE MODES
중복·지연·불일치를 숫자가 아닌 상태와 근거로 복구한다
# 장애 9가지
↻중복 POST
재시도 또는 두 화면이 같은 송금을 두 번 만들 수 있습니다.
대응 · idempotency key·fingerprint로 첫 transfer와 response만 재사용합니다.
≈projection lag
잔액 화면이 ledger commit보다 늦어 고객이 낡은 수치를 봅니다.
대응 · ledger position/as-of 표시, replay와 read-your-writes 경로를 둡니다.
▣outbox worker crash
reserve는 커밋됐지만 provider request가 아직 실행되지 않습니다.
대응 · transactional outbox replay와 stuck reserve age alarm으로 처리합니다.
?provider timeout
외부 송금이 됐는지 실패했는지 즉시 알 수 없습니다.
대응 · 새 호출 대신 provider reference 조회·signed webhook으로 unknown을 해소합니다.
⌁webhook 재전송·역순
늦은 event가 posted/reversal을 중복 적용하려 합니다.
대응 · inbox dedupe, signature, 단조 transition으로 차단합니다.
⌛risk review 지연
자금이 reserve에 너무 오래 묶여 사용 가능 잔액이 줄어듭니다.
대응 · review SLA, safe expiry release, case escalation을 둡니다.
±부분 분개
한쪽 entry만 보이면 잔액·재무 보고가 깨집니다.
대응 · atomic DB transaction, debit=credit monitor, append-only repair를 사용합니다.
≠reconciliation 차이
은행/PSP 명세와 내부 ledger totals가 다를 수 있습니다.
대응 · case 격리, 근거 기반 reversal/adjustment, ageing SLO를 둡니다.
!account takeover
탈취자가 수취인·출금을 바꾸고 자금을 이동하려 합니다.
대응 · step-up auth, velocity limit, device anomaly, hold와 human case를 결합합니다.
06 · OPERATIONS
보안·관측·비용은 원장의 주변 기능이 아니라 일부다
# 운영 관점
◇보안·개인정보
PII·credential·risk signal·ledger는 접근·보존 경계를 분리합니다. provider tokenization, secret rotation, step-up auth와 redacted telemetry를 기본으로 둡니다.
least privilege · redaction · audit
◉관측 가능성
debit/credit invariant, reserve age, transfer state, outbox/inbox lag, projection position, webhook reject, provider discrepancy를 따로 관측합니다.
Σ debit = Σ credit
₩비용 모델
immutable history·backup, balance read model, provider·webhook·settlement, risk/screening vendor와 human review, KMS·audit 비용을 합산합니다.
ledger + projection + risk + ops
면접 모드 · 추가 질문07:00
“외부 출금 요청이 timeout이 됐고, risk review는 아직 끝나지 않았으며 사용자는 다시 송금을 누릅니다. 금액 integer/통화, available/reserved account, idempotency, provider reference, webhook, 원장 reversal과 reconciliation을 어떤 순서로 설명하시겠습니까?”
available→reserved→posted/released 전이를 멱등 transfer 상태와 함께 기록한다.
provider timeout은 unknown으로 유지하고 같은 reference의 조회·webhook으로 확정한다.
provider settlement·ledger·balance projection을 대사하고 정정은 reversal entry로 남긴다. 이번 선택: 불변 이중 기입 원장과 reserve 상태를 사용해 외부 결과가 불명확한 동안 이중 지출을 막는다. 깨지는 신호: 통화별 debit·credit 합이 다르거나 provider 명세에 있는데 내부 clearing entry가 없다. 다음 검증: timeout·중복 webhook·projection 재구축·정산 차이를 주입해 reserve age와 reconciliation 완료 시간을 측정한다.