(TBD) NHN FORWARD 22 - 분산 시스템에서 데이터를 전달하는 효율적인 방법

본 글은 [NHN FORWARD 22] 분산 시스템에서 데이터를 전달하는 효율적인 방법을 정리한 글입니다.

다룰 내용

  • 데이터 전달 보장 방법론
  • RDB를 사용하는 애플리케이션에서 전달 방법
  • RabbitMQ를 사용한 전달 방법
  • Kafka를 사용하는 애플리케이션의 전달 방법

분산 시스템

  • 여러개의 컴퓨터 리소스를 사용하는 시스템
  • 시스템은 두 개 이상의 컴포넌트로 구성
    • 마이크로서비스
  • 네트워크를 통해 데이터를 전달
    • Remote API
      • 서버/클라이언트 방식
      • 요청시 즉각 응답하는 동기식 방식
      • 비교적 간단
    • Message Queue
      • Publisher/Consumer 구조
        • Consumer가 데이터 조작
      • 배치/비동기 작업
      • 비교적 복잡

분산 시스템에서 데이터를 전달하는 효율적인 방법

  • 분산 시스템은 네트워크로 연결
    • 유실가능
  • 데이터 전달 보장 방법
    • At most once (최대 한번)
      • Producer는 한번만 전송
      • Consumer는 한번만 수신
      • 간단하개 개발가능하나 Producer/Consumer 둘중 하나가 실패하면 메시지 유실 발생
    • At least once (최소 한번)
      • Producer는 최소 한번 이상 발송
      • Consumer는 최소 한번 이상 수신
      • Producer는 메시지발송 보장
      • Consumer는 메시지 처리시 멱등성(idempotent)을 보장해야함
    • Exactly once (정확히 한번)
      • 메시지는 정확하게 한번 전송
        • 누락과 중복이 없음
      • 가장 어려운 난이도
      • Producer, Consumer에서 모든 상태 관리
      • MessageQueue 기능에 의존한 개발
        • MessageQueue 추가로 인한 시스템 복잡도 증가
  • 우리는 개발시 최소 한번은 전달하고 있는가?

RDB를 사용하는 애플리케이션에서 전달 방법

  • 서비스별 독립된 데이터베이스를 가지고 있는 경우
    • @TransactionalEventlistener,
      • 네크워트 실패를 방어하지 못함
        • @Retryable을 사용하면?
          • 1번 재시도?
        • maxAttempts, backoff 설정
        • 그러나 네트워크는 계속 실패할 수 있음
    • TransactionSynchronizionManager, TransactionSynchronization
    • 다음과 같은 마이크로서비스 아키텍처 패턴 사용가능
      • Transactional Outbox Pattern
        • RDB를 Message Queue로 사용
        • OLTP에 Event Message를 포함하는 패턴
      • Polling Publisher Pattern
        • RDB Message Queue Polling & Publishing
    • 두개 혼합해서 사용하면 안전하게 메시지 전달 가능
      • 이벤트를 RDB에 넣음
        • 하나의 트랜잭션으로 서비스의 로직과 이벤트를 처리 가능
      • 데몬/스케줄러로 DB에 저장된 이벤트를 주기적으로 발행
    • 이벤트 저장시 중요한 값
      • event_id
        • pk로 사용하면 이벤트 순서 보장 가능
      • created_at
      • status
        • ready / done
      • payload

Transactional Outbox Pattern 예시

@Service
public class CreateTaskService implements CreateTaskUserCase {

  @Transactional
  public CreateTaskResponse createTask(CreateTaskCommand createTaskCommand) {
    Task task = createTaskCommand.toTask();

    taskRepository.save(task);
    eventRepository.save(CreateTaskEvent.of(task));

    return CreateTaskResponse.of(task);
  }
}

Polling Publisher Pattern 예시

@Service
public class MessagePublisher {

  @Scheduled(cron = "0/5 * * * * *")
  @Transactional
  public void publish() {
    LocalDateTime now = LocalDateTime.now();
    eventRepository.findByCreatedAtBefore(now, EventStatus.READY)
                    .stream()
                    .map(event -> restTemplate.execute(event))
                    .map(event -> event.done())
                    .forEach(eventRepository::save);
  }
}
  • 장점
    • REST-API 환경에서 At-least-once 가능
  • 단점
    • Polling, Publisher 과정으로 지연 처리
    • 데이터베이스 부하
      • 처리속도가 데이터베이스 속도에 의존됨

RabbitMQ를 사용한 전달 방법

  • AMQP(Advanced Message Queuing Protocal)을 구현한 메시지 브로커
  • Publish/subscribe 방식 지원
  • ACK(Acknowledgement) 기반 메시지 전송/수신 방식
    • Producer Confirm
    • Consumer Ack
  • (TBD)

Kafka를 사용한 전달 방법

  • (TBD)

마무리

  • Event driven Achitecture의 기본은 데이터 전달
  • At Leasst Once 설정
  • Producer Confirm, Consumer Ack 고려