MSA와 분산 데이터베이스, 어떻게 다뤄야하지?

easbui·2023년 10월 13일
0

마이크로서비스 아키텍처는 분산 데이터베이스를 권장한다.

도메인에 따라 적절히 분리된 마이크로서비스는 각각이 배타적으로 전담하는 데이터베이스를 별도로 가지는 구조가 되는 것이 바람직하기 때문이다.

그 이유는, 단일 데이터베이스를 사용할 경우 (1) DB 종류의 선택이 많은 서비스가 채택하는 RDBMS로 강제될 가능성이 높고, (2) 데이터 스키마가 불필요한 서비스간 결합을 가져올 가능성이 높으며 (3) 아무리 마이크로서비스가 분리되어 수평적 확장이 가능하다고 해도, DB가 단일 데이터베이스라면 말짱 도루묵이 되기 때문이다.

한편, MSA가 권장하는 분산 데이터베이스에서 어떤 마이크로서비스는 오직 자신이 담당하는 DB에만 직접적인 접근이 가능하다. 이로인한 제약사항은 몇 가지 현실적인 문제점을 만든다.

1. 트랜잭션?

단일 DB에선 DBMS가 제공하는 트랜잭션을 애플리케이션에 적절히 사용하면 되는 문제가, 분산 환경에선 불가능해진다. 2 phase commit 등 트랜잭션을 이어붙이는 식의 방법이 있지만, 솔직히 복잡해서 강한 일관성과 원자성이 필요하지 않는 이상 잘 사용안할 것 같다.

그 외로는 애플리케이션 층위에서 트랜잭션을 구현하는 SAGA 패턴 등이 있다. 이벤트를 통해 어떤 요청에 요구되는 마이크로서비스간 작업의 성공/실패 여부를 전파, 롤백시 '보상'을 이용하며 트랜잭션을 흉내낸다. '흉내'인 이유는 트랜잭션이 지향하는 ACID를 완벽히 보장하지 못하기 때문이다. 격리된 환경을 구성하지 못하며, 결과적인 일관성을 추구할 뿐이다. 또한 개인적인 생각으론, 롤백 시에도 완벽한 보상 트랜잭션을 구현하지 못할 수도 있으므로(어떤 서비스가 만약 NoSQL을 사용한다면, 쉽지 않은 일이다.) 원자성을 구현하는 것 역시 쉽지 않아 보인다.

근본적으로, 서비스 간 트랜잭션 자체가 어떻게 본다면 두 서비스 사이의 논리적 강결합을 의미하므로 애초에 MSA의 철학에 녹아들기 쉽지 않다. 따라서, SAGA 패턴을 트랜잭션의 변주로 이해하기 보다, 이벤트 기반 아키텍처에서 어떤 서비스가 발행하고 처리할 이벤트를 확실히 하는 측면에서 결과적으로 SAGA 패턴이 탄생하는 것으로 보아야하지 않을까.

2. 데이터 관리?

MSA 상에서 어떤 서비스가 처리하는 데이터가 자신이 담당하는 도메인에 속하는 데이터만 처리한다면 참 좋으련만, 그렇지 못하는 경우가 많다. 예를 들자면, 대부분 서비스는 회원 도메인이 존재하는데, 회원 도메인의 데이터를 여기저기 다른 마이크로서비스에서 필요로 하는 경우가 많다. (이에 관한 이벤트 처리를 다룬 배민 테크블로그의 좋은 게시글)

비즈니스 로직간 트랜잭션 처리는 이벤트로 해결한다 치더라도, 도메인내 데이터는 어떻게 공유되어야 할까? 아니, '공유'되어야 하는 것일까?

간단한 게시판 서비스를 구현한다고 해보자. 게시글을 조회할 때, 작성자의 정보를 함께 가져와야 한다.

모놀리식 아키텍처라면 그냥 생각할 것도 없이 JOIN을 이용할 텐데, 마이크로 서비스에선 조금 생각해봐야 한다. 아래는 여러 방식에 대한 조사 후 필자 나름의 평가다.

