이벤트 소싱의 단점 해결책 ? 스냅샷

hailey·2025년 2월 10일

시스템설계

목록 보기
7/8

📌 1. 이벤트 소싱의 조회 성능 문제

이벤트 소싱에서는 데이터의 최종 상태를 직접 저장하지 않고, 이벤트의 기록을 순차적으로 저장한다.
이렇게 하면 데이터의 현재 상태를 얻기 위해서는 모든 이벤트를 처음부터 다시 적용해야 한다.

🔴 문제점: "조회 시 성능 저하"

  • 예를 들어, 사용자의 accountBalance(잔액)을 조회한다고 가정하자.
  • 이벤트 로그가 다음과 같이 저장되어 있다고 해보자.

이벤트 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을 반환.

💡 이 방식은 이벤트 수가 적을 때는 괜찮지만, 이벤트 개수가 많아지면 성능 문제가 심각해진다.
예를 들어, 수천 개의 이벤트가 저장된 계좌라면, 단순히 잔액을 조회하는 데도 엄청난 계산이 필요할 것이다.

📌 2. 해결책: 스냅샷(Snapshot) 사용

스냅샷은 특정 시점에서의 현재 상태를 저장해두는 것이다.
이렇게 하면 모든 이벤트를 처음부터 적용할 필요 없이, 스냅샷 이후의 이벤트만 적용하면 됨.

✅ 스냅샷 적용 예제

위의 예제를 다시 보자.

🔹 기존 방식 (스냅샷 없이 조회)
이벤트 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.스냅샷을 불러온다.

  • { balance: 500 } (이벤트 3까지 반영된 상태)

2.스냅샷 이후의 이벤트만 적용한다.

  • balance = 500 + 2000 = 2500

3.최종적으로 balance = 2500을 반환.

💡 이제는 모든 이벤트를 처음부터 적용할 필요 없이, 스냅샷 이후 이벤트만 적용하면 됨!
➡️ 성능이 크게 향상된다.

📌 3. 스냅샷 저장 주기와 전략

❓ "언제 스냅샷을 저장해야 할까?"

스냅샷을 너무 자주 저장하면 스토리지 낭비가 발생하고,
너무 드물게 저장하면 여전히 많은 이벤트를 조회해야 하는 문제가 발생한다.

✅ 일반적인 스냅샷 저장 전략

1.N개의 이벤트마다 저장

  • 예: 이벤트 100개 단위로 한 번씩 스냅샷 생성

2.중요한 상태 변경 시점에서 저장

  • 예: 주문이 PAID 상태가 되면 스냅샷 생성

3.일정 시간마다 스냅샷 저장

  • 예: 하루에 한 번, 모든 계좌의 스냅샷 저장

❓ "스냅샷을 어디에 저장할까?"

1.같은 이벤트 저장소에 저장

  • 스냅샷을 이벤트 테이블에 별도로 저장하는 방식

2.별도 테이블에 저장

  • account_snapshot 같은 별도 테이블을 만들어 저장

3.NoSQL 또는 캐시에 저장

  • Redis 같은 메모리 DB에 저장하여 빠르게 조회 가능

📌 4. 스냅샷 적용 예제 코드 (Kotlin + Spring Boot)

1️⃣ 이벤트 저장소 구현 (Event Sourcing 방식)

data class AccountEvent(
    val userId: String,
    val type: String, // "CREATED", "DEPOSITED", "WITHDRAWN"
    val amount: Int? = null
)
  • 모든 이벤트를 저장하는 AccountEvent 데이터 클래스

2️⃣ 스냅샷을 저장하는 데이터 클래스

data class AccountSnapshot(
    val userId: String,
    val balance: Int
)
  • 특정 시점의 계좌 상태를 저장하는 AccountSnapshot

3️⃣ 현재 계좌 상태 조회 (스냅샷 사용)

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 반환

📌 5. 스냅샷의 장점과 단점

✅ 장점

1.조회 성능 향상

  • 모든 이벤트를 다시 읽을 필요 없이 최신 상태를 빠르게 조회 가능

2.이벤트 개수가 많아져도 성능 유지

  • 스냅샷 이후의 이벤트만 적용하면 됨

3.데이터 복원 속도 향상

  • 특정 시점의 상태를 빠르게 복원 가능

❌ 단점

1.스토리지 사용량 증가

  • 스냅샷을 저장해야 하므로 저장 공간이 필요함

2.스냅샷의 최신성 유지 필요

  • 너무 오래된 스냅샷을 사용하면 여전히 많은 이벤트를 적용해야 할 수도 있음

3.일관성 문제

  • 스냅샷과 이벤트 로그의 동기화 관리가 필요함

📌 6. 결론

✅ 이벤트 소싱은 모든 변경 이력을 저장하는 강력한 기법이지만, 조회 성능 문제가 발생할 수 있다.
✅ 스냅샷(Snapshot)은 특정 시점의 상태를 저장하여 성능을 최적화하는 방법이다.
✅ 스냅샷 이후의 이벤트만 적용하면 되므로 조회 속도를 빠르게 만들 수 있다.
✅ 하지만 저장 공간 관리, 동기화 문제 등을 고려해야 한다.

🔥 즉, 이벤트 소싱 + 스냅샷을 함께 사용하면 조회 성능과 데이터 복원 속도를 동시에 향상시킬 수 있다! 🚀

profile
Fail Fast, Fail Often

0개의 댓글