채팅 시스템을 개발하던 도중 코드 리뷰를 받게 되었는데, Kafka를 통해 메시지 발행, 채팅 저장을
하나의 트랜잭션에서 실행하는 코드를 보고, Kafka를 통해 메세지 발행이 실패하여도 채팅이 저장된다는 리뷰를 받게 되었고, 이를 해결하기 위해 여러 가지 방법을 찾아보았습니다.
먼저 문제의 함수를 먼저 보겠습니다.
함수를 간단하게 설명하면 채팅방안에 사람이 있다면 message를 읽은 메세지로 처리하고 메세지를 저장하고 메세지를 발행하는 함수입니다.
메세지 발행(messageSender.send)에서 오류가 생기면 메세지를 저장하지 않고 rollback을 하게 되지만,
메세지 발행은 무사히 됐으나 Transaction commit 과정에서 오류가 생긴다면 메세지를 발행했으나 메세지 저장이 되지 않는 상황이 생길 수 있습니다.
따라서 상대방은 채팅을 받지 않아야 되는 상황인데 채팅을 받고 데이터베이스에는 저장이 되지 않는 상황, 즉 일관성이 깨질 수 있습니다.
이를 해결하기 위해 Transactional Outbox Pattern을 적용하였습니다.
Transactional outbox (microservices.io)
위 글에 따르면 Transactional outbox Pattern 을 사용하여 메세지 브로커의 메세지 전송 및 메세지 저장을 atomic 하게 할 수 있습니다.
단계별로 보게 보면, 하나의 트랜잭션 안에서 메세지 저장, 메세지 발행을 Outbox Table에 저장한 후,
하나의 트랜잭션에서 Outbox Table을 읽어서 Message Relay를 통해 발행하여 정합성을 지키는 패턴입니다.
Message relay를 구현하기 위해 Polling을 사용하였습니다.
이를 코드로 구현하면
outboxRepository에 메세지를 저장한 후
Polling을 사용하여 Outbox에 담긴 메세지를 가져온 후 메세지를 발행하고 Outbox를 삭제하는 함수를 작성하였습니다.
현재 코드를 보게 되면 메세지의 중복 발행을 허용하기 때문에 컨슈머에서 데이터가 중복으로 쌓이지 않도록 멱등 처리가 필요한 상황입니다. 전송할 때와 달리 Exactly-Once Processing, 메세지가 정확히 한 번 처리되도록 해야합니다. 메세지의 고유 식별자를 통해 이미 처리한 메세지는 다시 처리하지 않도록 할 수 있습니다.
분산시스템에서 메세지를 안전하게 다루기 위해 Transactional Outbox Pattern에 대해 알아보고 구현하였습니다.
'스프링' 카테고리의 다른 글
쿠폰 발급 동시성 제어하기, 성능테스트로 성능 개선하기 (0) | 2023.11.29 |
---|---|
스케줄러와 Transactional 테스트 코드에서 생긴 문제 해결하기 (0) | 2023.10.09 |
Spring WebSocket Ping / Pong (2) | 2023.03.27 |
WAS, Servlet 용어 정리 (0) | 2023.01.04 |
Controller , Service , Repository 이해하기 (0) | 2022.05.19 |