요약
- 단식부기는 한 거래를 한 줄로 기록한다. "user_id가 +100, -30, -20" 식으로 잔액이 어떻게 변했는지만 남는다.
- 차감한 돈이 어디로 갔는지 기록하지 않으므로 매출/비용/부채 같은 재무 질문은 단식부기 테이블만으로는 풀리지 않는다.
- 게임 재화/콘텐츠 크레딧처럼 "발급 → 소진"이 일방향인 도메인은 단식부기로 충분하다.
본문
테이블 구조
CREATE TABLE balance_transaction (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
amount INTEGER NOT NULL, -- +충전, -차감
reason TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
한 거래 = 한 행. 잔액은 SUM(amount) WHERE user_id = ?로 계산.
한계: 돈의 행선지를 모른다
| user_id | amount | reason |
| A | -200 | 통화 매칭 |
"유저 A에서 200이 빠졌다"는 알 수 있지만 그 200이 회사 매출인지, 다른 유저에게 이체된 것인지, 환불 풀로 빠진 것인지 이 테이블만으로는 알 수 없다.
매출을 알고 싶으면 reason 문자열을 파싱하거나 별도 결제 시스템을 봐야 한다. reason이 자유 텍스트라 운영 중 표기가 바뀌면 집계가 깨진다.
시스템 전체 정합성 검증 불가
단식부기는 "유저 잔액 합"만 보장한다. "회사가 발급한 다이아 총량 = 유저들이 보유한 총량 + 사용된 총량" 같은 시스템 수준 불변식은 강제되지 않는다. 발급 로직과 사용 로직이 따로 짜여 있어 한쪽에 버그가 생겨도 구조적으로 감지되지 않는다.
그래도 단식부기가 적절한 경우
- 일방향 흐름: 재화가 "회사 → 유저"로만 발급되고 유저 간 이동이 없다
- 단위 분리: 결제(원화)와 재화(다이아)가 다른 단위로 다른 시스템에 기록된다
- 매출 집계가 별도 시스템에서 가능: IAP 결제 로그 등 외부 진실의 원천이 있다
connectingServer의 Inventory + InventoryChangeLog 조합이 이 패턴. 다이아 잔액·변경 이력은 단식부기로, 매출은 ExtnlIAPLog(애플/구글 영수증) 쪽에서 별도 집계. 둘은 tx_id로 연결.
단식부기로 부족해지는 시점
- 유저 간 재화 선물/이체가 생길 때 — A의 -100과 B의 +100이 같은 거래임을 보장해야 함
- 재화가 여러 풀(이벤트 풀, 운영 풀, 매출 풀) 사이를 이동할 때
- 회계 감사를 받아야 할 때 (IPO/투자)
이 시점이 오면 복식부기는 모든 거래에서 debit 합 = credit 합 불변식을 강제해 자금 흐름 정합성을 보장한다로 전환을 고려한다.
관련 노트
- 복식부기는 모든 거래에서 debit 합 = credit 합 불변식을 강제해 자금 흐름 정합성을 보장한다
- 멱등성은 같은 요청을 여러 번 해도 결과가 같음을 보장한다
- SELECT FOR UPDATE는 행 수준 잠금을 획득한다
참고
원장(Ledger) 설계:
- https://www.moderntreasury.com/journal/why-we-built-ledgers — Modern Treasury: Why We Built Ledgers
- https://www.moderntreasury.com/journal/how-to-scale-a-ledger-part-v — Modern Treasury: How to Scale a Ledger Part V
- https://developer.squareup.com/blog/books-an-immutable-double-entry-accounting-database-service/ — Square Books
- https://www.uber.com/blog/how-ledgerstore-supports-trillions-of-indexes/ — Uber LedgerStore
- https://docs.tigerbeetle.com/concepts/debit-credit/ — TigerBeetle Debit/Credit
멱등성·재시도 안전:
- https://stripe.com/blog/idempotency — Stripe: API idempotency
- https://brandur.org/idempotency-keys — Brandur Leach: Idempotency Keys in Postgres
- https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/ — AWS Builders' Library