반응형
우리 팀은 MSA에서 SNS를 사용하여 서버 간 통신을 할 때 어떻게 완전한 Zeropayload를 적용했는지 소개한다.
우리 팀은 MSA 아키텍처를 구성하면서 Zipkin을 사용하여 분산추적을 설계했고 그 과정은 traceId를 SNS메시지를 통해 서로 주고받아서 추적하도록 설계했는데 여기에서 무언가 찝찝함이 계속 느껴졌다. 왜냐하면 지금 설계한 아키텍처에서는 Zeropayload정책이 지켜지지 않은 것 같았기 때문이다. 최소한이면서 꼭 필요한 정보만 담는 것이 Zeropayload 정책인데 나는 message안에 memberId라는 값 1개만 넣어서 메시지를 발행하는 것이 아니라 traceId를 같이 담아서 보내줘야만 했다.
이런 상황을 해결해보고자 다른 기업이나 지식 공유자 분들께서는 어떤 방식으로 서버 간 ZeropayLoad를 적용시키고 traceId를 사용하여 분산 추적을 하고 있는지 찾아보던 도중 토스에서 올려준 SLASH23의 세미나 영상을 보게 되었고 여기에서 큰 도움을 얻었다.
아래의 토스 SLASH23 영상은 문제가 될 시 바로 내리도록 하겠습니다.
이 영상에서 분산 추적 확장을 설명할 때 Header에 X-Forwarded-For값으로 IP를 넣어줘서 추적 범위를 확장하는 부분을 보게 되었는데 이것을 보면서 우리 프로젝트에 대입해 봤더니 HTTP 요청 자체에 무언가를 담아주는 것이 있다면 당연히 SNS에도 Header처럼 무언가 담아서 보낼 수 있는 게 존재하지 않을까?라는 생각으로 확장이 되었고 바로 이것에 대해 알아봤더니 SNS에는 MessageAttribues라는 Http요청의 Header 같은 것이 존재했다. 우리는 이것을 활용해서 메시지에는 memberId만 담아주고 MessageAttributes에 traceId를 넣어 SNS메시지를 발행하여 Zeropayload정책을 제대로 적용시키게 되었다.
SNS의 Message Attributes가 뭘까?
1. 간단하게 알아보는 Message Attributes
1-1. Message Attributes란
- SNS의 Message Attributes는 메시지에 대한 구조화된 메타데이터 항목(예: 타임스탬프, 지리 공간 데이터, 서명, 식별자 등)을 제공하는 데 사용된다. 이러한 메시지 속성은 선택적이며 메시지 본문과 별도로 존재하지만 함께 전송된다. 수신자는 메시지 본문을 처리하기 전에 이 정보를 사용하여 메시지를 어떻게 처리할지 결정할 수 있다.
1-2. Message Attributes의 전송방식
- Message Attributes는 메시지 구조가 문자열일 때만 전송되며, JSON 형식이 아닐 때는 전송되지 않는다. 또한, 모바일 엔드포인트에 대한 푸시 알림 메시지를 구조화하는 데도 사용될 수 있다. 이러한 Message Attributes는 푸시 알림 메시지를 구조화하는 데만 사용되며, Amazon SQS 엔드포인트로 메시지를 전송할 때와 같이 엔드포인트에 전달되지는 않는다.
1-3. Message Attributes의 속성
- 각 메시지 속성은 이름, 유형, 값으로 구성되며, 이 모든 항목은 메시지 크기 제한(256 KB)에 포함된다. 이름은 다양한 문자를 포함할 수 있으며, 256자까지 가능하다. 유형은 String, String.Array, Number, Binary 등을 포함하며, 값은 사용자가 지정한 메시지 속성 값이다.
이전에 나는 Zeropayload 정책을 제대로 적용시키지 못했다.
1. 무언가 잘못된 Zeropayload 정책 사용
1-1. Zeropayload정책이 뭔데?
- Zeropayload 정책은 메시지 큐(SNS, SQS 같은)를 사용할 때 실제 데이터 대신에 최소한의 정보만 전달하는 것이다. 예를 들어, 멤버 서버에서 {"memberId": "2"} 이런 식으로 최소한의 정보만 전달하고, 실제 데이터는 레시피 서버에서 memberId를 기반으로 멤버 서버에 FeignClient 요청을 보내서 멤버 서버의 데이터베이스에서 조회하는 방식이다.
1-2. 우리는 프로젝트에 Zeropayload를 제대로 적용시키지 못했다.
- 처음 설계했을때는 SNS 메시지에 memberId와 traceId를 추가해서 다음과 같이 만들었다.
- 이렇게 메시지를 발행하는 것은 Zeropayload 정책을 제대로 지키지 않은 것이다. Zeropayload 정책의 기본 아이디어는 "메시지 자체에는 최소한의 정보만 담고, 그 정보를 통해 필요한 데이터를 조회하는 것"이기 때문이다. 위와같이 SNS메시지에 traceId를 추가해서 발행하면 이것은 추가적인 정보를 전달하는 것이 되어버린다.
1-3. 왜 적용시키지 못했는가?
- 우리도 처음에 설계할 때는 memberId만 담아주려고 했다. 근데 Zipkin을 연동하고 나서 문제가 발생했다. Zipkin은 http요청을 기준으로만 추적이 가능한데 AWS의 SNS, SQS는 http요청이지만 Zipkin이 SNS, SQS의 자동 추적기능을 지원하지 않아서 추적이 불가능했다. 그래서 우리는 추적을 수동으로 적용시키기로 했고 그렇게 하기 위해서는 서버끼리 통신할 때 호출되는 메서드는 같은 TraceId(추적ID)를 가져야 했다.
- 즉, 멤버 서버에서 TraceId를 SNS 메시지에 담아서 발행하고 레시피 서버의 SqsListener는 메시지를 polling해온 다음 그 메시지 안에서 TraceId를 꺼내서 수동으로 그 TraceId를 사용해서 메서드를 동작시킨다. 이렇게 하면 양 서버가 통신할때 같은 TraceId를 사용하게 되기 때문에 추적이 하나의 묶음으로 연결되었다.
그럼 이전에는 어떻게 코드를 작성해서 SNS 메시지를 발행했을까?
2. MessageAttributes를 사용하기 전에 작성한 코드
2-1. 닉네임 변경 스프링 이벤트 리스너
지금 프로젝트에서 SNS 메시지 발행은 다음과 같이 이뤄진다.
- 먼저 유저가 "닉네임 변경" 요청을 하면 내부적으로 NicknamChangeSpringEvent "스프링 이벤트"를 발행한다.
- 스프링 이벤트 리스너가 동작해서 message에 memberId, traceId를 둘 다 담아서 messageJson을 만든다. 이 형태는 다음과 같다.
- 위에서 만들어준 messageJson을 인자로 담아서 snsService.publishNicknameToTopic 메서드를 호출한다
2-2. SNS 메시지 발행
- 호출받은 publishNicknameToTopic 메서드는 인자로 받은 message값을 publishRequest 객체를 만들때 사용한다. 그리고 이렇게 만들어진 publishRequest는 snsClient.publish 메서드에 인자로서 담아줘서 SNS 메시지를 발행할 때 사용한다.
나는 위와 같이 message를 발행할때 memberId, traceId를 모두 담아서 보내줬다. 이렇게 하는 것은 traceId에 따라 로직이 변하게 될 테니 결합이 생긴 것이라고 생각한다. 예를 들어 내가 생각하는 좋은 설계는 멤버 서버에서 traceId를 통해 추적하던 것을 다른 추적 tool로 변경해도 레시피 서버에서는 예외나 문제가 발생하지 않고 동작해야 한다.
3. MessageAttributes 적용해서 Zeropayload정책 완성하기
3-1. 스프링 이벤트 리스너 수정
- 기존에는 messageJson에 traceId를 담아줬었다. 이제부터는 traceId를 넣지 않고 memberId값만 넣도록 코드를 수정했다. 대신 traceId는 SNS발행을 책임지는 publishNicknameToTopic메서드에 인자로 담아줬다.
3-2. SNS메시지를 발행할 때 messageAttributes 사용하기
- 이전에 SNS메시지를 발행하는 publishNicknameToTopic 메서드는 호출되면 인자로 전달받은 message값을 그대로 PublishRequest객체에 담은 후 SNS에 이 message를 발행했다. 이때 message값은 memberId, traceId를 가지고 있었다.
- 지금부터 설명할 수정한 코드에서는 PublishRequest에 memberId만 들어가 있는 message 객체를 담아서 SNS 메시지를 발행한다. 즉, message 본문은 {"memberId":"2"} 이런 식으로 작성되고 이전에 존재하던 traceId는 제거되었다. 그래도 traceId는 SNS메시지를 발행할 때 전달이 된다. 왜냐하면 SNS가 가진 messageAttributes라는 속성을 사용할 것이기 때문이다.
3-3. messageAttributes를 사용하여 SNS 메시지 발행하기
- messageAttributes는 Map<String, MessageAttributeValue> 형태로 선언하였으며 이 Map안에는 인자로 받은 traceId 값을 담아줬다. 이후 publishRequest 객체를 생성할 때 builder에서 체이닝을 통해 .messageAttributes()메서드를 연결해서 그 안에 messageAttributes를 세팅해 줬다. 이렇게 함으로써 내가 전송할 SNS 메시지 본문(message)에는 memberId값만 들어가게 되었다.
messageAttributes를 사용하도록 코드를 수정한 뒤에 message본문에는 memberId만 들어가게 되었다. 드디어 진정한 ZeroPayload방식을 적용한 것이다.
이렇게 뭔가 아쉬움이 느껴지던 ZeroPayload정책을 제대로 동작하도록 리팩토링했다. 요즘 문제가 발생했을 때 해결방법을 찾아보고자 생각도 많이 하지만 세미나도 엄청 많이 찾아서 보는데 이렇게 얻는 정보와 지식이 상당히 많다고 느껴진다. 많은 지식 공유자 분들이 좋은 정보를 제공해 주셔서 나처럼 공부할 때 도움을 받는 개발자들이 많을 것이라고 생각된다. 항상 감사함을 느끼며 나도 언젠가는 이런 발표를 해서 누군가에게 도움이 될 수 있는 사람이 개발자가 되고 싶다는 생각이 들었다.
이 글을 작성하게 된 이유는 배치와도 관련이 있다. 배치를 설계하던 도중 traceId에 대한 결함을 발견하고 리팩토링을 진행하게 되었기 때문이다. 이러한 내용은 생략을 했지만 배치가 어떻게 구성되었는지 궁금하다면 글을 읽어보면 좋을 것이다.
내용에 참고한 SNS에 대한 아마존 공식 가이드
이 포스트는 Team chillwave에서 사이드 프로젝트 중 적용했던 부분을 다시 공부하며 기록한 것입니다.
시간이 괜찮다면 팀원 '평양냉면 7'님의 블로그도 한번 봐주세요 :)
반응형
'Spring MSA' 카테고리의 다른 글
[MSA] Transactional Outbox Pattern (1) | 2024.10.13 |
---|---|
[Spring] 헥사고날 아키텍처 (1) | 2024.08.10 |
MSA 환경에서 SNS 메시지 재발행을 위한 스프링 배치 및 스케쥴러 구현 (1) | 2023.11.28 |
Spring MSA 프로젝트에서 단일 책임 원칙을 지키기 위한 리팩토링 (3) | 2023.11.24 |
Spring MSA: Sampling으로 원하는 http요청만 Zipkin으로 추적하기 (0) | 2023.11.23 |