분산 데이터 관리의 문제 및 솔루션

이 콘텐츠는 eBook, 컨테이너화된 .NET 애플리케이션을 위한 .NET 마이크로 서비스 아키텍처에서 발췌한 것이며, .NET 문서에서 제공되거나 오프라인 상태에서도 읽을 수 있는 PDF(무료 다운로드 가능)로 제공됩니다.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

과제 #1: 각 마이크로 서비스의 경계를 정의하는 방법

마이크로 서비스 경계를 정의하는 것은 아마도 처음 만나는 도전일 것입니다. 각 마이크로 서비스는 애플리케이션의 일부여야 하며, 각 마이크로 서비스에서 제공하는 모든 이점과 과제를 통해 독립적으로 작동해야 합니다. 하지만 이러한 경계는 어떻게 알아볼 수 있을까요?

먼저 애플리케이션의 논리 도메인 모델 및 관련 데이터에 집중해야 합니다. 동일한 애플리케이션 내에서 분리된 위치의 데이터와 다양한 컨텍스트를 식별해 보세요. 각 컨텍스트에는 다양한 비즈니스 언어(용어)가 있을 수 있습니다. 컨텍스트는 독립적으로 정의하고 관리해야 합니다. 이러한 다양한 컨텍스트에서 사용되는 용어와 엔터티는 비슷하지만, 특정 컨텍스트에서 하나의 비즈니스 개념이 다른 컨텍스트에서 다른 용도로 사용되고 다른 이름을 가질 수도 있다는 것을 알 수 있습니다. 예를 들어 사용자는 ID 또는 멤버 자격 컨텍스트에서 사용자, CRM 컨텍스트에서 고객, 주문 컨텍스트에서 구매자 등등으로 참조할 수 있습니다.

각 컨텍스트마다 다른 도메인이 있는 여러 애플리케이션 컨텍스트 간에 경계를 식별하는 방법은 정확히 각 비즈니스 마이크로 서비스와 해당 도메인 모델 및 데이터에 대한 경계를 식별하는 방법입니다. 항상 이러한 마이크로 서비스 간의 결합을 최소화하려고 합니다. 이 가이드는 나중에 각 마이크로 서비스에 대한 도메인 모델 경계 식별 섹션에서 이러한 식별 및 도메인 모델 디자인에 대해 자세히 설명합니다.

과제 #2: 여러 마이크로 서비스에서 데이터를 검색하는 쿼리를 만드는 방법

두 번째 과제는 원격 클라이언트 앱에서 마이크로 서비스에 대한 번거로운 통신을 피하면서 여러 마이크로 서비스에서 데이터를 검색하는 쿼리를 구현하는 방법입니다. 예를 들어 장바구니, 카탈로그 및 사용자 ID 마이크로 서비스에서 소유한 사용자 정보를 표시해야 하는 모바일 앱의 단일 화면이 될 수 있습니다. 또 다른 예로 여러 마이크로 서비스에 있는 많은 테이블이 포함된 복잡한 보고서가 있습니다. 올바른 해결 방법은 쿼리의 복잡성에 따라 달라집니다. 그러나 어떤 경우이든 시스템 통신의 효율성을 높이려면 정보를 집계하는 방법이 필요합니다. 가장 인기 있는 해결 방법은 다음과 같습니다.

API 게이트웨이. 서로 다른 데이터베이스를 소유한 여러 마이크로 서비스의 간단한 데이터 집계에 권장되는 방법은 API 게이트웨이라고 하는 집계 마이크로 서비스입니다. 그러나 시스템의 문제 지점(choke point)이 될 수 있고 마이크로 서비스의 자율 원칙을 위반할 수 있으므로 이 패턴을 구현하는 데 주의해야 합니다. 이러한 가능성을 줄이기 위해 각각 시스템의 수직적 "조각" 또는 비즈니스 영역에 집중하는 여러 개의 정교한 API 게이트웨이가 있을 수 있습니다. API 게이트웨이 패턴은 나중에 API 게이트웨이 섹션에서 자세히 설명합니다.

GraphQL 페더레이션 마이크로 서비스가 이미 GraphQL을 사용하고 있는 경우 고려해야 할 한 가지 옵션은 GraphQL 페더레이션입니다. 페더레이션을 사용하면 다른 서비스의 "하위 그래프"를 정의하고 이를 독립 실행형 스키마 역할을 하는 집계 "슈퍼 그래프"로 구성할 수 있습니다.

