강한 일관성과 최종 일관성
잠재적인 데이터 불일치 문제
MSA는 개발 및 배포의 유연성, 기술 스택의 다양성, 확장성, 장애 격리 등 다양한 장점을 제공하지만, 그로 인해 트레이드 오프되는 단점들도 분명히 존재합니다. 그 대표적인 부분이 비즈니스 로직 오류, 사용자 경험 저하, 심각한 경우 금전적 손실까지 야기할 수 있는 중요한 문제인 잠재적인 데이터 불일치 문제
일 겁니다. 이 문제는 다양한 이유로 발생할 수 있지만, 바로 생각나는 이유로는 다음과 같은 것들이 있을 겁니다.
- 각 서비스 마다 독립적인 데이터베이스를 사용한다거나
- EDA를 기반으로 요청과 처리를 해서 트랜잭션 처리가 어렵다거나
- 네트워크 불안정으로 인해 흐름이 순간적으로 끊긴다거나
- 특정 서비스의 장애로 트랜잭션 처리가 지연되거나 취소된다거나
결국 요지는 기존의 단일 데이터베이스에서 하던 트랜잭션을, 여러 서비스가 서로 다른 목적을 위해 각자의 데이터베이스에서 독립적으로 수행하다보니 연결이 느슨해지고, 그 틈새에서 발생할 수 있는 다양한 이슈로 인해 서비스 간 데이터 상태 동기화에 문제가 생길 수 있다는 것입니다.
강한 일관성과 최종 일관성
이에 대해 전통적인 관념을 강한 일관성이라고 합니다. 강한 일관성은 특정 데이터의 변경이 모든 노드와 유관 서비스에 완전히 적용되어 모두가 최신 데이터를 가지며 무결성을 보장하는 것입니다. 이로인해 항상 모든 데이터가 안정적으로 저장되고, 사용되는 장점을 보입니다. 하지만 이는 노드가 많아질수록, 유관 서비스가 많아질수록, 또한 데이터의 흐름이 설계 실수로 인해 복잡해질수록 절망적인 성능 하락폭을 보이는 단점, 또한 존재합니다.
이러한 점을 보완하기 위해 조명받은 관념이 최종 일관성입니다. 최종 일관성은 결과적으로 시간이 지남에 따라 자연적으로 모든 노드와 유관 서비스에 변경된 데이터가 반영이 되는 것입니다. 당장은 유저에게 일관된 데이터를 전송하지 못할 수 있지만, 시간이 지나면 결과적으로 모든 유저에게 동일 데이터를 반환하게 될 겁니다. 이러한 최종 일관성은 MSA의 분산 환경 및 확장성, 성능 요구 사항을 충족하면서 데이터 일관성을 확보할 수 있는 현실적인 대안이라 생각합니다.
최종 일관성을 잘 살린 솔루션에는 S3가 있으며, 사람에따라 평가가 갈리겠지만 저는 cassandra도 이에 포함될 수 있다 생각합니다. 쓰기가 발생한 노드는 자신의 변경 사항을 다른 노드에 전파하고, 다른 모든 노드는 전파를 받은 시점에 최신 데이터를 제공할 수 있게 됩니다.
최종 일관성을 확보하기 위한 기반
일반적으로 당연히 롤백을 고려할 수밖에 없겠지만, 저는 데이터의 전파와 오케스트레이터로 충분히 커버할 수 있다고 고려합니다.
CQRS
쓰기 작업을 할 수 있는 노드 및 서비스는 제한적으로 존재하지만, 읽기는 모두가 가능한 구조입니다. 이러한 구조를 가지게 되면 각 구간에 대한 불확정성이 줄어들고, 읽기를 위한 인프라에 데이터가 전파됨으로 모든 노드와 유관 서비스가 동일한 데이터를 공유하게 됩니다.
CDC
CDC는 데이터 변경 이벤트를 감지하여 전파하는 역할을 합니다. 그리고 이 전파를 보통 Message Bus로 하게 되며, 이러한 구조가 보통 CDC를 기반으로 EDA를 구현한 경우에 해당합니다. CQRS를 하게 되면 쓰기를 하는 곳, 예를 들어 결제와 같은 분야에서 정합성을 확보할 수 있을 것입니다. RDB를 씀으로요.
하지만 읽기는 굳이 정합성을 확보해야하는 RDB에서 할 필요가 없습니다. 제가 가상 재화를 구매했다해서 서버는 저에게 즉시 갱신된 지갑을 보여줄 필요는 없기 때문이죠. 그렇다고 너무 느리면 문제가 되겠지만요.
그럼 정합성을 챙길 필요가 없는, 읽기가 더 빠른 경우는 어떤 것이 있을까요? NoSQL이나 단순 스토리지, 분산 캐시 등이 있을 수 있습니다. 정합성을 챙긴 RDB에 기록 및 변경된 데이터를 CDC를 통해 이러한 NoSQL이나 분산 캐시에 기록하면, 정합성을 챙김과 동시에 최신 데이터를 빠르게 제공할 수 있을 겁니다.
또한 이 방식은 EDA와 비슷하게 다른 서비스에서 특정 서비스의 데이터 변경을 쉽고 거의 바로 파악할 수 있게 되며, 상호 의존성과 결합도를 낮추는 역할을 합니다.
Orchestration-based Saga Pattern
오케스트레이션 기반 사가 패턴은 일반적인 사가와 달리 여러 서비스 위의 레이어에 존재하는 오케스트레이터를 통해 트랜잭션을 관리하는 방법입니다.
- 먼저 각 서비스는 트랜잭션을 위한 데이터 CRUD와 롤백을 위한 RPC 혹은 API 엔드포인트를 제공합니다.
- 오케스트레이터는 요청을 받으면 해당 요청을 수행하기 위해 필요한 트랜잭션을 각 서비스의 API를 통해 구성합니다.
- 이를 시나리오 대로 실행하여 오케스트레이터는 분산 트랜잭션을 효과적으로 수행합니다.
이 방식의 장점은 무엇보다 기존 사가 패턴이 가지는 로직적 복잡성과 메시지 큐를 거치면서 발생하는 동시성 논리 에러를 부담할 필요가 없어진다는 것입니다.
유연하고 합리적인 복구
만약 CDC 과정이나 전파 과정 등에서 지연이 발생하여 데이터 변경 사항이 순서대로 전달되지 못할 수 있습니다. 이에 대해 이벤트에 대한 timestamp나 kafka의 경우처럼 offset을 활용하거나, 상황과 정책을 통해 충돌을 유연하고 합리적으로 복구할 수 있어야 합니다.
예를 들어, 특정 유저가 자신의 프로필을 숨김처리 했다고 가정하고, 데이터 변경 이벤트가 순서대로 처리되지 못하는 경우를 예시로 들어보겠습니다.
관련 서비스로는 다음과 같은 케이스가 있을 것입니다.
- 프로필 서비스 (Profile Service): 유저 프로필 정보 (이름, 소개, 프로필 사진 등)를 관리하는 서비스
- 개인정보 설정 서비스 (Privacy Service): 유저의 개인정보 설정 (프로필 공개 여부, 연락처 공개 여부 등)을 관리하는 서비스
- 알림 서비스 (Notification Service): 유저에게 프로필 변경 사항 등을 알리는 서비스
- 유저 A가 개인정보 설정 서비스 앱을 통해 자신의 프로필을 “숨김"으로 설정합니다.
- 개인정보 설정 서비스는 유저 A의 프로필 공개 처리 이벤트를 발행합니다.
- 유저 A가 개인정보 설정 서비스 앱을 통해 자신의 프로필을 “공개"로 설정합니다.
- 개인정보 설정 서비스는 유저 A의 프로필 숨김 처리 이벤트를 발행합니다.
- 유저 A는 프로필 숨김 처리 직후, 프로필 서비스 앱을 통해 다시 자신의 프로필을 “숨김"으로 설정합니다.
- 개인정보 설정 서비스는 유저 A의 프로필 공개 처리 이벤트를 발행합니다.
그러면 발생되는 이벤트는 총 프로필 공개, 프로필 숨김, 프로필 공개 순으로 3가지가 됩니다. 만약 발행에 문제가 되어 메시지가 프로필 공개 -> 프로필 공개 -> 프로필 숨김 순으로 오게 되고, 별도의 방어 로직이 없다면 알림 서비스는 유저 A의 프로필이 공개되어 있음에도 불구하고, 결과적으로 숨김 처리되었다고 다른 유저들에게 알림을 주게 될 것입니다.
하지만 예를 들어, 메시지에 timestamp가 포함되거나 offset이 들어가 있다면 이야기가 달라집니다. 쉬운 방법으론 아래 2가지 방법이 있을 겁니다.
- 해당 값을 보고 최근에 다룬 것에 비해 이전 값이면 메시지를 무시합니다.
- 단기간만 저장하는 프로필 공개 상태 변경 카운트를 작성하여 공개 2회면 +2, 숨김 1회면 -1로 결과적으로 공개로 처리합니다.
예시의 경우는 보기 힘든 케이스지만, 실제로 특정 상태 변경이 자주 발생하는 경우에는 이러한 유연하고 합리적인 복구 정책이 필요할 것입니다.
결론
MSA는 분명 훌륭한 아키텍처지만, 잠재적인 데이터 불일치
라는 중대한 문제가 남아 있습니다. 모놀리스와 단일 DB에선 당연했던 강한 일관성은 분산 시스템에서 유지하기 어려운 선택지가 되었습니다.
이에 대해 최종 일관성이 대안이 되었으며, 제가 괜찮다고 고려한 안을 몇가지 나열했습니다. CQRS, 오케스트레이션 기반 사가 패턴, 유연한 복구 등이 이러한 문제에 직면한 분들에게 도움이 되길 바랍니다.