Go와 PostgreSQL을 활용한 분산 시스템 메시지 전송 안정성 확보

by DD
2개월 전
조회수 20

분산 시스템(Distributed Systems)에서 메시지 브로커(Message Broker) 전송 실패로 인한 데이터 불일치 문제를 해결하기 위해 아웃박스 패턴(Outbox Pattern)을 소개함

Go, PostgreSQL, Google Cloud Pub/Sub을 활용하여 주문 생성(Order Creation)과 아웃박스 메시지 저장을 단일 트랜잭션(Single Transaction)으로 처리하는 예시를 제시함

메시지 릴레이(Message Relay)를 통해 아웃박스 테이블(Outbox Table)의 메시지를 주기적으로 처리하고, at-least-once delivery를 보장하기 위한 FOR UPDATE SKIP LOCKED 구문 활용

PostgreSQL의 논리적 복제(Logical Replication)를 활용한 푸시 기반(Push-based) 아웃박스 패턴 구현을 통해 폴링 방식의 단점을 보완하는 방법 제시

아웃박스 패턴(Outbox Pattern)의 핵심 원리

아웃박스 패턴(Outbox Pattern)은 분산 시스템에서 데이터 일관성(Data Consistency)을 보장하기 위한 핵심 아키텍처 패턴이다. 핵심은 메시지 브로커(Message Broker)에 직접 메시지를 전송하는 대신, 로컬 데이터베이스(Local Database)의 아웃박스 테이블(Outbox Table)에 메시지를 저장하는 것이다. 이 방식은 데이터베이스 트랜잭션(Database Transaction)의 원자성(Atomicity)을 활용하여 데이터 저장과 메시지 큐잉(Message Queuing)을 하나의 단위로 묶어, 두 작업 중 하나라도 실패하면 전체 작업이 롤백(Rollback)되도록 보장한다.

Go와 PostgreSQL을 활용한 구현 상세

본문에서는 Go, pgx, PostgreSQL, Google Cloud Pub/Sub를 사용하여 아웃박스 패턴(Outbox Pattern)을 구현하는 구체적인 예시를 제시한다. 특히, 주문 생성(Order Creation)과 아웃박스 메시지 생성을 단일 트랜잭션(Single Transaction)으로 묶어 데이터베이스의 ACID 특성(ACID Properties)을 활용한다. 또한, 메시지 릴레이(Message Relay)는 아웃박스 테이블(Outbox Table)을 주기적으로 폴링(Polling)하며, `FOR UPDATE SKIP LOCKED` 구문을 사용하여 여러 릴레이 인스턴스(Relay Instance)가 동시에 실행될 때 동일한 메시지를 중복 처리하는 문제를 방지한다.

At-Least-Once Delivery 보장 방법

아웃박스 패턴(Outbox Pattern)은 메시지 릴레이(Message Relay)가 메시지를 전송한 후, 데이터베이스 업데이트에 실패하는 경우를 고려하여 at-least-once delivery를 보장한다. 즉, 메시지가 전송되었지만, 상태 업데이트가 실패하여 메시지가 중복 전송될 수 있다. 이러한 문제를 해결하기 위해, 메시지 소비자(Message Consumer)는 멱등성(Idempotency)을 갖도록 설계되어야 한다. 즉, 동일한 메시지를 여러 번 처리해도 문제가 발생하지 않도록 설계해야 한다.

PostgreSQL 논리적 복제(Logical Replication)를 활용한 최적화

폴링 방식의 단점을 보완하기 위해, PostgreSQL의 논리적 복제(Logical Replication)를 활용한 푸시 기반(Push-based) 아웃박스 패턴 구현을 제시한다. 논리적 복제는 데이터베이스의 WAL(Write-Ahead Log)을 활용하여 아웃박스 테이블(Outbox Table)의 변경 사항을 실시간으로 감지하고, 메시지 릴레이(Message Relay)에 알림을 보낸다. 이 방식은 폴링 방식보다 낮은 지연 시간(Latency)을 제공하지만, 구현 복잡도(Implementation Complexity)가 증가한다는 트레이드오프(Trade-offs)가 존재한다.

How to implement the Outbox pattern in Go and Postgres