이벤트 저장소가 아닌 "스냅 샷"프로젝션에서 집계 재수 화


14

실제 이벤트에 패턴을 적용 할 기회는 없었지만 지금은 이벤트 소싱 및 CQRS를 사용하고 있습니다.

읽기 및 쓰기 문제를 분리하면 얻을 수있는 이점을 이해하고 있으며 이벤트 소싱을 통해 이벤트 저장소와 다른 "읽기 모델"데이터베이스에 대한 상태 변경을 쉽게 투영 할 수있는 방법에 대해 감사합니다.

내가 확실하지 않은 것은 이벤트 스토어 자체에서 집계를 다시 수화하는 이유입니다.

"읽기"데이터베이스에 대한 변경 사항을 투사하는 것이 매우 쉬운 경우 스키마가 도메인 모델과 완벽하게 일치하는 "쓰기"데이터베이스에 변경 사항을 항상 투영하지 않는 이유는 무엇입니까? 효과적으로 스냅 샷 데이터베이스가됩니다.

ES + CQRS 애플리케이션에서는 이것이 일반적으로 매우 일반적이어야한다고 생각합니다.

이 경우 이벤트 저장소는 스키마 변경의 결과로 "쓰기"데이터베이스를 재 구축 할 때만 유용합니까? 아니면 더 큰 것을 놓치고 있습니까?


쓰기 모델 상태 저장소에 비동기 적으로 쓰고이를 사용하여 엔터티를로드하는 데 아무런 문제가 없습니다. 그렇게하든 그렇지 않든 동일한 정확한 일관성 문제가 있습니다. 이러한 일관성 문제를 해결하는 핵심은 엔터티를 다르게 모델링하는 것입니다. 이러한 일관성 문제를 해결하는 이벤트 소싱에 대한 마술은 없습니다. 마술은 모델링에 있으며 돌보지 않습니다. 해당 수준에서 일관성을 요구하는 특정 응용 프로그램이 있습니다. 모델링 방식에 관계없이 높은 수준의 엔터티가 있으므로 특별한주의가 필요합니다.
Andrew Larsson

이벤트 전달을 보장 할 수있는 한. 이를 위해 애플리케이션은 지속적으로 이벤트를 지속 가능한 이벤트 버스에 공개해야합니다. 게시 후 응용 프로그램 작업이 완료되었습니다. 그런 다음 버스는 다양한 이벤트 핸들러로 전달합니다. 하나는 이벤트 저장소를 업데이트하고 다른 하나는 상태 저장소를 업데이트하고 다른 하나는 읽기 저장소를 업데이트하는 데 필요합니다. 이벤트 소싱을 사용하는 이유는 더 이상 즉각적인 일관성에 신경 쓰지 않기 때문입니다. 받아들이십시오.
Andrew Larsson

이벤트 저장소에서 엔티티를 지속적으로로드해야하는 이유는 없습니다. 그 목적이 아닙니다. 그 목적은 시스템에서 발생한 모든 것의 원시 원장을 제공하는 것입니다. 엔티티 상태 저장소 및 비정규 화 된 읽기 모델은로드 및 읽기를위한 것입니다.
Andrew Larsson

답변:


14

내가 확실하지 않은 것은 이벤트 스토어 자체에서 집계를 다시 수화하는 이유입니다.

"이벤트"는 기록적인 책이기 때문입니다.

"읽기"데이터베이스에 대한 변경 사항을 투사하는 것이 매우 쉬운 경우 스키마가 도메인 모델과 완벽하게 일치하는 "쓰기"데이터베이스에 변경 사항을 항상 투영하지 않는 이유는 무엇입니까? 효과적으로 스냅 샷 데이터베이스가됩니다.

