반응형

트랜잭션 아웃박스 패턴폴링 퍼블리셔 패턴분산 시스템에서 데이터베이스와 메시징 시스템 간의 메시지 전달을 보장하기 위해 자주 함께 사용되는 패턴입니다. 특히 이벤트 주도 아키텍처에서 데이터베이스 상태 변화와 해당 이벤트 발행의 일관성을 유지해 줍니다.

아웃박스 패턴(유실방지)

배경 및 필요성

분산 시스템에서는 데이터베이스에 데이터를 저장하는 트랜잭션과 외부 메시징 시스템에 이벤트를 보내는 작업이 분리되어 있기 때문에, 두 작업이 동시에 성공하거나 실패하도록 일관성 있는 처리가 어려울 수 있습니다. 특히, 트랜잭션 내에서 데이터베이스는 성공했는데 메시지 큐로의 전송은 실패하는 경우, 데이터 일관성이 깨질 수 있습니다. 이 문제를 해결하는 방법 중 하나가 아웃박스 패턴입니다.

트랜잭션 아웃박스 패턴의 주요 흐름:

  1. 데이터베이스 트랜잭션 및 아웃박스 기록 생성:
    • 애플리케이션이 데이터베이스 변경(예: 주문 생성)을 해야 할 때, 메인 테이블(예: Orders)에 데이터를 저장하는 동시에, 하나의 트랜잭션 안에서 "아웃박스" 테이블에 관련 이벤트 정보를 기록합니다.
    • 이 아웃박스 테이블에는 메시징 시스템에 발행할 이벤트와 관련된 정보가 포함되며, 아직 발행되지 않았음을 나타내는 상태 정보도 포함됩니다.
  2. 트랜잭션 커밋:
    • 모든 데이터베이스 작업(메인 테이블과 아웃박스 테이블에 저장 작업)이 하나의 트랜잭션으로 묶이기 때문에, 트랜잭션이 성공적으로 커밋되면 데이터베이스와 이벤트 기록이 일관성을 유지하게 됩니다.

아웃박스 패턴의 구조

  1. 아웃박스 테이블:
    • 서비스의 데이터베이스에 아웃박스 테이블을 둡니다. 이 테이블은 데이터베이스의 업데이트 내용과 함께 발행할 이벤트를 저장합니다. 즉, 비즈니스 로직에서 데이터베이스에 기록할 때, 메시지 큐에 보내야 할 이벤트 정보도 이 테이블에 같이 저장됩니다.
  2. 로컬 트랜잭션:
    • 데이터베이스에 데이터를 저장할 때, 같은 트랜잭션 내에서 아웃박스 테이블에도 이벤트 데이터를 함께 기록합니다. 단일 트랜잭션을 통해 데이터베이스의 데이터와 이벤트 정보를 동시에 커밋하므로, 이 과정에서 실패가 발생하면 전체 트랜잭션이 롤백됩니다.
  3. 이벤트 리스너/폴링 프로세스:
    • 별도의 폴링 서비스 또는 트리거가 아웃박스 테이블을 주기적으로 스캔하여 새로운 이벤트가 있으면 이를 메시징 시스템(예: Kafka, RabbitMQ)으로 전송합니다. 이 작업은 별도의 비동기 프로세스로 수행되며, 메시지 전송이 성공하면 해당 이벤트는 아웃박스 테이블에서 삭제되거나 상태가 업데이트됩니다. (폴링 주기에 따른 지연 있음)
  • 데이터베이스 상태와 이벤트 전송 상태의 일관성을 보장
  • 메시지 전송 실패 시에도 데이터는 손실되지 않으며, 시스템은 나중에 재시도할 수 있음
    • 유실 발생 시 배치로 재발행

폴링 퍼블리셔 패턴

폴링 퍼블리셔 패턴은 아웃박스 테이블에 저장된 이벤트를 주기적으로 조회(polling)하여 메시징 시스템에 발행하는 역할을 합니다.

폴링 퍼블리셔 패턴의 주요 흐름:

  1. 주기적인 조회:
    • 폴링 작업은 일정한 간격으로 아웃박스 테이블을 조회하여 발행되지 않은 이벤트를 찾습니다.
  2. 메시지 발행:
    • 발행되지 않은 이벤트를 찾아 메시징 시스템(예: Kafka 또는 RabbitMQ)으로 발행합니다.
    • 발행이 성공하면 해당 이벤트의 상태를 “발행 완료”로 업데이트하여, 동일한 이벤트가 다시 발행되지 않도록 합니다.

두 패턴을 결합한 이점

  • 일관성 보장: 트랜잭션 아웃박스 패턴을 통해 데이터베이스 변경과 이벤트 기록을 하나의 트랜잭션에서 처리할 수 있어 데이터와 이벤트의 일관성을 유지할 수 있습니다.
  • 내결함성: 폴링 퍼블리셔 패턴을 통해 이벤트 발행이 실패하더라도 재시도가 가능해 내결함성을 확보할 수 있습니다.
  • 확장성: 메시징 시스템을 통해 이벤트가 발행되므로, 여러 마이크로서비스가 이 이벤트를 구독하여 비동기로 처리할 수 있어 시스템 확장성에 유리합니다.

세팅 예시

  • 예시 1. mysql + kafka

1. 트랜젝션 처리 시 outbox 테이블에 이벤트 정보도 추가

@Transactional
public void createOrder(Order order) {
    // 1. Orders 테이블에 주문 정보 저장
    ordersRepository.save(order);
    
    // 2. Outbox 테이블에 이벤트 기록
    OutboxEvent event = new OutboxEvent(
        order.getId(),
        "ORDER",
        "ORDER_CREATED",
        new JSONObject().put("orderId", order.getId()).put("status", order.getStatus()).toString(),
        "PENDING"
    );
    outboxRepository.save(event);
}

