9/17/2019

마이크로 서비스(MSA)에서 분산 트랜잭션

 

위의 경우는 체크 아웃 요청에 대해 데이터베이스에서 트랜잭션이 생성된다. 각 비즈니스 단계에 대해서 데이터베이스에서 보장한다. ACID(Atomicity, Consistency, Isolation, Durability)로 알려져 있다.

아래는 마이크로 서비스에서의 커머스 시스템이다.

모놀리틱은 데이터베이스에 의존하여 트랜잭션을 처리하지만, 마이크로 서비스의 경우 데이터베이스에 의존할 수가 없다. 그 이유는 각 서비스마다 별도의 데이터베이스를 가지고 있기 때문이다.

마이크로 서비스에서 트랜잭션에 대한 문제

마이크로 서비스 아키텍처가 나온 후, 데이터베이스의 ACID 특성을 사용할 수 가 없다. 특정 로직을 처리하기 위해서는 여러 마이크로 서비스(여러 데이터베이스)에 걸쳐 있게 된다.

Atomic 트랜잭션을 어떻게 유지 할 것인가?

Atomic 트랜잭션은 모든 단계 중에 하나를 완료하는 것을 의미한다. 완료 되지 않은 작업(Inventory Microservice)에 대해서는 어떻게 롤백을 해야 하는지 고민이 되는 부분이다.

동시 요청에 대한 처리

Order 서비스가 완료된 후 Inventory 서비스의 정보를 보여줘야 하는데, Order가 완료 된 후 Inventory에 업데이트를 해야 하는지? 이렇게 되면 개발하는 개발자는 본인이 개발해야 할 부분외에 많은 것을 고민해야 하는 상황에 직면하게 된다.

가능한 해결책들

Two-Phase Commit

처리 방법을 준비 단계와 커밋 단계를 가지고 처리하는 기법이다.

연관된 모든 마이크로서비스에서 커밋을 준비하고 트랜잭션을 처리할 준비가 되었다고 코디네이터에게 통지를 해야 한다.

커밋 또는 롤백을 코디네이터에 의해 모든 마이크로 서비스에 전달된다.

아래는 성공 시나리오이다.

Transaction Coordinator는 글로벌 트랜잭션을 시작한다.

Order 서비스를 호출하고 OK를 받으면 Invventory 서비스를 호출한다.

즉, 모든 트랜잭션에 대한 부분을 Transaction Coordinator가 관여하게 된다.

아래는 실패 시나리오이다.

트랜잭션이 엮여 있는 서비스중에 하나라도 실패가 발생하게 되면, Transaction Coordinator는 롤백 프로세스를 수행한다.

Two-Phase commit의 장점은 아래와 같다.

  • 두 단계 커밋이기에 속도가 느리다.
  • Transaction Coordinator에 대한 의존성이 증가한다.
  • 교착 생태가 발생 할 수 있다.

SAGA pattern

각 마이크로 서비스는 데이터를 업데이트 할 때마다 이벤트를 개시한다. 다른 서비스는 이벤트를 구독하고, 이벤트가 수신되면 데이터를 업데이트 하는 방식이다.

각 마이크로 서비스는 이벤트 버스를 통해 상호 통신하게 된다.

Choreographer에 의해 트랜잭션 이벤트를 생성하고 각 서비스에서는 이벤트를 수신하여 업무를 처리하는 방식이다.

Inventory 서비스가 실패하게 되면, Choreographer에 실패 이벤트를 전달하고 Choreographer는 Order서비스에 삭제 이벤트를 생성한다.

이 방식의 장점은 아래와 같다.

  • 순서가 보장되지 않는다. (비동기 방식)
  • 마이크로 서비스가 많을 수록 디버깅 및 유지 보수가 어려워진다.

결론

가장 좋은 대안은 분산 트랜잭션을 없애는 것이다.

위의 대안이 불가하다면, 아래의 마틴파울러가 얘기한 내용을 고려해 볼 수 있다.

단일체로 시작하고 점차 마이크로서비스로 분리하는 것.

하나의 결과에 대해 두개 이상의 시스템에 데이터를 갱신할 필요가 있을 때, Two-Phase commit 보다는 SAGA pattern이 조금더 바람직한 방법이다.

그 이유는 Two-phase commit은 확장이 어렵기 때문이다.

References:

  • https://medium.com/@sohan_ganapathy/handling-transactions-in-the-microservice-world-c77b275813e0




9/07/2019

마이크로 서비스(MSA)를 사용하지 않는 경우

본 글은 찰스 페발의 블로그의 을 번역한 것이다.

