533. 이벤트 소싱 (Event Sourcing)과 상태 스트림
⚠️ 이 문서는 "통장에 1만 원이 있다"라는 현재의 최종 결과값만 딱 1줄 저장하는 전통적인 데이터베이스의 방식을 버리고, **"0원에서 5만 원 입금, 3만 원 출금, 1만 원 출금했다"라는 과거의 모든 변경 내역(Event)을 낱낱이 저장하여 완벽한 추적과 복구를 가능하게 만든 아키텍처 패턴인 '이벤트 소싱'**을 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: 데이터베이스에 데이터를 저장할 때
UPDATE나DELETE를 절대 쓰지 않는다. 오직 일어난 사건(Event)들을 순서대로INSERT만 하는(Append-only) 아키텍처다.- 현재 상태의 도출: "그럼 지금 내 잔고는 얼만데?"라는 질문에 대답하기 위해, 과거부터 지금까지 쌓인 모든 이벤트 로그를 처음부터 끝까지 수학적으로 다 더해서(Replay) 현재 값을 실시간으로 계산해 낸다.
- 가치: 완벽한 감사 로그(Audit Trail)가 기본으로 탑재되며, 특정 시점의 이벤트들만 다시 뭉치면 "어제 낮 12시의 장바구니 상태"를 100% 완벽하게 타임머신처럼 복원할 수 있다.
Ⅰ. 개요: 덮어쓰기의 저주 (Context & Necessity)
"내 장바구니에 콜라가 있었는데 왜 사라졌지? DB 확인해 봐!"
기존 RDBMS(CRUD 기반)의 방식은 너무나 무심하다.
장바구니테이블을 열어보니 콜라가 없다. 왜 없어졌는지, 언제 지웠는지 알 길이 없다. DB는 오직 **'현재의 상태(State)'**만 덮어쓰기(UPDATE,DELETE)로 저장하기 때문이다.
**이벤트 소싱(Event Sourcing)**은 이 덮어쓰기를 혐오한다. 장바구니 테이블을 아예 없애버리고, 대신 **'장바구니 활동 로그'**만 남긴다.
- 10:00
[이벤트: 장바구니 생성] - 10:01
[이벤트: 콜라 담기] - 10:02
[이벤트: 피자 담기] - 10:03
[이벤트: 콜라 빼기]◀── 아하! 여기서 지웠구나!
이벤트 소싱은 데이터가 꼬였을 때 "왜 이렇게 되었는가?"에 대한 100% 완벽한 대답을 내놓는 궁극의 추적 시스템이다.
📢 섹션 요약 비유: 기존 DB가 **'연필로 쓰고 지우개로 빡빡 지워가며 현재 잔액만 딱 하나 적어두는 가계부'**라면, 이벤트 소싱은 **'은행 통장 거래내역서'**와 같습니다. 옛날 거래를 지우개로 지우는 일은 절대 없죠. 무조건 "출금 10,000원"이라고 밑에 새로운 줄을 덧붙여서 쓸 뿐입니다.
Ⅱ. 이벤트 소싱의 3대 작동 원리 ★
1. Append-Only (오직 덧붙이기만 할 것)
- 모든 상태 변경은 '이벤트'라는 불변(Immutable)의 객체로 만들어져 디스크에 순차적으로 기록된다.
UPDATE나DELETE가 없으므로 디스크의 랜덤 I/O(428번 문서)가 사라지고, 쓰기 속도가 우주 끝까지 올라간다. (Kafka 등의 메시지 큐와 환상의 궁합이다.)
2. Replay (과거 재생)
- "현재 상태"를 알고 싶으면, 시스템은 1번 이벤트부터 오늘의 이벤트까지 빛의 속도로 쫙 읽으면서 값을 더하고 뺀다(상태 머신).
- 이 과정이 무거워 보이지만, 메모리에서 돌리면 1,000개의 이벤트도 0.001초 만에 덧셈이 끝난다.
3. Snapshot (스냅샷 - 최적화)
- "가입한 지 10년 된 유저의 이벤트를 1억 번 더하고 있어야 하나요?"
- 너무 무거워지는 걸 막기 위해, 매일 밤 12시에 "지금까지 계산해 보니 1만 원이네"라고 스냅샷을 하나 딱 찍어둔다.
- 다음 날 계산할 때는 1번 이벤트부터 안 읽고, 어젯밤 찍어둔 스냅샷(1만 원)부터 시작해서 오늘 발생한 이벤트 3개만 더하면 된다.
Ⅲ. 실무 아키텍처: CQRS와의 완벽한 짝꿍
이벤트 소싱은 치명적인 단점이 있다. "조회(SELECT)"가 끔찍하게 어렵다는 것이다. "장바구니에 콜라가 들어있는 고객 10만 명을 찾아줘!"
- 이 질문에 대답하려면 전 세계 모든 고객의 이벤트를 다 Replay 해봐야 한다. (서버 터짐)
그래서 이벤트 소싱은 반드시 **CQRS (명령과 조회의 책임 분리)**라는 아키텍처와 한 세트로 움직인다.
- 명령 (Command): 사용자가 버튼을 누르면 이벤트 스토어(Kafka 등)에
INSERT만 미친 듯이 한다. - 조회 (Query): 백그라운드 스레드가 이벤트 스토어를 몰래 읽어가면서, 읽기 전용 DB(Redis나 일반 MySQL)에 "현재 상태"를 예쁘게 만들어둔다.
- 사용자가 조회를 할 때는 뚱뚱한 이벤트 스토어를 뒤지지 않고, 얇고 빠른 읽기 전용 DB에서 그냥 가져온다.
┌──────────────────────────────────────────────────────────────┐
│ Event Sourcing과 CQRS 아키텍처 결합 시각화 │
├──────────────────────────────────────────────────────────────┤
│ │
│ [ 👨💻 유저 ] "콜라 담기!" │
│ │ │
│ ▼ (Command - 쓰기) │
│ [ 📝 이벤트 스토어 (Kafka / EventStoreDB) ] │
│ 1. 피자 담음 │
│ 2. 콜라 담음 (방금 추가됨!) │
│ │ │
│ ▼ (비동기 동기화) │
│ [ 📊 읽기 전용 DB (Redis) ] ◀── (Query - 조회) ── [ 👨💻 유저 ] │
│ - 철수 장바구니: [피자, 콜라] │
│ │
│ ★ 특징: 쓰는 곳과 읽는 곳이 물리적으로 분리되어 있어, 조회 쿼리가 미친듯이 빠르다!│
└──────────────────────────────────────────────────────────────┘
Ⅳ. 결론
"과거를 기억하는 자만이 완벽한 미래를 그릴 수 있다." 마이크로서비스(MSA) 시대가 오면서 시스템은 수십 개로 찢어졌고, 데이터가 꼬였을 때 "도대체 어느 시스템이 데이터를 망가뜨렸나?"를 추적하는 것은 불가능에 가까워졌다. 이벤트 소싱은 이 혼돈 속에서 유일한 진실의 원천(SSOT)을 제공한다. 물론 모든 테이블을 이벤트 소싱으로 짜면 개발자들이 미쳐버릴 것이다. 하지만 돈이 오가는 결제 시스템, 물건이 움직이는 재고 시스템처럼 "상태가 변한 모든 역사적 맥락(Context)" 자체가 비즈니스의 핵심 자산인 곳에서 이벤트 소싱은 선택이 아니라 생존을 위한 필수 아키텍처다.
📌 관련 개념 맵
- 관련 철학: Immutable Data (불변 데이터), Append-Only (추가 전용 쓰기)
- 단짝 아키텍처: CQRS (Command and Query Responsibility Segregation)
- 메시지 파이프라인: Apache Kafka, EventStoreDB, RabbitMQ
- 유사한 DB 내부 기술: Redo Log (455번 문서), 원장 DB (Ledger DB - 492번 문서)
👶 어린이를 위한 3줄 비유 설명
- 칠판(RDBMS)에 점수를 적을 때, 10점에서 5점을 깎으려면 지우개로 10을 지우고 5라고 다시 예쁘게 써야 하죠. (덮어쓰기)
- 이벤트 소싱은 지우개를 아예 버려버리는 거예요! 무조건 그 밑에다가 "+10점 획득", "-5점 차감"이라고 있었던 일(이벤트)만 주르륵 적어놔요.
- 나중에 "내 점수 몇 점이야?"라고 물어보면, 위에서부터 적힌 숫자를 암산으로 호다닥 더해서 "5점이네!"라고 대답해 주는 방식이랍니다!