GN⁺: 금융 시스템 구축을 위한 엔지니어링 원칙
(substack.wasteman.codes)- 회계는 지난 수백 년 동안 크게 변하지 않았음
- 그럼에도 불구하고, 금융 시스템을 위한 소프트웨어를 구축하는 올바른 방법에 대해 많은 혼란이 있음
- 이 글에서는 대기업에서 금융 시스템을 구축한 경험을 바탕으로 한 교훈을 공유
- 회계 시스템 구축에 초점을 맞추겠지만, 이 원칙들은 더 일반적인 금융 시스템에도 적용됨
기본 금융 용어 정의
- 일반 원장 (General Ledger, GL): 특정 기간 동안의 모든 금융 거래를 요약한 회사의 주요 회계 기록. 이는 해당 하위 원장의 집계로 생각할 수 있음
- 보조 원장 (Sub-ledger): 특정 GL과 관련된 모든 개별 거래에 대한 세부 정보를 포함. 보조 원장의 레코드에는 일반 원장보다 훨씬 더 세분화된 데이터가 포함되어 있음 (예: 특정 고객, 주문의 특정 항목 등). 보조 원장과 GL 간의 데이터 차이는 비즈니스 유형과 작업 중인 데이터 양에 따라 달라짐. 일부 소규모 기업은 보조 원장 없이 운영할 수 있지만, 규모가 작은 경우 맞춤형 소프트웨어가 필요할 가능성은 별로
- 재무 기록 (Financial Record): 일반 원장과 보조 원장을 통칭
- 중요성 (Material): 재무제표의 정보 왜곡이 합리적인 이해관계자의 의사 결정에 영향을 미치는지 여부를 나타냄. 이 정의는 의도적으로 다소 애매모호한데, 서로 다른 기업은 서로 다른 중요성 기준을 가지고 있기 때문. 예를 들어 연 매출 25만 달러를 버는 기업에게 중요한 것이 연 매출 10억 달러를 버는 기업에게는 중요하지 않을 수 있음. 설계 관점에서 이 개념의 주요 가치는 다양한 범주의 재무 데이터를 분류하는 것임
High Level Data Flow
Business System --(Financial Events)--> Sub Ledger(s) --(Summarized Accounting Entries)--> General Ledger
회계 시스템의 세 가지 주요 목표
- 정확성 (Accurate): 재무 기록은 비즈니스의 알려진 상태를 반영해야 함
- 예: $9.99의 제품 10개를 판매한 경우, 해당 재무 기록의 합계는 $99.90이어야 함
- 이는 명백해 보이지만 수천, 수백만 건의 거래를 집계할 때 시스템 간 단순 합산이나 반올림 오류로 인해 중대한 부정확성이 발생할 수 있음
-
Wasteman의 노트
- 사람들은 명명(naming)이 컴퓨터 과학에서 가장 어려운 문제라고 하지만, 덧셈이 그 다음으로 어렵다고 생각함
- 지난 몇 년 동안 대규모 금융 시스템에서 일하면서, 아주 작은 버그가 데이터에 큰 차이를 유발한 경우를 셀 수 없이 많이 봄
- float에 대한 합계는 얘기도 꺼내지 말 것. 항상 정수를 사용해야 하는 이유를 힘들게 배움
- 재무 기록은 완전 (complete) 해야 함
- 더 구체적으로, 보조 원장과 일반 원장은 특정 시점에 발생한 모든 비즈니스 활동을 완전히 표현해야 함
- 발생했지만 재무 기록에 없는 이벤트가 있다면 시스템이 완전하지 않은 것
- 이는 최종 일관성 (eventual consistency)이 허용되지 않음을 의미하지는 않음
- 데이터가 언제 완전해질지 알아야 이해관계자에게 데이터가 확정되었음을 알릴 수 있음
-
Wasteman의 노트
- 완전성을 보장하는 것도 놀라울 정도로 어려운 문제임
- 시스템이 확장됨에 따라 데이터가 여러 시스템을 거치면서 실수로 변형되거나 누락될 수 있음
- 감사 가능성 (Auditable): 이해관계자가 오류를 감지하고 비즈니스 성과를 정확하게 측정할 수 있도록 재무 기록을 쉽게 감사할 수 있어야 함
- 적시성 (Timely): 회계 시스템은 비즈니스의 특정 요구 사항을 충족해야 함
- 소규모 기업은 월말에 모든 숫자를 덤프하는 것으로 충분할 수 있지만, 대기업은 일반적으로 실시간에 가까운 시스템을 원함
- 이를 통해 한 달 내내 재무 상태를 모니터링하고, 재무 데이터에 기반한 의사 결정을 더 빠르게 내리며, 월/분기 초에 마감하기 위한 촉박함을 줄일 수 있음
- 그 필요가 무엇이든, 우리의 회계 시스템은 비즈니스의 요구 사항을 충족해야 하며, 그들에게 적시가 의미하는 바 그대로여야 함
-
Wasteman의 노트
- 사람들은 적시성과 관련하여 배치 대 스트리밍 시스템에 대한 대화에서 길을 잃는 경향이 있음
- 내 견해로는 대부분의 시스템에서 이것이 중요한 구분이 아님
- 초 단위에서 분 단위에 이르는 매우 짧은 지연 시간에 대해 신경 쓴다면 이것이 중요함
- 그러나 소비자가 하루에 몇 번 이상 업데이트를 볼 필요가 없을 때 사람들이 어떤 것을 해야 할지에 대해 논쟁하는 것을 듣는 경우가 놀라울 정도로 많음
- 요청했다고 해서 반드시 필요한 것은 아님
회계 시스템이 지켜야 할 세 가지 주요 엔지니어링 원칙
- 데이터의 불변성 (Immutability)과 내구성 (Durability)
- 감사 가능성을 허용하여 디버깅과 정확성에 도움이 됨
- 데이터가 불변하면 언제든지 시스템의 상태를 기록할 수 있음
- 이는 이전 상태에서 세계를 재계산하는 것을 정말 쉽게 만듦. 어떤 상태도 잃어버리지 않기 때문
- 한 번 재무 기록에 명시된 데이터는 삭제할 수 없음
- 시스템에 대한 모든 수정 사항은 새로운 금융 거래로 표시되어야 함
- 예: 시스템에 버그가 있어 $900이어야 할 서비스가 실수로 $1000에 판매된 것으로 보고된 경우
- 이 실수를 바로잡으려면 먼저 실수에 해당하는 회계 항목을 취소하고 정확한 금액으로 회계 항목을 다시 기술해야 함
- 데이터는 가장 작은 단위로 표현되어야 함(Data recorded at the smallest grain)
- 위의 원칙과 유사하게, 이것은 또한 명확한 감사 추적을 가능하게 하는 데 매우 중요함
- 재무 보고서와 일반 원장이 집계되더라도, 그것들은 더 세분화된 이벤트에서 계산됨
- 데이터가 이해되지 않을 때, 문제가 무엇이었는지 디버깅하기 위해 가장 세분화된 데이터가 필요함
- 가장 낮은 수준의 세분성으로 데이터를 저장하면 해당 데이터셋에서 파생된 데이터를 수정하는 것도 매우 쉬워짐
- 단일 불변 데이터셋이 해당 데이터의 모든 뷰에 대한 진실의 핵심 소스인 경우,
- 뷰를 수정하려면 데이터를 수정한 후 해당 뷰를 생성하는 파이프라인을 다시 실행하기만 하면 됨
- 이와 유사하게 회계사가 장부를 마감할 준비를 할 때,
- 그들은 장부가 정확한지 검증하기 위해 발생한 모든 거래와 계정 잔액을 조정함
- 불일치가 발견되면 문제를 일으키고 있는 정확한 거래를 파고들 수 있음
- 멱등성 (Idempotency)이어야 함
- 모든 재무 이벤트는 한 번만 처리될 수 있으며, 재무 기록의 중복은 명백한 부정확성을 야기할 것임
- 이러한 이유로 재무 기록을 생성하는 모든 코드는 멱등원 (idempotent)이어야 함
- 멱등원이란 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질을 의미
- 즉, 재무 이벤트를 여러 번 처리하더라도 결과는 처음 처리한 것과 동일해야 함
모범 사례
- 금융 금액을 나타내기 위해 정수를 선호할 것: 산술 연산이 훨씬 쉬워짐. float는 피할 것
-
통화 변환 시 정밀도 손실을 최소화할 수 있도록 금융 금액의 세분성을 지원할 것
- 달러만 다루는 경우 센트 단위로 표현하는 것으로 충분할 수 있음
- 글로벌 비즈니스의 경우 마이크로 단위나
DECIMAL(19, 4)
와 같은 소수를 선호 - 금융 시스템에서 소수 선택이 인기가 있지만, 광고 금융 시스템에서는 마이크로가 표준
-
일관된 반올림 방법을 사용할 것: 반올림 방식에 따라 예상 금액과 중요한 차이가 발생할 수 있음
- 모든 값을 5 이상은 다음 유효 숫자로, 4 이하는 내림하는 방법
- 항상 반올림하는 방법 등
- 중요한 것은 전체적으로 일관성을 유지하는 것 (거래당 1센트씩 차이나는 경우 1천만 건이면 $100k 차이)
-
가능한 한 통화 변환을 지연시킬 것: 통화를 미리 변환하면 정밀도 손실이 발생할 수 있음
- 현지 통화로 집계가 발생한 후까지 통화 변환을 지연
-
시간의 정수 표현 사용: 약간 논란의 여지가 있지만 강력 추천
- 타임스탬프를 객체로 파싱하는 다양한 라이브러리들이 각각 다르게 처리함
- 이러한 골치 거리를 피하고 정수를 사용하는 것이 좋음
- Unix 타임스탬프나 UTC 기반 정수 datetime도 완벽하게 작동
- 시스템 간 데이터 변환이 적을수록 좋음
-
Wasteman의 노트
- 일광 절약 시간제 관련 버그는 언급조차 하지 않았음. 증가하는 정수를 사용하면 이를 완전히 피할 수 있음
- datetime을 고집한다면 적어도 UTC를 사용할 것. 매우 큰 기업들이 UTC가 아닌 타임스탬프를 사용하는 경우가 놀라울 정도로 많음
Hacker News 의견
-
일관된 반올림 방법론 사용의 중요성 강조
- 비즈니스 도메인 코드에서 반올림 전략을 국가 코드별로 관리할 필요성 언급
-
시간을 정수로 표현하는 방법 추천
- Unix 타임스탬프나 정수 기반 UTC 날짜시간 사용 권장
- 과거 또는 미래의 특정 시간에 대해 유효함
- 예: 48시간 취소 정책을 가진 회사의 경우 미래 타임스탬프 계산 가능
- 국가별 세금 연도 종료 시간 등은 시간대 저장 필요
-
회계 시스템에서 관계형 데이터베이스 사용 권장
- ACID 특성 제공
- 임의 정밀도 숫자 데이터 타입과 검증된 연산 및 반올림 모드 제공
- SQL을 통한 계산 및 보고 가능
- SQL 전문가 고용 시 우아한 보고서 작성 가능
- 고속 성능 및 재해 예방 및 복구 도구 제공
- 다국적 기업의 금융 시스템 구축 경험 공유
-
회계 시스템의 주요 목표는 정확성, 감사 가능성, 적시성임
- 일관성도 중요한 요소로 언급
- 여러 시간 차원이 존재하며, 각 차원에 따라 일관된 뷰 제공 필요
- 예: 거래가 완료되었지만 정산되지 않은 경우 프론트 오피스 회계에만 포함
-
회계 시스템의 완전성에 대한 의견
- 모든 거래가 제때 처리되지 않음을 가정
- 여러 계층의 원장을 통해 거래를 처리하고 조정 프로세스 필요
-
글로벌 비즈니스의 경우 최소 8자리 소수점 사용 권장
- 환율 변환을 가능한 한 지연시킬 필요성 강조
- 환율 변환은 법적 및 회계적 의무의 일부임
-
사용자 인터페이스(UI)의 중요성 언급
- 회계 소프트웨어의 UI에 대한 실망감 표현
- 더 나은 UI 솔루션 필요성 강조
-
배치 처리와 스트리밍 처리의 차이점 설명
- 두 시스템의 설계가 완전히 다름
- 대량의 기존 데이터를 처리하는 데 어려움 존재
-
TypeScript를 사용한 송장 시스템 구축 경험 공유
- 반올림 오류 방지 방법 설명
- 관련 링크 제공
-
표준 라이브러리의 클래스 사용 권장
- Java의 BigDecimal 및 Python의 Decimal 사용 추천
- 동일한 스케일을 유지하거나 스케일을 저장하는 표준 적용 필요성 강조
-
반올림 및 데이터 공유의 어려움 설명
- 두 자리 소수점만 처리할 수 있는 시스템과의 데이터 공유 문제
-
미국 상위 10대 은행의 API 작업 경험 공유
- 이자율 저장 방식의 일관성 문제 언급
- 일관성의 중요성 강조
-
Martin Fowler의 "Accounting Patterns" 추천
- 금융 이벤트 관리 시스템 구축 경험 공유
- 관련 링크 제공