굳이 마이크로 서비스가 필요하지 않는 상황에서도 “마법의 키워드”와 같이 마이크로 서비스를 꼭! 해야 한다는 상황에서 정말 그래야 하는지 고민해 볼 필요가 있다.

마이크로 서비스란?

마이크로 서비스에는 많은 정의가 있다. 일반적으로는 아래와 같이 요약된다.

  • 마이크로 서비스는 구성 요소 설계 및 배치 아키텍처에 적용 되는 패턴이다.
  • 서비스를 작게 유지하고 기능별로 그룹화 한다.
  • 관심사를 분리하여 구현한다.
  • 서로 자율적으로 분리되어 있어야 한다.
  • 독립적 배포 및 버전을 조정하여 확장할 수 있어야 한다.

마이크로 서비스 패턴의 일반적인 구현은 다음과 같다.




주요 문제는 적용 가능한 패턴의 폭이 상당히 넓다는 것이다. 그렇기 때문에 마이크로 서비스를 선택하는 것이 최상의 옵션이 아닐 수 있다는 관점을 보려 한다.

마이크로 서비스를 구현하는 데에 있어서 절충점이 존재한다.

마이크로 서비스의 과제

1. 마이크로 서비스는 올바르게 설계 하기가 어렵다.

2. 기술적 복잡성이 포함되어 있다.



  • API 수가 증가한다.
  • Network bottleneck이 증가한다.
  • 여러 서비스간 트랜잭션 관리가 어렵다.
  • 분산 환경에서의 디버깅은 어렵다.

비즈니스 기능외에 기술적으로 고려해야 할 항목들이다.

3. 조직이 변경되어야 한다.

Conway의 법칙에 따라 Front/back end 개발자, 데이터 플랫폼 엔지니어, QA, 제품 관리자 및 운영팀이 단일팀으로 혼합되어 있다.

아래의 그림에서는 각 팀간 업무 우선순위가 다르기에 팀 간 종속성이 서로 충돌되거나 업무 지연이 발생할 것이다.

4. 마이크로 서비스의 개념 및 설계에 대한 경험과 이해가 부족한 경우

우리는 대부분 새로운 것을 배우는 것을 좋아하지만, 이런 상황은 실수에 대한 시간과 에너지가 필요하다.

5. 성숙되지 않은 스킬을 적용

일반적으로 사람들은 자신이 아는 것으로 대체하려는 습성이 있다. 자신이 알거나 가장 짧은 길을 가면서 복잡성을 피할 것이다.

성숙되지 않은 기술로 도배하면 각 구성요소간 연관된 부분, 복잡성을 이해하기도 어려울 것이다.


운영 및 디버깅의 복잡성으로 인해 효율성이 저하될 가능성도 있다.

하고 싶은 말

구축하려는 서비스에 명확한 도메인이 있고, 각 영역별 혼합된 형태의 팀을 구성할 수 있고, 팀내의 구성원이 기술에 대해 자신감이 있거나 경험이 있을 경우에는 마이크로 서비스로 갈 수 있는 가능성이 높다고 생각한다.

그렇지 않은 경우에는 역효과를 낳을 수 있다는 점을 명심해야 한다. K8S와 같은 컨테이너 플랫폼은 마이크로 서비스 아키텍처 혹은 마이크로 서비스 아키텍처 없이도 사용이 가능하다.

마이크로 서비스는 복잡한 분산 시스템 환경에서는 확실히 적합한 옵션이다. 하지만, 이것이 유일한 것은 아니기에 앞서서 언급한 것들을 고려하여 합리적인 선택을 해야 한다.



9/06/2019

마이크로 서비스(MSA)의 경계

마이크로 서비스를 사용하면 이점을 얻을 수 있지만, 경험에 비추어보면 몇 가지 문제가 있었다.

영향을 최소화 할 수 있도록 도출된 문제를 인식하는 것이 중요하기에 여기에 몇 가지 적는다.

문제 중 하나는 마이크로 서비스의 경계를 잡는 일이다. 이것은 가장 어려운 작업이다.

마이크로 서비스 범위 설정

각각의 마이크로 서비스가 단일 책임의 원칙을 수용하는 구조라면 이 글을 쓸일이 없었을 것이다.

커머스 플랫폼에서 결제를 담당하는 서비스를 생각해보자.

처음에는 두 가지의 지불 방법 (카드와 바우처)만 존재했고, 이 두가지 방법이 동일한 서비스에서 구현되었다고 가정한다. 여기에 계좌 이체, Paypal등 다른 결제 방식이 추가된다고 하면 어떻게 해야 하는가?

