180. 이벤트 소싱 (Event Sourcing)
⚠️ 이 문서는 데이터베이스에 현재의 최종 상태(Current State)만 덮어써서 저장하는 전통적인 CRUD 방식을 탈피하여, 데이터의 변경을 유발한 '모든 과거의 사건(이벤트 로그)'들을 순서대로 지우지 않고 영구적으로 저장하여 현재 상태를 계산해 내는 이벤트 소싱 아키텍처를 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: 은행 통장 잔고가 10만 원이라고 DB에 숫자 '100,000'만 덜렁 저장하는 것이 아니라, [만원 입금], [오만원 입금], [육만원 출금]이라는 3번의 이벤트를 순서대로 장부(Event Store)에 기록하는 데이터 저장 패러다임이다.
- 가치: 기존 데이터가 절대 삭제되거나 수정(Update)되지 않기 때문에 완벽한 데이터 감사(Audit)가 가능하며, 과거의 특정 시점으로 시스템 상태를 완벽하게 되돌릴 수 있는 타임머신 기능을 제공한다.
- 기술 체계: 이벤트는 추가(Append-only)만 가능하여 쓰기 속도가 극단적으로 빠르나, 현재 상태를 조회할 때마다 모든 이벤트를 재연산(Replay)해야 하는 오버헤드를 막기 위해 CQRS 패턴의 '읽기 전용 View' 및 '스냅샷(Snapshot)'과 짝을 이뤄 사용된다.
Ⅰ. RDBMS의 한계: 덮어쓰기로 인한 역사의 소실
기존의 RDBMS 구조는 데이터의 최종 결과만 기억할 뿐 과정은 기억하지 못한다.
- 상태 기반 (State-based) 저장의 문제:
- 쇼핑몰에서 고객 장바구니 DB 테이블의
item_count컬럼이 '3'으로 업데이트되었다. - 관리자는 고객이 처음부터 3개를 담았는지, 아니면 5개를 담았다가 2개를 취소한 것인지 알 방법이 없다. (비즈니스 인사이트 유실)
- 쇼핑몰에서 고객 장바구니 DB 테이블의
- 트랜잭션 데드락 (Locking):
- 한 고객의 장바구니에 여러 번의 업데이트가 동시에 들어오면 DB 레코드에 락(Lock)이 걸려 시스템 전체의 병목이 발생한다.
- 이벤트 소싱의 제안:
UPDATE cart SET count=3쿼리를 날리는 대신,CartCreated(1),ItemAdded(1),ItemRemoved(2)라는 불변의 객체(Event) 자체를 순서대로 메시지 큐(Kafka)나 Event Store에 밀어 넣는 방식으로 바꾼다.
📢 섹션 요약 비유: 도화지에 그림을 그릴 때 틀리면 지우개로 지우고 덧그리는 것(RDBMS)이 아니라, 모든 터치를 투명한 필름에 한 장씩 순서대로 그려서 겹쳐보는 방식(이벤트 소싱)입니다. 필름을 빼면 과거로 돌아갈 수 있고, 실수한 흔적도 모두 귀중한 정보로 남습니다.
Ⅱ. 이벤트 소싱의 3대 핵심 메커니즘
이벤트는 절대 변하지 않으며(Immutable), 순서가 생명이다.
- 이벤트 스토어 (Event Store):
- 이벤트를 저장하는 특수한 목적의 데이터베이스로, MySQL을 쓸 수도 있고 Kafka나 전용 EventStoreDB를 쓰기도 한다. 핵심은
Update나Delete연산이 아예 존재하지 않으며, 오직 꼬리에 꼬리를 무는Insert(Append)만 허용된다.
- 이벤트를 저장하는 특수한 목적의 데이터베이스로, MySQL을 쓸 수도 있고 Kafka나 전용 EventStoreDB를 쓰기도 한다. 핵심은
- 상태 복원 (Replay & Projection):
- 고객의 현재 장바구니 상태를 알고 싶으면, Event Store에 있는 해당 고객의 ID로 묶인 모든 이벤트를 처음부터 순서대로 쭉 읽어와 메모리 위에서 재생(Replay)하여 잔액을 계산(Projection)해 낸다.
- 스냅샷 (Snapshot):
- 고객이 10년 동안 1만 번의 입출금을 했다면, 현재 잔액을 구하려고 1만 번을 매번 더하는 것은 매우 비효율적이다.
- 따라서 매 100번째 이벤트마다 '현재 잔고는 50만 원'이라는 중간 요약본(Snapshot)을 저장해 둔다. 조회가 들어오면 가장 최근 스냅샷부터 이후의 이벤트 3~4개만 더하면 된다.
📢 섹션 요약 비유: 바둑을 둘 때 바둑판의 최종 모양(State)만 사진 찍어두는 것이 아니라, 1수부터 100수까지의 기보(Event)를 모두 적어두는 것과 같습니다. 복기(Replay)를 통해 전체 판을 다시 그릴 수 있고, 50수 시점의 모양(Snapshot)을 그려두면 더 빨리 복원할 수 있습니다.
Ⅲ. CQRS와의 필연적 결합
이벤트 소싱 단독으로는 시스템 조회가 거의 불가능하다.
- 조회의 지옥:
- "장바구니에 아이폰이 담겨 있는 모든 고객을 찾아라"라는 쿼리를 이벤트 소싱만으로 풀려면, 전 세계 수백만 고객의 수십억 개 이벤트를 다 Replay 해봐야 한다.
- CQRS 패턴 적용:
- 이벤트를 수신하는 즉시 백그라운드 프로세스가 작동하여, MongoDB 같은 조회 전용 DB(Read DB)에
[A고객: 아이폰 장바구니에 있음]이라는 최종 결과물 뷰(Materialized View)를 실시간으로 만들어둔다. - ┌────────────────────────────────────────────────────────┐ │ [Event Store(Append-Only)] --(Event 발행)--> [Read DB(View 업데이트)] │ │ (저장 속도/감사 목적 극대화) (조회 속도 극대화) │ └────────────────────────────────────────────────────────┘
- 이벤트를 수신하는 즉시 백그라운드 프로세스가 작동하여, MongoDB 같은 조회 전용 DB(Read DB)에
📢 섹션 요약 비유: 이벤트 소싱이 모든 재료의 수입/지출 영수증 1만 장(Event)을 철저히 보관하는 회계장부라면, CQRS는 그 영수증들을 보고 사장님이 한눈에 볼 수 있도록 오늘 치 일일 결산 보고서(Read DB)를 옆방에서 따로 만들어두는 것입니다.