분산 시스템과 발표 개요
NHN 두레 개발자가 분산 시스템에서 데이터를 전달하는 효율적인 방법을 소개한다. 두 개 이상의 컴포넌트가 협업하는 시스템은 모두 분산 시스템이며 네트워크로 통신한다는 정의에서 출발해, RDB·RabbitMQ·Kafka 순으로 다룰 것을 안내한다.
네트워크는 패킷 유실·다운이 가능한 신뢰할 수 없는 매체이므로 데이터 유실에 대비해야 한다고 짚는다. 최대 한 번(at-most-once), 최소 한 번(at-least-once), 정확히 한 번(exactly-once)의 특징과 신뢰성·복잡도 트레이드오프를 설명한다.
서비스별 데이터베이스 패턴에서 데이터를 저장하고 REST로 전파하는 코드를 살펴본다. @Transactional이 AOP 프록시로 동작해 REST 호출 뒤 커밋되므로, 롤백 시 원본 데이터 없이 이벤트만 나가버리는 문제가 발생함을 보여준다.
롤백 문제를 막기 위해 @TransactionalEventListener나 콜백으로 커밋 이후 전파하는 방법을 설명한다. 다만 이때는 REST 호출이 실패할 수 있어 @Retryable의 maxAttempts·backoff로 재시도하지만, 네트워크가 계속 죽으면 한계가 있음을 짚는다.
트랜잭션 처리와 이벤트 전달을 함께 보장하려면 트랜잭션 아웃박스와 폴링 퍼블리셔 패턴을 조합한다. RDB를 메시지 큐처럼 써서 이벤트를 한 트랜잭션에 저장하고, 스케줄러로 폴링해 발행한다. 이벤트 테이블의 핵심 4개 필드와 지연·부하 단점도 다룬다.
AMQP 메시지 브로커 RabbitMQ의 exchange·queue 구조와 ack/nack 메커니즘을 소개한다. 프로듀서 컨펌은 Correlation Data와 ConfirmCallback으로, 컨슈머 ack는 Channel 객체의 basicAck·basicNack로 수동 구현하는 코드와 설정을 설명한다.
컨슈머가 계속 nack를 날려 큐에 메시지가 쌓이는 상황을 데드레터 설정으로 해결한다. 세 번 실패 시 데드레터로 보내는 RetryInterceptorBuilder 설정과, 데드레터 큐를 리스너로 받아 얼럿·폴백·로그로 후속 처리하는 방법을 소개한다.
Kafka에서 KafkaTemplate.send의 ListenableFuture에 success·failure 콜백을 등록해 프로듀서 컨펌을 구현한다. 컨슈머 ack는 오토커밋을 끄고 Acknowledgment 인자로 매뉴얼 커밋한다. 성능과 데이터 성격에 따라 전달 방식을 선택하라며 발표를 마친다.