기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.
이벤트 소싱 패턴
Intent
이벤트 기반 아키텍처에서 이벤트 소싱 패턴은 데이터 스토어의 상태 변경을 초래하는 이벤트를 저장합니다. 따라서 상태 변경의 전체 기록을 캡처 및 유지할 수 있으며, 감사 가능성, 추적 가능성 및 과거 상태를 분석하는 능력이 향상됩니다.
목적
여러 마이크로서비스가 유기적으로 작동하여 요청을 처리할 수 있으며 이벤트를 통해 통신합니다. 이러한 이벤트로 인해 상태(데이터)가 변경될 수 있습니다. 이벤트 객체를 발생 순서대로 저장하면 데이터 엔터티의 현재 상태에 대한 중요한 정보와 어떻게 해당 상태에 도달했는지에 대한 추가 정보가 제공됩니다.
적용 가능성
다음과 같은 경우 이벤트 소싱 패턴을 사용합니다.
-
이벤트를 추적하려면 애플리케이션에서 발생하는 이벤트의 변경 불가능한 기록이 필요합니다.
-
신뢰할 수 있는 단일 소스(SSOT)를 기반으로 한 다국어 데이터 프로젝션이 필요합니다.
-
애플리케이션 상태의 특정 시점 재구성이 필요합니다.
-
애플리케이션 상태를 장기간 저장할 필요는 없지만 필요에 따라 재구성하기를 원합니다.
-
워크로드마다 읽기 및 쓰기 볼륨이 다릅니다. 예를 들어 실시간 처리가 필요하지 않은 쓰기 집약적인 워크로드가 있습니다.
-
애플리케이션 성능 및 기타 지표를 분석하기 위한 변경 데이터 캡처(CDC)가 필요합니다.
-
보고 및 규정 준수를 위해 시스템에서 발생하는 모든 이벤트에 대한 감사 데이터가 필요합니다.
-
재생 프로세스 중에 이벤트를 변경(삽입, 업데이트 또는 삭제)하여 가능한 종료 상태를 파악함으로써 가상(what-if) 시나리오를 도출하고자 합니다.
문제 및 고려 사항
-
낙관적 동시성 제어: 이 패턴은 시스템의 상태 변경을 유발하는 모든 이벤트를 저장합니다. 여러 사용자 또는 서비스가 동시에 동일한 데이터를 업데이트하려고 시도함에 따라 이벤트 충돌이 발생할 수 있습니다. 이러한 충돌은 충돌하는 이벤트가 동시에 생성되고 적용될 때 발생하며, 그 결과 최종 데이터 상태가 실제와 일치하지 않게 됩니다. 이 문제를 해결하기 위해 이벤트 충돌을 감지하고 해결하는 전략을 구현할 수 있습니다. 예를 들어 버전 관리를 포함하거나 이벤트에 타임스탬프를 추가하여 업데이트 순서를 추적함으로써 낙관적 동시성 제어 스키마를 구현할 수 있습니다.
-
복잡성: 이벤트 소싱을 구현하려면 기존 CRUD 운영에서 이벤트 기반 방식으로 발상을 전환해야 합니다. 시스템을 원래 상태로 복원하는 데 사용되는 재생 프로세스는 데이터 멱등성을 보장하기 위해 복잡하게 구현될 수 있습니다. 게다가 이벤트 스토리지, 백업 및 스냅샷도 복잡성을 가중시킬 수 있습니다.
-
최종 일관성: 명령 쿼리 책임 분리(CQRS) 패턴 또는 구체화된 뷰를 사용하여 데이터를 업데이트하는 데 따른 지연 시간 덕분에 이벤트에서 파생된 데이터 프로젝션에서는 결국 일관성이 유지됩니다. 소비자가 이벤트 스토어의 데이터를 처리하고 게시자가 새 데이터를 보내는 경우, 데이터 프로젝션 또는 애플리케이션 객체가 현재 상태를 반영하지 않을 수 있습니다.
-
쿼리: 이벤트 로그에서 현재 또는 집계 데이터를 검색하는 작업은 기존 데이터베이스에 비해 더 복잡하고 느릴 수 있습니다. 특히 복잡한 쿼리 및 보고 작업의 경우 더욱 그렇습니다. 이 문제를 완화하기 위해 이벤트 소싱은 CQRS 패턴으로 구현되는 경우가 많습니다.
-
이벤트 스토어의 크기 및 비용: 특히 이벤트 처리량이 많거나 보존 기간이 연장된 시스템의 경우, 이벤트가 지속적으로 유지됨에 따라 이벤트 스토어의 크기가 기하급수적으로 증가할 수 있습니다. 따라서 이벤트 스토어가 너무 커지는 것을 방지하기 위해 정기적으로 이벤트 데이터를 비용 효율적인 스토리지에 보관해야 합니다.
-
이벤트 스토어의 확장성: 이벤트 스토어는 대량의 쓰기 및 읽기 작업을 모두 효율적으로 처리해야 합니다. 이벤트 스토어는 규모를 조정하기가 어려울 수 있으므로, 샤드와 파티션을 제공하는 데이터 스토어를 사용하는 것이 중요합니다.
-
효율성 및 최적화: 쓰기 및 읽기 작업을 모두 효율적으로 처리하는 이벤트 스토어를 선택하거나 설계합니다. 이벤트 스토어는 애플리케이션의 예상 이벤트 볼륨 및 쿼리 패턴에 맞게 최적화되어야 합니다. 인덱싱 및 쿼리 메커니즘을 구현하면 애플리케이션 상태를 재구성할 때 이벤트 검색 속도를 높일 수 있습니다. 쿼리 최적화 기능을 제공하는 특수한 이벤트 스토어 데이터베이스 또는 라이브러리를 사용하는 것도 고려해 볼 수 있습니다.
-
스냅샷: 시간 기반 활성화를 통해 정기적으로 이벤트 로그를 백업해야 합니다. 마지막으로 성공한 것으로 알려진 데이터 백업의 이벤트를 재생하면 애플리케이션 상태가 특정 시점으로 복구됩니다. Recovery Point Objective(RPO)는 마지막 데이터 복구 시점 이후 허용되는 최대 시간입니다. RPO는 마지막 복구 시점과 서비스 중단 사이에 허용되는 데이터 손실로 간주되는 범위를 결정합니다. 데이터 및 이벤트 스토어의 일일 스냅샷 빈도는 애플리케이션의 RPO를 기준으로 해야 합니다.
-
시간 민감도: 이벤트는 발생한 순서대로 저장됩니다. 따라서 이 패턴을 구현할 때 고려해야 할 중요한 요소는 네트워크 신뢰성입니다. 지연 문제로 인해 잘못된 시스템 상태가 발생할 수 있습니다. 최대 한 번 전송되는 선입선출(FIFO) 대기열을 사용하여 이벤트를 이벤트 스토어로 전송하세요.
-
이벤트 재생 성능: 많은 수의 이벤트를 재생하여 현재 애플리케이션 상태를 재구성하려면 시간이 많이 걸릴 수 있습니다. 특히 보관된 데이터에서 이벤트를 재생할 때는 성능을 개선하기 위한 최적화 노력이 필요합니다.
-
외부 시스템 업데이트: 이벤트 소싱 패턴을 사용하는 애플리케이션은 외부 시스템의 데이터 스토어를 업데이트하고 이러한 업데이트를 이벤트 객체로 캡처할 수 있습니다. 이벤트 재생 중에, 외부 시스템이 업데이트를 예상하지 않으면 이는 문제가 될 수 있습니다. 이러한 경우 기능 플래그를 사용하여 외부 시스템 업데이트를 제어할 수 있습니다.
-
외부 시스템 쿼리: 외부 시스템 직접 호출이 호출 날짜 및 시간에 민감한 경우, 수신된 데이터를 내부 데이터 스토어에 저장하여 재생 중에 사용할 수 있습니다.
-
이벤트 버전 관리: 애플리케이션이 발전함에 따라 이벤트 구조(스키마)가 변경될 수 있습니다. 이전 버전과 이후 버전의 호환성을 보장하기 위해 이벤트에 대한 버전 관리 전략을 구현해야 합니다. 여기에는 이벤트 페이로드에 version 필드를 포함하고 재생 중에 다양한 이벤트 버전을 적절하게 처리하는 것이 포함될 수 있습니다.
구현
전반적인 아키텍처
명령 및 이벤트
분산된 이벤트 기반 마이크로서비스 애플리케이션에서 명령은 일반적으로 상태 변경을 시작할 목적으로 서비스에 전송되는 명령 또는 요청을 나타냅니다. 서비스는 이러한 명령을 처리하고 명령의 유효성과 현재 상태에 대한 적용 가능성을 평가합니다. 명령이 성공적으로 실행되면 서비스는 수행된 조치와 관련 상태 정보를 나타내는 이벤트를 보내 응답합니다. 예를 들어 다음 다이어그램에서 예약 서비스는 Ride booked 이벤트를 생성하여 Book ride 명령에 응답합니다.
![이벤트 소싱 패턴의 명령 및 이벤트](images/event-sourcing-1.png)
이벤트 스토어
이벤트는 변경 불가능하고 추가만 가능하며 시간순으로 정렬된 리포지토리 또는 이벤트 스토어라는 데이터 스토어에 기록됩니다. 각 상태 변경은 개별 이벤트 객체로 취급됩니다. 이벤트를 발생 순서대로 재생하여 초기 상태, 현재 상태 및 시점 뷰가 알려진 엔터티 객체 또는 데이터 스토어를 재구성할 수 있습니다.
이벤트 스토어는 모든 작업 및 상태 변경에 대한 기록 역할을 하며, 신뢰할 수 있는 단일 소스로서 중요한 역할을 합니다. 이벤트 스토어를 사용하면 재생 프로세서를 통해 이벤트를 전달하여 시스템의 최종, 최신 상태를 도출할 수 있습니다. 재생 프로세서는 이러한 이벤트를 적용하여 최신 시스템 상태를 정확하게 나타냅니다. 또한 이벤트 스토어를 사용하면 재생 프로세서를 통해 이벤트를 재생하여 특정 시점의 상태를 파악할 수 있습니다. 이벤트 소싱 패턴에서는 최신 이벤트 객체가 현재 상태를 완전하게 나타내지 않을 수 있습니다. 다음 세 가지 방법 중 하나로 현재 상태를 도출할 수 있습니다.
-
관련 이벤트를 집계. 관련 이벤트 객체를 결합하여 쿼리를 위한 현재 상태를 생성합니다. 이 방식은 이벤트가 결합되어 읽기 전용 데이터 스토어에 기록된다는 점에서 CQRS 패턴과 함께 사용되는 경우가 많습니다.
-
구체화된 뷰 사용. 구체화된 뷰 패턴과 함께 이벤트 소싱을 사용하여 이벤트 데이터를 계산하거나 요약하고 관련 데이터의 현재 상태를 얻을 수 있습니다.
-
이벤트를 재생. 이벤트 객체를 재생하여 현재 상태를 생성하는 작업을 수행할 수 있습니다.
다음 다이어그램은 Ride booked
이벤트 스토어에 저장되는 이벤트를 보여줍니다.
![이벤트 소싱 패턴의 이벤트 스토어 사용](images/event-sourcing-2.png)
이벤트 스토어는 저장하는 이벤트를 게시하고, 이벤트를 필터링하여 후속 작업을 위해 적절한 프로세서로 라우팅할 수 있습니다. 예를 들어 상태를 요약하고 구체화된 뷰를 보여주는 뷰 프로세서로 이벤트를 라우팅할 수 있습니다. 이벤트는 대상 데이터 스토어의 데이터 형식으로 변환됩니다. 이 아키텍처를 확장하여 다양한 유형의 데이터 스토어를 도출할 수 있으며, 이를 통해 데이터의 다국어 지속성이 보장됩니다.
다음 다이어그램은 차량 예약 애플리케이션의 이벤트를 설명합니다. 애플리케이션 내에서 발생하는 모든 이벤트는 이벤트 스토어에 저장됩니다. 그런 다음 저장된 이벤트가 필터링되어 다른 소비자에게 라우팅됩니다.
![이벤트 소싱 패턴의 전반적 구현의 예](images/event-sourcing-3.png)
주행 이벤트는 CQRS 또는 구체화된 뷰 패턴을 사용하여 읽기 전용 데이터 스토어를 생성하는 데 사용할 수 있습니다. 읽기 스토어를 쿼리하여 차량 서비스, 운전자 또는 예약의 현재 상태를 확인할 수 있습니다. Location changed
또는 Ride completed
같은 일부 이벤트는 결제 처리를 위해 다른 소비자에게 게시됩니다. 주행이 완료되면 모든 주행 이벤트가 재생되어 감사 또는 보고 목적으로 주행 기록이 작성됩니다.
이벤트 소싱 패턴은 시점 복구가 필요한 애플리케이션에서 자주 사용되며, 신뢰할 수 있는 단일 소스를 사용하여 데이터를 다양한 형식으로 프로젝션해야 하는 경우에도 자주 사용됩니다. 이 두 작업 모두 이벤트를 실행하고 필요한 종료 상태를 도출하기 위한 재생 프로세스를 필요로 합니다. 재생 프로세서에는 알려진 시작 지점이 필요할 수도 있습니다. 애플리케이션 시작 시부터 이 프로세스를 시작하는 것은 효율적이지 않습니다. 시스템 상태의 스냅샷을 주기적으로 생성하고 이벤트 수를 줄여 최신 상태를 도출하는 것이 좋습니다.
AWS 서비스를 사용한 구현
다음 아키텍처에서는 Amazon Kinesis Data Streams가 이벤트 스토어로 사용됩니다. 이 서비스는 애플리케이션 변경 사항을 이벤트로 캡처 및 관리하고 처리량이 많은 실시간 데이터 스트리밍 솔루션을 제공합니다. AWS에서 이벤트 소싱 패턴을 구현하려면 애플리케이션의 요구 사항에 따라 Amazon EventBridge 및 Amazon Managed Streaming for Apache Kafka(Amazon MSK)와 같은 서비스를 사용할 수도 있습니다.
내구성을 강화하고 감사를 활성화하려는 경우에는 Kinesis Data Streams에서 캡처한 이벤트를 Amazon Simple Storage Service(S3)에 보관할 수 있습니다. 이 이중 스토리지 접근 방식은 향후 분석 및 규정 준수를 위해 과거 이벤트 데이터를 안전하게 보존하는 데 도움이 됩니다.
![AWS 서비스를 사용한 이벤트 소싱 패턴 구현](images/event-sourcing-4.png)
이 워크플로는 다음 단계로 구성됩니다.
-
모바일 클라이언트를 통해 Amazon API Gateway 엔드포인트에 차량 예약 요청이 전송됩니다.
-
이 주행 마이크로서비스(
Ride service
Lambda 함수)는 요청을 수신하고, 객체를 변환하고, Kinesis Data Streams에 게시합니다. -
Kinesis Data Streams의 이벤트 데이터가 규정 준수 및 감사 기록 목적으로 Amazon S3에 저장됩니다.
-
이 이벤트는
Ride event processor
Lambda 함수에 의해 변환 및 처리되고 Amazon Aurora 데이터베이스에 저장되어 주행 데이터에 대한 구체화된 뷰를 제공합니다. -
완료된 주행 이벤트는 필터링되어 결제 처리를 위해 외부 결제 게이트웨이로 전송됩니다. 결제가 완료되면 주행 데이터베이스를 업데이트하기 위해 또 다른 이벤트가 Kinesis Data Streams로 전송됩니다.
-
주행이 완료되면 주행 이벤트가
Ride service
Lambda 함수로 재생되어 경로와 주행 기록을 생성합니다. -
Aurora 데이터베이스에서 데이터를 읽는
Ride data service
를 통해 주행 정보를 읽을 수 있습니다.
또한 API Gateway는 Ride service
Lambda 함수를 사용하지 않고 이벤트 객체를 Kinesis Data Streams로 직접 전송할 수 있습니다. 하지만 차량 호출 서비스와 같은 복잡한 시스템에서는 이벤트 객체를 데이터 스트림으로 수집하기 전에 처리하고 보강해야 할 수 있습니다. 이러한 이유로 이 아키텍처에는 이벤트를 Kinesis Data Streams로 보내기 전에 이벤트를 처리하는 Ride service
가 있습니다.