이벤트 소싱에서는 데이터의 최종 상태를 직접 저장하지 않고, 이벤트의 기록을 순차적으로 저장한다.
이렇게 하면 데이터의 현재 상태를 얻기 위해서는 모든 이벤트를 처음부터 다시 적용해야 한다.
이벤트 ID 이벤트 타입 데이터
1 AccountCreated { userId: 123, balance: 0 }
2 MoneyDeposited { userId: 123, amount: 1000 }
3 MoneyWithdrawn { userId: 123, amount: 500 }
4 MoneyDeposited { userId: 123, amount: 2000 }
✅ 현재 계좌 잔액(accountBalance)을 확인하려면?
1.이벤트를 처음부터 모두 읽는다.
2.각 이벤트를 적용하여 최종 상태를 계산한다.
balance = 0 (AccountCreated)
balance = 1000 (MoneyDeposited)
balance = 500 (MoneyWithdrawn)
balance = 2500 (MoneyDeposited)
3.최종적으로 balance = 2500을 반환.
💡 이 방식은 이벤트 수가 적을 때는 괜찮지만, 이벤트 개수가 많아지면 성능 문제가 심각해진다.
예를 들어, 수천 개의 이벤트가 저장된 계좌라면, 단순히 잔액을 조회하는 데도 엄청난 계산이 필요할 것이다.
스냅샷은 특정 시점에서의 현재 상태를 저장해두는 것이다.
이렇게 하면 모든 이벤트를 처음부터 적용할 필요 없이, 스냅샷 이후의 이벤트만 적용하면 됨.
위의 예제를 다시 보자.
🔹 기존 방식 (스냅샷 없이 조회)
이벤트 ID 이벤트 타입 데이터
1 AccountCreated { userId: 123, balance: 0 }
2 MoneyDeposited { userId: 123, amount: 1000 }
3 MoneyWithdrawn { userId: 123, amount: 500 }
4 MoneyDeposited { userId: 123, amount: 2000 }
모든 이벤트를 처음부터 다시 적용해야 함.
🔹 스냅샷 저장 방식
스냅샷을 저장하여 특정 시점 이후의 이벤트만 적용하도록 개선한다.
스냅샷 ID 저장 시점 데이터
S1 이벤트 ID 3 { userId: 123, balance: 500 }
이제 새로운 이벤트가 추가되면 이렇게 저장됨.
이벤트 ID 이벤트 타입 데이터
4 MoneyDeposited { userId: 123, amount: 2000 }
✅ 현재 잔액을 조회하는 방식 (스냅샷 적용)
1.스냅샷을 불러온다.
2.스냅샷 이후의 이벤트만 적용한다.
3.최종적으로 balance = 2500을 반환.
💡 이제는 모든 이벤트를 처음부터 적용할 필요 없이, 스냅샷 이후 이벤트만 적용하면 됨!
➡️ 성능이 크게 향상된다.
스냅샷을 너무 자주 저장하면 스토리지 낭비가 발생하고,
너무 드물게 저장하면 여전히 많은 이벤트를 조회해야 하는 문제가 발생한다.
1.N개의 이벤트마다 저장
2.중요한 상태 변경 시점에서 저장
3.일정 시간마다 스냅샷 저장
1.같은 이벤트 저장소에 저장
2.별도 테이블에 저장
3.NoSQL 또는 캐시에 저장
data class AccountEvent(
val userId: String,
val type: String, // "CREATED", "DEPOSITED", "WITHDRAWN"
val amount: Int? = null
)
data class AccountSnapshot(
val userId: String,
val balance: Int
)
fun getCurrentBalance(userId: String): Int {
val snapshot = snapshotRepository.findLatestSnapshot(userId)
var balance = snapshot?.balance ?: 0
val eventsAfterSnapshot = eventRepository.findEventsAfter(snapshot?.eventId ?: 0, userId)
for (event in eventsAfterSnapshot) {
when (event.type) {
"DEPOSITED" -> balance += event.amount!!
"WITHDRAWN" -> balance -= event.amount!!
}
}
return balance
}
📌 이 코드의 동작 방식
1. 스냅샷이 있으면 가져옴 (없으면 balance=0)
2. 스냅샷 이후의 이벤트만 적용
3. 최종 balance 반환
1.조회 성능 향상
2.이벤트 개수가 많아져도 성능 유지
3.데이터 복원 속도 향상
1.스토리지 사용량 증가
2.스냅샷의 최신성 유지 필요
3.일관성 문제
✅ 이벤트 소싱은 모든 변경 이력을 저장하는 강력한 기법이지만, 조회 성능 문제가 발생할 수 있다.
✅ 스냅샷(Snapshot)은 특정 시점의 상태를 저장하여 성능을 최적화하는 방법이다.
✅ 스냅샷 이후의 이벤트만 적용하면 되므로 조회 속도를 빠르게 만들 수 있다.
✅ 하지만 저장 공간 관리, 동기화 문제 등을 고려해야 한다.
🔥 즉, 이벤트 소싱 + 스냅샷을 함께 사용하면 조회 성능과 데이터 복원 속도를 동시에 향상시킬 수 있다! 🚀