(1) 게시글 서비스가 회원 서비스의 DB에 직접 혹은 View 통한 Read-Only 접근
-> MSA 설계 위배

(2) 게시글 서비스가 회원 서비스에 대해 API를 통해 접근 후, 조회 결과 반환
-> 네트워크 비용 발생. 게시글 서비스에서 회원 데이터를 다뤄야 하나? 서비스간 영역 침범

(3) 게시글 서비스와 회원 서비스가 공통으로 이용하는 서비스를 만든 후, 여기에 API를 통한 접근
-> 공통 서비스가 담당하는 도메인의 범위는 무엇인가? 정한다고 하더라도, 결국 2번의 변형에 불과. 회원 서비스에서 공통 서비스로 바뀌었을 뿐. 너무 땜질식 처방.

(4) Aggregator를 구현해 양 서비스에 접근 후 데이터를 조합해 조회 결과 반환 (애그리게이터 패턴)
-> 일종의 CQRS로 보인다. 그러나 CQRS는 엄밀히 따지면 하나의 도메인 내에서의 다 애그리거트 조회 문제 해결을 위한 접근법으로 봐야한다고 생각. 나쁘지 않은 해결책. 하지만, 단순 조회라면 상관 없겠으나 만약 '특정한 조건을 가진 작성자의 게시물 삭제' 같은 Command 성 요청이 들어온다면 두 서비스 중 하나에서 해결하긴 어렵고, 필연적으로 1. 게시물 서비스가 Aggregator를 통해 회원을 조회한 후 처리하거나, 2. Aggregator에서 조율해야 한다. 1번의 경우 Aggregator에 마이크로서비스의 의존이 발생하는 안티패턴이며, 2번의 경우 비즈니스 로직이 마이크로서비스 바깥에 존재하는 unclean architecture.

근본적으로 고민이 되었던 점은, 게시글 서비스에서 게시글 엔티티에 회원 식별자가 포함되는 시점에서 게시글 도메인이 회원 도메인에 의존하며 게시글 도메인은 자기완결적이지 못한 도메인이 된다는 점임. 분명 우리의 상식 속에선, 게시글의 작성자가 곧 회원이지만 게시글 도메인의 Context에서 작성자는 곧 회원일까?

아리스토텔레스의 존재론에서 비트겐슈타인의 언어게임이론으로 전환이 필요한 부분인 것 같다. 결국 맥락적 용법이 다른 두 언어를 우리는 '존재론적으로 같으므로 같게' 표현하려는 오류를 저지르는 것 같다.

따라서, 필자가 생각하는 가장 이상적인 방식은 회원 서비스에서 회원 엔티티가 생성되는 이벤트가 발생하면 게시글 서비스에서 작성자 엔티티를 생성하여 영속화하는 것이다. 작성자 엔티티의 식별자는 회원 엔티티의 그것과 동일하게 하면서도, 게시글 도메인에서 필요한 속성을 가지게끔 구현한다면 비로소 자기완결적인 게시글 도메인을 완성할 수 있다. 그렇게 된다면 '서비스 간 데이터 조회'라는 것 자체를 고민할 필요가 없어진다.

물론 이 방식 역시도, 데이터가 중복저장되는 큰 낭비가 발생할 가능성이 크다는 결함을 가진다. 이를 해결하고자 도메인 모델을 데이터로 저장하는 것이 아닌, 관련 이벤트의 내역(생성, 변경 등)으로 저장하는 것으로 사고를 전환한 '이벤트 소싱' 방법론도 존재하나 상당히 구현 난이도가 높아 엄두를 못내겠다...

따라서 현재, 현실적으로 Case by case로 위 방법들 중에 선택하여 해결해야 할 것 같다. 개인적으로는 가장 깔끔한 '자기 완결적 도메인'을 만드는 것이 가장 좋다. 편-안 하니까.

profile
개발자 - 프로그램을 개발새발짜는 사람

0개의 댓글