쿼리/읽기 테이블이 포함된 CQRS. 여러 마이크로 서비스에서 데이터를 집계하는 또 다른 해결 방법은 구체화된 뷰 패턴입니다. 이 방법에서는 여러 마이크로 서비스에서 소유한 데이터가 있는 읽기 전용 테이블을 미리 생성합니다(실제 쿼리가 수행되기 전에 비정규화된 데이터 준비). 이 테이블에는 클라이언트 앱의 요구 사항에 적합한 형식이 있습니다.

모바일 앱에 대한 화면과 같은 것을 사용하는 것이 좋습니다. 단일 데이터베이스가 있는 경우 여러 테이블과 관련된 복잡한 조인을 수행하는 SQL 쿼리를 사용하여 해당 화면에 대한 데이터를 함께 가져올 수 있습니다. 그러나 여러 데이터베이스가 있고 다른 마이크로 서비스에서 각 데이터베이스를 소유하는 경우 이러한 데이터베이스를 쿼리하고 SQL 조인을 만들 수 없습니다. 복잡한 쿼리가 문제가 됩니다. CQRS 방법을 사용하여 요구 사항을 해결할 수 있습니다. 즉 쿼리에 대해서만 사용되는 다른 데이터베이스에 비정규화된 테이블을 만들 수 있습니다. 이 테이블은 복잡한 쿼리에 필요한 데이터에 맞게 특별히 설계할 수 있으며, 애플리케이션의 화면에 필요한 필드와 쿼리 테이블의 열 사이에 일대일 관계가 있습니다. 또한 보고 목적으로도 사용될 수 있습니다.

이 방법은 원래의 문제(여러 마이크로 서비스 간에 쿼리하고 조인하는 방법)를 해결할 뿐만 아니라, 애플리케이션에 필요한 데이터가 이미 쿼리 테이블에 있으므로 성능도 복잡한 조인에 비해 상당히 향상됩니다. 물론 쿼리/읽기 테이블이 포함된 CQRS를 사용하는 경우, 추가 개발 작업이 필요하고 최종 일관성을 수용해야 합니다. 그럼에도 불구하고 공동 작업 시나리오(또는 관점에 따라 경쟁 시나리오)에서 성능 및 높은 확장성 요구 사항이 있는 경우, 여러 데이터베이스가 포함된 CQRS를 적용해야 합니다.

중앙 데이터베이스의 "콜드 데이터" 실시간 데이터가 필요하지 않을 수 있는 복잡한 보고서 및 쿼리의 경우, 일반적인 방법은 보고에만 사용되는 대형 데이터베이스에 "핫 데이터"(마이크로 서비스의 트랜잭션 데이터)를 "콜드 데이터"로 내보내는 것입니다. 이 중앙 데이터베이스 시스템은 빅 데이터 기반 시스템(예: Hadoop), 데이터 웨어하우스(예: Azure SQL Data Warehouse 기반) 또는 보고서에만 사용되는 단일 SQL 데이터베이스(크기가 문제가 되지 않는 경우)일 수 있습니다.

이 중앙 집중식 데이터베이스는 실시간 데이터가 필요 없는 쿼리 및 보고서에만 사용됩니다. 원래의 업데이트 및 트랜잭션은 진실의 원본으로 마이크로 서비스 데이터에 있어야 합니다. 데이터를 동기화하는 방법은 이벤트 기반 통신(다음 섹션에서 설명됨)을 사용하거나 다른 데이터베이스 인프라 가져오기/내보내기 도구를 사용하는 것입니다. 이벤트 기반 통신을 사용하는 경우 해당 통합 프로세스는 CQRS 쿼리 테이블에 대해 앞에서 설명한 대로 데이터를 전파하는 방식과 비슷합니다.

그러나 애플리케이션 디자인에서 복잡한 쿼리에 대해 여러 마이크로 서비스의 정보를 지속적으로 집계하는 경우 잘못된 디자인의 증상일 수 있습니다. 즉, 마이크로 서비스는 가능한 한 다른 마이크로 서비스로부터 격리되어야 합니다. (콜드 데이터 중앙 데이터베이스를 항상 사용해야 하는 보고서/분석은 제외됩니다.) 이 문제가 종종 마이크로 서비스를 병합하는 이유가 될 수 있습니다. 각 마이크로 서비스의 진화 및 배포의 자율성과 강력한 종속성, 응집력 및 데이터 집계의 균형을 유지해야 합니다.

과제 #3: 여러 마이크로 서비스 간에 일관성을 유지하는 방법

앞에서 설명한 대로 각 마이크로 서비스에서 소유한 데이터는 해당 마이크로 서비스 전용이며, 마이크로 서비스 API를 통해서만 액세스할 수 있습니다. 따라서 여러 마이크로 서비스 간에 일관성을 유지하면서 엔드투엔드 비즈니스 프로세스를 구현하는 방법을 제시해야 합니다.

