단일 트랜잭션 유지 그리고 PGMQ 본문
Overview
애플리케이션을 개발하다 보면 어떤 특정한 목적을 위해 비동기 처리를 구현해야 하는 상황을 종종 마주하게 된다.
비동기 처리를 구현하기 위해 흔히 Apache Kafka 혹은 RabbitMQ와 같은 메시지 큐를 도입하곤 한다.
특히나 요즘은 Application Modernization
이라는 명목 하에 여러 기업 뿐만 아니라 공공까지도 MSA와 클라우드 사용이 주가 되는 Cloud Native
환경으로 넘어가는 추세이니 만큼 애플리케이션의 복잡도가 수직 상승하고 있기에 비동기 처리를 위한 메시지 큐 사용은 필수적이라고 볼 수 있다.
MSA 환경에서는 각 서비스가 설계자의 의도에 따라 분리되어 있고 모놀리틱 방식에 비해 결합도가 떨어지기 때문에 메시지 큐를 얼마나 잘 활용하느냐가 MSA 환경 구축의 난이도 혹은 기능 구현 여부를 판가름한다.
이러한 상황에서 메시지 큐의 대표격이라 할 수 있는 Apache Kafka와 RabbitMQ의 사용 난이도와 학습 곡선은 꽤나 가파른 편이다. 특히 Kafka의 경우 학습 난이도와 난해함으로 악명이 자자하다.
필자 또한 채팅 기능 구현을 위해 RabbitMQ를 사용해본 경험이 있는데 RabbitMQ의 난이도 또한 그리 쉽지만은 않았다.
거기에 메시지 큐 도구 자체의 복잡성뿐만 아니라 DB와 메시지 큐 간의 데이터 일관성 유지까지 고려한다면, 클라우드 네이티브 환경 구축이라는 목표는 꽤 아득히 먼 곳에 있는 것처럼 느껴진다.
단일 트랜잭션 유지
메시지 큐와 데이터베이스 간 데이터 일관성을 유지하기 위해서는 DB와 메시지 큐를 단일 트랜잭션으로 묶어야 한다.
예를 들어, 비즈니스 로직에 의해 데이터베이스를 수정한 다음 메시지 큐에 메시지를 보냈지만, 이후의 로직에서 에러가 발생하면 동일 트랜잭션에서 수행된 데이터베이스는 Rollback이 발생할 것이다. 하지만 메시지 큐에 보낸 메시지를 다시 롤백시키기에는 어려움이 뒤따른다.
이런 문제를 해결하기 위해 트랜잭션에 데이터가 완전히 커밋된 이후 메시지 큐에 데이터를 전송하도록 구현하는 방법이 존재하지만, 알 수 없는 네트워크 상의 이유로 메시지 전송에 실패할 수도 있다.
이러한 실패에 대비하려면 메시지를 어딘가에 저장해둬야 한다. 하지만 메시지를 어딘가에 저장해둬야 한다는 전제는 우리를 다시 데이터 일관성에 대해 고민하게 만든다.
우리는 이러한 문제를 Transactional Messaging
을 통해 해결할 수 있다. 이는 메시지를 데이터베이스 트랜잭션의 일부로 발행하는 것을 의미한다. 즉, 비즈니스 로직에서 데이터베이스를 수정하는 것과 메시지 큐에 메시지를 발행하는 일련의 과정을 원자적으로 수행하여 데이터 일관성을 보장한는 것이다.
Transactional Messaging을 구현하는 두 가지 방법
마이크로서비스 패턴의 저자인 크리스 리처드슨이 제시하는 두 가지 패턴에 대해 간략히 소개하려고 한다. 두 패턴 모두 Outbox
라는 테이블을 메시지 큐로 사용한다.
Transactional Outbox Pattern
메시지를 바로 메시지 큐에 보내지 않고 데이터베이스의 아웃박스 테이블에 넣는 방식이다. 이어 데이터베이스 트랜잭션이 커밋되면 주기적으로 아웃박스 테이블 내용을 읽어 메시지 큐에 전달하는 방식이다.
Transactional Log Tailing Pattern
아웃박스 테이블에 메시지 큐에 전달한 메시지를 저장하는 것까지는 Transactional Outbox 패턴과 동일하지만 아웃박스 테이블의 데이터를 읽는 방식에서 차이가 있다.
해당 패턴의 이름대로 데이터베이스의 트랜잭션 로그를 읽고 아웃박스 테이블의 데이터 변경만을 필터링하여 메시지 큐에 전달한다.
우발적 복잡성의 증가
비동기 처리를 위해 메시지 큐를 도입하고, 데이터베이스와 메시지 큐 사이의 데이터 일관성을 보장하기 위해 위에서 소개한 패턴 등을 적용하는 과정은 우발적 복잡성을 증가시킨다.
PGMQ (Postgres Message Queue)
이러한 우발적 복잡성의 증가를 막기 위해 PostgreSQL의 PGMQ(Postgres Message Queue)
를 활용하여 위에서 언급한 문제들을 해결할 수 있다. PostgreSQL과 PGMQ를 같이 사용하면 데이터베이스 단일 트랜잭션으로 두 가지 작업을 한 번에 묶을 수 있다.
비즈니스 로직을 수행하다 오류가 발생할 경우, 데이터베이스뿐만 아니라 PGMQ에 넣었던 메시지 또한 롤백한다는 것이다.
PGMQ의 사용법은 PostgreSQL 내에서 SQL을 사용하기 때문에 Kafka나 RabbitMQ에 비해 복잡성이 현저히 낮은 편에 속한다.
또한 PGMQ의 메시지를 읽어 Kafka로 전송하는 메시지 릴레이를 추가할 수 있기 때문에 굉장히 다양한 환경에서 적용이 가능하다.
PGMQ는 자체적으로 메시지 삭제 기능을 제공하기 때문에 PGMQ를 사용하는 것으로 아웃박스 테이블을 구현하고 메시지를 추가 및 삭제하는 로직을 추가로 작성할 필요없이 우발적 복잡성을 현저히 낮출 수 있다.
Conclusion
사실 개발판에서는 "은탄환은 없다"라는 말이 자주 쓰인다. 한 번에 모든 것을 해결해주는 만능 도구는 없다는 의미인데, PGMQ 또한 적절한 환경과 상황이 갖추어 졌을 때 사용해야 그 진가를 발휘한다고 생각한다.
이번 포스팅은 아래의 아티클을 참조하여 작성되었다. MSA 환경에 대해 많이 고민하고 있던 필자에게 매우 도움이 된 글이기에 한 번씩 읽어본다면 분명 도움이 될거라고 생각한다.
'Dev' 카테고리의 다른 글
[Go] 함수 고급 (0) | 2024.12.02 |
---|---|
[Go] 인터페이스 (3) | 2024.11.10 |
[Go] 메소드 (2) | 2024.11.09 |
[Go] 슬라이스 (0) | 2024.11.07 |
그림으로 이해하는 객체 지향 설계 5원칙 [SOLID] (2) | 2024.07.20 |