본문 바로가기
IT 초보코딩의 세계/취미 코딩

[MicroService] MSA의 이해(애플리케이션 패턴, CQRS 패턴, API 조합과 CQRS, 이벤트 소싱 패턴 ) 5장

by 조이럭키7 2023. 4. 21.
반응형

앞어서 4장에 애플리케이션 패턴을 보지 못하였다면 4장을 읽고 오도록 하자

https://joylucky7.tistory.com/53

 

[MicroService] MSA의 이해(Micro Service 운영과 관리를 위한 플랫폼 패턴, 애플리케이션 패턴) 4장

◆ MSA 구성 요소 및 MSA 패턴 ▶ Micro Service 운영과 관리를 위한 플랫폼 패턴 ● MSA 기술 변화 흐름 ⊙ 앞에서 언급한 패턴들은 모두 모노리스 시스템이 여러 조각의 Micro Service로 나눠져서 발생하

joylucky7.tistory.com


 MSA 구성 요소 및 MSA 패턴

애플리케이션 패턴

     ● 데이터 일관성에 대한 생각의 전환: 결과적 일관성

          ⊙ 모든 애플리케이션에는 비즈니스 처리를 위한 규칙이 있고 이러한 비즈니스 규칙을 만족하도록 데이터 일관성이 유지돼야 하는데 이전까지는 이 같은 데이터 일관성이 실시간으로 반드시 맞아야 한다는 생각이 일반적

          ⊙ 과연 모든 비즈니스 규칙들이 실시간으로 일관성을 맞춰야 할까 - 정말 그래야 하는가는 생각해 볼 문제

          ⊙ 쇼핑몰에서 주문을 하면 결제 처리가 돼야 하고 그 결제가 완료되면 결제 내용과 주문 처리 내역이 주문자에게 이메일로 전송돼야 하는데 일반적으로 주문된 다음에 결제가 되고 결제 내용이 이메일로 통보되는 것이 순차적인 일 처리 순서

          ⊙ 주문 지연 상황 발생

            → 주문자가 폭주한 경우를 생각해 보면 주문, 결제, 이메일 처리가 이러한 순차적인 동시 일관성을 추구하는 경우 주문이 폭주하면 결제 처리가 되지 않거나 지연되는 경우가 발생할 수 있는데 그럼 앞선 주문 처리 역시 지연될 것

            → 북미의 블랙 프라이데이(Black Friday) 같은 대규모 쇼핑 이벤트를 생각해 보면 수만 개의 주문이 발생하는데 결제 서비스에서 타사 외부 연동 장애가 발생해 더는 주문을 받을 수 없는 상황이 발생할 수도 있는데 이러한 상황을 고려했을 때 비즈니스 관점에서 보면 주문과 결제, 이메일 전송을 순차적으로 처리하기보다 무조건 먼저 주문을 많이 받아 놓는 것이 좋을 수 있음

            → 모든 비즈니스 처리가 반드시 실시간성을 요구하는 것은 아닌데 어떤 비즈니스는 데이터의 일관성이 실시간으로 맞지 않더라도 어느 일정 시점이 됐을 때 일관성을 만족해도 되는 것이 있는데 이러한 개념을 결과적 일관성(eventual consistency)이라고 함

            → 결과적 일관성의 개념은 고가용성을 극대화

            → 실시간성을 강조해서 결제 서비스에서 발생한 장애가 다른 서비스의 가용성을 떨어뜨린 사례로 결제 서비스의 장애로 주문 서비스마저 사용할 수가 없는 것

          ⊙ 사가 패턴과 이벤트 메시지 기반 비동기 통신을 적용

               → Micro Service의 트랜잭션은 독립적이고 각 트랜잭션이 성공했을 때 상태 변경 이벤트를 발행해 이 이벤트를 구독한 다른 서비스의 로컬 트랜잭션이 작동하도록 변경

               → 변경된 아키텍쳐

                   ∵ 가주문이 생성되고 가주문됨 이벤트를 발행하는데 주문은 독립적 로컬 트랜잭션이기 때문에 끊임없이 받을 수 있는데 주문이 몰릴 경우 주문 서비스만 확장해서 가용성을 높일 수 있음

                   ∵ 가주문됨 이벤트는 메시지 브로커에 비동기로 전송

                   ∵ 결제 서비스는 발행된 가주문됨 이벤트를 확인하고 대금 결제 트랜잭션을 수행하고 결제 처리됨 이벤트를 발행

                   ∵ 이메일 서비스는 결제 처리됨 이벤트를 확인하고 주문 결제 완료 이메일을 사용자에게 발송

                   ∵ 주문 서비스는 결제 처리됨 이벤트를 확인하고 가주문으로 처리됐던 주문을 최종 승인하고 최종 주문 완료됨 이벤트를 발행

                   ∵ 이메일 서비스는 주문 서비스가 발행한 최종 주문 완료됨 이벤트를 확인해 최종적으로 주문이 완료됐다 는 이메일을 사용자에게 발송

                   ∵ 서비스는 각기 작업을 수행하다 오류가 발생하면 실패 이벤트를 발행해 다른 서비스가 비즈니스 정합성을 맞출 수 있게 함

                   ∵  별도로 메시지 큐에 쌓이는 이벤트들을 모니터링 서비스와 연계해 모니터링하고 추적해서 전체적인 비즈니스 정합성 여부를 관리자가 확인할 수도 있음

               →  이벤트 기반 아키텍처와 메시지 브로커, 사가 패턴으로 비즈니스 정합성을 결과적으로 보장할 수 있고 비즈니스 및 시스템 가용성을 극대화 할 수 있음

 

     ● 읽기 와 쓰기 분리: CQRS 패턴

          ⊙ 비즈니스에 대한 기존 관념을 조금만 바꾸면 가용성을 높일 수 있는 방법이 다양

          ⊙ Micro Service의 핵심 설계 철학인 서비스 별 데이터 저장소 패러다임을 채택하면 서비스의 성능 향상을 위해 서비스 인스턴스를 스케일 아웃해서 여러 개로 실 행한 경우 데이터 읽기/수정 작업으로 인한 리소스 교착상태가 발생할 수 있음

          ⊙ 이 문제를 해결하기 위한 방법으로 CQRS 패턴이 있는데 CQRS(Command Query Responsibility Segregation) 명령 조회 책임 분리를 의미

          ⊙ CQRS는 기존의 일반적이었던 동일한 저장소에 데이터를 넣고 입력, 조회, 수정, 삭제를 모두 처리하는 방식에 도전하는 흥미로운 패러다임

          ⊙ 일반적으로 사용자의 비즈니스 요청은 크게 시스템 상태를 변경하는 명령과 시스템의 상태를 조회하는 부분으로 나눌 수 있는데 일반적인 비즈니스 모델에서는 입력, 수정, 삭제가 조회보다 적게 쓰이고 조회 요청이 훨씬 많이 사용되는데 서비스 내에 이러한 모든 기능을 넣어 두면 조회 요청 빈도가 증가함에 따라 다른 명령 기능도 함께 확장해야 하므로 효율적이지 않음

          ⊙ 하나의 저장소에 쓰기 모델과 읽기 모델을 분리하는 방식으로 변화시켜 쓰기 서비스와 조회 서비스를 분리할 수도 있고 더 나아가 아예 물리적으로 쓰기 트랜잭션 용 저장소와 조회 용 저장소를 따로 준비할 수 있음

          ⊙ 쓰기 전략과 조회 전략을 각각 분리 하면 쓰기 시스템의 부하를 줄이고 조회 대기 시간을 줄이는 등 엄청난 이점을 누릴 수 있음

          ⊙ 쓰기 와 읽기를 분리하는 과정

          ⊙ CQRS 방식을 이벤트 메시지 주도 아키텍처와 연계

               → 명령 측면 Micro Service는 입력, 수정, 삭제(Create, Update, Delete) 처리를 수행하고 저장소로는 쓰기에 최적화된 관계형 데이터베이스를 사용하고 프로그래밍 언어도 업무 규칙을 표현하기 좋은 자바 언어를 사용

               → 조회 측면의 Micro Service에서는 조회 성능이 높은 몽고디비나 엘라스틱서치 같은 NoSQL 데이터베이스를 저장소로 사용하고 프로그래밍 언어도 조회를 간단하게 구현할 수 있는 스크립트 기반의 Node.js 사용

               → 조회 서비스는 사용량이 많기 때문에 스케일 아웃해서 인스턴스를 증가시켜 놓음

               → 이러한 구조에서는 명령 서비스가 사용됨에 따라 조회 서비스 와의 데이터 일관성이 깨지게 되는데 이때 데이터 일관성 유지를 위해 필요한 것이 이벤트 주도 아키텍처

               → 명령 서비스는 저장소에 데이터를 쓰면서 저장한 내역이 담긴 이벤트를 발생시켜 메시지 브로커에 전달하고 조회 서비스는 메시지 브로커의 이벤트를 구독하고 있다가 이벤트 데이터를 가져와 데이터를 최신 상태로 동기화

               → 명령 서비스에 데이터가 들어간 즉시 조회 서비스의 데이터와 일치할 수 없고 시간적 간격이 있을 수 있지만 어느 시점이 되면 결과적으로 일치하게 되는데 이는 앞에서 언급한 결과적 일관성을 추구하는 것

 

     ●  API 조합과 CQRS

          ⊙ Micro Service의 저장소가 격리돼 있고 각 Micro Service마다 각기 다른 기능을 구현했을 때 여러 개의 Micro Service 연계해서 서비스로 제공하는 경우에 사용할 수 있는 첫 번째 방법은 API 조합(composition)

               → 주문 이력 서비스는 제품 서비스가 제공하는 제품 정보, 주문 서비스의 주문 정보, 고객 서비스의 특정 고객 정보. 배송 서비스의 배송 정보가 모두 다 필요하기 때문에 각 기능을 제공하는 Micro Service 조합하는 상위 Micro Service 만들어 조합된 기능을 제공할 수 있음

               → 하위 서비스는 각자 독립적인 API 제공하면서 연계 API 위해 상위 서비스에 정보를 제공

               → 그렇지만 이러한 구조는 상위 서비스가 하위 서비스에 의존하는 결과를 가져오는데 상위 서비스가 제공하는 API에 정보를 제공하는 하위 서비스 중 하나라도 API 변경하면 상위 서비스는 그에 따라 변경될 수밖에 없고 하위 서비스의 실패가 상위 서비스에 영향을 주는데 이러한 의존성을 줄이기 위한 방법이 필요

               → Micro Service의 저장소가 격리돼 있고 각 Micro Service마다 각기 다른 기능을 구현했을 때 여러 개의 Micro Service 연계해서 서비스로 제공하는 경우에 사용할 수 있는 첫 번째 방법은 API 조합(composition)

          ⊙ 두 번째 방법은 CQRS 적용

               → 주문 이력 서비스를 제공하는 Micro Service가 독자적인 저장소를 갖도록 만들고 주문 이력의 세세한 원천 정보를 보유하고 있는 각각의 서비스도 독자적으로 자신의 저장소를 가지고 서비스를 제공하고 있고 이 원천 정보를 보유한 여러 Micro Service는 자신의 서비스의 정보가 변경되는 시점에 변경 내역을 각자의 변경 이벤트로 발행

               → 그럼 주문 이력 Micro Service에서는 이 이벤트를 구독하고 있다가 이벤트를 가져와서 자신의 서비스의 저장소에 기록함으로써 다른 서비스의 데이터와 데이터 일관성을 맞추고 서비스의 주문 이력 조회 기능으로 제공

               → 이런 경우에는 다른 원천 서비스가 순간적인 장애가 생긴다고 해도 주문 이력 서비스가 영향을 받지 않는데 조회용 Micro Service 별도로 생성하고 다른 서비스로부터 비동기 이벤트로 일관성을 맞춤으로써 API 조합 방식의 단점인 직접적인 의존성을 줄일 수 있는 것

 

     ● 쓰기 최적화: 이벤트 소싱 패턴

          ⊙ 사가 패턴 및 CQRS 패턴에서 비즈니스 불일치를 피하기 위해서는 저장소에 저장하는 것과 메시지를 보내는 것이 원자성을 지녀야 하는데 저장소에 저장하는 일과 메시지를 보내는 작업이 언제나 완전하게 진행되어 함께 실행돼야 하지만 객체의 상태 변화를 이벤트 메시지로 발행하고 또 객체 상태를 관계형 데이터베이스에 저장하는 경우 SQL 질의어로 변환해서 처리하기가 매우 번거롭고 까다롭고 메시지 발행과 저장 처리라는 두 가지 기능을 수행하므로 빠르지도 않음

          ⊙ 메시지 발행 및 저장 처리의 원자성을 보장하고 성능도 최적화하는 방법은 비즈니스 처리를 수행할 때 데이터 처리는 항상 처리 상태의 결괏값을 계산하고 데이터의 최종 상태를 확정해서 저장하는 방식으로 진행

          ⊙ 쇼핑몰의 장바구니에 품목을 추가, 삭제하는 과정을 보면 만약 일반적인 관계형 데이터베이스와 자바 언어를 사용한다면 장바구니, 품목 데이터 모델이 정의돼 있어야 하고 장바구니 상태를 변경할 때 매번 트랜잭션 결과를 반영해서 장바구니 데이터 모델의 결과를 계산해야 함

          ⊙ 객체의 상태 변화와 데이 터 모델의 결과의 변화 양상

               → 사용자 A의 장바구니 객체 생성 -> 장바구니 테이블에 사용자 A의 장바구니 로우(row) 생성

               → 품목 1 객체 추가 -> 품목 테이블에 사용자 A 장바구니의 품목 1을 추가

               → 품목 2 객체 추가 -> 품목 테이블에 사용자 A 장바구니의 품목 2 추가

               → 품목 1 객체 제거 -> 품목 테이블에 사용자 A 장바구니의 품목 1을 삭제

               → 품목 2 객체의 수량 변경 -> 품목 테이블에 사용자 A 장바구니의 품목 2의 수량 정보를 수정

          ⊙ 이처럼 객체의 상태 변경에 따라 데이터 모델로 처리되고 최종값이 반영돼야 하고 이 같은 과정은 복잡해지고 변환 처리로 느릴 수밖에 없는데 특히 인스턴스가 여러 개로 확장될 때 동시 업데이트 및 교착 상태로부터 안전하지 못할 수도 있음

          ⊙ 객체 상태를 데이터 모델에 맞춰 계산하지 않고 상태 트랜잭션 자체를 저장하는 방법을 사용할 수 있는데 이를 이벤트 소싱(event sourcing) 기법이라고 하며 트랜잭션 자체를 저장하자는 전략으로 상태 변경 이벤트를 계산해서 데이터 모델로 변경하지 않고 바로 이벤트 저장소에 그대로 저장

          ⊙ 이렇게 하면 메시지 브로커와 데이터 저장소를 분리하지 않고 하나로 사용할 수 있으며 복잡한 과정이 없으니 쓰기 속도가 훨씬 빠름

          ⊙ 현재 시점의 상태가 필요할 때는 상태의 출발점부터 모든 기록된 상태 변경 트랜잭션을 순차적으로 계산

          ⊙ 처음부터 모든 트랜잭션을 처리하는 것이 부담된다면 매일 자정에 상태를 계산한 후 스냅숏으로 저장한 후 현재 상태 정보가 필요해지면 스냅숏 이후의 트랜잭션만 처리하면 되는데 이 같은 방식은 특정 시점의 상태가 필요하면 재현할 수 도 있기 때문에 별도의 트랜잭션성 이력 로그 데이터를 기록할 필요도 없음

          ⊙ 또 한 가지 중요한 점은 명령 측면과 조회 측면의 서비스가 이벤트 저장소에 대한 CRUD 모두 처리할 필요 없이 입력/조회(CR) 만 처리하면 된다는 것

          ⊙ 저장소에서 변경과 삭제가 발생하지 않기 때문에 명령 측면의 서비스를 여러 개 확장해도 동시 업데이트 및 교착상태가 발생하지 않음

          ⊙ 이벤트 저장소의 데이터 형태

               → 이벤트 아이디가 있고 이벤트 타입으로 어떠한 상태인지 엔티티 타입으로 어떠한 객체의 이벤트인지 그리고 변경 내용이 엔티티 데이터 항목에 JSON 형태로 그대로 저장

               → JSON은 객체 형태라서 상태 객체가 그대로 들어간 것과 같음

               → 이벤트는 한번 발생한 후에 수정되지 않고 업데이트나 삭제 없이 입력만 되는 개념이라 동시성이나 정합성 등의 문제에 비교적 자유로움

               → 이벤트 소싱은 모든 트랜잭션의 상태를 바로바로 계산하지 않고 별도의 이벤트 스트림으로 이벤트 스트림 저장소에 저장하는 방식으로 이벤트 스트림 저장소는 오로지 추가만 가능하게끔 해서 계속 이벤트들이 쌓이게 만들고 실제로 내가 필요한 데이터를 구체화하는 시점에서는 그때까지 축적된 트랜잭션을 바탕으로 상태를 계산해서 구성

               → 이벤트 저장소는 이벤트 데이터베이스의 역할뿐 아니라 메시지 브로커처럼 작동하는데. 이는 데이터 저장 처리 메커니즘과 메시지 큐 같은 이벤트를 전달하기 위한 메커니즘을 통합해서 복잡성을 줄이고 특히 쓰기 성능을 최적화하고 상태를 저장하기 때문에 정확한 감사 로깅을 제공하고 객체의 예전 상태를 재구성하는 것이 간단해지며 외부 애플리케이션에 이벤트를 전달하는 것도 저장한 이벤트를 그대로 전송하면 되기 때문에 간편

          ⊙ 이벤트 소싱 개념도

          ⊙ 이벤트 스토밍CQRS 구현을 지원하는 프레임워크로 자바 진영에서는 네덜란드의 Axon Framework Eventuate 가 주로 사용

반응형

댓글