← 학습 카테고리

Learn

Kafka

42개 모듈 · 현재 23번째

Kafka 모듈 23/42 kafka-learn-23

JVM·OS 모니터링과 로깅 설정

Kafka: The Definitive Guide (O'Reilly, 2017, 1st Edition) — Neha Narkhede, Gwen Shapira, Todd Palino Chapter 10: Monitoring Kafka — JVM Monitoring / OS Monitoring / Logging (pp.231-235)

브로커 지표만으로는 성능 저하의 원인을 다 볼 수 없다. 이 모듈은 GC 상태를 보는 JVM 지표, JVM이 알려 주는 파일 디스크립터 수, OS 수준에서 봐야 할 CPU·메모리·디스크·네트워크 지표와 시스템 로드 평균의 진짜 의미, 그리고 어떤 로거를 어떤 레벨로 분리해 둘지를 다룬다.

가비지 컬렉션 모니터링

JVM에서 반드시 봐야 할 것은 가비지 컬렉션(GC) 상태다. GC 활동이 늘어나면 브로커 성능이 떨어지므로, 브로커 지표에서 보이는 변화의 이유를 설명해 주기도 한다.

어떤 빈을 봐야 하는지는 사용하는 JRE와 GC 설정에 따라 다르다. Oracle Java 1.8 JRE에서 G1 가비지 컬렉션을 쓰는 경우 빈은 두 개다.

- Full GC cycles: java.lang:type=GarbageCollector,name=G1 Old Generation - Young GC cycles: java.lang:type=GarbageCollector,name=G1 Young Generation

GC 용어에서 'Old'와 'Full'은 같은 것을 가리킨다. 각 지표에서 볼 속성은 CollectionCount와 CollectionTime이다. CollectionCount는 JVM 시작 이후 해당 타입 GC 사이클의 횟수이고, CollectionTime은 JVM 시작 이후 그 타입의 GC에 쓴 시간(밀리초)이다. 둘 다 카운터라서 모니터링 시스템에서 단위 시간당 GC 횟수와 GC 시간을 산출할 수 있고, GC 사이클당 평균 시간도 계산할 수 있다(다만 평상시 운영에서는 유용성이 떨어진다).

각 지표에는 LastGcInfo 속성도 있다. 다섯 개 필드로 이루어진 복합 값으로, 해당 타입의 마지막 GC 사이클 정보를 준다. 여기서 중요한 값은 duration으로, 마지막 GC 사이클이 몇 밀리초 걸렸는지를 알려 준다. 나머지 값(GcThreadCount, id, startTime, endTime)은 참고용이며 유용성이 낮다. 이 속성으로 모든 GC 사이클의 소요 시간을 볼 수는 없다는 점도 유의해야 한다. 특히 young GC는 매우 빈번하게 일어나기 때문이다.

핵심 포인트

  • G1 기준 빈 두 개: G1 Old Generation(=Full), G1 Young Generation
  • 속성 CollectionCount / CollectionTime은 JVM 시작 이후 누적 카운터
  • LastGcInfo의 duration이 마지막 GC 소요 시간 — 모든 사이클을 볼 수는 없다

JVM이 알려 주는 OS 정보 — 파일 디스크립터

JVM은 java.lang:type=OperatingSystem 빈으로 OS 정보를 일부 제공하지만, 브로커가 도는 시스템에 대해 알아야 할 모든 것을 담지는 못한다.

다만 OS에서 직접 수집하기 어려운 두 속성은 여기서 얻을 가치가 있다. MaxFileDescriptorCount는 JVM이 열 수 있는 파일 디스크립터(FD)의 최대 개수, OpenFileDescriptorCount는 현재 열려 있는 FD 수다.

Kafka는 모든 로그 세그먼트와 모든 네트워크 연결마다 FD를 열기 때문에 이 수가 빠르게 늘어난다. 네트워크 연결이 제대로 닫히지 않는 문제가 있으면 브로커가 허용된 FD를 순식간에 소진할 수 있다.

핵심 포인트

  • java.lang:type=OperatingSystem 빈에서 MaxFileDescriptorCount / OpenFileDescriptorCount 확인
  • 로그 세그먼트마다, 네트워크 연결마다 FD가 열리므로 빠르게 증가한다
  • 연결이 제대로 닫히지 않으면 FD 고갈로 이어진다

OS 모니터링 — CPU, 메모리, 디스크, 네트워크

JVM만으로는 부족하므로 OS 자체에서도 지표를 모아야 한다. 반드시 봐야 할 영역은 CPU 사용량, 메모리 사용량, 디스크 사용량, 디스크 IO, 네트워크 사용량이다.

**CPU**는 최소한 시스템 로드 평균을 본다. 여기에 더해 타입별 CPU 사용 비율(us 유저 공간, sy 커널 공간, ni 저우선순위 프로세스, id 유휴, wa 디스크 대기, hi 하드웨어 인터럽트, si 소프트웨어 인터럽트, st 하이퍼바이저 대기)을 잡아 두면 유용하다.

시스템 로드는 자주 오해된다. 로드 평균은 실행 가능(runnable) 상태로 프로세서를 기다리는 프로세스의 개수이며, Linux는 디스크를 기다리는 것처럼 인터럽트 불가능한 sleep 상태의 스레드도 포함한다. 세 개의 숫자로 표시되고 각각 1분, 5분, 15분 평균이다. 단일 CPU 시스템에서 값 1은 시스템이 100% 로드되어 항상 실행 대기 스레드가 하나 있다는 뜻이다. 따라서 다중 CPU 시스템에서 100%에 해당하는 로드 평균값은 CPU 개수와 같다. 프로세서가 24개라면 100%는 로드 평균 24다.

Kafka 브로커는 요청 처리에 상당한 연산을 쓰므로 CPU 사용률 추적이 중요하다.

**메모리**는 브로커 자체에 대해서는 상대적으로 덜 중요하다. Kafka는 보통 상대적으로 작은 JVM 힙으로 실행되고, 압축 기능에 힙 바깥 메모리를 조금 쓰지만, 대부분의 시스템 메모리는 캐시로 쓰이도록 남겨 둔다. 그래도 다른 애플리케이션이 브로커를 침범하지 않는지 확인하기 위해 메모리 사용률을 추적하고, 스왑 총량과 여유량을 모니터링해 스왑 메모리가 쓰이지 않는지 확인해야 한다.

**디스크**는 Kafka에서 가장 중요한 서브시스템이다. 모든 메시지가 디스크에 영속화되므로 Kafka 성능은 디스크 성능에 크게 좌우된다. 디스크 공간과 inode(유닉스 파일시스템의 파일·디렉터리 메타데이터 객체) 사용량을 모두 모니터링해 공간이 바닥나지 않게 해야 하며, 특히 Kafka 데이터가 저장되는 파티션에서 그렇다. 디스크 IO 통계도 봐야 하는데, 최소한 Kafka 데이터가 있는 디스크에 대해 초당 읽기/쓰기, 평균 읽기/쓰기 큐 크기, 평균 대기 시간, 디스크 사용률(utilization percentage)을 모니터링한다.

**네트워크**는 인바운드·아웃바운드 트래픽 양이며 보통 초당 비트로 보고된다. 여기서 기억할 것은, 컨슈머가 없어도 브로커로 들어온 1비트마다 토픽의 replication factor에 해당하는 만큼 아웃바운드 비트가 발생한다는 점이다. 컨슈머 수에 따라 아웃바운드 트래픽은 인바운드보다 쉽게 한 자릿수 크기 이상 커질 수 있다. 알람 임계값을 정할 때 이를 고려해야 한다.

핵심 포인트

  • OS 필수 영역: CPU, 메모리, 디스크 공간·IO, 네트워크
  • 로드 평균 = 실행 대기 프로세스 수(+Linux는 인터럽트 불가 sleep 포함). CPU 24개면 100%는 24
  • 브로커 메모리는 작은 힙 + 나머지는 페이지 캐시. 스왑 사용 여부를 감시한다
  • 디스크는 공간과 inode를 함께 모니터링, IO는 읽기/쓰기 rate·큐 크기·대기 시간·사용률
  • 컨슈머가 없어도 아웃바운드는 복제 팩터만큼 발생한다

로깅 — 어떤 로거를 어떤 레벨로 분리할 것인가

Kafka 브로커는 그대로 두면 몇 분 만에 디스크를 로그 메시지로 채운다. 유용한 정보를 얻으려면 올바른 로거를 올바른 레벨로 켜야 한다.

모든 메시지를 INFO 레벨로 남기는 것만으로도 브로커 상태에 대한 중요한 정보를 상당량 얻는다. 다만 몇 개의 로거는 별도 파일로 분리하는 것이 깔끔하다.

- kafka.controller (INFO, 별도 파일): 클러스터 컨트롤러 관련 메시지 전용. 어느 시점에나 브로커 한 대만 컨트롤러이므로 한 대만 이 로거에 쓴다. 토픽 생성·수정, 브로커 상태 변경, preferred replica election이나 파티션 이동 같은 클러스터 활동이 기록된다. - kafka.server.ClientQuotaManager (INFO, 별도 파일): produce/consume 쿼터 활동 관련 메시지. 유용하지만 메인 브로커 로그에 섞이지 않는 편이 낫다.

로그 컴팩션 스레드의 상태에 대한 로깅도 유용하다. 이 스레드들의 건강 상태를 보여 주는 단일 지표가 없고, 단 하나의 파티션에서 컴팩션이 실패해도 로그 컴팩션 스레드 전체가 조용히 멈출 수 있기 때문이다. kafka.log.LogCleaner, kafka.log.Cleaner, kafka.log.LogCleanerManager 로거를 DEBUG 레벨로 켜면 컴팩션되는 각 파티션의 크기와 메시지 수를 포함한 스레드 상태 정보가 출력된다. 정상 운영 시 로그 양이 많지 않아서 기본으로 켜 두어도 부담이 없다.

디버깅 시에만 켜는 로거도 있다. kafka.request.logger를 DEBUG 또는 TRACE로 켜면 브로커로 온 모든 요청 정보가 기록된다. DEBUG에서는 연결 엔드포인트, 요청 타이밍, 요약 정보가, TRACE에서는 토픽·파티션 정보까지 포함되어 메시지 페이로드를 제외한 거의 모든 요청 정보가 남는다. 어느 레벨이든 데이터 양이 매우 많으므로 디버깅에 꼭 필요한 경우가 아니면 켜지 않는 것이 좋다.

핵심 포인트

  • kafka.controller (INFO) 와 kafka.server.ClientQuotaManager (INFO) 는 별도 파일로 분리
  • 로그 컴팩션 스레드는 건강 상태 지표가 없고 한 파티션 실패로 조용히 전체가 멈출 수 있다
  • kafka.log.LogCleaner / Cleaner / LogCleanerManager 를 DEBUG로 켜 두면 부담 없이 상태 확인 가능
  • kafka.request.logger는 DEBUG/TRACE 모두 데이터가 매우 많아 디버깅 시에만 사용

이 모듈과 연관된 문항 1개가 문제 은행에 있습니다.

이 내용으로 문제 풀어보기