예; 매번 상태를 처음부터 다시 생성하지 않고 캐시 된 집계 상태 사본을 사용하는 것이 유용한 성능 최적화 인 경우가 있습니다. 기억하십시오 : 성능 최적화의 첫 번째 규칙은 "안함"입니다. 솔루션에 추가 복잡성을 가중 시키므로 강력한 비즈니스 동기가 생길 때까지는 피하는 것이 좋습니다.

이 경우 이벤트 저장소는 스키마 변경의 결과로 "쓰기"데이터베이스를 재 구축 할 때만 유용합니까? 아니면 더 큰 것을 놓치고 있습니까?

더 큰 것이 없습니다.

첫 번째 요점은 이벤트 소스 솔루션을 고려하는 경우 발생한 변경 사항의 히스토리를 보존하는 데 가치가있을 것으로 예상되기 때문입니다. 즉, 비파괴적인 변경을 수행하려고합니다 .

그래서 우리는 이벤트 저장소에 전혀 글을 쓰고 있습니다.

특히, 모든 변경 사항을 이벤트 저장소에 기록해야합니다.

경쟁 작가는 서로의 글을 훼손하거나 서로의 편집 내용을 모르는 경우 시스템을 의도하지 않은 상태로 만들 수 있습니다. 따라서 일관성이 필요할 때 일반적인 접근 방식은 저널의 특정 위치에 대한 쓰기를 처리하는 것입니다 (HTTP API의 조건부 PUT과 유사). 쓰기 실패는 작가에게 저널에 대한 현재의 이해가 동기화되지 않았으며 복구해야한다고 알려줍니다.

알려진 양호한 위치로 돌아간 다음 추가 이벤트를 재생하는 것이 일반적인 복구 전략이므로이 시점부터입니다. 잘 알려진 위치는 로컬 캐시에있는 내용의 사본이거나 스냅 샷 저장소에있는 표현 일 수 있습니다.

행복한 길에서는 집계의 스냅 샷을 메모리에 보관할 수 있습니다. 사용 가능한 로컬 사본이없는 경우에만 외부 상점에 연락해야합니다.

또한, 기록부에 접근 할 수 있다면 완전히 따라 잡을 필요가 없습니다 .

따라서 일반적인 접근 방식 ( 스냅 샷 저장소를 사용하는 경우 )은 비동기 적 으로 유지 관리하는 것 입니다. 이렇게하면 복구해야 할 경우 집계의 전체 기록을 다시로드하고 재생하지 않고도 복구 할 수 있습니다.

범위가 지정된 수명을 가진 세분화 된 집계는 일반적으로 스냅 샷 캐시 유지 관리 비용을 초과 할 수있는 충분한 이벤트를 수집하지 않기 때문에 이러한 복잡성이 관심이없는 경우가 많습니다.

그러나 문제에 적합한 도구 인 경우 집계의 오래된 표현을 쓰기 모델에로드 한 다음 추가 이벤트로 업데이트하는 것이 매우 합리적입니다.


이 모든 것이 합리적으로 들립니다. 얼마 전에 ES의 더미 구현을 가지고 놀았을 때 일관성을 보장하기 위해 EventStore를 사용했지만 "스냅 샷 저장소"라고하는 것에 동 기적으로 썼습니다. 이는 현재 상태가 이벤트를 재생할 필요없이 항상 읽을 수 있음을 의미했습니다. 나는 이것이 잘 확장되지 않을 것이라고 생각했지만, 운동 이었기 때문에 신경 쓰지 않았습니다.
MetaFight

6

"쓰기"데이터베이스의 용도를 지정하지 않기 때문에 여기서 의미하는 바를 가정하겠습니다. 이벤트 저장소에서 집계를 다시 작성하는 대신 집계에 새 업데이트를 등록 할 때 "쓰기"데이터베이스에서 들어 올리고 변경 사항을 검증 한 후 이벤트를 발행하십시오.

