디스크 I/O 스케줄러(Deadline, CFQ 등)와 ActiveMQ의 궁합?
엔터프라이즈 환경에서 메시지 브로커(ActiveMQ 등)의 성능을 영혼까지 끌어올리기 위해 인프라 엔지니어들은 JVM 힙 메모리를 튜닝하고, KahaDB의 저널 파일 크기를 조절하며, 비동기 전송(Async Send) 옵션을 켭니다. 하지만 이러한 애플리케이션 레벨의 튜닝을 모두 마쳤음에도 불구하고 트래픽 스파이크 시 원인 불명의 I/O Wait가 치솟고 지연 시간(Latency)이 출렁인다면, 시선을 운영체제(OS)의 가장 깊숙한 곳으로 돌려야 합니다.
바로 리눅스 커널이 디스크의 읽기/쓰기 순서를 통제하는 지휘자, '디스크 I/O 스케줄러(Disk I/O Scheduler)'입니다.
ActiveMQ의 스토리지 엔진은 디스크 I/O 패턴이 매우 극단적이고 독특합니다. 이 가이드에서는 브로커의 I/O 특성을 분석하고, 리눅스의 대표적인 I/O 스케줄러들(CFQ, Deadline, NOOP) 중 어떤 것이 ActiveMQ와 최악의 상극인지, 그리고 어떤 것이 최고의 궁합을 자랑하는지 상세히 해부합니다.

