핵심 인사이트 (3줄 요약)
- 본질: 이벤트 소싱 (Event Sourcing)은 현재 상태 한 줄을 계속 덮어쓰는 대신, 상태를 바꾼 사건들을 시간 순서대로 불변 로그 (Append-Only Log) 에 쌓고 그 합으로 현재를 계산하는 저장 모델이다.
- 가치: 왜 이런 상태가 되었는지, 어제와 오늘 사이에 무엇이 달라졌는지, 특정 시점 상태를 다시 만들 수 있는지를 정확히 추적할 수 있어 감사·복구·재현에 강하다.
- 판단 포인트: 이벤트를 진실 원천으로 삼는 대신 버전 관리, 스냅샷, 프로젝션 재생, 최종 일관성까지 감당해야 한다. 따라서 이력의 의미가 큰 도메인에 선택적으로 적용해야 한다.
Ⅰ. 개요 및 필요성
전통적인 CRUD (Create, Read, Update, Delete) 저장 방식은 보통 "현재 값"을 기준으로 시스템을 표현한다. 예를 들어 계좌 잔액이 7만 원이면 데이터베이스에는 balance = 70000만 남는다. 이 방식은 단순하고 직관적이지만, 왜 7만 원이 되었는지, 어떤 순서로 상태가 바뀌었는지는 별도 감사 로그가 없으면 잃어버리기 쉽다.
이벤트 소싱은 이 지점에서 출발한다. 시스템의 핵심 진실을 현재 상태가 아니라 상태를 변화시킨 사건의 연속으로 본다. 즉 DepositMade, WithdrawalMade, TransferCancelled 같은 도메인 이벤트를 과거형 사실로 저장하고, 현재 상태는 그 이벤트들을 순서대로 적용한 결과로 계산한다. 그래서 이벤트 소싱에서는 현재 상태가 "원본"이 아니라 투영된 결과가 된다.
이 방식이 필요한 이유는 상태 그 자체보다 "변화의 과정"이 중요한 업무가 분명히 존재하기 때문이다. 금융 원장, 주문 수명주기, 포인트 적립·차감, 규제 감사, 장기 보상 처리처럼 과거 사건의 맥락이 비즈니스 의미를 가지는 영역에서는 단순 덮어쓰기만으로는 설명력이 부족하다. 장애 분석이나 재현 테스트 관점에서도 "결과"만 있는 시스템보다 "발생 순서"가 남는 시스템이 훨씬 강하다.
┌────────────────────────────────────────────────────────────────────┐
│ State overwrite vs event history │
├────────────────────────────────────────────────────────────────────┤
│ State-based storage │
│ balance = 70000 │
│ └─ current value is visible, path to it is mostly lost │
│ │
│ Event-sourced storage │
│ +100000 DepositMade │
│ -20000 PurchaseApproved │
│ -10000 FeeCharged │
│ = 70000 current balance by replay │
│ │
│ same current state, much richer history │
└────────────────────────────────────────────────────────────────────┘
중요한 포인트는 이벤트가 단순한 행 변경 이력이 아니라는 점이다. 좋은 이벤트는 status_changed_to_3보다 OrderShipped처럼 비즈니스에서 이해 가능한 사실이어야 한다. 그래야 이벤트 로그가 단순 기술 기록이 아니라 도메인 언어가 된다.
- 📢 섹션 요약 비유: 현재 상태만 저장하는 방식은 통장 잔액 숫자만 적어 두는 것과 같고, 이벤트 소싱은 입금·출금 내역이 시간순으로 남는 가계부와 같다. 같은 잔액이라도 이해할 수 있는 정보량이 전혀 다르다.
Ⅱ. 아키텍처 및 핵심 원리
이벤트 소싱의 쓰기 경로는 보통 명령 처리기, 애그리게이트 (Aggregate), 이벤트 저장소, 프로젝션으로 이어진다. 사용자의 명령이 들어오면 애그리게이트는 과거 이벤트 스트림을 읽어 현재 상태를 재구성하고, 규칙 위반 여부를 검사한 뒤 새 이벤트를 만든다. 그 이벤트는 기존 이벤트를 수정하지 않고 맨 뒤에 추가된다.
여기서 중요한 기술 요소는 순서와 동시성이다. 같은 주문이나 같은 계좌에 대해 두 명령이 동시에 들어오면, 어떤 이벤트가 먼저 기록되는지가 결과를 바꿀 수 있다. 그래서 이벤트 저장소는 보통 스트림 버전이나 예상 버전 검사를 사용해 낙관적 동시성 제어 (Optimistic Concurrency Control) 를 수행한다. 즉 "내가 본 마지막 버전 다음에 이어 붙인다"는 조건이 깨지면 다시 읽고 재판단하게 만든다.
┌────────────────────────────────────────────────────────────────────┐
│ Event sourcing write and rebuild flow │
├────────────────────────────────────────────────────────────────────┤
│ Command │
│ │ │
│ ▼ │
│ load snapshot + past events │
│ │ │
│ ▼ │
│ Aggregate validates business rules │
│ │ │
│ ├─ invalid -> reject command │
│ ▼ │
│ append new event(s) with expected stream version │
│ │ │
│ ├──────────────► projector updates read model │
│ │ │
│ └──────────────► replay later to rebuild current state │
└────────────────────────────────────────────────────────────────────┘
구성 요소를 요약하면 다음과 같다.
| 요소 | 역할 | 설계 포인트 |
|---|---|---|
| 명령 처리기 (Command Handler) | 입력 명령 수신 및 권한·형식 검증 | 중복 명령 처리와 에러 모델 분리 |
| 애그리게이트 (Aggregate) | 과거 이벤트를 접어 현재 규칙 판단 | 불변식 보장, 명령 허용 여부 판단 |
| 이벤트 저장소 (Event Store) | 이벤트 스트림의 원본 보관 | append-only, 순서, 버전 충돌 처리 |
| 스냅샷 (Snapshot) | 긴 스트림의 중간 상태 저장 | 재생 비용 절감, 저장 주기 설계 |
| 프로젝션 (Projection) | 이벤트를 조회 모델로 변환 | 멱등성, 재생 가능성, 버전 대응 |
| 읽기 모델 (Read Model) | 화면·검색용 현재 뷰 | CQRS와 결합 시 별도 저장소 사용 가능 |
스냅샷은 자주 오해되는 요소다. 스냅샷은 이벤트를 대체하는 원본이 아니라, 재생 시간을 줄이기 위한 중간 체크포인트다. 예를 들어 한 계좌에 10만 건 이벤트가 쌓였다면, 최근 스냅샷 이후의 이벤트만 다시 적용해 현재 상태를 빠르게 계산할 수 있다. 따라서 이벤트 소싱의 핵심 공식은 이벤트가 원본, 스냅샷은 캐시라고 정리하는 편이 맞다.
또한 읽기 모델은 언제든 다시 만들 수 있어야 한다. 이벤트 로그가 진실 원천이라면, 화면용 테이블이나 검색용 인덱스는 깨져도 다시 투영하면 된다. 이 점 때문에 이벤트 소싱은 CQRS (Command Query Responsibility Segregation) 와 자주 짝을 이룬다.
- 📢 섹션 요약 비유: 레고 작품 사진 한 장만 남겨 두는 것이 아니라, 조립 설명서의 모든 단계를 보관하는 셈이다. 중간 완성 사진은 스냅샷이고, 진짜 원본은 블록을 어떤 순서로 끼웠는지에 대한 기록이다.
Ⅲ. 비교 및 연결
이벤트 소싱은 종종 감사 로그, 변경 데이터 캡처, 메시지 브로커와 혼동된다. 하지만 핵심 차이는 무엇을 시스템의 진실 원천으로 삼느냐다. 감사 로그는 보통 현재 상태 저장이 먼저 있고, 그 뒤에 참고용 흔적을 남긴다. 반면 이벤트 소싱은 이벤트 스트림 자체가 먼저이고, 현재 상태는 그로부터 계산된다.
| 방식 | 진실 원천 | 장점 | 한계 | 잘 맞는 상황 |
|---|---|---|---|---|
| CRUD 현재 상태 저장 | 현재 테이블 | 단순함, 학습 비용 낮음 | 과거 맥락과 재생 능력 약함 | 일반 관리 시스템 |
| CRUD + 감사 로그 | 현재 테이블 + 보조 로그 | 추적성 일부 확보 | 로그와 현재 상태가 분리됨 | 기본 감사 요구 |
| 이벤트 소싱 | 이벤트 스트림 | 완전한 이력, 재생, 시점 복원 | 모델링·운영 복잡도 큼 | 금융, 주문 수명주기, 규제 도메인 |
| 트랜잭셔널 아웃박스 (Transactional Outbox) | 현재 상태 + 발행용 기록 | 외부 이벤트 전달 안정화 | 저장 원천 자체는 바뀌지 않음 | 분산 이벤트 발행 |
이 차이는 CQRS와 연결될 때 더 중요해진다. CQRS는 읽기 모델과 쓰기 모델을 분리하는 패턴이고, 이벤트 소싱은 쓰기 모델의 저장 방식을 바꾸는 패턴이다. 둘은 자주 함께 쓰이지만 동일 개념은 아니다. CQRS 없이 이벤트 소싱을 적용할 수도 있고, 이벤트 소싱 없이 CQRS만 적용할 수도 있다.
또 하나의 오해는 "Kafka 같은 메시지 브로커가 있으면 곧 이벤트 소싱"이라는 생각이다. 브로커는 이벤트를 전달하는 인프라일 수는 있지만, 이벤트 재생 가능성, 스트림 버전 관리, 애그리게이트 단위 순서 보장, 장기 보존 정책이 설계되지 않았다면 그 자체로 이벤트 저장소가 되지는 않는다. 이벤트 소싱은 단순 발행이 아니라 도메인 이벤트를 저장 모델의 중심에 놓는 설계다.
- 📢 섹션 요약 비유: CCTV 영상은 흔적을 보는 용도이고, 경기 공식 기록지는 경기 결과를 인정하는 원본이다. 이벤트 소싱은 후자가 되는 것이지, 단순히 흔적을 더 남기는 수준이 아니다.
Ⅳ. 실무 적용 및 기술사 판단
이벤트 소싱은 감사 요구가 크고, 상태 변화의 순서 자체가 업무 의미를 갖고, 나중에 새로운 읽기 모델을 다시 만들 가능성이 높은 시스템에서 빛난다. 예를 들어 결제 승인·취소·환불 흐름, 포인트 적립·차감, 주문 생성·결제·출고·반품 같은 도메인은 사건의 흐름이 곧 비즈니스 설명이다. 반대로 단순 게시판 설정값처럼 "현재 값만 맞으면 되는" 영역에 적용하면 복잡도만 늘어날 수 있다.
기술사 관점에서는 특히 이벤트 설계 품질이 중요하다. 이벤트는 row_updated처럼 기술적인 차분보다 PaymentAuthorized, InventoryReserved처럼 도메인 언어로 표현되어야 한다. 그래야 시간이 지나도 이벤트 의미를 이해할 수 있고, 다른 읽기 모델이나 다른 서비스가 재사용하기 쉽다.
┌────────────────────────────────────────────────────────────────────┐
│ When event sourcing is worth the cost │
├────────────────────────────────────────────────────────────────────┤
│ Need full audit / replay / temporal reconstruction? │
│ ├─ Yes -> event sourcing candidate │
│ └─ No -> current-state model may be enough │
│ │
│ Need separate read models and eventual consistency is acceptable? │
│ ├─ Yes -> pair with CQRS │
│ └─ No -> keep simpler projection strategy │
│ │
│ Can the team operate versioning, snapshot, reprocessing? │
│ ├─ Yes -> adopt selectively │
│ └─ No -> complexity may outweigh benefit │
└────────────────────────────────────────────────────────────────────┘
기술사 판단 체크리스트
- 상태 변화 순서와 근거가 비즈니스적으로 중요한 도메인인가?
- 이벤트 이름이 기술 용어가 아니라 도메인 사실로 정의되어 있는가?
- 스트림 버전 충돌, 중복 명령, 프로젝션 재처리를 감당할 운영 절차가 있는가?
- 스냅샷 주기와 전체 재생 시간이 현실적인가?
- 이벤트 스키마 변경 시 업캐스팅 (Upcasting) 또는 버전 공존 전략이 준비되어 있는가?
- 개인정보 삭제 요구가 있을 때 불변 로그를 어떻게 처리할지 정책이 있는가?
자주 나오는 안티패턴
- 현재 테이블 변경 전후 값을 그대로 덤프하고 그것을 이벤트라 부르는 경우
- 이미 기록된 이벤트를 수정해 "조용히 정정"하는 경우
- 프로젝션은 실시간만 믿고 전체 재생 절차를 만들지 않은 경우
- 간단한 CRUD 화면에도 유행처럼 이벤트 소싱을 도입하는 경우
특히 불변 로그와 개인정보 삭제 요구는 함께 고민해야 한다. 이벤트를 절대 물리 삭제하지 않는 구조라면, 민감 정보는 이벤트 본문에 직접 넣기보다 참조 키로 분리하거나 암호화 키 폐기 전략을 함께 준비해야 한다. 즉 이벤트 소싱은 개발 패턴이면서 동시에 데이터 거버넌스 설계이기도 하다.
- 📢 섹션 요약 비유: 박물관에 모든 전시 교체 기록을 남기면 역사 연구에는 좋지만, 기록 체계를 제대로 관리하지 않으면 나중에 오히려 창고가 더 복잡해진다. 좋은 기록은 많이 남기는 것만이 아니라, 나중에 다시 읽고 재구성할 수 있게 남기는 것이다.
Ⅴ. 기대효과 및 결론
이벤트 소싱이 잘 맞는 시스템에서는 감사 추적, 장애 재현, 시점 복원, 새 조회 모델 생성, 외부 이벤트 연계가 훨씬 강력해진다. 현재 화면이 잘못되어도 이벤트 로그만 살아 있으면 읽기 모델을 다시 만들 수 있고, 특정 시점 상태를 재생해 문제를 분석할 수 있다. 그래서 이벤트 소싱은 "현재 상태를 잘 저장하는 기술"보다 시간 위에서 시스템을 설명하는 기술에 가깝다.
하지만 그만큼 비용도 분명하다. 이벤트 설계 품질이 낮으면 오히려 이해하기 어려운 로그만 남고, 스냅샷·프로젝션·스키마 진화·관측 체계를 잘못 설계하면 운영 부담이 커진다. 또한 단순 업무에는 얻는 가치보다 학습 비용과 복잡도가 더 클 수 있다. 따라서 이벤트 소싱은 전면 도입보다, 이력과 재현 가치가 큰 핵심 도메인에 선택적으로 적용하는 편이 현실적이다.
결론적으로 기억해야 할 문장은 이것이다. 이벤트 소싱에서 현재 상태는 버려도 다시 만들 수 있지만, 이벤트 로그는 버리면 안 되는 원본이다. 이 관점을 이해하면 이벤트 소싱은 단순 저장 방식이 아니라, 시스템의 시간을 설계하는 아키텍처로 남는다.
- 📢 섹션 요약 비유: 오늘의 날씨 한 줄만 적는 달력보다, 매일 어떤 날씨였는지 일기를 쓰는 편이 시간이 지나도 더 많은 답을 준다. 이벤트 소싱은 시스템의 일기를 원본으로 삼는 방식이다.
📌 관련 개념 맵
| 개념 | 연결 포인트 |
|---|---|
| 도메인 이벤트 (Domain Event) | 비즈니스에서 실제로 일어난 사실을 과거형으로 표현한 기본 단위 |
| 이벤트 저장소 (Event Store) | 이벤트 스트림의 원본을 append-only로 보관하는 저장소 |
| 애그리게이트 스트림 (Aggregate Stream) | 한 업무 개체에 대한 이벤트 순서를 유지하는 단위 |
| 스냅샷 (Snapshot) | 긴 이벤트 스트림의 재생 비용을 줄이는 중간 상태 |
| 프로젝션 (Projection) | 이벤트를 현재 조회 형태로 변환하는 과정 |
| CQRS (Command Query Responsibility Segregation) | 이벤트 기반 쓰기 모델과 읽기 모델 분리를 자주 결합하는 패턴 |
| 낙관적 동시성 제어 (Optimistic Concurrency Control) | 스트림 버전 충돌을 감지해 이벤트 순서를 보호하는 방법 |
| 업캐스팅 (Upcasting) | 오래된 이벤트를 새 스키마 의미로 해석하게 돕는 진화 전략 |
📈 관련 키워드 및 발전 흐름도
비즈니스 명령
│
▼
과거 이벤트 재생 + 규칙 검증
│
▼
새 이벤트 append
│
├──────────────► 스냅샷 생성으로 재생 비용 절감
│
├──────────────► 프로젝션으로 읽기 모델 생성
│
└──────────────► 감사 추적 · 시점 복원 · 외부 연계
이 흐름도는 "명령 → 이벤트 기록 → 재생/투영 → 운영 가치"로 이어지는 이벤트 소싱의 핵심 구조를 압축한다.
👶 어린이를 위한 3줄 비유 설명
- 이벤트 소싱은 지금 점수가 몇 점인지 한 줄만 적는 대신, 점수가 올라가고 내려간 모든 일을 차례대로 적는 거예요.
- 그래서 나중에 "왜 이렇게 됐지?" 하고 물어보면 처음부터 다시 따라가며 알 수 있어요.
- 대신 기록이 많아지니, 중간 정리표와 규칙을 잘 만들어 두어야 헷갈리지 않아요.