Direct Exchange 사용
메세지 라우팅의 간결성
정확한 라우팅 Direct Exchange는 라우팅 키(Routing key)를 기반으로 메세지를 특정 큐로 직접 전달합니다.
특히 사용자가 특정 채팅방(큐)에만 메세지를 보내야 할 경우 적합합니다.
다른 라우팅 방식과 비교했을땐?
Fanout Exchange: 메시지를 모든 큐에 브로드캐스트함 → 1:1 채팅에서는 과도한 자원 낭비.
Topic Exchange: 패턴 매칭을 통한 라우팅 → 복잡성이 증가할 수 있음.
성능 효율성으로는 어떤 이점이 있나요?
낮은 오버헤드
메시지가 라우팅 키로 정확히 하나의 큐로만 전달되므로 라우팅 오버헤드가 적습니다. 메시지를 복사하거나 불필요한 큐로 보내는 작업이 없기 때문에 메시지 처리 속도가 빨라집니다.
1:1 채팅에서는 메시지가 해당 채팅방(큐)으로만 가야 하므로 최적의 선택입니다.
메모리 사용 최소화
메시지가 여러 큐로 복사되지 않으므로 메모리 사용량이 줄어듭니다.
그러면 Direct Exchange의 확장성에 대해 설명해주세요
채팅방 단위로 큐 관리 가능
Direct Exchange를 사용하면 채팅방 ID를 라우팅 키로 사용하여 큐를 관리할 수 있습니다.
새로운 채팅방이 생성되면 해당 큐를 등록하기만 하면 됩니다.
Ex) -> 채팅방 ID가 room.1이면, 라우팅 키는 "room.1"로 설정.
큐는 room.1에 연결된 큐로 생성.
큐 분리
큐를 채팅방별로 독립적으로 운영할 수 있으므로, 각 채팅방의 메시지 처리 부하를 분산할 수 있습니다.
부하가 많은 큐만 추가 컨슈머를 할당하거나 별도로 관리할 수 있습니다.
메세지 손실 방지도 있나요?
Direct Exchange는 라우팅 키를 사용해 메시지를 전달하므로, 라우팅 실패 가능성이 적습니다.
예외 상황(큐가 없거나 연결되지 않은 경우)이 발생하면 메시지가 반환되므로(설정에 따라), 이를 기반으로 재처리를 할 수 있습니다.
1:1 채팅 서비스에 적합한 이유가 무엇인가요?
개별 큐 운영 가능:
1:1 채팅에서는 각 채팅방(또는 사용자 간 연결)이 독립적이어야 하며, 메시지가 정확히 해당 방으로만 가야 합니다. Direct Exchange는 이 요구사항에 가장 적합합니다.
낮은 성능 비용:
메시지를 특정 큐로 바로 전달하기 때문에, 1:1 메시지 전송 시 네트워크 및 서버 자원의 낭비를 방지합니다.
Jmeter를 통해 동시에 100명의 유저를 10초간 점진적 과부화를 시켜 각각 10번의 채팅 메세지를 보내는 시나리오를 거쳤다.
총 1000건을 처리하는데
Throughput(초당 처리된 요청 수)대부분 1.1/sec이지만 마지막 총합은 100.8/sec로 나타나 높은 성능을 보여줍니다.
Received KB/sec(서버로부터 받은 데이터의 크기(KB 단위)) 대부분 0.34KB로 일정합니다.
Sent KB/sec(서버로 보낸 데이터의 크기(KB 단위)입니다) 대부분 0.16KB로 일정합니다.
테스트 시작일시는 02:39~02:40입니다.
각각 25.6%, 36%의 점유율을 가지고 있습니다.
Duration과 G1 Eden Space의 값도 일정합니다.
그리고 여기서 성능 고도화를 위해 비동기 처리를 할 예정입니다.
비동기 처리 방식 설명
메시지 큐 사용:
메시지를 처리하기 위해 messageQueue라는 큐를 사용합니다. 이는 생산자-소비자 패턴을 구현한 것입니다.
메시지는 offer 메서드를 통해 큐에 추가되며, 큐가 가득 찬 경우(offer가 false를 반환) 메시지를 드롭하고 로그를 남깁니다. 이를 통해 큐 오버플로 방지와 함께 백프레셔(backpressure) 효과를 제공합니다.
CompletableFuture를 이용한 비동기 작업:
메시지를 처리하는 소비자 역할을 하는 코드가 CompletableFuture.runAsync를 통해 비동기로 실행됩니다.
비동기 처리는 별도의 스레드에서 실행되므로 메인 스레드가 블로킹되지 않습니다. 즉, 메시지를 큐에 추가한 후 메인 스레드는 즉시 반환되고, 메시지 전송은 백그라운드에서 처리됩니다.
비동기 작업 중 발생할 수 있는 예외(InterruptedException)는 처리하여 시스템 안정성을 유지합니다.
STOMP로 메시지 전송:
큐에서 메시지를 가져온 후 STOMP를 통해 특정 대상(topic/room.<chatRoomId>)으로 메시지를 전송합니다.
이는 STOMP 프로토콜을 사용한 WebSocket 기반의 실시간 통신을 제공합니다.
비동기 처리시 얻는 이점은?
장점
효율적인 비동기 처리:
CompletableFuture를 통해 작업을 병렬로 처리하므로 메인 스레드가 차단되지 않습니다.
큐를 사용해 메시지를 순서대로 처리하므로 데이터 일관성을 유지할 수 있습니다.
백프레셔 지원:
큐가 가득 찬 경우 메시지를 드롭하여 불필요한 리소스 낭비를 방지합니다.
이로 인해 시스템이 과부하를 받지 않고 안정적으로 동작할 수 있습니다.
유연한 확장성:
큐 크기를 조정하거나, CompletableFuture의 스레드 풀을 조정하여 성능을 쉽게 확장할 수 있습니다.
STOMP와의 통합:
메시지 전송은 STOMP를 통해 이루어져 실시간 업데이트가 가능합니다.
처리속도는 CompletableFuture 적용전하고 거의 동일하다
2시 39분 30초에 측정한 Duration
704 us 속도로 측정되었지만
비동기 처리 후 Duration값은
575us로 22.4%가 개선되었다.
또한 I/O Overview rate에서 성능차이가 나타났는데
비동기 처리 전에는 13.5 ops/s 로 나타나였지만
비동기 처리 후 11.1 ops/s 로 17% 감소하여 I/O가 효율적으로 관리됨을 나타내고 있습니다.
QOS 설정
메시지 속도와 안정성을 최적화하기 위해 RabbitMQ의 QoS (Quality of Service) 설정을 조정합니다. 예를 들어, 메시지의 prefetch count를 조정해 서버가 감당할 수 있는 수준으로 메시지를 처리하게 할 수 있습니다.
QOS로 얻는 이점은?
1. 메시지 처리 안정성
이점: QoS는 한 번에 처리할 수 있는 메시지의 수를 제한하거나, 특정 조건에서 메시지 처리를 제어합니다. 이를 통해 시스템이 과도하게 많은 메시지로 인해 메모리 초과, 연결 끊김, 또는 처리 실패와 같은 문제를 겪지 않게 됩니다.
구체적 효과:
메시지가 과부하로 인해 손실되는 것을 방지.
소비자(Consumer)가 안정적으로 메시지를 처리할 수 있도록 보장.
2. 처리량 최적화
이점: 메시지 큐에서 한 번에 처리하는 메시지 수를 적절히 조정하면 시스템의 처리량(Throughput)을 최적화할 수 있습니다.
구체적 효과:
QoS를 설정하지 않은 경우, 너무 많은 메시지를 동시에 가져오면 처리 속도가 떨어지거나 응답 시간이 늘어날 수 있습니다.
QoS 설정으로 메시지를 제한적으로 가져오면 메시지 처리 속도와 효율성이 높아집니다.
3. 메시지 중복 처리 방지
이점: QoS는 메시지의 자동 Ack(인증) 또는 수동 Ack 방식을 활용하여 메시지가 **정확히 한 번 처리(At-Least Once Processing)**되도록 보장합니다.
구체적 효과:
네트워크나 소비자 장애로 인해 메시지가 중복으로 처리되지 않게 함.
메시지가 누락되지 않도록 보장(최소 한 번 처리 보장).
4. 네트워크 안정성
이점: QoS는 네트워크의 데이터 흐름을 제어하여 네트워크 병목 현상을 예방합니다.
구체적 효과:
속도 조절(Flow Control) 기능을 통해, 메시지 큐가 가득 찼을 때 메시지 전송 속도를 줄여 네트워크를 안정적으로 유지.
생산자(Producer)가 메시지를 너무 빠르게 전송하여 브로커가 과부하되는 상황을 방지.
즉 QoS는 메시징 시스템의 안정성, 효율성, 그리고 리소스 활용을 최적화하여 시스템의 성능을 크게 향상시킬 수 있습니다. 특히, 고부하 환경이나 실시간 메시징 시스템에서는 필수적인 기능으로 간주됩니다.
해당 코드에는 어떤 기능을 동작하고 있나요?
QoS 적용: Prefetch Count를 통해 한 번에 처리하는 메시지 수를 제한하여 시스템 안정성과 성능을 향상시킵니다.
자동 메시지 확인: Ack 모드를 설정하여 RabbitMQ와의 메시지 처리를 안정적으로 관리합니다.
RabbitMQ 리스너 설정: Spring과 통합하여 메시지를 비동기로 처리할 수 있는 리스너를 생성합니다.
그러면 이렇게 코드 짜면 어떤 이점이 있나요?
안정적인 메시지 처리:
메시지가 너무 많이 소비되어 메모리가 초과되는 문제를 방지.
메시지 처리 실패 시 RabbitMQ가 자동으로 다시 큐에 메시지를 넣어 재처리 가능.
리소스 최적화:
Prefetch Count로 인해 처리할 수 있는 메시지 수를 제한하여, CPU와 메모리를 과도하게 사용하지 않음.
개발 편의성:
Ack 신호를 자동으로 처리하기 때문에, 수동으로 메시지 상태를 관리할 필요가 없음.
QOS를 통해 개선된 부분이
Collections와 Pause Durations에서 있었다.
Garbage Collections
Java와 같은 프로그래밍 언어에서 더 이상 사용되지 않는 메모리 객체를 자동으로 회수하여 메모리를 효율적으로 관리하는 기능입니다. 개발자가 직접 메모리를 해제할 필요 없이, JVM이 이 작업을 수행한다.
3시10분 15초에 테스트한 QOS 적용전은 0.0957 ops/s가 나왔습니다.
3시 45분 45초에 테스트한 QOS 적용후는 0.0733 ops/s가 나와 30.6% 개선되었습니다.
Pause Duration(일시 중지 시간)
Garbage Collection(GC) 과정에서 Stop-the-World(STW) 이벤트로 인해 애플리케이션의 실행이 중단되는 시간을 의미한다.
3시 10분 15초에 테스트 결과는 5 ms가 나왔지만
3시42분 45초에 테스트결과는 8.75ms가 나와 오히려 성능이 더디게 되었다.
하지만 이것은 소비자가 한 번에 가져올 수 있는 메세지 수(Prefetch Count)가 제한되기 때문에 일어나는 일입니다. 그러기에
이로 인해, 메시지를 처리하는 동안 메모리가 더 오래 점유될 수 있고, 이로 인해 GC(Garbage Collection) 작업이 더 자주 발생하거나 Pause Duration이 증가할 수 있습니다.
그러면 QOS 설정으로 Pause Duration 증가가 나쁜 건가요?
반드시 그렇지 않습니다. QoS 설정은 전체 시스템 안정성을 위한 중요한 도구이며, 다음과 같은 상황에서는 오히려 바람직할 수 있습니다.
시스템 안정성 확보
QoS로 인해 메시지를 처리하는 속도가 제한되면, 시스템이 과부하를 방지하고 안정적으로 동작할 수 있습니다.
결과적으로 CPU 사용량이 줄어들거나, 처리량이 일정하게 유지될 수 있습니다.
메시지 손실 방지
QoS 설정으로 메시지 처리 속도를 제어하면, 소비자가 처리할 수 없는 메시지가 누적되는 상황을 방지할 수 있습니다.
이는 메시지 손실을 줄이고, 안정적인 성능을 유지하는 데 도움이 됩니다.
GC의 효과적인 동작
메시지 처리량을 제어하면 힙 메모리 사용 패턴이 안정화되며, GC가 더 효과적으로 작동할 수 있는 기회를 제공합니다.
GC Pause Duration 증가가 특정 상황에서는 GC의 효과적인 동작을 보여주는 신호일 수도 있습니다.
'우리 지금 만나' 카테고리의 다른 글
1대1매칭 이후 1대1 채팅으로 이어지는 시퀸스 다이어 그램-(11)(우리 지금 만나) (0) | 2024.11.28 |
---|---|
1대1 채팅 시퀸스 다이어그램 정리-(10)(우리 지금 만나) (2) | 2024.11.28 |
1대1 채팅 서비스 구현 계획-(8)(우리 지금 만나) (0) | 2024.11.07 |
인프라 난항2-(7)(우리 지금 만나) (2) | 2024.11.03 |
소모임 단건 조회에 RedisLimiter를 적용 후 Jmeter 테스트-(6)(우리 지금 만나) (0) | 2024.10.30 |