[MSA] SAGA pattern
지난번 MSA 포스팅에 사용한 Outbox pattern글에 이어 SAGA pattern을 작성하려한다.
Saga패턴
Saga 패턴은 간단히 말해 이벤트에 대한 보상트랜잭션을 발행할 수 있는 것이다.
가장 흔한 예로 주문서비스가 있는데, 재고-주문-결제 라는 하위 이벤트가 존재하게 된다.
이 때, 기존 프로세스라면 재고->주문->결제를 정상적으로 수행하는데 만약 결제에서 잔액부족의 이유로 결제취소가 이루어진다면 해당 주문건에 대한 트랜잭션 롤백을 할 수 없게 된다. 재고는 이미 -1 상태이고, 주문도 정상적으로 들어가고, 결제쪽에서만 실패인 상황. 이렇게되면 이미 재고가 -1 상태임으로 주문실패건에 대한 회복이 불가하기 떄문에 다른 사용자마저 구매를 할 수 없게 된다. 이를 개선하고자 MSA 에서 saga패턴 개념이 들어온 것 같다.
위 프로세스에서 saga패턴이 들어간다면 재고-주문-결제 라는 하위 이벤트는 각 서비스가 되어 상태를 메지시큐(kafka)에 저장하여 주문에 대한 롤백이 가능하게 된다.
Saga 패턴에는 2가지 형식이 존재한다.
- Choreograph(코레오그래피) : 각 서비스들은 개별 event Queue 를 사용하는 형태
- Orchestration(오케스트레이션) : 중앙관리 별도 서비스(app)를 통해 2개의 event Queue를 사용하는 형태
더 상세하게 알아보자.
Choreograph (코레오그래피)
코레오그래피 방식은 위에서 설명한대로 마이크로서비스가 각각의 event를 가지게 되어 kafka의 Topic 관리포인트가 매우 많아지게 되므로 운영자는 메시지큐 솔루션의 이해가 많아야 한다. 그리고 무조건 kafka를 통해 하지만 각 마이크로서비스는 port를 점유하고 있지 않기 때문에 전체적인 네트워크 관점에서 자유로운(?) 것 같은 느낌이 든다...ㅋㅋㅋ
그럼 각 서비스의 역할을 보자.
주문서비스는 주문이벤트라는 토픽을 발행한 뒤, 각 마이크로서비스의 event를 listen 하여 주문상태를 체크하게 된다.
서비스A,B...은 주문이벤트를 읽어 각 프로세스를 수행한 뒤 각자의 event 토픽에 수행내역을 저장한다.
추가로 이런 프로세스들이 진행되며 RDBS 보다는 NOSQL을 사용한 사례가 몇몇 보인다. 이유를 생각해보면 서비스A,B..는 실제 테이블 반영전에 별도의 주문상태를 테이블에 저장하게 되는데 외부테이블에 종속적인게 아닌 nosql 성향의 데이터를 정의하기 때문이다.
상세한 프로세스를 예를 들어보자. (이해를 돕기위해 Topic 로그를 확인)
프로세스
1. 주문서비스에서 주문이벤트가 들어온다.
여기서 주문상태(orderStatus)가 최초 생성상태이고, 이후 각서비스들의 토픽상태를 통해 변경된다.
- 최초:ORDER_CREATED
- 완료:ORDER_COMPLETED
- 실패:ORDER_CANCELLED
{
"eventId":"9de11486-99c7-450c-b215-885df191f876",
"date":"2022-10-27T01:52:51.461+00:00",
"purchaseOrder":
{
"orderId":"00516b17-63c5-4018-8168-861f23c9cfc4",
"productId":2,
"price":200,
"userId":1
},
"orderStatus":"ORDER_CREATED"
}
2. 주문이벤트 기다리던 서비스A,B...가 프로세스를 수행 한뒤 각자의 토픽에 데이터를 전송
인벤토리 프로세스가 정상처리되었으면 status:RESERVED 를 리턴한다.
{
"eventId":"39d0c4e1-a79b-4682-bc18-730d32232c9b",
"date":"2022-10-27T01:52:51.490+00:00",
"inventory":
{
"orderId":"00516b17-63c5-4018-8168-861f23c9cfc4",
"productId":2
},
"status":"RESERVED"
}
결제 프로세스가 만약 비정상처리되었으면 status:REJECTED 를 리턴한다.
{
"eventId":"50ab7508-0f12-4f3d-9659-6287e82403ba",
"date":"2022-10-27T01:52:51.478+00:00",
"payment":
{
"orderId":"00516b17-63c5-4018-8168-861f23c9cfc4",
"userId":1,
"amount":200
},
"paymentStatus":"REJECTED"
}
3. 각 서비스들의 상태체크 및 롤백
모든 마이크로서비스 프로세스의 event 토픽을 기다리고 정상인지 체크하는데 정상일 경우 주문 상태를 'ORDER_COMPLETED' 로 변경하게 되고, 위와 같이 특정서비스가 REJECTED 상태를 내보냈다면 주문상태를 'ORDER_CANCELLED' 로 변경하여 각 마이크로서비스는 해당 주문건을 rollback 하는 프로세스를 진행하게 된다.
rollback 프로세스는 어렵지 않다. -count 한 내용을 거꾸로 +count 해주면 된다.
(필자는 rollback 이라하여 database transaction 발생 이전으로 돌리는 줄알았으나, 샘플코드에서 간단히 update로 구현하여 롤백을 구현하였음)
Orchestration (오케스트레이션)
오케스트레이션 방식은 별도의 관리서비스를 통한다는 부분이 다르다. 코레오그래피와 달리 관리자는 메시지큐(kafka)에서 딱 2개 토픽만 모니터링하면 되어 관리포인트가 줄어들었고, 대신 별도의 중앙관리서비스를 직접 구현 및 관리를 필요로 한다. 이 방식은 아무래도 MSA 아키텍처를 제대로 이해한 AA급의 개발자가 있어야 현실적으로 가능해보인다. 그래도 back-end 서비스는 중앙관리서비스와 tcp 통신을 하기 때문에 기존과 동일한 개발로직을 가져가면 되어 편하다.
프로세스
이전에 설명한 코레오그래피와 다른 점만 짚고 넘어가보자.
주문서비스에서 주문 토픽이 생성되면 중앙관리서비스에서 모든 제어를 담당하게되어 각 백엔드 서비스들에게 tcp 통신을 요청하게 되고 모든 서비스의 상태를 체크한뒤 주문성공여부에 대한 내용을 업데이트한다.
마치며
MSA(마이크로서비스아키텍처)를 설계하며 Saga패턴에 대해 알아보았다.
배달의민족 같은 트래픽이 많은 대고객 서비스에서 충분히 사용되고 있는데, 금융이나 실제 현업의 IT담당자들은 이렇게까지 MSA 구조를 설계하고 수용하려하지 않는 것 같다...
서비스가 어마어마하게 커져가는 기업만... 가능할 것 같은(?)
뭐, 작은서비스에 이렇게까지 들어가면 오버스펙이겠지만 ㅎㅎㅎㅎ
결국 차세대, 차세대해도... 비즈니스를 설계하는 사람이 잘 파악해야 가능할듯