미래의 요구 사항을 미리 알면 설계 시 혹은 경계 설정시 보다 현명한 결정을 내리겠지만, 현실은 그렇지 않다. 따라서 현재의 정보를 바탕으로 결제를 담당하는 서비스를 단일로 하기로 결정 했다고 가정해보자.

요구 사항 변경

시간이 지나서 Paypal 및 Apple Pay로 결제를 하기 위한 새로운 요구사항이 생겼다. 기존 서비스에서 이런 새로운 결제 방식을 구현 하면 서비스가 커지게 되고 의도치 않게 “많은 책임” 이 생기게 된다.

따라서 새로운 요구 사항은 결제 방식에 따라 다른 서비스로 결제할 수 있도록 책임을 분할하기로 결정을 하게 된다.

Paypal과 Apple Pay를 사용하는 결제는 별도의 서비스로 구현되지만 이미 결제를 구현한 기존 서비스는 어떻게 되는지 고민이 필요하다.

Paypal과 Apple Pay를 결제 방식에 따라 단일 책임을 정의했기에 기존 서비스는 위에서 결정된 접근 방식과 맞지 않게 된다.

결과적으로 기존 서비스를 새로운 서비스로 분할이 되어야 한다.

Monolithic 어플리케이션에서는 위의 예시가 비교적으로 간단한 코드 리팩토링에 해당되지만, 마이크로 서비스 아키텍처에서는 새로운 서비스를 옮겨져야 한다. (새로운 코드 저장소, 새로운 빌드 파이프라인, 환경 구성 등)

마이크로 서비스 이름 짓기

서비스의 범위가 변경되면 이름도 변경되어야 한다. 위의 예에서 원래 서비스의 이름이 “Payment”라고 가정하면, 결제 방식이 새롭게 정의되었기에 기존 이름은 더 이상 사용하기가 애매해진다.

“Paypal” 및 “Apple Pay”라는 서비스가 있는 공간에서 기존 이름인 “Payment”는 무엇을 의미하는지 고민을 하게 된다.

마이크로 서비스 경계에 대한 접근법

끊임없이 변화하는 환경에서 불가피하게 일부 서비스의 책임은 시간이 지남에 따라 재정의 될 필요가 있다. 마이크로 서비스의 크기에 대한 규칙으로 재정의 하긴 애매하다. 최적의 솔루션이 없다면 경험에 기반한 접근 방식이 최선의 방법일 수 있다.

  1. 서비스의 일부가 자주 변경되면 서비스를 두 가지로 나눌 수 있다는 신호
  2. 특정 테스트가 필요하거나 테스트 시간이 오래 걸리는 서비스는 다른 서비스를 방해하지 않도록 자체 서비스로 만드는 것이 좋다.
  3. 데이터 베이스 또는 Queue와 같은 외부 리소스에 접근하는 코드도 서비스에 캡슐화 해야 한다. 서비스를 시작한 다음 외부와 전혀 관련이 없는 일부 기능을 사용하기 위해 외부 리소스를 사용하여 로컬 환경을 구성하지 않아도 되기 때문이다.

하나의 사례를 보자.

Eurostar라는 회사는 25년 동안 런던과 다른 유럽간 열차 티겟을 판매해오는 회사이다. 약 3년전에 그들은 Monolithic을 마이크로 서비스 아키텍처로 변경하기 위해 “검색”, “체크아웃”과 같은 새로운 서비스를 개발했다.

새로운 마이크로 서비스가 개발된 직 후, Eurostar는 기차표를 판매하는 비즈니스외에 여행사가 되고, 호텔 숙박 및 패키지 상품을 판매하기로 전략적으로 결정을 하게 된다. “호텔 검색”, “호텔 체크아웃”등의 새로운 서비스가 만들어졌다.

기존 기차표를 판매하던 “검색”, “체크아웃”등의 서비스의 이름이 적절하다고 생각되지는 않는다.

결국 비즈니스가 변하는 시기에 서비스를 다시 검토하고 리팩토링을 해야 할 시점이 오게 된다는 것을 인지해야 한다.

즉, 비즈니스가 변경되는 상황에서는 처음 수립한 마이크로 서비스의 경계가 적절하지 않기에, 상황 발생시 기존 서비스에 대해서도 리팩토링을 염두해야 한다는 것이다.

따라서, 마이크로 서비스 경계 설정시 그 당시의 정보를 기준으로 설정하고 이후 비즈니스 상황이 변경되면 리팩토링에 대한 접근법을 지녀야 한다.