---
description: '트러블슈팅: ackMode = ‘NONE’'
---

# RabbitMq: ackMode = None

## 상황

부하테스트를 여러번 시도 했었는데, 이상한 현상이 동일하게 발견됐습니다.

* API 서버는 RabbitMQ로 초당 수백 개의 메시지를 쏟아내고 있었습니다.
* RabbitMQ는 Consumer의 처리량과 관계없이 메시지를 소비하고 있었습니다.
* Consumer 서버의 CPU 사용률은 20% 미만으로 매우 여유로웠고, 스레드들도 분명히 일을 하고 있었습니다.
*   **문제 발생:** 처음에는 50개 이상의 스레드가 정상적으로 동작하는 듯 보이다가, 특정 시점 이후 **메시지를 처리하는 스레드가 갑자기 2개로 급격하게 줄어들었습니다.**

    * 증상 1: Consumer 서버에서 2개의 스레드만 처리를 하게됨
    * 증상 2: RabbitMq에서 분당 소비되는 메시지 수가 급격하게 줄음

    <figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FSUYpVYGB35Kr5sAzHhxc%2Fimage.png?alt=media&#x26;token=aeb25dea-32fe-46c1-b9d8-580ff8cec7b5" alt=""><figcaption></figcaption></figure>

    * 증상 3: RabbitMq의 분당 컨텍스트 스위칭 횟수가 급격하게 줄음

    <figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FNpGxRks612OYzC36nFqK%2Fimage.png?alt=media&#x26;token=3438ccf6-1a37-4ce4-8545-65f44cd3e88d" alt=""><figcaption></figcaption></figure>

이 미스터리를 풀기 위해 두가지 가설을 세우고 검증하는 과정을 거쳤습니다.



## **가설 1: CPU 라이브락(Livelock)**

* **내용:** `busyWait`와 같은 CPU 집약적 코드가 너무 많은 스레드와 만나, OS 스케줄러가 스레드 교체에만 모든 자원을 낭비하는 '라이브락' 상태에 빠졌을 것이다.

```yaml
vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st gu
 1  0      0 250348  25144 681148    0    0    78    26  255    1  2  0 98  0  0  0
 0  0      0 250348  25144 681148    0    0     0     0  192  296  0  1 100  0  0  0
 0  0      0 250348  25144 681148    0    0     0     0  270  367  1  0 99  0  0  0
 0  0      0 250348  25144 681148    0    0     0     0 1486 1242  1  1 98  0  1  0
 0  0      0 250348  25144 681148    0    0     0    12  196  311  1  0 100  0  0  0
 1  0      0 248584  25144 681148    0    0     0     0  287  292  5  1 95  0  0  0
 0  0      0 248584  25144 681148    0    0     0     0  208  295  1  0 99  0  0  0
 0  0      0 248584  25144 681148    0    0     0    24  179  290  0  0 100  0  0  0
 0  0      0 248584  25144 681148    0    0     0     0  162  274  1  0 100  0  0  0
 0  0      0 248584  25144 681148    0    0     0     0  183  287  0  0 100  0  0  0
 0  0      0 248584  25144 681152    0    0     0     0  215  315  2  0 98  0  0  0
 0  0      0 248584  25144 681152    0    0     0     0  151  274  0  0 100  0  0  0
 0  0      0 248584  25144 681152    0    0     0     0  164  289  0  0 99  0  0  0
 0  0      0 248584  25144 681152    0    0     0     0  160  266  0  0 100  0  0  0
 ......
```

* **기각:** `vmstat` 지표 확인 결과, 실제 CPU는 대부분 유휴(Idle) 상태였음이 보여 이 가설은 폐기.



## **가설 2: TCP Zero-Window로 인한 네트워크 마비**

* **내용:** `ackMode=NONE`으로 인해 Consumer가 메시지를 감당 못 하여 TCP 수신 버퍼가 가득 찼고, 이로 인해 RabbitMQ에 메시지 전송 중단을 요청하여 모든 것이 멈췄을 것이다.
* **기각:** RabbitMQ의 `Ready` **큐에 메시지가 쌓인 적이 없습니다**. 메시지는 계속 끊임없이 Consumer 애플리케이션으로 전달되고 있었습니다.



## **가설 3: 메시지가 Heap에 쌓이고, GC가 자주 실행**

