분산 환경과 Event-Driven Architecture : SAGA , Event Sourcing, CQRS

bo-yoon·2021년 10월 27일
12

msa

목록 보기
2/7
post-custom-banner

일단 Event Driven Architecture 라는 것이 무엇인지 알기 전에 이 용어를 구성하고 있는 단어에 대해서 분석해보자.

먼저 확인해야 할 것은 메시지이다

Message 란?

소비자가 인식하고 그에 따라 행동할 수 있도록 정보를 배포하기 위해 생산자가 만든 데이터그램

왜 갑자기 Message 부터 나오나, 메시지가 Event Driven Architecture 와 무슨 상관이 있나 싶은데 그 이유는 메시지의 종류가 다음과 같기 때문이다

Message 종류

1) Command

2) Event

3) Query

즉, 이벤트는 메시지 종류 중 하나이다.

이벤트의 정의는 다음과 같다


Event 란?

어떠한 일이 일어난 것, 사건(과거)

일어난 일에 대해 알림만 전달

이벤트 자체는 변경 불가능, 시스템 상태는 변경

이벤트는 상태 변경에 대한 알람이라고 생각한다. 알람 자체는 변경 불가능하지만 이벤트가 발생했다는 것은 시스템 상태가 변경되었다는 것을 의미한다.




Event Driven 이란?

한 이벤트에 의해 필수적으로 일어나는 또 다른 이벤트를 연관하여 아키텍처 설계. 이벤트에 반응하는 것

비동기 메시지 메커니즘을 선택하면 효과적

여기서 비동기 메시지 메커니즘이란 무엇일까? Kafka, RabbitMQ, Redis, ActiveMQ 같은 메시지 브로커를 말한다.

그럼 나머지 Message의 종류도 살펴보자.




Command란?

명령으로 다른 당사자에게 응답을 제공해야하는 것(미래)

작업을 완료하기 위한 전체 명령 집합을 포함하기 때문에 무거움

시스템의 상태를 변경




Query 란?

검색을 요청하는 것

시스템 생태를 변경하지 않음

따라서 Event는 Command 보다 가볍다. 또한 Command Producer는 Consumer로 부터 작업 완료를 확인하기 위해 응답을 기다리지만, Event Producer는 Consumer에게 관심이 없다. 서로 느슨한 결합 으로 연결되어 있기 때문이다. 이 차이는 어떠한 서비스에 성질에 따라 사용하는 메시지 종류를 선택할 수 있게 한다.




Event driven architecture의 구성

앞서 Event driven이 메시지 브로커를 사용한다고 언급했었다. 따라서 Event driven architecture 는 다음과 같이 구성되어있다.

  • producer
  • broker
  • consumer

Producer가 이벤트를 생성하고 Broker가 해당 이벤트를 구독하고 있던 Consumer로 이벤트를 리다이렉션한다. 이것이 Event driven architecture의 동작 방식이다.




Event driven architecture?

비동기 통신 방식을 이용해 느슨한 결합을 지향하는 아키텍처로, 분산 시스템 상에서 producer가 이벤트를 발행(publish) 하고, 이벤트를 구독하고 있던 consumer가 이벤트를 받아 처리하는 시스템을 말한다.




Why Event driven architecture?

Event driven architecture를 사용하는 이유는 다음과 같다

  • Decoupling : 다른 구성요소를 분리할 수 있다

  • Dependency Inversion : 의존성을 반전시킬 수 있다.

  • Scalability : 더 나은 확장 제공

이렇게 말하면 어떤 의미인지 잘 이해가 되지 않는다. 예시를 들어 살펴보자.

다음 서비스가 2개가 있다고 하자

서비스 1에서 서비스 2를 호출할 때 서비스가 1은 서비스 2를 알아야하고, 이것은 두개의 서비스 사이의 의존성을 만든다.

이벤트 기반 아키텍처에서는 이 서비스1에서 이벤트를 보낸다. 그리고 서비스 2 같은 경우 이 이벤트만 알고 서비스1의 존재는 모른 채 이벤트를 처리 할 수있다. 그래서 의존성을 반전 시킬 수 있다.