이 문제를 분석하기 위해 eShopOnContainers 참조 애플리케이션의 예제를 살펴보겠습니다. 카탈로그 마이크로 서비스는 제품 가격을 포함하여 모든 제품에 대한 정보를 유지 관리합니다. 장바구니 마이크로 서비스는 장바구니에 사용자가 추가하고 있는 제품 항목에 대한 임시 데이터를 관리합니다. 여기에는 장바구니에 추가된 시점의 항목 가격이 포함됩니다. 카탈로그에서 제품 가격이 업데이트되면 해당 가격은 동일한 제품을 보관하는 활성 장바구니에서도 업데이트되어야 하고, 추가로 시스템은 사용자에게 특정 항목의 가격이 장바구니에 추가한 이후 변경되었음을 경고해야 합니다.

이 애플리케이션의 가상 모놀리식 버전에서 제품 테이블의 가격이 변경되면 카탈로그 하위 시스템은 ACID 트랜잭션을 사용하여 장바구니 테이블의 현재 가격을 업데이트할 수 있습니다.

그러나 마이크로 서비스 기반 애플리케이션에서는 제품 및 장바구니 테이블은 각각의 마이크로 서비스가 소유합니다. 그림 4-9와 같이 마이크로 서비스에는 직접 쿼리가 아닌 다른 마이크로 서비스가 소유한 테이블/스토리지를 자체 트랜잭션에 포함해서는 안됩니다.

Diagram showing that microservices database data can't be shared.

그림 4-9 마이크로 서비스는 다른 마이크로 서비스의 테이블에 직접 액세스할 수 없습니다.

장바구니 테이블은 장바구니 마이크로 서비스가 소유하기 때문에 카탈로그 마이크로 서비스는 장바구니 테이블을 직접 업데이트해서는 안됩니다. 장바구니 마이크로 서비스를 업데이트하려면 카탈로그 마이크로 서비스는 통합 이벤트(메시지 및 이벤트 기반 통신)와 같은 비동기 통신을 기반으로 최종 일관성을 사용해야 합니다. 이는 eShopOnContainers 참조 애플리케이션이 마이크로 서비스 전반에 걸쳐 이러한 유형의 일관성을 수행하는 방법입니다.

CAP 정리에서 명시한 대로 가용성과 강력한 ACID 일관성 중에서 선택해야 합니다. 대부분의 마이크로 서비스 기반 시나리오에서는 강력한 일관성과 달리 가용성과 높은 확장성을 요구합니다. 중요 업무용 애플리케이션은 계속 유지되고 실행되어야 하고, 개발자는 약한 일관성 또는 최종 일관성 작업을 위한 기술을 사용하여 강력한 일관성을 해결할 수 있습니다. 이는 대부분의 마이크로 서비스 기반 아키텍처에서 사용하는 방법입니다.

더욱이 ACID 방식 또는 2단계 커밋 트랜잭션은 마이크로 서비스 원칙에 위반되는 것이 아닙니다. 대부분의 NoSQL 데이터베이스(예: Azure Cosmos DB, MongoDB 등)는 분산 데이터베이스 시나리오에서 일반적인 2단계 커밋 트랜잭션을 지원하지 않습니다. 그러나 서비스와 데이터베이스 간의 데이터 일관성은 반드시 유지해야 합니다. 또한 이 과제는 특정 데이터가 중복될 필요가 있을 때(예: 카탈로그 마이크로 서비스 및 장바구니 마이크로 서비스에 제품의 이름 또는 설명이 있어야 할 때), 여러 마이크로 서비스에 변경 내용을 전파하는 방법에 대한 질문과 관련이 있습니다.

이 문제에 적합한 해결 방법은 이벤트 기반 통신과 게시 및 구독 시스템을 통해 연결된 마이크로 서비스 간에 최종 일관성을 사용하는 것입니다. 이러한 항목은 이 가이드의 뒷부분에 나오는 비동기 이벤트 기반 통신 섹션에서 설명합니다.

과제 #4: 마이크로 서비스 경계 간에 통신을 설계하는 방법

마이크로 서비스 경계 간의 통신은 실제적인 과제입니다. 이 컨텍스트에서 통신은 사용해야 하는 프로토콜(HTTP 및 REST, AMQP, 메시징 등)을 참조하지 않습니다. 대신, 사용해야 하는 통신 방식, 특히 마이크로 서비스가 결합되어야 하는 방식을 해결해야 합니다. 오류가 발생하는 경우 결합 수준에 따라 시스템에 미치는 오류의 영향은 상당히 다를 수 있습니다.

