가상 면접 사례로 배우는 대규모 시스템 설계 기초 2 - 분산 메시지 큐 설계를 정리한 내용입니다.
분산 메시지 큐 기능, 비기능 요구사항
분산 메시지 큐 기능 요구사항은 메시지 반복 수신, 오래된 이력 데이터 삭제, 메시지 전달 방식이다. 비기능 요구사항으로는 높은 대역폭과 낮은 전송, 규모 확장성, 지속성 및 내구성이다.
point-to-point vs publish-subscribe
메시지 큐는 소비 방식에 따라 point-to-point, publish-subscribe 로 구분된다. point-to-point 는 한 소비자가 메시지를 소비하고 큐에게 알리면(acknowledment) 메시지가 삭제된다. 반면, publish-subscribe 는 topic 개념을 도입해 topic 을 구독하는 소비자에게 메시지를 전달하는 방식이다.
partition
파티션은 “토픽에 보관되는 데이터 양이 커져 서버 한 대로 감당하기 힘든 상황이 벌어지면 어떻게 될지?” 에 관한 해결 방법이다. 파티션은 토픽의 메시지를 큐 클러스터의 파티션으로 균등하게 분할한다. 파티션을 브로커로 분산하면 높은 확장성을 제공한다.
각 토픽의 파티션은 FIFO 로 동작하므로 메시지 순서가 유지된다. 파티션 내의 메시지 위치는 오프셋(offset) 을 통해 관리한다. 생산자 보낸 메시지는 토픽의 파티션 중 하나로 보낸다. 만약 메시지의 키 값이 같다면 같은 파티션으로 보낸다. 만약 키가 없다면 무작위로 파티션을 선택해 저장된다.
각 구독자는 해당 토픽을 구성하는 파티션의 일부를 담당하며, 해당 토픽을 구독하는 소비자 그룹을 이룬다. 소비자 그룹은 토픽의 메시지를 소비하기 위해 서로 협력한다. 하나의 소비자 그룹은 여러 토픽을 구독하고, 오프셋을 별도로 관리한다.
개략적 설계, 상세 설계
브로커 : 파티션을 유지한다.
저장소
- 데이터 저장소 : 메시지 저장 공간 (파티션 별로 관리된다.)
- 상태 저장소 : 소비자 상태를 저장하는 공간
- 메타데이터 : 토픽 설정, 토픽 속성 등을 저장 공간
조정 서비스
- 서비스 탐색 : 브로커의 healthcheck 하는 서비스
- 리더 선출 : 브로커 중 하나를 컨트롤러 역할을 당담하고, 파티션 배치를 책임진다.
- 주키퍼 : 컨트롤러 선출을 담당한다.
상세 설계
- 하드웨어의 회전 디스크의 디스크 캐시 전략, 디스크 기반 자료 구조 활용
- 메시지가 생산자로 부터 소비자에게 까지 전달하는 순간까지 불변 상태가 가능하도록 자료구조를 설계해야 한다.
- 일괄 처리 우선 시스템으로 설게한다. 소규모의 I/O 를 최소화해 높은 대역폭을 제공하기 위함이다.
데이터 저장소
메시지 큐의 소비 패턴
- read, write 가 빈번하게 일어난다.
- update, delete 연산이 발생하지 않는다.
- 순차적인 read, write 가 대부분이다.
데이터를 관리하는 방법
데이터 베이스는 read, write 를 빈번한 형태를 제공하는데 한계가 있어 병목 현상이 발생할 우려가 있어 좋은 선택지가 아니다.
위와 같은 메시지 소비 패턴을 해결하기 위한 방법으로 쓰기 우선 로그 (WAL : Write-Ahead Log) 가 도입되었다. 쓰기 우선 로그는 새로운 항목이 추가되기만 하는 일반 파일이다. 비슷한 예시로는 MySQL redo log 이다.
로그 파일은 점진적으로 증가하고 일반적으로 순차적으로 적재된다. 디스크는 순차 읽기, 쓰기는 좋은 성능을 보이기 때문에 디스크에 보관할 것을 권장한다.
로그 파일은 또한 점진적으로 증가하므로 세그먼트 단위로 나눠 관리하는 형태이다. 하지만 로그 파일은 한없이 증가할 수 없으므로 세그먼트 단위로 나눠 관리한다. 메시지는 활성 상태의 세그먼트 파일에 추가한다.
메시지 자료 구조
메시 구조는 높은 대역폭 달성이 열쇠다. 메시지 정의 시, 계약(contract) 가 필요한 이유는 계약이 없을 경우 메시지를 받아들일 수 없어 메시지를 변경해야 하고 이 경우 값 비싼 복사가 일어날 수 있다는 점이다. 이와 같은 문제 해결을 위해 분산 메시지 큐는 메시지 형태를 계약(contract) 로 정의되어 있다.
메시지 키는 파티션을 정의할 때 사용되며, 파티션은 hash(key) % numPartition 으로 결정된다. 메시지 데이터 스키마는 아래와 같다.
소비자의 작업 흐름
소비자 모델은 푸시 모델과 풀 모델이 있다. 모델 차이는 메시지를 전달하는 주체의 차이인데, 브로커가 메시지를 전달한다면 푸시 모델, 소비자가 메시지를 가져가는 것을 풀 모델이라 한다.
푸시 모델의 장점은 낮은 지연이다. 메시지를 받는 즉시 소비자에게 보낼 수 있다. 단점은 주로 생산자의 데이터 전송 속도에 따라 부하가 걸릴 가능성이 있어 그에 맞는 처리 가능한 자원을 소비자가 준비되어 있어야 한다.
풀 모델의 장점은 처리 속도를 소비자가 결정할 수 있고, 일괄 처리가 적합하다. 푸시 모델의 경우 소비자가 메시지 처리 속도를 알지 못해 전송 속도가 높을 경우 소비자의 버퍼에 쌓아 처리를 기다린다. 반면 풀 모델은 소비자가 마지막으로 가져간 로그 위치 다음의 모든 메시지를 한 번에 가져갈 수 있어 메시지 일괄 처리에 적합하다.
하지만 풀 모델 또한 단점이 존재한다. 처리할 메시지가 없는 경우에 불필요한 데이터 요청으로 소비자 컴퓨팅 자원이 낭비된다. 이를 해결하기 위해 많은 메시지 큐가 롱 폴링 모드를 지원한다. 롱 폴링 모드는 일정 시간을 기다리도록 하는 것을 말한다.
사본 동기화
한 노드의 장애로 인한 메시지 소실을 막기 위해 여러 파티션을 두고, 각 파티션은 여러 사본으로 복제한다. ISR(In-Sync Replication) 은 리더와 동기화된 사본을 일컫는 말이다. ISR 은 리더와 동기화된 메시지 차이를 확인해 사본 여부를 판단한다. ISR 은 성능과 영속성의 타협점이다. 메시지를 관리하는 가장 안전한 방법은 메시지 수신 응답(acknowledment, ACK) 을 전달하기 전에 모든 사본을 동기화 하는 것이지만 사본 중 하나라도 동기화가 늦으면 파티션이 모두 느려지거나 못 쓸 수 있어 주의해야 한다.
메시지 수신 응답(ACK)
- ACK=all : 생산자는 모든 ISR 이 메시지 수신한 뒤에 ACK 응답을 받는다. ISR 응답을 기다려야 하므로 메시지 보내기 위한 시간이 길어지지만 영속성 측명에서 장점이 있다.
- ACK=1 : 생산자는 리더가 메시지를 저장하고 나면 바로 ACK 응답을 받는다. 데이터 동기화될 때까지 기다리지 않아 응답 지연은 개선되지만 ACK 응답 전달 후, 리더에 장애가 발생하면 사본을 반영하지 못해 복구할 수 없는 단점이 있다.
- ACK=0 : 생산자는 보낸 메시지에 대한 ACK 를 기다리지 않는다. 어떤 재시도를 하지 않으며 낮은 응답 지연을 달성하며 메시지 손실을 감수하는 구성이다.
메시지 전달 방식
최대 한 번(at-most once) : 메시지가 전달 과정에서 소실되더라도 다시 전달되는 일이 없다. 생산자가 토픽에 비동기적으로 메시지를 보내고 수신 응답을 기다리지 않는다.(ACK=0) 메시지 전달이 실패해도 다시 시도하지 않는다. 소비자는 메시지를 읽고 처리하기 전에 오프셋부터 갱신한다.
최소 한 번(at-least-once) : 같은 메시지가 한 번 이상 전달될 수 있으나 메시지 소실은 발생하지 않는 전달 방식이다. 생산자는 메시지가 브로커에게 전달되었음을 반드시 확인하고, 실패하거나 타임아웃이 발생할 경우 계속 재시도한다. 소비가는 데이터를 성공적으로 처리한 뒤에만 오프셋을 갱신한다. 메시지는 브로커나 소비자에게 한 번 이상 전달될 수 있다.
정확히 한 번(exactly once) : 브로커나 소비자에게 정확히 한 번 전송하는 것을 보장하는 방식이다. 구현하기 까다로운 전송 방식이며, 시스템 성능 및 구현 복잡도 측면에서 난이도가 높다.
Refefence
- https://ably.com/blog/achieving-exactly-once-message-processing-with-ably#types-of-messaging-semantics
- https://programmingsharing.com/point-to-point-and-publish-subscribe-messaging-model-2efc4d2b6726
- https://product.kyobobook.co.kr/detail/S000211656186
- https://www.cloudamqp.com/blog/part1-rabbitmq-best-practice.html
'architecture' 카테고리의 다른 글
[데이터 중심 애플리케이션 설계] 분산 시스템의 골칫거리 (1) | 2024.06.08 |
---|---|
[가상 면접 사례로 배우는 대규모 시스템 설계 기초 2] 호텔 예약 시스템 (0) | 2024.05.21 |
[가상 면접 사례로 배우는 대규모 시스템 설계 기초 2] 광고 클릭 이벤트 집계 (1) | 2024.05.11 |
[가상 면접 사례로 배우는 대규모 시스템 설계 기초 2] 지표 모니터링 및 경보 시스템 (0) | 2024.05.04 |
모놀로식, MSA 장단점 (1) | 2024.03.04 |