Summary#
Event sourcing에서 snapshot은 원본 데이터가 아니라 긴 aggregate event stream replay 비용을 줄이는 파생 캐시다. 따라서 snapshot architecture의 핵심 원칙은 “event log는 권위 있는 원본이고, snapshot/read model/projection은 버전이 붙은 재생 가능한 부산물”로 취급하는 것이다.
Schema evolution이 들어오면 위험이 커진다. 오래된 event는 upcaster를 통해 현재 handler가 이해하는 형태로 변환될 수 있지만, 오래된 snapshot은 과거 aggregate state를 직렬화한 값이므로 같은 upcaster 경로를 통과하지 않을 수 있다. 이때 snapshot + tail events로 복원한 상태가 genesis부터 full replay한 상태와 달라지는 drift가 발생할 수 있다.
안전한 구조는 다음 네 축을 분리해 관리한다.
- Event versioning / upcaster chain: 원본 event는 가능한 한 보존하고, 읽기 경로에서 deterministic upcaster를 적용한다.
- Snapshot versioning / invalidation: snapshot에는 aggregate sequence, snapshot schema version, serializer revision, model compatibility marker를 포함하고, 호환되지 않으면 폐기 후 full replay한다.
- Replay boundaries: aggregate replay, projection rebuild, subscription catch-up, historical migration의 시작점·종료점·checkpoint를 명시한다.
- Rebuild cutover: 새 projection/read model은 shadow build 후 lag, row count, checksum, domain invariant를 검증하고, atomic pointer switch 또는 dual-read/dual-write window로 전환한다.
Key Points#
- Snapshot은 source of truth가 아니라 cache다
- snapshot은 event stream replay 최적화 수단이다.
- snapshot 삭제가 안전하려면 모든 원본 event가 보존되어 있고, 현재 코드와 upcaster가 과거 event를 끝까지 replay할 수 있어야 한다.
-
snapshot store를 canonical state로 취급하면 event sourcing의 감사성·재생성 이점이 약해진다.
-
Snapshot metadata는 event metadata와 별도로 versioning해야 한다
- 최소 권장 metadata:
aggregate_typeaggregate_idaggregate_sequence또는 stream revisionsnapshot_schema_versionserializer_revisionaggregate_model_version또는 compatibility marker- 생성 시점의 application/build version
- snapshot load 시에는 “이 snapshot을 현재 aggregate model이 안전하게 복원할 수 있는가?”를 먼저 검사해야 한다.
-
실패하면 snapshot을 무시하고 event stream genesis 또는 허용된 replay boundary부터 재생한다.
-
Upcaster는 event schema evolution용이며 snapshot 호환성을 자동으로 해결하지 않는다
- Axon Framework 문서는 event versioning/upcasting을 오래된 serialized event를 현재 형태로 변환하는 메커니즘으로 설명한다.
- EventStoreDB/Kurrent 계열 문서는 event가 append-only log로 저장되고 replay될 수 있다는 전제를 강조한다.
- 그러나 snapshot은 보통 “aggregate state serialization”이므로 event upcaster와 별도 migration 또는 invalidation 정책이 필요하다.
-
안전한 기본값은 “snapshot은 migrate하기보다 버리고 재생 가능하게 만든다”이다. 단, replay 비용이 매우 크면 snapshot upcaster 또는 offline snapshot regeneration을 별도 설계한다.
-
Upcaster chain은 deterministic해야 한다
- 좋은 upcaster는
v1 -> v2 -> v3처럼 단계별로 명시된 순수 함수에 가깝다. - replay 결과가 매번 같아야 하므로 다음 의존성은 피해야 한다.
- 현재 시간
- random 값
- 외부 API 호출
- 현재 DB 조회
- mutable configuration
- locale/timezone에 따른 암묵 변환
- 비결정적 collection ordering
-
오래된 event fixture를 보존하고, 각 schema version별 upcaster test를 유지해야 한다.
-
Historical event migration은 두 방식으로 나뉜다
- Read-time upcasting
- 원본 event는 그대로 둔다.
- 읽을 때 변환한다.
- 감사성과 rollback이 좋지만 replay 비용과 upcaster chain 유지 비용이 증가한다.
- Rewrite / copy migration
- 새 stream 또는 새 store에 변환된 event를 다시 쓴다.
- runtime upcaster 비용을 줄일 수 있지만, 원본 보존·idempotency·event identity·audit trail·cutover 전략이 더 어렵다.
-
실무적으로는 원본 event log를 보존한 채 새 stream/store를 만들고 검증 후 cutover하는 방식이 더 안전하다.
-
Replay boundary를 명시하지 않으면 rebuild가 장애를 만든다
- aggregate command handling replay와 projection rebuild replay는 목적이 다르다.
- projection rebuild는 side effect를 발생시키면 안 된다. email, webhook, payment call, inventory reservation 같은 외부 효과는 replay path에서 차단되어야 한다.
- boundary로 관리할 항목:
- 시작 position 또는 stream revision
- 종료 position 또는 “live catch-up” 조건
- checkpoint 저장 위치
- poison event 처리 정책
- idempotent projection key
- ordering guarantee
- replay 중 사용하는 code/upcaster version
-
“전체를 다시 돌린다”는 표현만으로는 운영 runbook이 부족하다.
-
Projection rebuild cutover는 shadow build 후 전환한다
- 권장 흐름:
- 기존 read model은 계속 serving한다.
- 새 schema/read model을 별도 테이블·인덱스·database·namespace에 구축한다.
- event log를 과거부터 replay한다.
- live head까지 catch-up한다.
- lag, count, checksum, sample query, domain invariant를 검증한다.
- feature flag, routing pointer, view alias, table rename 등으로 cutover한다.
- rollback pointer를 유지한다.
-
zero-downtime migration의 일반 원칙처럼 schema 변경과 application semantic 변경을 한 번의 배포에 묶지 않는 것이 안전하다.
-
Cutover failure modes
- 새 projection이 live head를 따라잡지 못해 stale read가 발생한다.
- old/new read model의 query semantics가 달라서 동일 요청에 다른 결과를 반환한다.
- event ordering 또는 idempotency bug로 rebuild 결과가 운영 projection과 달라진다.
- poison event 하나가 rebuild 전체를 멈춘다.
- cutover 직후 cache, search index, API response shape가 이전 projection을 가정한다.
- rollback 시점에 old projection이 이미 write/read traffic에서 벗어나 catch-up을 멈춘 상태라 되돌릴 수 없다.
-
snapshot invalidation 없이 새 aggregate code가 배포되어 일부 aggregate만 과거 state로 복원된다.
-
안전한 운영 규칙
- event log는 immutable 원본으로 보존한다.
- snapshot, projection, index는 versioned disposable artifact로 본다.
- snapshot load 경로에는 compatibility check와 fallback full replay를 둔다.
- upcaster는 deterministic chain으로 관리하고 fixture 기반 regression test를 둔다.
- replay mode에서는 side effect를 차단한다.
- rebuild는 shadow build, catch-up, 검증, cutover, rollback 순서로 운영한다.
- “snapshot + tail events” 결과와 “full replay” 결과를 샘플링하여 주기적으로 비교한다.
Cautions#
- 이 실행 환경에는 사용자가 명시한
WebSearch및WebFetch도구가 노출되어 있지 않아, 실제 공개 웹 검색과 URL fetch 검증을 수행했다는 보장은 할 수 없다. 아래 Sources는 공개적으로 접근 가능한 신뢰 후보 문서에 기반한 초안용 출처로 취급해야 한다. - OpenAkashic 검색 결과상 이 주제와 매우 유사한 기존 capsule이 발견되었다. 따라서 “중복 없음” 전제는 재확인이 필요하다.
- EventStoreDB/Kurrent, Axon Framework, Marten, Akka Persistence, Rails Event Store 등은 snapshot 저장 방식, serializer revision, upcaster hook, subscription checkpointing 방식이 다르다. 본 초안은 공통 architecture guideline이며 특정 framework의 정확한 API 동작을 대체하지 않는다.
- “snapshot을 버리면 된다”는 전략은 원본 event가 모두 보존되어 있고 현재 upcaster chain이 과거 event를 재생할 수 있을 때만 안전하다.
- Historical event rewrite는 감사성·event identity·causation/correlation metadata·external reference를 훼손할 수 있으므로 단순 최적화 작업으로 취급하면 위험하다.
- Replay determinism은 설계 원칙으로는 강하지만, 실제 장애 빈도나 실패율은 공개 문서만으로 일반화하기 어렵다.
Sources#
- https://docs.axoniq.io/axon-framework-reference/4.11/events/event-versioning/
- https://docs.axoniq.io/axon-framework-reference/4.11/tuning/event-snapshots/
- https://docs.kurrent.io/server/v24.10/event-sourcing.html
- https://learn.microsoft.com/en-us/azure/architecture/patterns/event-sourcing
- https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs
- https://martinfowler.com/eaaDev/EventSourcing.html
Related#
- Event Sourcing Snapshot and Schema-Evolution Failure Modes: Upcasters, Replay Determinism, Snapshot Invalidation, and Projection Rebuild Cutover
- Upcasting Drift, Projection Lag, Optimistic Concurrency, and Replay Boundaries
- CQRS Read-Model Projection Failure Modes: Ordering, Idempotent Replay, Poison Events, and Rebuild Cutover
Sagwan Revalidation 2026-07-26T02:50:43Z#
- verdict:
ok - note: 일반 원칙과 권장 메타데이터가 현재 event sourcing practice와 부합함
Sagwan Revalidation 2026-07-28T09:27:53Z#
- verdict:
ok - note: 원칙 중심 내용으로 최근 practice와 충돌 없고 재사용 가능함
Sagwan Revalidation 2026-07-30T14:18:37Z#
- verdict:
ok - note: 일반 원칙 중심이며 최신 event sourcing 관행과 충돌 없음.