서론
최근 회사 동료분과 Database Sharding 관련해서 실시간 이벤트 동기화 방식에 대해 이야기를 나누다가 Thin, Fat Payload 방식에 대해 의견을 나누는 시간이 있었는데, 이 과정에서 두 전략 모두 명확한 트레이드 오프를 갖고있다는 것을 다시 한번 느꼈고, 휘발되기전에 내용들을 정리해보려한다.
Thin Payload (aka. Event Notification, Zero Payload)
위 예시로 그려본 도식처럼 Thin Payload는 이벤트가 발생했다는 사실만을 알리는 방식으로 최소한의 정보만을 포함한다. 주로 이벤트 타입과 관련된 식별자만을 포함하며, 구체적인 데이터(속성)는 포함하지않는다.
따라서, Thin Payload는 소비자가 필요한 데이터를 이벤트 원천 소스로부터 조회를 통해 필요한 값들을 획득하는 특징이 있다.
장점
상태의 식별값 외 속성값을 포함하고있지않아 메세지 사이즈가 작다.
- 따라서, Network 대역폭과 Storage 사용량이 적다.
상태 모델이 확장 되더라도 소비자의 이벤트 워커에 부작용(Side Effect)이 발생하지않는다.
단점
이벤트 워커에서 수신할 때마다, 수신자가 생산자 API를 조회하는 Network I/O가 발생한다.
- 따라서, 이벤트 원천 서비스의 가용성과 성능에 강한 의존성이 생긴다. (생산자 API 장애 발생 시 수신자 이벤트 워커에도 장애가 전파된다)
Fat Payload (aka. Event Carried State Transfer)
위 예시로 그려본 도식처럼 Fat Payload는 이벤트와 관련된 상태 정보를 포함한 방식이다. 소비자가 이벤트와 관련된 모든 필요한 데이터를 페이로드에 넣어 별도로 생산자 API를 조회하지않고도 처리할 수 있도록 설계하는것이 특징이다.
장점
페이로드에 상태의 속성 값들이 모두 들어있어, 수신자가 생산자 API 조회 비용없이 이벤트 워커의 작업을 수행할 수 있다.
- 따라서, 소비자가 생산자에 대한 물리적 의존성을 느슨하게 만들 수 있다.
단점
상태의 속성 값들이 모두 들어있어, 메세지 사이즈가 크다.
- 따라서, Network 대역폭과 Storage 사용량이 크다.
모델의 확장 또는 변경에 따라 생산자와 소비자 모두 부작용(Side-Effect)이 발생한다.
이벤트 생산 시 상태의 확장된 부분을 이벤트 페이로드에 반영해야한다.
만약 모든 속성이 표기 되어있지 않은 Fat이라면, 소비자가 생산자에게 이벤트 확장을 요구하는 논의가 필요해진다.
이벤트 생산 시점의 데이터를 신뢰하고 사용하기때문에, 소비자 워커 작업 시 최신 상태의 데이터가 아닐 수 있다. (일관성 이슈)
선택 기준이 항상 명확할 수 있는건가?
아니다. 뻔한 얘기일 수 있지만, 당연하게 시스템 요구사항에 따라 다르다.
하지만.. 필자는 대게 일반적인 상황이라면 Fat Payload 방식을 우선 고민하는편인것같다.
이유는 필자에겐 꽤나 결정적이라고 생각하는데, Thin Payload 방식은 Fat Payload 방식을 제공할 수 없지만, Fat Payload는 Thin Payload 방식을 제공할 수 있다. 이를 공식적인 포함 관계로 표현하기는 조금 위험해보이지만, Fat에도 식별값이 들어있기때문에 기능을 지원하는 영역에선 포함관계라고 생각된다.
그리고 무엇보다, Thin Payload 방식은 수신 시 생산자 API를 콜백하는 방식으로 강하게 의존하는 형태이기때문에 API 지연이 걸릴 수 있는 시간적 결함문제가 존재한다. (Fat Payload도 필요하다면 생산자에게 API를 콜백할 수 있다)
그럼 Fat Payload가 적합하지않은 상황은 어떤것들이 있을까?
가장 단순하게는 상태의 모든 속성들이 필요하지않은 경우이다.
제어가능한 서비스에서 소비자가 필요로하는 데이터가 모든 속성이 아니라면, 굳이 네트워크 대역폭을 오염시킬 필요가 없다. 앞에 “제어가능한”을 붙인 이유는 EDA 특성 상 생산자의 입장에서 소비자들의 컨텍스트는 알 필요가 없지만, 엔지니어가 이를 필요로 하는 소비자들에 대해 화이트 박스인 상황이고, 확장의 여지가 없거나 확장이 되더라도 제어가 가능하다면 DTO(Data Transfer Object) 패턴을 통해 프로젝션하여 필요한 데이터만 조회하는것이 더 나은 판단일 수 있다.
두번째로는 대역폭이 낮게 설정된 인프라를 사용하고있는 경우라면 Fat Payload이 제한될 수 있다. Fat 방식은 상태의 특징에 따라 이벤트 사이즈가 기하급수적으로 증가할 가능성이 있기때문에 정말 무거워질 수 있다. 예를들어, 필자가 만들고있는 시스템에는 TicketGroup
이라는 도메인 엔터티가 존재하는데, 내부에 Tickets
이라는 배열 속성이 있다. 이는 1:N 관계로 Ticket
은 상황에 따라 100개가 넘어갈수도 있다. 만약 AWS SQS를 사용하고있다면, 현재 SQS의 Size Max가 262,144바이트(256KiB)이기때문에 발행에 실패할 수 있다.
세번째로는 이벤트 워커 작업 시 최신 데이터를 보장해야하는 경우라면 이벤트 원천 서비스와 직접 상호작용하는 방식인 Thin Payload를 선택하는것이 적합하다. 물론 위에서 언급했듯이 Fat Payload도 식별값을 들고있기때문에 최신 데이터를 조회할 수는 있지만, 발행 시점의 상태를 포함하여 이벤트 소비자 간 독립성을 높이는 Fat Payload의 장점이 희석될 수 있다.
일반적으로 소비자 입장에선 모든 데이터가 이벤트에 포함되면 처리하는데 용이하겠지만, 모든 객체 그래프를 포함시키는것은 느슨하게 결합된 Bounded Context에서 애그리거트 간 외부 엔터티를 얼마나 포함하는지 도메인 모델의 속성들을 이벤트에 모두 노출하는 것과 동일할 수 있기때문에 고민이 꼭 필요하다.
또한, Thin Payload의 가장 큰 장점중에 하나가 내부 도메인 모델링의 확장 또는 변경이 되더라도 소비자들은 식별값으로 조회하기때문에 확장에 대해 사이드 이펙트가 없다. 따라서, 이벤트 생산자를 담당하고있는 작업자와 다른 팀이여도 Fat Payload에 비해 커뮤니케이션이 적게들어가는 이점이 있다. (* 없는것은 아님!)
마치며
이번 포스팅에서는 Thin Payload와 Fat Payload의 개념과 각 트레이드 오프에 대해서 알아보았다. 결국 이 두가지 전략의 선택은 시스템 요구사항에 따라 달라질 수 밖에 없다. 이 두 전략을 공부하면서 느낀 한가지 분명한 점은 이벤트 설계는 단순한 데이터 전달을 넘어 시스템 아키텍쳐링의 설계 철학까지 고려해야한다는 점이였다.
필자가 기여하고있는 제품에선 많은 서비스에서 이벤트들을 소싱하고있는데, 초반엔 시스템 특성, 요구사항을 우선 고려하기보단 큰 고민 없이 이벤트를 설계하면서 여러번 넘어진 경험이 있어 소개해보고싶었으며, 무엇보다 가장 중요한것은 완벽한 설계가 아닌, 팀과 시스템에 가장 적합한 설계를 찾아가는 과정인것같다.