이것이 의미하는 바라면이 전략은 불일치에 대한 조건을 만듭니다. 마지막 업데이트가 "쓰기"데이터베이스로 업데이트되기 전에 새로운 업데이트가 발생하면 새 업데이트는 오래된 데이터에 대해 유효성이 검사됩니다. 따라서 "불가능"(즉, "허용되지 않음") 이벤트가 발생하고 시스템 상태가 손상 될 수 있습니다.

예를 들어, 극장 좌석 예약의 대표적인 예를 생각해보십시오. 이중 예약을 방지하려면 예약중인 좌석이 아직 확보되지 않았는지 확인해야합니다. 이것이 바로 "유효성 확인"입니다. 이를 위해 이미 예약 된 좌석 목록을 "쓰기"데이터베이스에 저장합니다. 그런 다음 예약 요청이 들어 오면 요청한 좌석이 목록에 있는지 확인하고 그렇지 않은 경우 "예약 된"이벤트를 발행하고 그렇지 않으면 오류 메시지로 응답하십시오. 그런 다음 "예약 된"이벤트를 듣고 예약 된 좌석을 "쓰기"데이터베이스의 목록에 추가하는 프로젝션 프로세스를 실행합니다.

일반적으로 시스템은 다음과 같이 작동합니다.

1. Request to book seat #1
2. Check in the "already booked" list: the list is empty.
3. Issue a "booked seat #1" event.
4. Projection process catches the event, adds seat #1 to the "already booked" list.
5. Another request to book seat #1.
6. Check in the list: the list contains seat #1
7. Respond with an error message.

그러나 요청이 너무 빨리 들어 와서 4 단계 전에 5 단계가 발생하면 어떻게 되나요?

1. Request to book seat #1
2. Check in the "already booked" list: the list is empty.
3. Issue a "booked seat #1" event.
4. Another request to book seat #1.
5. Check in the list: the list is still empty.
6. Issue another "booked seat #1" event.

이제 같은 좌석을 예약하는 두 가지 이벤트가 있습니다. 시스템 상태가 손상되었습니다.

이 문제가 발생하지 않도록하려면 프로젝션에 대해 업데이트의 유효성을 검사해서는 안됩니다. 업데이트를 확인하려면 이벤트 저장소에서 집계를 다시 빌드 한 후 업데이트를 확인하십시오. 그런 다음, 이벤트를 발행하지만 시간 소인 가드를 사용하여 상점에서 마지막으로 읽은 이후에 새로운 이벤트가 발행되지 않았는지 확인하십시오. 이것이 실패하면 다시 시도하십시오.

이벤트 저장소에서 집계를 재구성하면 성능이 저하 될 수 있습니다. 이를 완화하기 위해 스냅 샷이 작성된 이벤트의 ID로 태그가 지정된 집계 스트림을 이벤트 스트림에 바로 저장할 수 있습니다. 이렇게하면 항상 전체 이벤트 스트림을 처음부터 재생하는 것이 아니라 최신 스냅 샷을로드하고 그 이후에 발생한 이벤트 만 재생하여 집계를 다시 작성할 수 있습니다.


답변 해 주셔서 감사합니다 (응답 시간이 오래 걸리 서 죄송합니다). 쓰기 데이터베이스에 대한 유효성 검사에 대해 말하는 것이 반드시 사실은 아닙니다. 다른 의견에서 언급했듯이 ES 구현 예에서 쓰기 데이터베이스를 동 기적으로 업데이트하고 동시성 ID / 타임 스탬프를 저장해야합니다. 이를 통해 EventStore에서 준비 할 필요없이 낙관적 동시성 위반을 감지 할 수있었습니다. 물론 동기식 쓰기만으로는 데이터 손상을 방지 할 수 없지만 단일 액세스 (단일 스레드) 쓰기도 수행하고 있습니다.
MetaFight

그래서 일관성 문제를 정리했습니다. 그러나 이것이 확장 성을 희생한다고 가정했습니다.
MetaFight

