Java

이벤트 소싱이란?

도입

이번에 우아한 테크코스에서 체스를 구현하는 미션을 진행했었는데, 체스를 DB에 저장하고 게임을 진행할 수 있도록 하는 기능이 요구사항에 있었다. 관건은 체스 게임을 어떻게 DB에 저장하는가? 였는데, 나는 이벤트 소싱이라는 방법을 이용하였다. 하지만 막상 이벤트 소싱이 어떠한 장단점을 갖는지에 대해 알지 못한 채 사용하여서, 이번에 한번 정리해보고자 한다.

 

이벤트 소싱이란?

이벤트 소싱은 데이터 저장 방법에 대한 것이다. 로직을 처리할 때 일반적으로 결과값을 저장한다. 하지만 그와 달리, 이벤트 소싱은 순차적으로 발생하는 이벤트를 모두 저장한다. 또한 이벤트 소싱 방식은 도메인의 이벤트를 기록하기 때문에 갱신이나 삭제 연산은 수행되지 않는다. 도메인 모델 데이터베이스에는 최종 상태가 아닌, 그동안 발생한 도메인 이벤트 기록이 저장된다. 예를 들어 설명해보자면, 이번 체스 미션에서는 최종적인 체스 보드 상태를 데이터베이스에 저장하는 것이 아니라, 말의 움직임(move a1 a3)이라는 이벤트를 데이터베이스에 저장하는 것을 이벤트 소싱 방식을 적용했다고 이야기할 수 있다.

 

체스 미션을 모르는 분들을 위해 더 자세히 예를 들어 보자면, 체스 게임을 2판 진행한 뒤 체스 보드의 상태가 다음과 같다고 가정하자.

 

// 수행한 명령어
move h2 h3
move h7 h6

// 최종 보드의 상태
RKBQKBKR
PPPPPPP.
.......P
........
........
.......p
ppppppp.
rkbqkbkr

 

이 때, 기존의 데이터 저장 방식이라면 최종 보드의 상태를 저장할 것이고, 이를 한 줄의 스트링으로 변환하여 데이터베이스에 저장한다면 다음과 같이 저장할 수 있다.

 

id board
1 RKBKQKBKRPPPPPPP........P.......................pppppppp.rkbqkbkr

 

같은 상황에서 이벤트 소싱 방법으로 데이터를 저장한다면, 다음과 같이 저장할 수 있다.

 

id event
1 move h2 h3
2 move h7 h6

 

하지만 이벤트 소싱 방법을 이용한다면, 최종 상태를 만들어내기 위해 저장된 모든 이벤트를 불러와 재생해야한다. 그런데 만약, 체스 게임이 너무 박빙이라 50번의 게임이 진행되었다면? 최종 상태의 보드를 만들어내기 위해 우리는 50 라인의 데이터를 데이터베이스로부터 불러온 뒤, 내부에서 체스 게임을 50번 진행한 뒤, 나타난 보드를 화면에 그려야한다.

 

이를 해결하기 위해 우리는 snapshot이라는 개념을 도입할 수 있다.

 

예를 들어, 50번째 이벤트가 발생할 때 스냅샷으로 저장해둔다면 최종 결과 값은 51번째부터 재생하여 얻을 수 있다. 이 방법을 적용한다면 특정 횟수마다 스냅샷을 저장할 수 있다. 예를 들어 체스 말은 최대 100번 움직일 수 있다고 가정해보자.

 

그럼 우리는 최대 100개의 움직임을 기록할 것이고, 최종 상태를 불러오기 위해서는 100개의 이벤트를 적용해야한다. 여기서 20번의 움직임마다 스냅샷을 기록한다고 해 보자. 그렇다면 21번째 상태를 만들어내기 위해서는 20번째의 스냅샷에 한번의 이벤트만 적용하면 된다.

 

100번 움직인 경우에는 20번마다 스냅샷을 기록했으므로 100(20*5)번째의 스냅샷을 불러오기만 하면 된다. 하지만 이 방법은 설계가 매우 어렵다. 그리고 스냅샷이 저장되는 순간을 따로 설정해주어야한다.

 

이벤트 소싱 방식을 적용하는 것은 그리 어렵지 않지만, 마음에 걸리는 것은 최종 상태를 불러올 때 많은 양의 연산이 필요하다는 점이다. (위에서 말한 스냅샷으로도 해결이 가능하지만 어쨌든 최종 상태를 만들기 위해서는 이벤트를 불러와야하기 마련이다.) 동시에 3개의 방에서 게임이 플레이되고, 각 게임이 50번씩만 플레이된다고 해도 총 50*3라인의 이벤트를 불러와야하고, 각 이벤트마다 보드의 말을 움직이는 연산을 수행해야한다.

 

그렇다면 이벤트 소싱은 어떤 장점을 가질까? 모든 이벤트의 기록이 다 남기 때문에 신뢰할 수 있는 시스템 기록을 확보할 수 있다.특정 시점까지의 이벤트를 재생하면 해당 시점의 도메인 모델을 복원할 수 있다. 시스템에 오류가 발생하면 프로그래머는 오류가 발생한 시점의 도메인 모델을 복원해 분석할 수 있다. 이벤트 소싱이 더 큰 장점을 가지기 위해서 CQRS(Command and Query Responsibility Segregation)와 주로 함께 사용된다. 그래서, 다음 번 포스팅에서는 CQRS에 대해 알아보자.