Polling Publisher 방식을 중심으로
The post Transactional Outbox 패턴으로 메시지 발행 보장하기 appeared first on 리디주식회사 RIDI Corporation.
||
안녕하세요. 리디 백엔드 엔지니어 강규입니다.
오늘은 Event Driven Architecture에서 메시지 발행의 신뢰성을 보장하는 Transactional Outbox 패턴을 소개하고, 이를 리디 서비스에 적용하며 느낀 바를 공유하고자 합니다.
Transactional Outbox 패턴이란 무엇인가요?
Event Driven Architecture를 따르는 서비스에서는 대개 Message Broker를 이용해 다양한 메시지(이벤트)를 publish(발행) 하고, 그에 연관된 작업을 비동기적으로 처리하여 시스템을 통합합니다.
이때 DB 트랜잭션을 실행한 뒤 연관 메시지를 Message Broker에 publish 하게 되는데, 때로 메시지 publish가 반드시 완료되어야 하는 경우가 있습니다.
리디 주문 기능을 예로 들어볼까요? 먼저 주문이 발생하면, 사용한 캐시·포인트 금액을 차감하고 상품을 지급하며 주문 완료로 상태를 바꾸는 DB 트랜잭션이 발생합니다. 그리고 Message Broker에 주문 완료 메시지를 publish 합니다.
DB 트랜잭션은 DB 차원에서 원자성(atomicity)을 보장하므로 트랜잭션에 포함된 query들은 원자적으로 실행되지만, 대개는 DB와 Message Broker가 다른 기종이라 원자적인 처리가 불가능합니다.
따라서 DB 상 주문 완료 처리가 되었더라도 Message Broker에 메시지를 publish 하는 데 실패할 수 있고, DB의 주문 완료 처리를 rollback 하기도 어렵습니다.
Transactional Outbox 패턴 (그림 출처)
이런 문제를 해결하기 위해 Transactional Outbox 패턴이 등장합니다. Outbox는 주로 웹 메일에서 ‘보낸 편지함’을 의미합니다. Outbox를 Message Broker에 publish 할 메시지로 대응해서 생각해 보면 패턴의 이름을 이해하는 데 도움이 됩니다.
즉, Transactional Outbox 패턴은 Message Broker로 publish 하려는 메시지의 생성을 DB 트랜잭션에 포함시켜서 원자적으로 처리되게 하는 것을 의미합니다.
리디에 Transactional Outbox 패턴을 도입한 배경
지난번 ‘리디에서 Kafka를 사용하는 법‘에서 소개한 것처럼, 리디 서비스는 Kafka를 중심으로 통합되고 있습니다. 그리고 API, batch process, Kafka consumer 등 다양한 서비스들이 데이터 영속화를 위해 MySQL를 사용합니다. 주로 MySQL을 이용해 비즈니스 로직을 처리하고, 비동기적인 처리를 위해 메시지를 Kafka에 publish 합니다.
Kafka 도입 초기에는 아래 코드와 같이 메시지를 publish 했습니다. DB 트랜잭션이 완료된 이후 Kafka에 메시지를 publish 하고, 실패한 메시지를 dead_letter_queue DB 테이블에 저장한 뒤 별도의 batch process에서 retry를 하는 방식이었죠.
CREATE TABLE `dead_letter_queue` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`topic` varchar COLLATE utf8mb4_unicode_ci NOT NULL,
`key` varchar COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`message` mediumblob NOT NULL,
`status` enum('queued','processed','skipped') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'queued',
`updated_at` datetime NOT NULL DEFAULT current_timestam

