AWS 기술 블로그
AWS 에 구축하는 클라우드 디자인 패턴 시리즈 5부: 데이터 관리
현대적인 소프트웨어 아키텍처에서는 분산 시스템과 마이크로서비스 아키텍처가 많이 사용되면서 데이터의 일관성과 안정성을 보장하는 것이 중요한 과제로 부각되고 있습니다. 특히, 여러 서비스 간의 효과적인 통신과 데이터의 동기화는 시스템의 신뢰성을 높이는 핵심 요소 중 하나입니다. 이러한 도전에 대응하기 위해 트랜잭션 아웃박스 패턴이 등장했으며, 이 패턴은 데이터베이스 업데이트와 이벤트 알림을 조율하여 안정성 있는 분산 시스템을 구축하는데 도움을 주고 있습니다. 이 블로그에서는 트랜잭션 아웃박스 패턴의 핵심 개념과 이를 활용한 안정성 있는 시스템 구축에 대해 살펴보겠습니다.
트랜잭션 아웃박스 패턴
Transactional Outbox Pattern은 분산 시스템에서 데이터 일관성을 보장하기 위해 사용되는 디자인 패턴 중 하나입니다. 이 패턴은 트랜잭션 완료 후에 외부 시스템에 이벤트를 안전하게 전송하고, 실패한 경우에는 재시도할 수 있는 메커니즘을 제공합니다. 마이크로서비스가 데이터베이스에 데이터를 보관하고 다른 시스템에 알리기 위해 메시지를 보내야 할 때와 같이, 서로 다른 두 시스템에 쓰기 작업을 할 때 발생할 수 있는 이중 쓰기 작업문제 를 해결합니다.
마이크로서비스가 데이터베이스 업데이트 후 이벤트 알림을 보낼 때, 데이터 일관성과 시스템 안정성을 보장하기 위해 이 두 작업을 원자적으로 실행해야 합니다. 업데이트에 성공하고 알림에 실패하면 시스템은 일관성 없는 상태로 빠질 수 있으며, 업데이트에 실패하고 알림은 성공하면 데이터 손상이 발생할 수 있습니다.
일반적인 사용 사례
- 마이크로서비스 아키텍처에서의 이벤트 드리븐 아키텍처 : 여러 마이크로서비스 간의 통신이 필요한 경우, 트랜잭션 완료 후에 이벤트를 안전하게 전송할 수 있습니다. 이로써 서비스 간의 결합도를 낮추고, 각 서비스가 독립적으로 확장 가능합니다.
- 데이터 일관성 보장 : 분산 시스템에서 트랜잭션 완료 후에 발생하는 이벤트를 동기적으로 처리하려면 모든 작업이 원자적으로 수행되어야 합니다. Transactional Outbox는 트랜잭션 완료 후에 안전하게 이벤트를 보내는 메커니즘을 제공하여 데이터 일관성을 보장합니다.
- 이벤트 소싱 : 이벤트 소싱 패턴을 사용하는 경우, 데이터 변경을 이벤트로 추적하고자 할 때, 트랜잭션 완료 후에 이벤트를 안전하게 저장소에 기록할 수 있습니다.
트랜잭션 아웃박스 패턴을 적용할 때는 다음 내용들을 고려해야 합니다.
중복 메시지
이벤트 처리 서비스에서 메시지나 이벤트를 중복으로 전송할 수 있으므로, 각 메시지에 고유한 식별자를 할당하여 메시지의 상태를 추적 및 메시지 처리 상태를 기록하고 중복 처리를 방지하기 위한 메시지 추적 메커니즘을 구현하여 사용 중인 서비스가 독립적으로 작동하도록 만드는 것이 좋습니다.
알림 순서
서비스가 데이터베이스를 업데이트하는 순서와 동일한 순서로 메시지 또는 이벤트를 전송합니다. 이는 이벤트 스토어를 사용할 수 있는 이벤트 소싱 패턴에 매우 중요합니다. 알림 순서가 유지되지 않으면 최종 일관성과 데이터베이스 롤백으로 인해 문제가 복잡해질 수 있습니다.
트랜잭션 롤백
트랜잭션이 롤백된 경우 이벤트 알림을 보내지 마십시오. 롤백된 트랜잭션의 경우 시스템의 일관성을 해치지 않도록 메시지를 제어하는 방법이 필요합니다. 롤백 시에는 트랜잭션과 연동된 메시지를 삭제하거나, 해당 메시지를 트랜잭션과 함께 보관하여 롤백에 대응하는 전략을 수립해야 합니다.
서비스 수준 트랜잭션 처리
트랜잭션이 데이터 저장소 업데이트가 필요한 서비스에 걸쳐있는 경우 사가 오케스트레이션 패턴을 활용하여 트랜잭션 경계를 정의하고, 각 서비스에서의 데이터 무결성을 보장해야 합니다.
하이레벨 아키텍처
다음 시퀀스 다이어그램은 이중 쓰기 작업 중에 발생하는 이벤트의 순서를 보여줍니다.
- 항공 서비스는 데이터베이스에 쓰고 결제 서비스에 이벤트 알림을 보냅니다.
- 메시지 브로커는 메시지와 이벤트를 결제 서비스로 전달합니다. 메시지 브로커에 장애가 발생하면 결제 서비스가 업데이트를 받을 수 없습니다.
항공 데이터베이스 업데이트에 실패했지만, 알림이 전송되면 결제 서비스는 이벤트 알림에 따라 결제를 처리합니다. 이로 인해 다운스트림 데이터가 일치하지 않는 문제가 발생할 수 있습니다.
AWS 서비스를 사용한 구현
패턴을 설명하기 위해 아래 시퀀스 다이어그램에서 다음의 AWS 서비스를 사용합니다.
- 마이크로서비스는 AWS Lambda를 사용하여 구현됩니다.
- 기본 데이터베이스는 Amazon Relational Database Service(Amazon RDS)에서 관리됩니다.
- Amazon Simple Queue Service(Amazon SQS)는 이벤트 알림을 수신하는 메시지 브로커 역할로 사용됩니다.
먼저 이중 쓰기 작업에서 발생할 수 있는 이슈를 다이어그램으로 표현하면 다음과 같습니다.
아래 다이어그램처럼 트랜잭션을 완료한 후 항공 서비스가 실패하면 이벤트 알림이 전송되지 않을 수 있습니다.
또한 아래 다이어그램처럼 트랜잭션이 실패하여 롤백될 수 있지만 이벤트 알림이 계속 전송되어 결제 서비스에서 결제를 처리하게 될 수 있습니다.
이 문제를 해결하려면 아웃박스 테이블을 사용하거나 CDC (변경 데이터 캡처)를 사용할 수 있습니다. 다음 섹션에서는 이러한 두 가지 옵션과 AWS 서비스를 사용하여 이를 구현하는 방법에 대해 설명합니다.
관계형 데이터베이스에서 아웃박스 테이블 사용하여 구현
아웃박스 테이블에는 항공 서비스의 모든 이벤트가 타임스탬프 및 시퀀스 번호와 함께 저장됩니다.
항공 테이블이 업데이트되면 아웃박스 테이블도 동일한 트랜잭션에서 업데이트됩니다. 다른 서비스(예: 이벤트 처리 서비스)는 아웃박스 테이블을 읽고 Amazon SQS로 이벤트를 전송합니다. Amazon SQS는 추가 처리를 위해 이벤트 관련 메시지를 결제 서비스에 전송합니다. Amazon SQS 표준 대기열은 메시지가 한 번 이상 전송되고 분실되지 않도록 보장합니다. 하지만 Amazon SQS 표준 대기열을 사용하는 경우 동일한 메시지 또는 이벤트가 두 번 이상 전송될 수 있으므로 이벤트 알림 서비스가 idempotent 한지 확인해야 합니다.(즉, 동일한 메시지를 여러 번 처리해도 부작용이 없어야 함). 메시지 정렬과 함께 메시지를 정확히 한 번 전송해야 하는 경우 Amazon SQS 선입선출 (FIFO) 대기열을 사용할 수 있습니다.
항공 테이블 업데이트가 실패하거나 아웃박스 테이블 업데이트가 실패하는 경우 전체 트랜잭션이 롤백 되므로 다운스트림 데이터가 일치하지 않는 문제가 발생하지 않습니다.
다음 다이어그램에서 트랜잭션 아웃박스 아키텍처는 Amazon RDS 데이터베이스를 사용하여 구현되었습니다. 이벤트 처리 서비스는 아웃박스 테이블을 읽을 때 커밋된 (성공한) 트랜잭션에 해당하는 행만 식별한 다음 해당 이벤트 메시지를 SQS 대기열에 배치합니다. 이후 결제 서비스에서 SQS 대기열을 읽고 추가 작업을 진행합니다. 이 설계는 이중 쓰기 작업 문제를 해결하고 타임스탬프와 시퀀스 번호를 사용하여 메시지 및 이벤트의 순서를 보존합니다.
CDC를 사용하여 구현
CDC는 데이터베이스에서 데이터 항목의 수정 사항을 포착하고 이를 이벤트 알림으로 전송하는 기능을 지원하는 개념입니다. 이를 통해 변경된 항목을 식별하고 해당 변경 사항에 따라 이벤트 알림을 보낼 수 있습니다. 이는 데이터 업데이트를 추적하기 위해 별도의 테이블을 생성하는 오버헤드를 줄일 수 있습니다.
Amazon DynamoDB는 key-value 형태의 NoSQL 데이터베이스로, CDC 업데이트를 지원합니다. 다음 다이어그램에서 DynamoDB가 데이터 항목의 수정 내용을 Amazon DynamoDB Streams에 발행합니다. 그런 다음 이벤트 처리 서비스가 이 스트림에서 내용을 읽어서 이벤트 알림을 생성하고, 이를 결제 서비스에 전달하여 추가 작업을 진행할 수 있습니다.
DynamoDB Stream은 시간 순서에 따라 DynamoDB 테이블의 항목 수준의 변경(item-level change)과 관련된 정보의 흐름을 캡처합니다.
DynamoDB 테이블에서 스트림을 활성화하여 트랜잭션 아웃박스 패턴을 구현할 수 있습니다. 이벤트 처리 서비스를 위한 Lambda 함수는 이러한 스트림과 연결됩니다.
- 항공 테이블이 업데이트되면 DynamoDB Stream이 변경된 데이터를 캡처하고 이벤트 처리 서비스가 스트림을 폴링하여 새 레코드를 찾습니다.
- 새 스트림 레코드를 사용할 수 있게 되면 Lambda 함수는 추가 처리를 위해 이벤트에 대한 메시지를 동기적으로 SQS 대기열에 배치합니다. DynamoDB 항목에 속성을 추가하여 필요에 따라 타임스탬프와 시퀀스 번호를 캡처하여 구현의 견고성을 개선할 수 있습니다.
요약
지금까지 이중 쓰기 작업으로 인한 문제를 예방하기 위해 아웃박스 테이블을 사용하거나 CDC를 활용하는 방법을 소개했습니다. 또한, 다양한 시나리오에서의 적용 가능성과 구현 방법에 대해 설명했습니다. 트랜잭션 아웃박스 패턴은 분산 시스템에서의 이중 쓰기 작업 문제를 효과적으로 해결하는 효과적인 방법입니다. 이 패턴은 데이터베이스 업데이트와 이벤트 알림을 안전하게 조율하여 데이터 일관성과 안정성을 보장합니다. 트랜잭션 아웃박스 패턴을 AWS Lambda, Amazon RDS, Amazon SQS 그리고 Amazon DynamoDB와 같은 클라우드 서비스를 활용하여 구현하는 방법을 알아보았습니다.
결론
트랜잭션 아웃박스 패턴은 안정적이고 일관성 있는 분산 시스템을 구축하는데 필수적인 도구로써, 현대적인 마이크로서비스 아키텍처에서 활용할 수 있습니다. 이를 통해 서비스 간 통신에서 발생할 수 있는 다양한 문제를 해결하고 안정성과 일관성이 있는 시스템을 구축하여, 이를 통해 사용자 경험 향상과 신속한 서비스 제공에 도움이 되기를 바랍니다.
AWS 기반의 클라우드 디자인 패턴에 대한 블로그는 총 5개의 시리즈로 구성이 되어 있습니다. 아래에서 관심 있는 주제를 추가로 살펴보시기 바랍니다.
AWS 에 구축하는 클라우드 디자인 패턴 시리즈 1부: 안정성
AWS 에 구축하는 클라우드 디자인 패턴 시리즈 2부: 연결성 및 조합
AWS 에 구축하는 클라우드 디자인 패턴 시리즈 3부: 마이그레이션
AWS 에 구축하는 클라우드 디자인 패턴 시리즈 4부: API 관리
AWS 에 구축하는 클라우드 디자인 패턴 시리즈 5부: 데이터 관리