1. ActiveMQ 스토리지 엔진의 I/O 특성 분석
I/O 스케줄러를 평가하기 전에, ActiveMQ(KahaDB 또는 Artemis Journal)가 디스크를 어떻게 혹사시키는지 정확히 이해해야 합니다.
- 극단적인 순차 쓰기 (Sequential Write): 브로커는 수신한 메시지를 저널 파일(
db-*.log)의 끝에 덧붙이는(Append-Only) 방식만 사용합니다. 디스크 헤드가 이리저리 튈 필요가 없는 가장 단순하고 빠른 쓰기 패턴입니다. - 빈번하고 동기화된 플러시 (Frequent fsync): 메시지 유실을 막기 위해 브로커는 데이터를 OS 캐시에만 두지 않고, 물리적 디스크에 안전하게 기록될 때까지 메인 스레드를 대기시키는 동기화(
fsync) 작업을 초당 수십~수백 번 호출합니다. - 돌발적인 무작위 읽기 (Random Read): 평소에는 읽기 작업이 없지만, 메모리가 꽉 차서 페이징(Paging)이 발생하거나 과거의 메시지를 조회할 때 갑작스럽게 디스크 곳곳을 긁어오는 무작위 읽기가 발생합니다.
즉, 브로커는 "다른 프로세스의 방해를 받지 않고, 내가 원할 때 즉각적으로 디스크에 데이터를 내려쓸 수 있는 예측 가능한 응답성"을 가장 절실하게 요구합니다.
2. 최악의 궁합: CFQ (Completely Fair Queuing)
리눅스의 전통적인 기본 스케줄러인 CFQ는 '완전히 공평한 큐잉'이라는 이름처럼, 시스템에서 실행 중인 모든 프로세스에게 디스크 I/O 대역폭을 공평하게 나누어 주려 노력합니다. 일반적인 데스크톱이나 웹 서버에서는 훌륭하지만, ActiveMQ와 같은 데이터베이스/브로커 인프라에서는 재앙을 부르는 최악의 스케줄러입니다.
- 불필요한 대기(Idle)와 지연: CFQ는 한 프로세스(예: ActiveMQ)가 I/O 작업을 마친 후에도, 혹시 이 프로세스가 연속해서 I/O를 요청할까 봐 디스크를 잠시 쉬게(Idle) 하면서 기다려줍니다. 1밀리초가 아쉬운 브로커 입장에서는 이 '공평함을 위한 헛된 대기 시간'이 치명적인 병목으로 작용합니다.
- 노이즈 프로세스의 간섭: 백그라운드에서 로그 로테이션(Logrotate)이나 서버 백업 에이전트, 백신 스캔 작업이 도는 순간 시스템의 비극이 시작됩니다. CFQ는 백업 프로세스에게도 공평한 I/O 시간을 분배하느라, 정작 0.001초 만에 디스크 쓰기(fsync)를 마치고 클라이언트에게 ACK를 줘야 하는 ActiveMQ의 메인 라우팅 스레드를 뒤로 미뤄버립니다.
- 결과: 평소에는 멀쩡하다가 백그라운드 작업만 돌면 브로커의 프로듀서들이 타임아웃 예외를 뿜어내며 멈춰버리는 현상의 주범입니다.
3. 가장 안정적인 궁합: Deadline (또는 mq-deadline)
디스크가 HDD이거나 구형 SATA SSD 환경일 때, ActiveMQ의 성능을 가장 예측 가능하게 방어해 주는 훌륭한 스케줄러는 Deadline입니다.
- 기한(Deadline) 보장 아키텍처: Deadline 스케줄러는 공평함 따위는 신경 쓰지 않습니다. I/O 요청이 큐에 들어온 순간부터 타이머를 작동시켜, "무슨 일이 있어도 이 시간(Write는 보통 5초, Read는 0.5초) 안에는 반드시 물리적 디스크로 내려보낸다"는 기한을 엄격하게 지킵니다.
- 읽기(Read) 우선 처리 메커니즘: I/O의 세계에서 쓰기(Write) 작업은 OS 캐시를 활용해 비동기로 미룰 수 있지만, 읽기(Read) 작업은 데이터를 가져올 때까지 애플리케이션 스레드가 완전히 멈춰야 합니다. Deadline 스케줄러는 Read 요청의 기한(0.5초)을 Write보다 훨씬 짧게 주어 읽기를 우선 처리합니다.
- 결과: 메모리가 가득 차서 디스크로 밀려난 메시지를 다시 퍼 올려야 하는 페이징(Depaging) 상황에서, 브로커가 디스크 응답을 기다리느라 멈춰있는 블로킹 타임을 최소화하여 지연 시간(Latency)을 매우 안정적으로 평탄화해 줍니다.
4. 현대 인프라의 궁극적 해답: NOOP (또는 None)
NOOP(No Operation) 스케줄러는 이름 그대로 "OS 커널 단에서는 아무런 스케줄링 연산도 하지 않겠다"는 극단적인 방식입니다. 애플리케이션이 I/O를 요청하면, OS는 이를 재배열하거나 병합하지 않고 들어온 순서(FIFO)대로 즉시 디스크 하드웨어 컨트롤러에 던져버립니다.
과거 HDD 시절에는 헤드가 디스크 판을 이리저리 긁는 물리적 지연 때문에 OS의 스케줄링이 필수적이었습니다. 하지만 인프라 스토리지가 기업용 NVMe나 고성능 엔터프라이즈 SSD로 도배된 현대 환경에서는 NOOP이 절대적인 진리입니다.
- 하드웨어への 완벽한 위임: SSD와 NVMe는 내부에 수십 개의 낸드 플래시 메모리 칩과 자체적인 쿼드코어 컨트롤러를 탑재하고 있습니다. 디스크가 알아서 수십 개의 I/O를 병렬로 완벽하게 처리할 수 있는데, 리눅스 커널의 낡은 스케줄러가 중간에 끼어들어 순서를 바꾸려 드는 것 자체가 CPU 낭비이자 병목입니다.
- 제로 오버헤드: OS 스케줄러가 작동하지 않으므로, 커널의 CPU 연산 오버헤드가 완전히 사라집니다.
- 결과: 순차 쓰기(Append)만 주구장창 수행하는 ActiveMQ의 특성과, 랜덤 I/O 지연이 0에 수렴하는 NVMe의 하드웨어 특성이 맞물려 브로커가 낼 수 있는 최대 대역폭(Line Rate)을 한 치의 손실 없이 뽑아낼 수 있습니다. (최신 리눅스 커널에서는 멀티 큐 기반의
none으로 표기됩니다.)
5. 적용 및 확인 가이드 (실무 튜닝)
현재 운영 중인 ActiveMQ 서버의 스토리지 I/O 스케줄러 상태를 확인하고 변경하는 것은 서비스 무중단으로 즉각 적용 가능합니다.
1. 현재 스케줄러 확인 (디스크가 sda인 경우):
cat /sys/block/sda/queue/scheduler
출력 예시: noop deadline [cfq] (대괄호로 쳐진 것이 현재 활성화된 스케줄러입니다.) 만약 cfq나 mq-deadline에 묶여 있다면 즉시 튜닝이 필요합니다.
2. 런타임 즉시 변경 (NOOP 적용 시):
echo noop > /sys/block/sda/queue/scheduler
또는 최신 RHEL/CentOS 8 이상의 멀티 큐 블록 장치 환경이라면:
echo none > /sys/block/sda/queue/scheduler
3. 영구 적용 (OS 재부팅 시 초기화 방지):
서버가 재부팅되어도 설정이 유지되도록 하려면, GRUB 설정(/etc/default/grub)의 GRUB_CMDLINE_LINUX 라인 끝에 elevator=noop (또는 elevator=none)을 추가하고 grub2-mkconfig 명령어를 실행하여 부트로더를 업데이트해야 합니다.
결론적으로, 메시지 브로커는 단순한 웹 서버가 아니라 극도의 I/O 민감도를 지닌 인프라의 심장입니다. 백그라운드 프로세스가 브로커의 발목을 잡는 것을 방치하지 마십시오. 현재 사용 중인 디스크 하드웨어가 NVMe/SSD라면 커널의 간섭을 완전히 끄는 NOOP(None)을, 구형 스토리지 환경이라면 응답 시간을 멱등하게 보장하는 Deadline을 선택하여 어떠한 상황에서도 흔들림 없는 브로커 파이프라인을 완성하시기 바랍니다.
'1. 개발 > 1.8. ActiveMQ' 카테고리의 다른 글
| 가상화 환경(AWS EBS 등)에서 스토리지 처리량(IOPS) 부족 시 징후? (1) | 2026.04.08 |
|---|---|
| 저장소 수준의 'Duplicate ID Cache' 크기 최적화 방법은? (0) | 2026.04.08 |
| Artemis의 'Journal-type' 선택 가이드(AsyncIO vs NIO)? (0) | 2026.04.07 |
| maxBrowsePageSize가 대량 큐 조회 시 브로커 메모리에 주는 부하? (0) | 2026.04.07 |
| 브로커 재시작 없이 저장소 설정을 변경할 수 있는 범위는? (0) | 2026.04.05 |