2. DB 분할
"한 DB 에 다 넣지 않는다" — 데이터 종류별로 DB 7 개로 쪼개는 이유
Fireblocks 가 cloud 를 3 개로 쪼개는 이유와 같다 — 한 곳에 다 넣으면 한 군데 사고가 전체로 번진다.
DB 도 같은 원리로 7 개로 쪼갠다. 기준은 두 가지:
① 누가 쓰는 DB 인가 (ownership)
한 DB 는 한 service 만 write. 다른 service 는 못 씀. 권한 escalation (한 service 가 다른 도메인을 침범) 사고 차단.
② 데이터가 어떻게 변하는가 (mutability)
잔액 같은 변경 데이터, 감사 log 같은 append-only 데이터, chain event 같은 read-mostly 데이터 는 운영 방식이 달라서 같이 두면 둘 다 비효율.
Fireblocks 는 internal DB 구조를 공개하지 않는다. 본 페이지의 7-DB 분할은 Fireblocks 의 6 system component / 3-cloud 분할 원칙을 "DB 단위로 옮기면 이렇게 된다" 의 직접 구축 가이드 (hypothesis).
각 DB 의 책임 + 들어가는 데이터 예시
| DB | 책임 | 들어가는 데이터 (예시) | 변경 패턴 |
|---|---|---|---|
| walletdb | 사용자·지갑 metadata | workspaces · vault_accounts · wallets · addresses · users · roles | mutable (사용자 이름 등은 변경됨) |
| ledgerdb | 잔액 원장 + 이체 이력 | ledger_accounts · ledger_entries · internal_transfers · withdrawals · deposit_observations | append-only (이체 이력은 추가만) |
| approverdb | 정책 + 승인 요청·결정 | policy_rules · policy_change_log · approval_requests · approval_decisions · admin_quorums · approval_groups | partial-mutable (정책은 바뀜, 결정은 set-once) |
| auditdb | 변조방지 audit log + 서명·키 이력 | audit_events · audit_checkpoints · signing_events · signing_requests · key_lifecycle · master_key_operations · recovery_events | strictly append-only (절대 UPDATE / DELETE 불가) |
| chaindb | blockchain event 거울 (mirror) | chain_events · transactions · broadcast_attempts · confirmations | append-only |
| providerdb | 외부 system 매핑 (AML provider · IdP · 거래소 등) | provider_accounts · provider_external_references · provider_event_log | mutable (외부 정보 변경 반영) |
| recondb | 정산 결과 + mismatch 발견 | reconciliation_sessions · reconciliation_snapshots · mismatch_findings | append-only |
서비스 → DB write 매핑 (누가 어디에 쓰는가)
graph LR WS["Wallet / WorkspaceFigure 2. 한 service = 한 DB 의 주인. 다른 service 는 쓰지 못함. (Signing + Audit 둘 다 auditdb 에 쓰는 것만 예외 — audit log 는 공동 영역)
Service"] --> DB1["walletdb"] LS["Ledger Service"] --> DB2["ledgerdb"] APS["Approval / Policy
Service"] --> DB3["approverdb"] SGS["Signing Service"] --> DB4["auditdb"] AUS["Audit Service"] --> DB4 BCS["Broadcast Service"] --> DB5["chaindb"] PMS["Provider Mapping
Service"] --> DB6["providerdb"] RCS["Reconciliation
Service"] --> DB7["recondb"] classDef svc fill:#dbeafe,stroke:#1e40af classDef db fill:#fef3c7,stroke:#92400e class WS,LS,APS,SGS,BCS,AUS,RCS,PMS svc class DB1,DB2,DB3,DB4,DB5,DB6,DB7 db
Reconciliation Service 의 cross-DB read 흐름
본 service 는 직접 구축 시 권장 architecture (출처: persistence-architecture/09 + reference-architecture/recurring-patterns P6).
Fireblocks vendor 자료에는 "Reconciliation Service" component 이름으로 명시되지 않음 — "Daily reconciliation report" 만 언급. Fireblocks 가 이를 별도 service 로 운영하는지 미공개.
모든 DB 의 데이터가 서로 일관되는지 검증하는 별도 service. 다른 DB 들로부터 read 만 하고 자기 DB (recondb) 에만 결과를 쓴다.
graph LR RCS["ReconciliationFigure 3. Reconciliation Service 는 6 개 DB read + recondb 1 개 write. mismatch 가 발견되면 사람이 검토.
Service"] DB1["walletdb"] DB2["ledgerdb"] DB3["approverdb"] DB4["auditdb"] DB5["chaindb"] DB6["providerdb"] DB7["recondb"] DB1 -. read .-> RCS DB2 -. read .-> RCS DB3 -. read .-> RCS DB4 -. read .-> RCS DB5 -. read .-> RCS DB6 -. read .-> RCS RCS ==> DB7 classDef svc fill:#dbeafe,stroke:#1e40af classDef db fill:#fef3c7,stroke:#92400e classDef target fill:#dcfce7,stroke:#166534 class RCS svc class DB1,DB2,DB3,DB4,DB5,DB6 db class DB7 target
분할의 5 가지 근거
| 근거 | 의미 + 구체 예시 |
|---|---|
| ① 민감도 격리 (Sensitivity isolation) |
같은 DB 에 두면 안 되는 두 종류:
예시: 신규 직원이 사용자 이름 검색 query 를 만들다가 실수로 같은 DB 의 audit log 를 삭제하는 사고. 두 데이터를 다른 DB 에 두면 권한 자체가 분리됨. |
| ② 변경 패턴 분리 (Mutability isolation) |
append-only DB (auditdb) 와 mutable DB 는 운영 방식이 다르다:
예시: 같은 DB 에 두면 audit log 의 무거운 write 부하가 사용자 metadata 의 빠른 query 를 막을 수 있다. |
| ③ 사고 범위 축소 (Blast-radius reduction) |
한 DB 가 corrupt 되거나 해킹당해도 다른 DB 는 무사. 전체 시스템이 동시에 죽지 않는다. 예시: providerdb (AML provider 연동) 가 SQL injection 으로 뚫려도 ledgerdb (잔액) 와 auditdb (audit log) 는 별개 DB 라서 그대로 안전. |
| ④ 운영 권한 분리 (Operational separation) |
각 service 의 DB 계정이 자기 DB 만 접근 가능. 한 service 가 다른 도메인을 침범할 수 없음. 예시: Broadcast Service (chain 에 tx 보내는 역할) 의 코드가 버그로 ledger entries 를 조작하려 해도 권한 자체가 없어서 실패. 같은 DB 였으면 가능했을 일. |
| ⑤ 외부 감사 단순화 (Audit separation) |
외부 감사관이 auditdb 한 곳만 read-only 로 보면 됨. 다른 DB 에 있는 고객 PII (개인정보) 는 볼 필요 없음 → privacy 보호 + 감사 범위 명확. 예시: 분기별 SOC 2(?) 감사 때 감사관에 walletdb 의 사용자 email 까지 노출하지 않아도 됨. |
대가 (Trade-offs) — 7 개로 쪼개면 무엇이 어려워지는가
| 제약 | 의미 + 해결책 |
|---|---|
| ① DB 끼리 직접 연결 불가 (Cross-DB FK 불가) |
같은 DB 안에서는 해결: application code 에서 검증 + Reconciliation Service 가 정기적으로 일관성 확인 (예: walletdb 의 user 가 가리키는 workspace 가 실제 존재하는지). |
| ② 여러 DB 에 동시 commit 불가 (Multi-DB transaction 불가) |
"approval 통과 → audit log 기록" 을 한 transaction 으로 묶을 수 없음. approverdb 에 결정 저장 후 auditdb 에 기록하는 도중 시스템이 죽으면 audit log 가 누락될 위험. 해결: outbox pattern(?). 같은 DB 의 transaction 으로 "결정 + outbox 행" 두 개를 함께 쓰고, worker 가 outbox 를 읽어 다른 DB 로 비동기 복제. 결과적으로 eventual consistency 보장. |
| ③ 운영 복잡도 증가 |
7 개 DB 인스턴스 = 7 배의 backup / 모니터링 / 권한 관리 / version upgrade. 해결: 자동화 도구 (Terraform / Ansible) + monitoring dashboard 표준화 + 작은 institution 은 1 개 DB + schema 분리 로 시작해서 점진 분할. 25. DB 운영 참조. |