즉, 서비스 1은 서비스 2가 있다는 사실을 알 필요가 없고, 서비스 2도 마찬가지다. 오직 이벤트 라는 존재를 알면 된다.

그리고 이 의존성 반전은 애플리케이션 구성 요소가 더 이상 결합되지 않게(Decoupling) 기능을 제공해준다.

이벤트는 또한 변경 불가능(Immutable) 한 상태인데, 이것은 이 이벤트를 병렬로 처리해도 문제가 없는 상태를 말한다. 즉, 이 이벤트를 실행하는 서비스가 여러개 여도 상관 없다.

그러므로 더 나은 확장(Scalability)를 제공한다.

Decoupling 이라는 특징은 또 다른 이점을 제공해준다. 바로 단일 장애점 없다는 것이 특징으로 가질 수 있다는 것이다.

단일 장애점 : 시스템 구성요소에서 동작하지 않으면 존체 시스템이 중단되는 특징

용어 참고 : https://ko.wikipedia.org/wiki/%EB%8B%A8%EC%9D%BC_%EC%9E%A5%EC%95%A0%EC%A0%90

일반적인 구조에서는 한 서비스가 중단 될시 전체 통신이 안되지만

이벤트 기반 아키텍처에서는 broker가 이벤트를 보관하고 있어서 소비자가 복구 될때까지 이벤트를 유지할 수 있다.

즉, 장애 발생시 더 많은 유연성이나 견고성을 가질 수 있다.




Event driven architecture 단점

그러면 단점은 존재할까?

단점 중 하나는 성능이다. 위 그림에서 보다시피 service 1은 broker로 이벤트를 보내고 service2로 보내기 때문에 약간의 지연이 발생할 수 있다.

두번째 문제는 복잡함이다

어떤 이벤트가 언제, 어떻게 발생하는지 추적하기 어렵다.




When Event driven architecture?

그러면 이러한 이벤트 기반 아키텍처는 언제 사용할까?

  • 성능보다 확장성이 중요할 때
  • 데이터 복제를 원할 때
  • 병렬로 동시에 실행해야하는 여러 서비스가 있을 때

사용하는 것이 좋다.





SAGA 패턴

마이크로 서비스란?

A way to design Software as a suite of independently deployable service. Usually around a business capability

하지만 이런 마이크로서비스 아키텍처 같은 경우 문제점이 있는데 바로 트랜잭션이다. 기존 Monolithic 환경에서는 DBMS 가 기본적으로 제공해주는 트랜젝션 기능을 사용해 관리하였지만, 이 구조에서는 디비가 여러 개이다. 따라서 트랜젝션 처리를 단일로 해줄 수 없어졌다. 마이크로서비스 아키텍처에서는 Anomaly(이상 현상) 이 발생하기 쉬워, 이 데이터베이스 트랜젝션을 ACID 를 지키며 제공하게 도와줘야한다.

ACID

Atomicity : 원자성, 트랜잭션과 관련된 작업이 한번에 수행(부분적으로 실행 x)

Consistency : 일관성, 트랜잭션이 성공적으로 완료되었다면 언제나 일관성 있는 데이터 베이스 상태로 유지

Isolation : 독립성, 트랜잭션 수행시 다른 트랜잭션이 연산작업에 끼어들지 못함

Durability : 지속성, 성공적으로 수행된 트랜잭션은 영원히 반영되어야 함

용어 참고 : https://ko.wikipedia.org/wiki/ACID

마이크로 서비스에서 트랜젝션은 어떻게 처리할까? 그 중 하나가 사가 패턴이다.

사가 패턴

각 서비스의 트랜젝션을 순차적으로 처리하는 패턴, 관리 주체가 DBMS 가 아닌 Application 에 있다.

사가 패턴에서는 서로 다른 서비스(Application)에서 일련의 트랜젝션을 실행하는데 이 트랜잭션이 하나의 트랜잭션 처럼 보이게 할 수 있다. 그래서 만약 이 서비스 중에 하나가 문제가 발생한다면 각각의 서비스에서 다른 보상 트랜잭션을 취해 데이터 정합성을 유지할 수 있다.

이러한 보상을 하는 방법, 즉 사가 패턴의 종류는 2가지가 있다. 또한 여기에서 중점으로 두는 메시지의 종류도 다르다.




1. Choreography Saga : Event

: 서비스들은 서로 이벤트를 구독하고 이에 따라 반응한다.

코레오그레피 패턴 같은 경우 이벤트를 기반으로 한다. 서비스가 트랜젝션을 실행한 뒤 이벤트를 생성하고, 이 이벤트를 구독하고 있던 다른 서비스가 이벤트를 수신한다. 따라서 적절한 성공 및 실패 이벤트를 설계할 필요가 있고, 이벤트 producer, consumer 또한 실패시 보상 트렌젝션을 설계해야 한다.

서비스가 느슨하게 결합되어 있고, 구축하기 쉽다는 장점이 있지만 msa 구조가 점점 확장될 수록 복잡해지고 서비스가 서비스를 구독하기 때문에 순환 종속성이 발생할 수 있다는 단점이 있다.

코레오그레피 사가 패턴을 구현하게 도와주는 툴로는 Axon, Eventuate, LRA, Seata 가 있다.




2. Orchestration Saga : Command

: 모든 통신을 담당하는 중앙 오케스트레이터가 올바른 순서로 서비스를 제공하고 문제가 발생하면 서비스마다 보상 트랜잭션을 취한다.

중앙 오케스트레이터는 서비스를 호출하지만 서비스는 오케스트레이터를 호출하지 않기 때문에 종속성을 방지할 수 있다. 또한 중앙 집중적으로 관리할 수 있어 크고 복잡한 마이크로 서비스에 알맞는 패턴이다.

오케스트레이션 사가 패턴을 구현하는 프래임워크로는 Camunda, Apache Camel 이 있다.





Choreography Saga vs Orchestration Saga

코레오그레피 사가 패턴 같은 경우 아직 구현되지 않는 마이크로 서비스 애플리케이션을 개발할 때 사용하는 것이 적합하다. 왜냐하면 한 서비스가 다른 서비스에서 발생시킨 이벤트를 구독하여 동작하기 때문이다. 또한 서비스가 이벤트를 구독하고 있어 자칫 설계가 복잡해질 수 있기 때문에 서비스 대상이 적을때 적합하다.

오케스트레이션 사가 같은 경우 아미 구현된 마이크로 서비스가 있고 그 MSA 에 사가 패턴을 적용하고 싶을 때 사용하는 것이 적합하다.




Saga 패턴 정리

사가 패턴은 ACID 중 원자성, 일관성, 지속성(ACD) 에 대해 지원해준다. 사가 패턴에는 보상 트랜젝션이 존재하기 때문에 데이터의 원자성을 보장할 수 있고, 일관성을 보장할 수 있다. 또한 데이터 베이스에 수행한 트랜젝션이 반영이 되고 보존되기 때문에 지속성도 보장한다.

하지만 사가 패턴은 독립성(I) 에 대해 보장을 하지 않는다. 분산환경에서는 각 서비스가 각각의 데이터 베이스를 가지고 있기 때문에 트랜젝션 수행시 독립성을 보장할 수 없다.

그렇다면 트랜젝션 자체를 저장하는 것은 어떨까? 그 개념이 이벤트 소싱이다.




Event Sourcing

순차적으로 발생하는 이벤트를 모두 저장하는 패턴

Command에 의해 Processor 가 생성한 Event를 Event Store에 저장한다. 이렇게 저장한 데이터를 Event Handler 를 통해 과거부터 지금까지 이벤트를 확인하여 현재 상태(state)를 유추해낸다.

Processor 가 생성한 이벤트는 Event Handler 가 동작하여 상태를 유추함과 동시에 Event Store에 이벤트를 저장한다.

만약, 문제가 발생한다면 Event Store에서 이벤트를 가져와 다시 Event Handler가 상태를 유추해낸다.

이벤트 소싱에서는 데이터를 삭제(delete) 하거나 수정(update) 하지 않고 삽입(insert) 에 대한 작업만 일어난다.

하지만 이벤트 스토어에 저장하는 이벤트가 많을 경우, 문제 발생시 가져와야 하는 양이 많다. 그런 경우에 사용하는 기능이 Snapshot 기능이다.

Snapshot 은 이벤트 일정한 이벤트 횟수 마다 ( 예를 들어 1000번째 이벤트) State를 저장해두어서 처음부터 이벤트 스토어에서 가져와 상태를 유추할 필요없이 최근 스냅샷의 마지막 이벤트의 다음 이벤트 (1001번째 이벤트) 부터 이벤트를 가져와 상태를 복구하는 개념이다.

이벤트 소싱에서 저장하는 이벤트는 그럼 어떻게 도출하고 저장될까?

일단 그전에 DDD 란 개념부터 이해할 필요가 있다.



DDD : Domain Driven Design

비즈니스 도메인 중심으로 설계하는 소프트웨어 디자인 방법론

DDD 에서는 비즈니스 로직을 도메인에 담는다. 또한 Aggregate 라는 것을 하나의 단위로 생각할 수 있는 객체들의 집합이다.

이벤트 소싱에서는 Aggregate의 상태 변화에 의해 발생하는 이벤트를 순차적을 event저장소에 저장한다.

이 이벤트 소싱같은 경우에는 데이터를 저장하는데에는 적합하지만, 읽기에는 비효율적이다. 그래서 CQRS 와 같이 쓴다.




CQRS

: 명령과 조회의 책임 분리 = Command and Query Responsibility Segregation


CQRS 란?

시스템의 상태를 변경하는 Command 와 시스템을 조회만 하는 Query를 분리하는 마이크로 서비스 패턴

서비스가 스케일 아웃된다면 db 를 접근할 때 여러 개의 서비스로 접근하게 되어 리소스 교착상태가 발생할 수 있다. 일반 서비스에서는 조회(select) 하는 부분이 많이 쓰이고 비교적 update, write, delete 하는 부분이 적게 사용한다. 그래서 데이터를 변경하는 로직과 조회하는 로직을 분리하므로서 데이터 저장소 접근으로 인해 발생하는 부하를 줄인다. 또한 Command와 Query를 분리하는 것은 서비스가 고도화 될 수록 복잡해진 도메인과 잘 맞는다.

cqrs 는 마이크로서비스에 이러한 구성을 적용한 것이다.

CQRS는 바운더리 컨텍스트 안에서만 사용되어야 하고, 전체 시스템에서는 사용되면 안된다.

앞서 언급했듯이 이벤트 소싱과 같이 사용되는데, 이유는 이벤트 소싱과 CQRS 가 서로의 단점을 보완해주기 때문이다.

CQRS는 읽기 DB와 쓰기 DB가 분리되어 사용되 데이터 불일치가 발생할 수 있다. 이벤트 소싱은 이 단점을 보완해준다. Command로 변경된 Aggregate를 Event Store에 저장하여, Event Handler로 Read db의 데이터 일관성을 보장해주는 것이다. 따라서 이벤트 소싱의 단점인 READ에 적합하지 않다는 문제 또한 해결할 수 있다.



결론

단순한 애플리케이션에는 다음과 같은 구조가 필요하지 않을 수 있다. 하지만 서비스가 커질 수록 서버의 분리가 필요하고, 그에 따라 분산 환경의 필요성이 생겨난다. Event driven architecture 은 그 분산환경에서 서비스를 안정적이게 제공하는 가장 인기있는 방법 중 하나이다. 기존에 DB에 있던 비즈니스 로직을 애플리케이션으로 이동시켜 DB의 부하를 줄이고, 그에 따른 트렌젝션 관리를 어떻게 할 것인가? 데이터의 정합성을 어떻게 유지할 것인가? 라는 이슈를 이벤트 중심으로 풀어나간 설계이다.







참고

profile
개발 로그 🍎 🍎 🍎
post-custom-banner

1개의 댓글

comment-user-thumbnail
2022년 5월 19일

좋은 글 감사합니다! 몇가지 제가 아는것과 다른부분이 있어서 댓글 남겨요 😀
https://eventuate.io/docs/manual/eventuate-tram/latest/getting-started-eventuate-tram-sagas.html
Eventuate 는 Orchestration 과 Chreography Saga 둘 다 지원하고있어요!
+AxonFramework 의 Saga지원은 Orchestration 방식을 기반으로 하고있는걸로 알고있습니다 ㅎㅎ

답글 달기