2. 폴링용 스케줄 생성 -> 카프카로 이벤트 발행

@Scheduled(fixedDelay = 5000) // 5초마다 실행
public void publishEvents() {
    List<OutboxEvent> pendingEvents = outboxRepository.findByStatus("PENDING");
    
    for (OutboxEvent event : pendingEvents) {
        try {
            // 1. Kafka에 이벤트 발행
            kafkaTemplate.send("order-events", event.getPayload());
            
            // 2. 발행 성공 시, Outbox 테이블에서 상태를 'COMPLETED'로 업데이트
            event.setStatus("COMPLETED");
            outboxRepository.save(event);
            
        } catch (Exception e) {
            // 발행 실패 시 별도의 로깅 또는 재시도 처리
            logger.error("Failed to publish event: " + event.getEventId(), e);
        }
    }
}

  • 예시2. 스케줄 대신 디비 변경 감지 이용하여 이벤트 전송

MySQL Kafka 커넥터Debezium을 사용하면 트랜잭션 아웃박스 패턴을 더욱 쉽게 구현할 수 있습니다. 이 조합은 변경 데이터 캡처(Changed Data Capture, 데이터베이스에서 발생하는 삽입(INSERT), 업데이트(UPDATE), 삭제(DELETE)와 같은 변경 사항을 실시간으로 감지하고 추적하는 기술) 방식을 통해 MySQL 데이터베이스의 변화(즉, 새로운 아웃박스 레코드)를 자동으로 Kafka에 발행하는 구조를 제공합니다. 이를 통해 폴링을 위한 추가 프로세스 없이, 이벤트가 발생할 때마다 Kafka에 실시간으로 이벤트를 전송할 수 있습니다.

  • Debezium은 MySQL Kafka 커넥터 중에서도 가장 널리 사용되고 인기 있는 CDC 커넥터입니다. Debezium은 MySQL을 포함해 다양한 데이터베이스의 변경 사항을 실시간으로 감지하여 Kafka에 전송하는 강력한 기능을 제공하는 오픈소스 CDC 플랫폼입니다. Kafka와 MySQL을 연동하는 데 있어서 CDC를 필요로 할 때, Debezium이 대표적인 솔루션으로 많이 활용됩니다.
  • MySQL Kafka 커넥터는 Kafka Connect 프레임워크를 사용하여 MySQL 데이터베이스와 Apache Kafka 간에 데이터를 스트리밍하는 도구입니다. 주로 MySQL에서 발생한 데이터 변경 사항을 Kafka로 전송하는 데 사용됩니다. 이를 통해 MySQL 데이터베이스의 변경 로그를 실시간으로 Kafka로 스트리밍하고, Kafka의 여러 소비자가 이 데이터를 처리할 수 있습니다.

Debezium을 활용한 MySQL Kafka 커넥터 구현

Debezium은 CDC 플랫폼으로, MySQL의 바이너리 로그(binlog)를 읽어 Kafka로 변경 사항을 스트리밍할 수 있도록 합니다. MySQL의 binlog는 데이터베이스에 일어나는 모든 변경 사항을 기록하며, Debezium은 이를 Kafka 이벤트로 변환해 전송합니다. 

  1. 트랜잭션 아웃박스 패턴 적용: 애플리케이션에서 데이터베이스에 트랜잭션을 수행할 때, Outbox 테이블에 이벤트 정보를 함께 기록합니다.
  2. Debezium이 변경 감지 및 Kafka로 발행:
    • Debezium은 MySQL 바이너리 로그를 모니터링하고, Outbox 테이블에 새로운 행이 추가되면 해당 이벤트를 자동으로 Kafka로 발행합니다.
      • 바이너리 로그를 통한 순서 보장 및 오프셋을 활용한 발행 보장 -> 실패시 재시도로 발행 보장
    • 예를 들어, Outbox 테이블에 새로운 주문 이벤트가 추가되면 Debezium이 이를 감지해 Kafka의 dbserver1.yourDatabase.Outbox 토픽으로 해당 데이터를 스트리밍합니다.
  3. Kafka 소비자 서비스: Kafka에 연결된 소비자 서비스는 dbserver1.yourDatabase.Outbox 토픽에서 이벤트를 수신하여, 그 이벤트를 바탕으로 필요한 로직을 실행하거나 다른 서비스로 전달합니다.

장점

  • 실시간 처리: Debezium이 CDC 방식으로 아웃박스 테이블의 변경을 감지하여 바로 Kafka에 발행하므로 실시간으로 이벤트를 처리할 수 있습니다.
  • 추가 폴링 프로세스 불필요: 별도의 폴링 프로세스를 구현할 필요가 없으므로 시스템 자원을 절약할 수 있습니다.
  • 내결함성: Debezium은 Kafka로 이벤트 발행 중 문제가 발생하더라도 Kafka의 내장된 내결함성 기능 덕분에 안정적으로 이벤트를 재시도하고, 누락 없이 처리할 수 있습니다.
  • 처리량 증설 가능; 아웃박스 테이블 (이벤트 키를 기반으로) 파티셔닝을 통한 처리량 증대 가능 
    • 로그를 순서대로 읽느라 단일 커넥터 사용 -> 테이블에 쌓이는 속도가 더 많아서 slow
    • 분산처리로 속도 향상: 토픽(이벤트 키) 별로 outbox table 분리하여 분산 처리 가능토록(같은 키는 같은 테이블에 쌓이도록 -> 순서보장)

 

참고: https://youtu.be/DY3sUeGu74M?si=L4jk0qBOdTcRYHPb

728x90
반응형

+ Recent posts