MSA 아키텍처!
MSA 패턴
서비스가 쪼개지면서 모든 트랜잭션은 서비스 단위로 모두 분리되어 분산트랜잭션(분산 워크플로) 으로 취급되며,
RDB의 트랜잭션이 지원하는 원자성, 일관성, 스냅샷 격리와 같은 기능을 사용하지 못하게되면서 ACID 속성을 유지할 수 없게된다.
- 원자성: 쪼개진 서비스에서 데이터를 한번에 커밋 롤백하는 것은 불가능.
- 일관성: 데이터가 덜 삭제되어 일관성 없는 데이터가 특정 서비스에 남아있을수 있음.
- 격리성: 서비스들의 작업들 사이에 다른 요청이 충분히 끼어들 수 있음.
분산트랜잭션은 ACID 를 포기하는 대신 BASE 를 지원하는 방향으로 개발해야한다.
- 기본 가용성 Basic Availabiltiy
모든 서비스는 분산 트랜잭션에 참여해 데이터 동기화를 위해 동작하도록 개발하는 것. - 소프트 상태 Soft sate
분산 트랜잭션이 진행중인 상황에서 데이터의 일관성이 일부 서비스에서 불일치하는 소프트한 상황임을 인정하고 개발하는 것. - 최종 일관성 Eventual consistency
시간이 지나면 모든 서비스의 데이터 동기화가 이루어져 일관성이 맞춰지도록 개발하는 것.
BASE
를 지원하기 위한 여러가지 패턴이 존재한다.
백그라운드 동기화 패턴
스케줄링, 배치잡을 사용해 데이터를 주기적으로 확인 및 동기화 진행.
최종적 일관성이 맞춰지는대 가장 긴 시간이 필요한 패턴.
MSA에서 사용되기 보다는 내부 폐쇄적인 서비스에서 자주 사용되는 패턴이다.
백그라운드 동기화 프로세스가 모든 서비스에 연결되어 경게 컨텍스트가 흐릿해진다.
백그라운드 동기화 프로세스 장애 발생시 전체 장애로 이어진다.
- 탈퇴한 회원 정보를 새벽3시마다 체크 후 모든 서비스에서 관련 데이터를 삭제
- 삭제 프로세스와 도메인 로직을 분리하기 위해 expired 와 같은 별도 필드를 사용하는 것을 권장.
이벤트 기반 패턴
데이터를 변경하고 동일한 트랜잭션을 처리하는 다른 서비스에게 메시지 브로커를 통해 전송한다.
서비스들은 메세지를 통해 비동기적으로 이벤트를 처리한다.
- 브로커 라는
SPoF
가 생기고 오류 복원력이 낮아진다 - 메세지 처리에 장애가 발생하더라도 다른 서비스들은 장애에 대해 알아차릴 수 없다.
- 도메인 로직 실패시 보상 트랜잭션을 위한 프로세스를 추가정의해야한다.
- 메세지 처리 실패시 Dead Letter Queue 와 이에 대한 처리로직을 추가정의 해야한다.
- 트랜잭션에 참여하는 서비스가 많아질 수록 일관성 유지가 힘들어진다.
서비스간 의존성이 높아 오히려 메세지에 기반한 도메인 구성이 힘들다면 서비스 병합을 고려해야 한다.
서비스 메시 패턴(사이드카 패턴)
대표적인 프로젝트가 구글의 Istio.
초창기 넷플릭스 OSS 기반의 Gateway, Config, Service Registry 등 운영서비스가 별도의 서버로 분리되어 있었고 플랫폼 의존적인 요소또한 있었는데, 이런 기능을 서비스 메시 패턴을 통해 해결할 수 있다.
쿠버네티스에 기본 탑재되어 있으며 배포되는 컨테이너의 라우팅, 디스커버리, 로드밸런싱, 모니터링, 보안, 트레이싱 기능을제공
서비스 컨테이너 서비스 내부에 특정 설정이 들어가지 않고 외부에 별도의 컨테이너가 추가 배포, 네트워크 프락시를 통해 운영되는 환경이다. 그렇기 때문에 넷플릿 OSS 환경에서 벗어나 진정한 폴리글랏 구조의 백앤드를 구축가능하다.
CQRS 패턴
Command Query Responsibility Segregation
약자로 명령(Command)모델
과 조회(Query)모델
을 분리하는 패턴.
CRUD
중에 Read
가 압도적으로 많이 사용되며 불리적으로 두 저장소를 분리, 부하를 줄이고 반환시간을 앞당길 수 있다.
이벤트만을 사용해 읽기전용 서비스의 데이터를 수정하고 사용자는 API 를 사용해 데이터를 읽어들인다.
[주문내역조회기능] 구현시 아래와 같이 여러개의 도메인이 필요하다.
Order
Product
Mmember
하나의 조회기능에 도메인이 많을 수록 JOIN 이 많아지고 JPA 최적화 문제가 발생할 수 있다.
그림처럼 명령모델과 조회모델을 나눠 구현하면
조회기능은 MyBatis 와 같은 라이브러리를 사용하거나 더 나아가서 NoSQL 을 사용해도 되고
명령모델을 JPA 와 같은 논리적 구성이 강한 라이브러리를 사용할 수 있다.
이 때 이벤트와 같은 방식을 사용해 NoSQL 로 데이터를 동기화시킨다.
Event Sourcing 패턴
데이터의 일관성을 모델로 관리하는 것이 아닌 차곡차곡 쌓이는 이벤트 형식으로 관리하는 패턴 으로, 각 데이터 상태변화(이벤트) 순서를 저장하는 시스템이다.
일반적으로 데이터의 상태 자체를 저장하는 것이 일반적인 DB의 역할이지만, 이벤트 소싱은 상태변화(이벤트)가 일어날 때 마다 데이터를 DB 에 저장한다.
CRUD 에서 UD 가 사라지고 CR 만을 사용해 데이터의 일관성을 유지한다.
이벤트는 절대 수정/삭제 되지 않고 추가되기만 한다.
은행 입/출금, git 의 버전제어 등이 이벤트 소싱패턴으로 개발되었다 할 수 있다.
Event Sourcing 패턴
은 외부의 어뎁터가 내부 사정을 깊게 알수 없도록, 캡슐화, 높은 응집도 원칙을 가장 중요시한다.
Tolerant Reader 패턴
Tolerant Reader(관대한 독자) 패턴
은 아래와 같이 지속해서 새로운 필드가 추가됨에도, 과거 필드를 사용하는 서비스들에게는 영향이 없고 무시되어도 상관 없는 방식을 뜻한다.
/api/users/31ac9124c
{
"userId": "31ac9124c", // 230201 추가
"userName": "홍길동", // 230201 추가
"usserDpt": "개발팀", // 230201 추가
"userEnName": "Gildong Hong", // 230501 추가
"userGd": "연구원" // 230501 추가
}
관대한 독자 패턴으로 개발된 서비스들은 API 가 업데이트 되어도 기존에 [userId, userName, usserDpt]
만 있으면 되기 때문에,
새롭게 추가된 [userEnName, userGd]
필드 상관없이 API 를 사용할수 있다.
Transactional Outbox 패턴
Outbox: 보낼 편지함
DB 에 이벤트 메세지를 위한 저장한 뒤 별도의 프로세스가 저장된 정보를 읽어 메세지를 보내는 패턴이다.
사용자의 회원탈퇴, 환불요청 같이 DB 상태를 변경하는 트랜잭션과 함께 이벤트를 발행해야할 때, DB 에 등록된 데이터를 변경함과 동시에 이벤트를 발행해야할 때 가 있다.
하지만 물리적으로 다른 서드파티를 하나의 트랜잭션으로 묶는건 불가능하기에 이런 패턴을 사용해서 해결한다.
create table outbox
(
outbox_id bigint auto_increment primary key,
created_date_time datetime default current_timestamp,
topic varchar(255),
message varchar(255),
send_yn tinyint default 0,
);
위와같은 outbox
테이블을 조회하는 Message Relay
코드를 작성한다. 코드 구성에 따라 DB 부하가 발생할 수 있음으로 적절한 설계가 필요하다.
전송이 완료되면 메세지 전송처리(수정 혹은 삭제)를 진행한다. 최소 한번 이상(at least one) 정책을 수행한다.
outbox
을 풀링하는 프로세스를 추가로 개발하기 부담스러울 경우 Debezium(CDC)
같은 DB의 트랜잭션 로그를 테일링하는 서비스를 사용할 수 있다.
DB 의 binlog 가 장애로인해 rollback 되지 않는 이상 안전하다 할 수 있다.
풀링방식이 아니기에 DB 부하가 덜하다.
Dead Letter Queue 패턴
Kafka 의 DLT, SQS 의 DLQ 를 사용하여 Consume 실패에 대한 처리를 수행할 수 있다.
Dead Letter Queue 패턴
은 메세지 브로커의 연결 장애, 일지정 오류로 인해 메세지 발행/구독 실패에 대한 걱정을 해소하고자 새로운 메세지 브로커를 사용하여 발행/구독 실패한 메세지를 관리하는 방법이다.
출처: https://andrew-jones.com/blog/the-dead-letter-queue-pattern/
물리적으로 Message Broker 를 나눠 단일장애지점에 대한 데이터 일관성 불일치에 대한 Risk 를 낮추고 최종적 일관성 유지를 위한 복원을 쉽게 처리할 수 있도록 지원한다.
사가패턴
Saga
패턴은 여러개의 서비스가 하나의 워크플로를 만드는 분산 워크플로를 처리하기 위한 패턴이다.
분산 워크플로를 하나의 트랜잭션인 원자성
으로 처리할지, 최종 일관성
으로 느슨하게 유지할지, 동기
통신을 사용할지, 비동기
통신을 사용할지, 중앙 중재자가 있는 오케스트레이션
방식을 사용할지, 서비스들간의 협업을 통해 처리하는 코레오그래피 방식
을 사용할지,
아래 정의되어 있는 사가패턴을 통해 결정할 수 있다.
- 오케스트레이션 사가(Orchestation saga)
이벤트 교환, 동기화를 중앙화해서 처리함. 별도의 중재자 서버가 존재. - 코레오그래피 사가(Choreography Saga)
이벤트 교환, 동기화를 참가자에 처리를 맡김. 워크플로에 맞춰 체인형태로 서비스가 이어져 있음.
- 오케스트레이션 장점
- 워크플로 중앙화되어 복잡도가 낮음.
- 중재자가 서비스들을 모니터링 가능하기에 쉬운 에러처리, 쉬운 재시도요청(복원성).
- 중재자가 워크플로의 상태를 트래킹 가능하여 상태관리가 용이함.
- 오케스트레이션 단점
- 워크플로가 단일장애지점이 되기에 내고장성이 낮음.
- 요청이 중앙집중화 되어 확장성, 가용성이 낮음.
- 모든 서비스가 중재자와 연결되어 있어 결합도가 높음.
- 코레오그래피 장점
- 중재자와 같은 단일장애지점이 없어 내고장성이 강힘.
- 서비스의 수평 확장이 가능해 확장성, 가용성이 높음.
- 각 서비스가 트랜잭션을 스스로 관리할 경우 결합도가 낮음.
- 코레오그래피 딘점
- 워크플로가 분산되어 복잡도가 높음, 통신 오버헤드가 증가함.
- 워크플로의 추적이 힘들어 생태관리가 힘들거나 추적이 아예 불가능함.
- 서비스간의 보상트랜잭션과 같은 인터랙션이 많아지면 결합도가 높아짐.
워크플로가 복잡할수록 오케스트레이션
을 사용하고,
워크플로가 간단하거나 높은 처리량을 필요로 한다면 코레오그래피
를 사용해야 한다.
사가패턴 종류는 아래와 같다.
패턴명 | 통신 | 일관성 | 조정 |
---|---|---|---|
에픽 사가(Epic Saga) | 동기 | 원자성 | 오케스트레이션 |
페어리 테일 사가(Fairy Tale Saga) | 동기 | 최종 일관성 | 오케스트레이션 |
폰태그 사가(Phone tag saga) | 동기 | 원자성 | 코레오그래피 |
타임 트래블 사가(Time travel saga) | 동기 | 최종 일관성 | 코레오그래피 |
판타지 픽션 사가(Fantasy fiction saga) | 비동기 | 원자성 | 오케스트레이션 |
호러 스토리 사가(Horror story saga) | 비동기 | 원자성 | 코레오그래피 |
패러렐 사가(Parallel Saga) | 비동기 | 최종 일관성 | 오케스트레이션 |
앤솔로지 사가(Anthology saga) | 비동기 | 최종 일관성 | 코레오그래피 |
기억하기 좋게
통신/일관성/조정
에 3가지 방식에 대한 조합의 이름을 붙인것 뿐이다.
페어리 테일 사가, 페러렐 사가가 가장 많이 사용되는듯, MSA 구조에선 최종 일관성이 여러모로 적합한 아키텍처이다.
원자성 특징을 가진 사가 패턴들은 보상 트랜잭션
을 통해 기존에 수행한 내용을 undo 해야 한다.
최종 일관성 특징을 가진 사가 패턴들은 보상 트랜잭션
을 통해 일광성을 유지하기 위한 정해진 로직대로 알아서 처리하면 된다.
사가 상태 기계(saga state machine)
사가패턴에서 말하는 최종 일관성은 일종의 불확실성 상태를 분산 워크플로 일부로 인정하고 불확실한 상황까지 트랜잭션에 포함시키는 것이다.
예를들어 물건을 주문하면 주문데이터가 발생할 것인데 [결제정보 확인 중] 이라는 불확실성 요소를 집어 넣어 더 이상의 주문데이터 일관성을 해칠 수 없도록 방지해야 한다.
결제서버로부터 결제 확인 메시지가 올 때 까지 불확실성 요소를 유지하여 일관성을 유지한다.
분산 워크플로에서 상태값을 기준으로 데이터의 최종 일관성을 유지하기 때문에,
상태값은 사가에서 중요하게 사용되어 사가 상태 기계(saga state machine) 으로 불리기도 한다.
사가 패턴별로 분산 워크플로 사이사이에 상태값을 업데이트 하기위해 서비스간 요청과 보상 트랜잭션
에 대해 상세한 시나리오를 구성해야 한다.
- 오케스트레이션 특징을 가진 사가 패턴은 중재자를 통해 쉽게 상태값을 동기화할 수 있다.
- 코레오그래피 특징을 가진 사가 패턴은 서비스간 상태값의 전이를 위해 첨밀한 시나리오를 구성해야 한다.
에픽 사가
동기통신 + 원자적 일관성 + 오케스트레이션
오케스트레이션 사가
의 대표적(기본) 패턴
도중에 특정 트랜잭션이 실패할경우 중재자는 보상 트랜잭션을 모든 서비스에 전달하고 각 서비스의 undo 상태를 확인한다.
하나의 호출이라도 실패하면 모두 실패한것으로 간주, 모두 원점으로 되돌린다.
아래와 같은 특징이 있다.
- 높은 커플링(통기 + 오케스트레이션)
- 낮은 확장성(중앙관리자)
- 낮은 가용성(동기 + 원자성)
- 낮은 복잡도
현세계의 대부분의 도메인이 원자적 일관성
을 요구하고 복잡도가 낮아 구현하기 쉬워 많이 사용되는 사가패턴이다.
조정자와 모든 요청을 undo 시키는 측면에서
2PC, XA 트랜잭션
과 비슷한 기능을 하지만
2PC, XA 트랜잭션
는 어플리케이션과 서드파티(DB, 메시지큐) 간의 동작이고 사가패턴은 어플리케이션들간의 동작이다.
페어리 테일 사가
동기통신 + 최종적 일관성 + 오케스트레이션
에픽 사가와 비슷한 상태이지만 중재자가 트랜잭션을 따로 관리하지않는다,
중재자가 각 서비스의 요청/응답/에러 처리를 조정하지만 중재자는 보상 트랜잭션만 보낼 뿐 일관성에 대한 처리는 각 서비스가 알아서 처리해야한다.
중재자 역할을 수행하는 프레임워크로 Apache Seata 가 유명하다.
원자성 트랜잭션
이 필요없는 덜 민감한 데이터를 다루는 워크플로에서 사용되는 패턴이다.
서비스가 중단되더라도 내부 캐시와 같은 기능을 사용하면 실패한 요청의 복구가 가능하다.
- 높은 커플링(동기 + 오케스트레이션)
- 높은 확장성(비원자성이라 수평 확장 가능)
- 보통 가용성(트랜잭션을 각자 처리해 수평확장가능, 하지만 동기통신이라 느림)
- 낮은 복잡도
높은 확장성
, 낮은 복잡도
를 지원하는 효율 좋은 사가 패턴이라 MSA 에서 자주 사용된다.
폰 태그 사가
동기통신 + 원자적 일관성 + 코레오그래피
폰 태그(전화 옮겨 말하기)
라는 이름이 붙은 만큼 서비스간의 워크플로가 체인저럼 연결되어있다.
최초로 트랜잭션을 시작한 서비스가 조정점(cordination point)이 된다
최초로 트랜잭션을 시작한 서비스를 프런트 컨트롤러 라고 부르기도 함
각 서비스는 자신의 트랜잭션이 실패할 경우 책임 체인 통해 상위 워크플로에 보상 트랜잭션을 보내고 상위 워크플로는 기존 작업을 undo 한다.
다음과 같은 특징이 있다.
- 높은 커플링(동기 + 책임 체인)
- 낮은 확장성(동기 + 원자성)
- 낮은 가용성(동기 + 원자성)
- 높은 복잡도(코레오그래피)
코레오그래피
를 사용하는 사가패턴이지만 동기 + 원자성
방식을 사용하기에 확장성, 가용성
이 낮다.
워크플로가 간단하고 에러가 발생할일이 거의 없는 트랜잭션에 적합하다.
타임 트래블 사가
동기통신 + 최종 일관성 + 코레오그래피
폰 태그 사가
처럼 체인
형태로 구성되지만 상위 워크플로에게 보상트랜잭션을 보내지 않는다.
각 서비스는 자체적으로 워크플로의 상태와 일관성을 유지해야한다.
- 중간 커플링(동기 + 체인)
- 높은 확장성(비원자성이라 수평 확장 가능)
- 보통 가용성(트랜잭션을 각자 처리해 수평확장가능, 하지만 동기통신이라 느림)
- 낮은 복잡도(트랜잭션이 없음)
데이터의 일관성 중요도가 크지 않는 서비스에서 사용된다.
데이터 수집, 벌크 트랜잭션 과 같이 fire and forget
형태의 워크플로와 잘 맞는다.
판타지 픽션, 호러 스토리 사가 패턴
- 판타지 픽션 사가 패턴
비동기 + 원자성 + 오케스트레이션
, 비동기 통신을 사용하는애픽 사가 패턴
.
중재자는 서비스에게 요청과 보상 트랜잭션 모두 비동기 메세지로 전달하여 병렬처리한다. - 호러 스토리 사가 패턴
비동기 + 원자성 + 코레오그래피
, 비동기 통신을 사용하는폰 태그 패턴
.
하위 워크플로 서비스에겐 비동기 메세지 요청, 상위 워크플로 서비스에겐 비동기 메세지로 보상 트랜잭션를 전달하며 병렬처리한다.
비동기로 원자성을 처리하는것은 불가능에 가깝다. 구현 가능성이 낮아 두 패턴 모두 안티패턴으로 자주 사용되는 사가 패턴이다.
패러렐 사가 패턴
비동기 + 최종 일관성 + 오케스트레이션
비동기 통신을 사용하는 페어리 테일 사가 패턴
, 중재자는 요청과 보상 트랜잭션을 비동기 메세지로 보내고, 일관성 관리는 각 서비스가 알아서 진행한다.
중재자는 공유 데이터를 동기화해 서비스들이 데이터 일관성을 맞출수 있도록 도움을 준다.
- 낮은 커플링(비동기 + 최종 일관성)
- 높은 확장성(비원자성이라 수평 확장 가능)
- 높은 가용성(트랜잭션을 각자 처리해 수평확장가능, 하지만 동기통신이라 느림)
- 낮은 복잡도(오케스트레이션 + 최종 일관성)
장점이 많아 대규모 서비스 중 워크플로가 복잡한 서비스에서 자주 사용된다.
State Machine 형태의 도메인을 효과적으로 처리할 수 있다.
오케스트레이터에서 순차적으로 메세지들을 각 서비스에 발행하고 처리결과를 수신받아 State 를 기록하는 형태.
메세지 전송 실패에 대한 처리는 Transactional Outbox 패턴 등을 사용.
앤솔로지 사가 패턴
비동기 + 최종 일관성 + 코레오그래피
비동기 통신을 사용하는 타임 트레블 사가 패턴
, 모든 요청은 비동기 메세지를 통해 전달되고 보내고, 보상 트랜잭션은 없다.
타임 트레블 사가 패턴
와 마찬가지로 각 서비스들은 자체적으로 워크플로의 상태와 일관성을 유지해야한다.
- 매우 낮은 커플링(비동기 + 최종 일관성 + 코레오그래피)
- 매우 높은 확장성
- 매우 높은 가용성
- 매우 높은 복잡도(비동기 + 코레오그래피)
비동긱 코레오그래피 조합인 만큼 복잡한 워크플로 처리는 불가능에 가깝다.
높은 처리량을 요구하고 워크플로가 단순한 서비스에서 주로 사용한다.
보상 트랜잭션 실패
보상 트랜잭션
을 보내어 모든 상위 워크플로가 undo 하거나 데이터의 최종적 일관성을 지킬수 있으면 좋겠지만 생각한대로 동작하지 않을 수 있다.
보상 트랜잭션
을 지원하지 않는 서비스(email, SNS, 결제)- 네트워크, 시스템 오류로 인한
보상 트랜잭션
실패 - 분산 워크플로 안에 삭제 로직이 있는 경우 복구 어려움
보상 트랜잭션
을 지원하지 않는 서비스는 실행 순서를 최대한 늦게 설정해 보상 트랜잭션
실패 횟수를 떨어뜨릴수 있도록 해야한다.
보상 트랜잭션
자체가 실패할 경우 관리자에게 알리는 것 외에는 추가 조치방법이 없다.
이 경우 원자성 처리방식을 사용하는 도메인의 경우 중대한 일관성 결함이 발생하게 된다, 하지만 보상 트랜잭션
실패 마저도 사가 상태 기계
로 관리할 수 있다면 [실패 후 관리자 알림]
상태로 변경될 뿐이다.
최대한 도메인을 최종 일관성을 지원하는 방식으로, 도메인들간의 의존성을 분리하여 하나의 일관성으로 묶이지 않도록 구성하는것이 보상 트랜잭션
실패를 피하는 길이다.