Zettelkasten

단식부기는 잔액 변화만 기록해 시스템 전체 자금 흐름을 추적하지 못한다

·수정 1

요약

  • 단식부기는 한 거래를 한 줄로 기록한다. "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 합 불변식을 강제해 자금 흐름 정합성을 보장한다로 전환을 고려한다.

관련 노트

참고

원장(Ledger) 설계:

멱등성·재시도 안전: