MSA이론3. Database Design for Microservices

Database Design for Microservices

트랜잭션, Aggregates에 영향을 많이끼친다.

Front Microservice는 쉽다.

ACID가 적용되는 것들

Transaction, Aggregates, Entity

ACID vs BASE

ACID

  • Atomicity, Consistency, Isolation, and Durability
  • Strong consistency
  • commit에 초점을 맞춘다.
  • 보수적이다.(혁신이 어렵다)

BASE

  • Basically Available, Soft-state, Eventual consistency
  • Weak consistency
  • Availability first
  • 간단하고 빠르다(혁신이 쉽다)

구현을 위해 생각해보자

  • 마이크로서비스에서 데이터는 dependency를 갖기 때문에 어려운부분이다.
  • microservice에 각각의 db가 있을 때 어떻게 읽고 어떻게 업데이트할까? join활용
  • data inside, 서비스, data outside 를 이해할 필요가있다.
  • 데이터를 어떻게 읽고 업데이트하는가?
  • 읽고 쓰는 명령 쿼리를 분할한다.(CQRS, Command Query Responsibility Segregation)

Two-phase commit

모든 트랜잭션은 트랜잭션 관리자를 거쳐야 한다. 이는 모든 서비스에서 트랜잭션을 보장하며 커밋 요청, 커밋 두단계의 메커니즘을 거친다.

커밋요청단계(커밋 투표단계라고도 함) : 관리자가 모든 서비스에 커밋메세지 쿼리를 보낸다. 관리자는 모든 서비스의 투표 결과를 대기하고 각 서버는 커밋해야 할 지점까지 트랜잭션을 수행한다. 서비스는 일부가 준비되지 않아도 동의한다는 응답을 보낸다.

커밋단계(완료단계라고도 함)

  • 관리자가 모든 서비스에서 동의를 받았을 때: 관리자는 트랜잭션 커밋을 요청하는 메세지를 모든 서비스에게 전송한다. 커밋 후에 서비스는 트랜잭션이 끝났다는 확인을 관리자에게 보낸다.
  • 관리자가 하나 이상의 거절을 받았을 때 : 관리자는 모든 서비스들에게 롤백을 요청하고 서비스는 롤백 후 관리자에게 승인응답을 보낸다.

Two-phase commit 의 장점

많은 DB가 영향을 받을경우 이를 하나의 트랜잭션으로 다루는것이 좋다.
준비단계는 모든 구성요소가 커밋을 허용하는지 확인하는 단계이다.

Two-phase commit 의 단점

  • 관리자는 서비스에서 메세지를 수신할 때 까지 잠금이 걸려 리소스가 해제되지 않음.(가용성 측면)
  • 서비스 중 하나가 커밋을 거부하면 모든 서비스가 롤백한다.(일관성을 유지할 수 있다는 장점)
  • 서비스에서 응답을 수신할 때 까지 잠금,리소스가 해제되지 않음
  • NoSQL을 지원하지 않음.
  • 최소 O(4n) 메세지 발생, 여러번시도할 경우 O(n^2)
  • lock으로 인한 처리량 감소

Distributed Transactions in Microservices

microservice에는 ACID를 적용하기가 복잡하다. 이는 microservice Architecture가 Single Responsibility Principle (SRP)를 따르므로 각 microservice가 해당 데이터를 유지하고 관리해야하고, microservice의 책임에 대한 지식없이는 다른서비의 데이터에 접근할 수 없기때문이다.

위 그림에서 두개의 microservice가 하나의 db에 연결되어있는게 왜 잘못되었을까? 우측의 경우에는 API, Queue로 해당 호출이 가능하다. 하지만 좌측의 경우에는 두개의 db 아답터가 가 존재해야 하는데 Isolation 원칙을 위배한다.

Distributed Transaction – Event Driven Architecture & Two Phase Commit

  • 이는 가장 일관된 시스템을 제공하지만 Two Phase Commit으로 인해 시스템 성능에 부정적 영향을 끼친다.
  • 이벤트는 트랜잭션의 매 단계를 실행시키는 트리거이며 각 이벤트는 고유한 ID를 갖는다. 또한 작업이 처리되었으면 커밋되었음을 나타내야 한다.

위의 그림은 microservice1이 microservice2에 자동이체를 하는 트랜잭션이다.

  • completed라는 컬럼은 트랜잭션이 커밋되어야 하는지를 나타내며 microservice1이 완료되면 microservice2를 호출하여 트랜잭션을 완료한다.(그림은 microservice1의 작업이 커밋되고 microservice2의 작업이 커밋되기 전 단계)

CAP Theorem

분산시스템에서는 일관성(Consistency), 가용성(Availability,항상 데이터를 읽고 쓸 수 있음), 분할 용인(Partition tolerance,데이터베이스를 분할할 수 있고 네트워크 중단에도 계속 동작할 수 있다.)이라는 세 가지 조건을 모두 만족할 수 없다. 대부분 세 조건중 두 가지만 만족시킬 수 있다.
만약 가용성을 만족시키려면 BASE(Basically available, soft state, eventually consistent)를 생각해 볼 수 있다. 이를 위해 앞서 설명한 Event Driven Architecture를 도입할 수 있다.
BASE : 기본적으로 Availability하고, 사용자가 관리하지 않으면 데이터가 expire 될 수 있으며, 언젠가는 데이터가 일관성을 가진다는 것.

Eventual Consistency and Compensation

  • microservice에서 일관성을 다루는 것중 가장 실현 가능한것은 Eventual Consistency이다.
  • microservice에서 분산 ACID 트랜잭션을 사용하지 않는다. 대신 미래의 어떤시점에서는 시스템이 결국 일관성을 유지할것을 제안한다.
  • Eventual Consistency 서비스는 종종 BASE를 제공하는것으로 분류된다.
  • Eventual Consistency는 분산 소프트웨어의 복잡성을 증가시킨다고 비판받기도 하는데 이는 동일값을 읽는다는 안전보장을 하지 않기 때문에 발생한다(결국에는 읽었을 때 동일값을 반환 함 예시로 배치프로그램이 있음).

Eventual Consistency의 예시

다음의 문제를 해결해야 된다고 가정해보자.

  • user profile 등록
  • 백그라운드에서는 사용자가 시스템에 접근할 수 있는지 자동으로 확인

Eventual Consistenc 적용을 하려면..

  • user의 접근 가능유무는 반드시 필요하다.
  • user검증 시간이 오래 걸리더라도 microservice로 분할해야한다.

compensation을 포함한 message-driven방법 적용

  • user profile을 등록하는 작업
  • 백그라운드에서 유효성을 검사하는 microservice
  • 지속적인 큐를 제공하는 메세지 플랫폼
    (메세지 플랫폼은 microservice가 보낸 메세지가 지속가능토록 한다. 수신부가 문제가 있다면 나중에 배달해주기도 함)

-이는 하나의 microservice가 중지될 경우 정보는 다른 큐, 서비스에 존재한다. 즉 지속성을 보장한다.

위의 방법을 적용했을 때 좋은 시나리오

  • user microservice는 user를 등록하고 로컬 db에 정보를 저장한다.
  • user microservice는 플래그를 활용해 해당 user가 검증 과정을 거치지 않음을 표시한다.
  • user에게 지금 당장은 접근 불가능하다는 경고를 보낸다.
  • user microservice는 백그라운드에서 user검증을 위해 validation microservice에 메세지를 보낸다.
  • 검증결과 접근가능하다면 user microservice는 user를 unblock한다/ 검증결과 접근 불가능하다면 user microservice는 user 계정을 제거한다. (이를 compensation(보상)단계라고 볼 수 있다.)

일련의 과정을 거친후에는 시스템은 일관된 상태이다. 하지만 일정기간동안은 사용자 엔티티가 불완전한 상태였다.

위의 방법을 적용했을 때 좋지 않은 시나리오

  • validation microservice에 접근할수 없는경우 메세지 플랫폼은 나중에 validation microservice가 접근 가능하게 되었을 때 validation microservice는 메세지를 받아볼 수 있다.
  • 메세지 플랫폼에 문제가 발생하면 user microservice는 또 다시 다른 user의 검증 메세지를 보낸다.
  • validation microservice가 메세지를 받게되면 user를 검증하지만 메세지 플랫폼 문제가 발생하면 응답을 보내지 못하고 추 후 다시 보낸다.

그러나 일부메세지가 여러번 발행되더라도 데이터 일관성에는 영향을 미치지 않는다.
발생할 수 있는 좋지 않은 시나리오들을 고려함으로써 Eventual Consistency를 충족시킬 수 있으며 비용소모가 심한 distributed transactions를 처리할 필요가 없다.

Distributed Transactions: The Solution

결제하고 배송하는 시스템을 Distributed Transaction방식으로 구현한다고 해보자

위와 같은 방식으로 할 수 있으나 일련의 과정은 동기방식이다.
이를 비동기 방식으로 처리하기 위해 아래와 같은 방식을 따르면 된다. 즉 쓰레드를 하나 만들어서 처리한다.

안전한 Distributed Transactions를 위해서

  • 원격 업데이트에 대한 영향 감수
  • 원격 업데이트를 시도하고 응답을 받지 못했을 경우 재시도를 해야한다. 또한 응답이 오더라도 자체 대기열을 업데이트하지 못할 경우 대기열이 사용가능해지면 다시 시도해서 업데이트 해야한다.
  • 재시도로 발생하는 중복수신은한번만 처리한다
  • 하나의 작업단위를 완료하더라도 시스템 오류가 발생하면 동기화 되지 않을 수 있다.
  • 이를통해볼 때 microservice는 Distributed 트랜잭션에 호의적이지 않다.

동기관련 문제를 해결할 수 있는 메세지 브로커

Two Consequences of Eventual Consistency

  • 재시도를 하는것은 시스템에 문제가 생긴것을 의미한다.
  • 일관성이 결국에는 생기는것이기 때문에 비즈니스 일관성이 충돌하는 경우도 있을 수 있다.
    만약 책을 구매하는 비즈니스가 있다고 할 때 결제를 하는순간 재고가 있어 결제가 진행되었는데 결제가 완료되는 순간 재고가 없을 때를 가정해보자. 이 때는 비동기식으로 사용자에게 다시 보고하지 않고 환불절차를 진행 후 직원이 처리 할 내용을 conflicts큐에 넣고 제공할 수 있다.

Saga Pattern : microservice에서 어떻게 비즈니스 트랜잭션을 구현 할것인가.

트랜잭션은 어플리케이션의 필수적인 부분이다. 트랜잭션이 없다면 데이터의 일관성을 유지하는 것은 불가능하다.

가장 강력한 트랜잭션 유형중 하나는 Two-Phase Commit이다. 이는 여러 엔티티를 동시에 업데이트 할 때 유용하다.(ex. 주문확인 및 재고 업데이트)

그러나 microservice로 작업할 경우 데이터베이스가 분리되므로 로컬 Two-Phase Commit를 활용하여 전체 시스템의 일관성을 간단하게 유지할 수 없다. 이 경우 RDBMS와 마찬가지로 단일엔티티 원자 트랜잭션이 가능한 Couchbase와 같은 NoSQL 데이터베이스를 사용하면 수십배 빠르게 처리할 수 있다. 그래서 microservice를 사용하는 대다수 기업들이 NoSQL을 사용하고 있다.

SAGA Pattern

  • 분산 트랜잭션의 잘알려진 패턴중 하나가 SAGA이다.
  • SAGA는 일련의 local 트랙잭션들을 의미하며 각 트랜잭션은 하나의 서비스안에서 데이터를 업데이트 한다. 첫 번째 트랜잭션은 시스템 작업에 해당하는 외부 요청에 의해 시작되고 이후엔 이전단계 완료가 될 때마다 트리거링되어 작동한다.

-SAGA 트랜잭션을 구현하는 인기있는 두 가지 방법이 있다.

1. Events/Choreography

각 서비스는 다른 서비스의 event, decides를 보고 action을 할지 말지 결정하며 non centralize 한것. (발레와 유사함)

첫번째 서비스는 트랜잭션을 실행하고 이벤트를 publish한다. 발행된 이벤트는 하나 혹은 그 이상의 서비스가 지켜보며 해당 이벤트는 로컬 트랜잭션을 실행하고 새로운 이벤트를 publish한다.
분산 트랜잭션은 마지막에 서비스가 로컬 트랜잭션을 실행할 때 종료된되며 마지막 이벤트는 이벤트를 publish하지 않는다.
분산 트랜잭션의 경우 롤백에 대한 로직은 직접 만들어야 한다.

Event/Choreography design의 장단점

  • 이해하기 쉽고 SAGA패턴을 구현하는 자연스러운 방법이다. 구축에 많은 노력이 필요하지 않으며 느슨한 결합을 유지한다. 2~4단계로 구성되는 트랜잭션에 매우 적합하다.
  • 어떤 서비스가 어떤 이벤트를 수신하는지 추적하기 어렵기 때문에 단계를 계속 추가할 경우 혼란스러울 수 있다.

2. Command/Orchestration : coordinator 서비스가 의사결정 및 sequncing 비즈니스 로직에 대한 책임이 있는 것. 즉 centralize 한것. (오케스트라와 유사함)

  • Orchestration 접근법에서는 각 참가자에게 할 일을 알려줄 책임이 있는 새로운 서비스를 정의한다. 각 서비스와 명령/응답 형태로 통신하여 수행해야 할 작업을 알려준다.
  • 위 그림의 경우 Orchestration은 트랜잭션을 실행하는데 필요한 흐름을 알고있다.
  • 만약 트랜잭션이 실패하면 이전 작업을 취소하기 위해 각 참가자에게 롤백 명령을 보내야 한다. 롤백은 Orchestrator을 갖고있으면 훨씬 쉽다.
  • saga orchestrator을 모델링하는 표준 방법은 각 변환이 명령 또는 메세지에 해당하는지에 대한 State Machine이다. State Machine는 구현하기 쉽고 테스트에 적합하기 때문에 잘 정의된 동작을 구조화 하는데 훌륭한 패턴이다.

Event/Choreography design의 장단점

  • saga orchestrator는 saga participants를 호출할 수 있지만 saga participants는 saga orchestrator를 호출할수 없기에 서비스간 cyclic 종속성을 피할수 있다.
  • command에 대해 응답 혹은 실행만 하므로 participants간 복잡성을 줄일 수 있다.
  • 새로운 단계가 추가될 때 트랜잭션의 복잡성은 linear하게 늘어난다.
  • 쉬운 롤백관리 가능
  • 첫번째,두번째 트랜잭션이 동일한 개체를 변경하고자 할 때 orchestrator를 활용하여 첫번째 트랜잭션이 끝날때 까지 두번째 트랜잭션을 보류상태로 둘 수 있다.
  • 하지만 이는 orchestrator에 많은 로직이 들어가 risk가 있다
  • 추가 서비스를 관리해야 하므로 인프라 복잡성이 증가한다.

Saga Pattern Tips

트랜잭션마다 unique Id를 만들어라

  • 이를 통해 추적이 가능하고 participants간 통신을 위한 표준방법을 갖는데 도움을 준다.
    command에 응답 address를 추가해라
  • participant가 고정된 주소에 응답하기 보다는 message에 응답 address를 추가하여 보내는것을 고려해봐라. 이를 통해 participants는 여러 orchestrator에 응답할 수 있다.
  • 큐(SQS, Kafka, RabbitMQ, etc)를 사용할 경우 서비스간 통신할 수 있다.
  • 버그로 인해 원치않은 메세지를 수신함으로써 데이터베이스가 엉망이 될 수 있다.
    동기식 통신을 피하라
  • 이를 통해 더 많은 데이터를 요청할 수 있고 다른 서비스가 오프라인 일 때도 서비스가 로컬 트랜잭션을 실행할 수 있다.
  • orchestrator는 각 요청/응답을 다뤄야하므로 선형적으로 복잡성이 증가한다.
Share