* **내용:** 메시지들은 Consumer 애플리케이션으로 성공적으로 전달되며 **많은 객체들이 JVM 힙 메모리(Heap Memory)에 쌓이고, GC에 부담을 줬을 것이다.**
*   **기각:**

    * **GC Pressure** 그래프는 0%를 기록하며 GC로 인한 CPU 부하가 전혀 없음을 보여주었습니다.
    * **Pause Durations** 그래프 역시 GC로 인한 멈춤 시간이 10\~30ms 수준으로 매우 짧고 안정적이어서, 시스템이 GC 때문에 멈췄다고 보기 어려웠습니다.

    <figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FzjfnNfBawXbWCVgj7fZO%2Fimage.png?alt=media&#x26;token=2af5cda1-da23-4026-a303-9793dfec8453" alt=""><figcaption></figcaption></figure>

    * **JVM Heap** 그래프는 완만하게 사용량이 증가할 뿐, 시스템을 붕괴시킬 만한 급격한 메모리 적체 현상은 보이지 않았습니다.

    <figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FUzf47YrX5d8gJ1AZJk2X%2Fimage.png?alt=media&#x26;token=4828d7cc-4c9f-430b-9628-3a82e763de0c" alt=""><figcaption></figcaption></figure>



## AckMode = MANUAL

모든 가설이 기각된 지금, 매우 이상한 상황에 놓였습니다.

* CPU는 충분히 여유롭습니다 (라이브락 X)
* 메시지는 Consumer 애플리케이션까지 잘 전달되고 있습니다 (네트워크 마비 X)
* 메모리와 GC는 안정적입니다 (GC 문제 X)
* 분명히 50개 이상의 스레드가 일을 할 준비가 되어 있습니다.

하지만 현실에서는 단 2개의 스레드만이 일을 처리하고 있었습니다. 모든 자원이 정상인데도 애플리케이션이 제 성능을 내지 못하는 이 상황은, 문제의 원인이 하드웨어나 OS 같은 외부 환경이 아닌 **애플리케이션과 프레임워크의 내부 동작 방식**에 있다고 생각이 듭니다.

지금까지 기각된 모든 가설들의 공통된 전제는 **`ackMode=NONE`** 이었습니다. 이 설정은 RabbitMQ와 Consumer 사이의 **'흐름 제어(Flow Control)'**&#xB97C; 완전히 제거해 버립니다.

그렇다면, 문제의 원인이 CPU, 네트워크, 메모리 같은 자원의 문제가 아니라, **흐름 제어가 부재한 이 특수한 상태에서 스프링 AMQP 프레임워크(`SimpleMessageListenerContainer`)가 예상치 못하게 동작**하는 것일지도 모른다고 생각하게 됐습니다.

어쩌면 프레임워크가 스스로를 보호하기 위해, 혹은 비정상적인 상황에 대응하기 위해 스레드 수를 극단적으로 줄여버리는 것일 수 있습니다.

이 가설을 검증하는 가장 확실한 방법은 `ackMode=NONE` 설정을 `MANUAL`로 변경하여, **시스템에 흐름 제어를 도입**해보는 것이었습니다.



## **결과 1: 병목 지점의 이동**

가장 먼저 눈에 띈 긍정적인 변화는 **메시지가 쌓이는 위치**였습니다.

* **변경 전 (`ackMode=NONE`):** RabbitMQ의 `Ready` 큐는 항상 0이었습니다. 모든 메시지는 Consume 서버로 보내지고 있었습니다.
* **변경 후 (`ackMode=MANUAL`):** 이제 **메시지들은 RabbitMQ의 `Ready` 큐에 차곡차곡 쌓이기 시작했습니다.** 이제 메시지 백로그는 Consumer의 메모리가 아닌, 안정적이고 영속성이 보장되는 RabbitMQ에 의해 관리되었고, 제대로된 모니터링이 가능해졌습니다.

## **결과 2: 모든 스레드의 활성화**

`ackMode`를 변경하자, 이전의 가장 큰 미스터리였던 **'2개의 스레드만 일하던 현상'이 거짓말처럼 사라졌습니다.**

* **변경 전:** `ackMode=NONE`이 유발한 내부 마비와 '패닉 리셋' 때문에, 50개 이상의 스레드 중 단 2개만 동작하는 기현상이 발생했습니다.
* **변경 후:** 명시적인 흐름 제어가 도입되자, 스프링 컨테이너의 '비상 재시작' 메커니즘이 더 이상 발동하지 않았습니다. 설정된 **50개 이상의 모든 Consumer 스레드가 깨어나 동시에 RabbitMQ에 메시지를 요청하며 바쁘게 움직이기 시작했습니다.**

위 과정으로 인해 유추할 수 있는게 버퍼 문제라고 생각이 됩니다.



`ackMode=MANUAL`로의 전환 실험은 매우 성공적이었습니다. '2개 스레드 문제'의 정확한 이유는 설명하지 못하지만, 원인은 ackMode 였던것을 찾아냈습니다.

하지만, 흐름 제어 문제를 해결하자마자 Consumer 서버의 CPU 사용률이 100%에 도달하는 상황이 발생하였고, 새로운 도전 과제가 생기게 됐습니다.