쓰기 데이터베이스에 동기식으로 쓰는 경우 여전히 손상의 위험이 있습니다. 이벤트 저장소에 대한 쓰기는 성공하지만 쓰기 데이터베이스에 대한 쓰기는 실패하면 어떻게됩니까?
Fyodor Soikin

1
읽기 프로젝션이 실패하면 성공할 때까지 다시 시도합니다. 완전히 충돌하면 깨어 난 곳부터 계속됩니다. 다시 말해 다시 시도하십시오. 외부 관찰 가능한 효과는 조금 느리게 실행되는 것과 다르지 않습니다. 투영이 계속해서 실패하고 실패하면 버그가 있다는 것을 의미하며 수정해야합니다. 수정 후 마지막 양호한 상태에서 다시 실행됩니다. 버그로 인해 전체 읽기 데이터베이스가 손상되면 이벤트 기록을 사용하여 데이터베이스를 처음부터 다시 작성합니다.
Fyodor Soikin

1
데이터가 손실되지 않습니다. 이것이 가장 큰 문제입니다. 데이터는 한동안 불편한 (판독을 위해) 형태로 멈출 수 있지만 결코 잃어 버리지 않습니다.
Fyodor Soikin

3

주된 이유는 성능입니다. 모든 커밋에 대한 스냅 샷을 저장할 수 있습니다 (커밋 = 단일 명령으로 생성 된 이벤트, 일반적으로 하나의 이벤트 만). 그러나 비용이 많이 듭니다. 스냅 샷과 함께 커밋도 저장해야합니다. 그렇지 않으면 이벤트 소싱이 아닙니다. 그리고이 모든 것은 원자 적으로, 또는 전혀 수행되지 않아야합니다. 귀하의 질문은 별도의 데이터베이스 / 테이블 / 컬렉션을 사용하는 경우에만 유효하며 (그렇지 않으면 정확하게 이벤트 소싱이 될 수 있음) 일관성을 보장하기 위해 트랜잭션 을 사용해야 합니다 . 거래는 확장 할 수 없습니다. 추가 전용 이벤트 스트림 (이벤트 저장소)은 확장 성의 어머니입니다.

두 번째 이유는 집계 캡슐화입니다. 당신은 그것을 보호해야합니다. 즉, 집계는 언제든지 내부 표현을 자유롭게 변경할 수 있어야합니다. 당신이 그것을 저장하고 그것에 크게 의존한다면 버전 관리에 매우 어려움을 겪을 것입니다. 스키마가 변경 사항 만 최적화로 스냅 샷을 사용하는 상황에서 단순히 그 스냅 샷 (무시 단순히 ? 정말 그렇게 생각하지 않는다 결정 행운이 집계의 스키마 변경 - 모든 중첩 된 엔티티와 값 객체를 포함하여 - A의 효율적인 방법과 관리).


집계 스키마가 변경 될 때 업데이트 된 "쓰기"데이터베이스를 생성하기 위해 이벤트를 재생하는 것이 간단하지 않습니까?
MetaFight

문제는 그 변화를 감지하는 것입니다. 많은 파일 / 클래스가있는 집계는 매우 클 수 있습니다.
Constantin Galbenu

이해가 안 돼요 소프트웨어 릴리스로 변경 될 수 있습니다. 릴리스는 아마도 "쓰기"데이터베이스를 재생성하기위한 데이터베이스 스크립트와 함께 제공 될 것입니다.
MetaFight

마이그레이션 스크립트를 위해해야 ​​할 일이 많습니다. 앱이 실행되는 동안 앱이 다운되어 있어야합니다.
Constantin Galbenu

@MetaFight 스트림이 매우 큰 경우 새 집계 스키마를 다시 작성하는 데 많은 시간이 걸립니다 ... 이제 새 집계가 릴리스되기 전에 실행될 수있는 라이브 투영 상태 인 스냅 샷에 대해 생각하고 있습니다. 스키마
Narvalex
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.