이벤트 소싱: 관계형 데이터베이스를 넘어선 데이터 영속성 기법

Storing data as sequence of facts in Ruby on Rails - Łukasz Reszke [EN]

작성자
Visuality.pl
발행일
2025년 12월 29일

핵심 요약

  • 1 이벤트 소싱은 데이터의 모든 변경 이력을 불변의 이벤트 시퀀스로 저장하여, 전통적인 스냅샷 방식에서 발생하는 정보 손실을 방지하는 영속성 기법입니다.
  • 2 Rails Event Store는 기존 PostgreSQL, MySQL 등 관계형 데이터베이스 위에서 이벤트 소싱을 구현하며, 데이터 감사, 디버깅, 과거 상태 재구성을 용이하게 합니다.
  • 3 이벤트 소싱은 비동기 처리나 새로운 데이터베이스 학습이 필수가 아니며, 시스템 규모와 관계없이 비즈니스 '이유'에 대한 심층적인 통찰력을 제공하여 다양한 애플리케이션에 적용될 수 있습니다.

도입

본 발표는 데이터 저장 방식에 대한 새로운 접근 방식인 이벤트 소싱(Event Sourcing)을 소개합니다. 기존의 관계형 데이터베이스 기반 CRUD(Create, Read, Update, Delete) 모델은 현재 상태를 스냅샷 형태로 저장하여, 상태 변경 과정에서 이전 정보가 손실된다는 한계가 있습니다. 예를 들어, 상태 A에서 B로 변경될 때, 우리는 현재 상태가 B라는 것만 알 뿐, 어떻게 B에 도달했는지에 대한 이력은 알 수 없습니다. 이벤트 소싱은 이러한 정보 손실 문제를 해결하기 위해, 데이터를 불변의 비즈니스 이벤트(사실) 시퀀스로 저장하는 방식을 제안합니다.

이벤트 소싱의 핵심 개념

이벤트 소싱은 데이터를 ‘스냅샷’으로 저장하는 대신, ‘사실’의 시퀀스로 저장합니다. 여기서 ‘사실’은 애플리케이션 내에서 발생하는 비즈니스 이벤트(Event)를 의미합니다. 이벤트는 불변하며(Immutable), 이벤트 스토어(Event Store)라는 추가 전용(Append-only) 데이터베이스에 저장됩니다. 이벤트 스토어에 한 번 저장된 이벤트는 수정하거나 삭제할 수 없습니다.

이벤트와 스트림

  • 이벤트(Event): 시스템 내에서 발생한 특정 사실을 나타내며, 고유 식별자, 데이터(payload), 메타데이터(예: 클라이언트 ID, 작업자 ID, 발생 시간)를 포함합니다.

  • 스트림(Stream): 특정 비즈니스 엔티티(예: 장바구니, 배송)에 대한 논리적인 이벤트 시퀀스입니다. 하나의 이벤트는 최소한 하나의 전역 스트림(Global Stream)에 속하며, 필요에 따라 여러 스트림에 연결될 수 있습니다. 예를 들어, 장바구니 이벤트는 특정 장바구니 스트림과 특정 날짜의 주문 스트림에 동시에 연결될 수 있습니다.

상태 재구성 및 활용

이벤트 스토어에 저장된 이벤트 스트림을 읽고 순차적으로 적용(Left Fold)함으로써, 특정 시점의 엔티티 상태를 정확하게 재구성할 수 있습니다. 이는 비즈니스 로직 테스트, 과거 데이터 분석, 감사 추적 등에 매우 유용합니다. 예를 들어, ‘어떤 상품이 장바구니에서 가장 많이 버려졌는가?’와 같은 비즈니스 질문에 정확하게 답할 수 있습니다.

Rails Event Store

Rails Event Store는 Ruby on Rails 개발자를 위한 이벤트 소싱 라이브러리로, 기존의 PostgreSQL, MySQL, SQLite와 같은 관계형 데이터베이스 위에서 작동합니다. 이는 새로운 데이터베이스를 학습할 필요 없이 이벤트 소싱을 도입할 수 있게 합니다. Rails Event Store는 이벤트 및 스트림 정보를 저장하는 두 개의 테이블과 함께 마이그레이션, 인덱스, 이벤트 브라우저 등의 기능을 제공합니다.

실제 사용 사례: 창고 관리 시스템

창고 배송(Shipment) 관리 시스템에서, 배송 상태가 ‘포장됨’으로 변경될 때 기존 스냅샷 모델은 ‘포장’ 비용을 한 번만 청구합니다. 하지만 배송이 포장되었다가 운송 문제로 인해 다시 ‘보류’ 상태로 되돌아간 후 재포장되는 경우, 스냅샷 모델은 단 한 번의 포장 비용만 청구하게 되어 실제 작업 횟수를 반영하지 못합니다. 이벤트 소싱을 사용하면 ‘포장됨’, ‘포장 해제됨’, ‘재포장됨’과 같은 모든 이벤트가 기록되므로, 실제 발생한 포장 횟수에 따라 정확하게 비용을 청구할 수 있습니다.

이벤트 소싱에 대한 오해 해소

  • 새로운 데이터베이스 학습 필요: Rails Event Store처럼 기존 RDB 위에서 작동하는 라이브러리가 존재합니다.

  • 모든 것이 비동기여야 함: 동기식으로도 충분히 구현 가능하며, 점진적으로 비동기 방식을 도입할 수 있습니다.

  • 느리다: 바운디드 컨텍스트(Bounded Context)를 잘 정의하고 엔티티의 라이프사이클이 적절하다면 성능 문제는 발생하지 않습니다.

  • 결과적 일관성(Eventual Consistency) 필수: 동기식 구독을 통해 즉각적인 읽기 모델 업데이트가 가능하므로, 시작 단계에서는 필수가 아닙니다.

  • 시스템 재시작 시 모든 상태 재구성: 읽기 모델(Read Model)을 Active Record 모델에 저장하여 영속화할 수 있습니다.

  • 리팩토링이 어렵다: 기존 CRUD 방식과는 다르지만, 충분히 가능한 기술과 방법론이 존재합니다.

  • 고확장성 시스템 전용: 비즈니스 의사결정의 ‘이유’를 파악하는 데 중요하므로, 모든 규모의 시스템에 유용합니다.

  • DDD(Domain-Driven Design)에만 국한: DDD 원칙은 Rails 프로젝트에도 적용될 수 있으며, 이벤트 소싱과 잘 통합됩니다.

결론

이벤트 소싱은 단순히 데이터를 저장하는 것을 넘어, 시스템 내에서 발생하는 모든 상태 변화의 완전한 이력을 불변의 이벤트 형태로 보존하는 강력한 영속성 기법입니다. 이를 통해 데이터 손실 없이 완벽한 감사 추적, 효과적인 디버깅, 정확한 상태 재구성, 그리고 비즈니스 의사결정에 필요한 심층적인 통찰력을 얻을 수 있습니다. Rails Event Store와 같은 라이브러리를 활용하면 기존 Ruby on Rails 환경에서도 새로운 데이터베이스 학습 없이 이벤트 소싱을 쉽게 도입할 수 있습니다. 이벤트 소싱은 비즈니스 질문에 대한 '이유'를 명확히 하고자 하는 모든 프로젝트에 큰 가치를 제공하며, 특정 고확장성 시스템에만 국한되지 않고 유연하게 적용될 수 있습니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!