마이크로 서비스 기반 애플리케이션과 같은 분산 시스템에서는 여러 서버 또는 호스트에서 분산 서비스를 통해 많은 아티팩트가 이동하므로 결국에는 구성 요소가 실패합니다. 부분적인 실패와 더 큰 중단이 발생할 수 있으므로 이 유형의 분산 시스템에서 일반적인 위험을 고려하여 마이크로 서비스와 이들 간의 통신을 설계해야 합니다.

인기 있는 방법은 단순성으로 인해 HTTP(REST) 기반 마이크로 서비스를 구현하는 것입니다. HTTP 기반 방법은 완벽하게 허용됩니다. 여기서의 문제는 이를 사용하는 방법과 관련이 있습니다. HTTP 요청과 응답을 사용하여 클라이언트 애플리케이션 또는 API 게이트웨이에서 마이크로 서비스와 상호 작용하는 경우에는 문제가 없습니다. 그러나 마이크로 서비스 전체에서 동기식 HTTP 호출의 긴 체인을 만들고 마이크로 서비스가 모놀리식 애플리케이션의 개체인 것처럼 경계 간에 통신하는 경우 결국에는 애플리케이션에 문제가 발생합니다.

예를 들어 클라이언트 애플리케이션이 주문 마이크로 서비스와 같은 개별 마이크로 서비스에 대한 HTTP API 호출을 수행한다고 가정합니다. 이에 따라 주문 마이크로 서비스에서 동일한 요청/응답 주기 내에서 HTTP를 사용하여 마이크로 서비스를 추가로 호출하면 HTTP 호출 체인이 만들어집니다. 처음에는 합리적일 수도 있습니다. 그러나 다음과 같은 상황에 있을 때 고려해야 할 중요한 사항이 있습니다.

  • 차단 및 성능 저하. HTTP의 동기식 특성으로 인해 모든 내부 HTTP 호출이 완료될 때까지 원래 요청에서 응답을 받지 못합니다. 이러한 호출 횟수가 크게 증가하는 동시에 마이크로 서비스에 대한 중간 HTTP 호출 중 하나가 차단되는 경우가 있다고 가정합니다. 그 결과 성능에 영향을 미치고, 추가 HTTP 요청이 증가함에 따라 전반적인 확장성이 기하급수적으로 영향을 받습니다.

  • 마이크로 서비스와 HTTP의 결합. 비즈니스 마이크로 서비스는 다른 비즈니스 마이크로 서비스와 결합하면 안됩니다. 이상적으로 다른 마이크로 서비스의 존재에 대해 "인식하지" 못합니다. 예를 들어 애플리케이션에서 마이크로 서비스의 결합을 사용하는 경우 마이크로 서비스별 자율성을 달성하는 것은 거의 불가능합니다.

  • 어느 한 마이크로 서비스에서 발생한 오류. HTTP 호출로 연결되는 마이크로 서비스 체인을 구현한 경우, 마이크로 서비스 중 하나에 오류가 발생하면(결국에는 전체 마이크로 서비스가 실패함) 마이크로 서비스 체인 전체에 오류가 발생합니다. 마이크로 서비스 기반 시스템은 부분적인 오류 발생 시에도 가능한 한 계속 작동하도록 설계되어야 합니다. 지수 백오프 또는 회로 차단기 메커니즘을 통해 다시 시도를 사용하는 클라이언트 논리를 구현하는 경우에도 HTTP 호출 체인이 복잡할수록 HTTP 기반 오류 전략을 구현하는 것이 더 복잡해집니다.

실제로 내부 마이크로 서비스에서 설명한 대로 HTTP 요청 체인을 만들어 통신하는 경우, 모놀리식 애플리케이션이 있지만 내부 처리 통신 메커니즘이 아닌 프로세스 간 HTTP를 기반으로 한다고 주장할 수 있습니다.

따라서 마이크로 서비스 자율성을 적용하고 복원력을 높이려면 마이크로 서비스 간에 요청/응답 통신 체인을 최소한으로 사용해야 합니다. 비동기 메시지 및 이벤트 기반 통신을 사용하거나 원래의 HTTP 요청/응답 주기와 별도로(비동기) HTTP 폴링을 사용하여 마이크로 서비스 간 통신에 비동기 상호 작용만 사용하는 것이 좋습니다.

비동기 통신 사용에 대한 자세한 내용은 이 가이드의 뒷부분에 나오는 비동기식 마이크로 서비스 통합에서 마이크로 서비스 자율성 적용비동기 메시지 기반 통신 섹션에서 설명합니다.

추가 리소스