🚨개요

사내에서 운영 중인 특정 API(tomcat기반 APP)의 JVM Heap 사용률이 지속적으로 70%-80% 이상 유지되며 성능 저하 가능성이 확인되었습니다. 이에 따라 Heap 증설 및 메모리 누수(Memory Leak) 여부를 포함한 분석을 수행했습니다.

 

🛠️ 이슈 사항

  • 4월 중순:
    • JVM모니터링 시작 후 운영 대시보드에서 JVM Heap 사용률이 80% 이상으로 꾸준히 유지됨을 감지
    • 초기 대응으로 개발자와 논의 후 API JVM Heap 사이즈를 1GB → 2GB로 증설
  • 5월 초:
    • 동일 API에서 다시 Heap 사용률이 80% 이상 점유됨
    • Heap 증가 패턴이 동일 API에서 반복됨에 따라 GC 및 Heap 사용 분석 진행

🔍 원인 분석

1. JVM 내부 지표 분석

  • Memory Pool 별 사용률 확인 결과,
    → PS Old Gen 영역의 메모리 사용량이 계속적으로 우상향 (증가-하기이미지참고)
    → Full GC 이후에도 충분히 회수되지 않는 패턴 확인
  • 이는 객체가 장기적으로 메모리에 머무는 현상을 의미하며, Memory Leak 가능성으로 추정

2. Heap Dump 분석

  • 대상 프로세스에서 heap dump 수행 
    ※ -live 옵션은 현재 GC 후 생존 객체만 덤프하며, 내부적으로 Full GC를 트리거
  •  
  • 명령어 : jmap -dump:live,format=b,file=heap_0513.hprof < tomcat PID>
  • Eclipse Memory Analyzer Tool(MAT)로 분석 결과:
    • Problem 1: MySQL Connector 관련 객체가 해제되지 않고 계속 유지


    • Problem 2: SSL Handshake 객체가 누적되어 소멸되지 않음
      
  • 해당 누수 객체들의 Retained Size를 분석한 결과:
    • 총 약 200MB 수준의 메모리 누수 확인
    • 전체 Heap 대비 소규모이나, FullGC 트리거 이후이기에, 누적 시 OutOfMemoryError 가능성 존재



     

3. 누수 규모 판단 관련 유의사항

  • jmap -dump:live는 GC 후 생존 객체만 덤프하므로,
    → 실제로는 GC에 의해 소멸된 일부 객체가 누락될 수 있음
    → 따라서 실제 누수 규모보다 작게 집계될 가능성 존재
    → 추후 heapdump 진행시에는 live 옵션 제외

✅ 조치 내용

  • 개발팀에 Problem 1,2 관련 메모리 누수 공유 및 수정 요청
    • MySQL Connector 재사용 방식 점검
    • SSL Handshake 객체 재사용 또는 해제 로직 적용
  • 모니터링 항목 추가
    • PS Old Gen 사용률 별도 대시보드화
    • Heap 사용량 및 GC 발생 간격 추이 주기적 분석
  • 향후 대응 계획
    • 주기적 HeapDump 스케줄링을 통한 누수 패턴 조기 탐지
    • GC 로그 기반 메모리 증감 패턴 수집 및 자동 알림 설정

 

1. 증상

  • API 서버에서 OutOfMemoryError: unable to create new native thread 발생
  • 해당 서버에서의 장애로 인해 서비스 접속 간헐적 에러 발생

2. 선조치

  • 장애 복구를 위해 배포를 시도했으나, SFTP 접근 중 secure 로그상에 do_exec_no_pty: fork: Resource temporarily unavailable 오류 발생
  • 톰캣 강제 종료 후 커널 파라미터 (open files, max user processes) 조정 및 배포 진행정상화

3. Root Cause 분석

(1) 주요 확인 사항

  1. OOM 발생 로그 분석 결과, 신규 쓰레드 생성이 불가능한 상태 (추가로 secure로그를 통해 파일 디스크립터 부족 가능성 시사)
  2. 동일한 환경의 이중화 서버 중 API 1번에서만 문제 발생

(2) 상세 분석

  • OOM 발생 후 JMX Exporter를 통한 모니터링을 시작한 결과, api 1에서만 
    쓰레드 개수가 비정상적으로 증가하는 현상 확인
  • jstack을 이용한 분석 결과, 아래와 같이 쓰레드풀에서 WAITING 상태의 쓰레드가 지속적으로 생성됨
"pool-xxxx-thread-1" #xxxx prio=5 os_prio=0 cpu=0.12ms elapsed=567.11s tid=0x00007f23e5048000 nid=0x740f9 waiting on condition  [0x00007f215cd92000]
   java.lang.Thread.State: WAITING (parking)
       at jdk.internal.misc.Unsafe.park(java.base@xx.x.xx/Native Method)
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ중략ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

(3) 원인 분석

  • API 소스 내 Java 쓰레드 생성 관련 코드를 점검한 결과,
    ExecutorService executor = Executors.newFixedThreadPool(5);
    특정소스의 위 코드에서 생성된 쓰레드풀을 명시적으로 종료 (shutdown)하지 않는 문제를 확인
  • 추가로 개발 담당자와의 프로세스 검토 결과, 로드밸런싱이 되는 서비스이기에, 1번 서버에 요청이 집중되는 현상의 원인 파악이 확인 필요
    •  VIP의 L4 로드밸런싱 설정이 Hash 기반 → 동일 클라이언트 IP에서 들어오는 요청이 특정 서버에 집중될 가능성이 있음
    •  이를 검증하기 위해 L4 로드밸런싱 메트릭을 Round Robin 방식으로 변경 후 테스트 진행
    •  그 결과, 1번과 2번 서버 모두에서 쓰레드풀 WAITING 증가 현상 확인 → 원인 파악 완료

(4) 주말간 OOM 발생 원인

  • 쓰레드풀 생성 후 회수가 이루어지지 않아 ulimitmax user processes 임계치 도달
  • 임의의 환경(max user processes 최소값)에서 테스트 코드 실행 시 동일한 OOM 발생 재현 확인

4. 조치 사항

  1. 소스 수정
  2. 커널 파라미터 조정
  3. 어플리케이션 모니터링 강화

5. 결론:

  • 쓰레드풀 미회수로 인한 max user processes 임계 도달이 OOM 발생의 직접적인 원인, 이를 해결하기 위해 소스 수정 및 커널파라미터 튜닝 진행

1. 들어가며.

Prometheus와 Grafana를 활용해 데이터를 수집하고 대시보드를 구성하던 중, 기존에 설정된 패널에서 "Found Duplicate Series for the Match Group" 오류가 발생하며 정상적으로 데이터를 표시하지 못하는 문제가 발생했습니다. 이 글에서는 해당 오류가 발생한 원인과 해결 방법을 정리합니다.

2. 오류 발생 원인

2.1 문제가 발생한 PromQL 쿼리 예시

오류가 발생한 PromQL 쿼리는 SNMP Exporter를 통해 수집한 ifHcInoctets 메트릭(네트워크 인터페이스의 바이트 단위 수신량)을 활용한 아래와 같은 형태였습니다.

ifHcInoctets{instance="$instance", type="$type", category="network"}

이 쿼리는 특정 instance 및 type에 해당하는 네트워크 트래픽 데이터를 가져오는 과정에서 중복된 시계열(series)이 존재하여 오류가 발생한 것입니다.

2.2 type 라벨 중복 문제

오류의 원인은 type 라벨에 두 개의 서로 다른 값(“active”와 “standby”) 이 존재했기 때문입니다. 이는 연동 작업 중 IP 설정 오류로 인해 원래 type='standby'로 설정되어야 할 대상이 type='active'로 잘못 설정되었고, 이후 이를 수정하여 Prometheus를 재시작하면서 동일한 instance에 대해 두 개의 type 값을 가지는 시계열이 생성되었기 때문입니다.

Prometheus에서는 동일한 instance와 같은 라벨 조합을 가진 메트릭이 중복될 경우, irate()와 같은 함수에서 오류가 발생할 수 있습니다. 아래는 실제 패널상에서 보여지는 에러이미지입니다.

 

3. 해결 방법

중복된 시계열을 해결하는 방법은 여러 가지가 있습니다.

3.1 불필요한 메트릭 삭제 (필자가 선택한 방법)

 *delete_series API 사용 방법

해당 메트릭이 실수로 수집된 것이므로 사용하지 않도록 제거하는 방법을 선택했습니다. Prometheus의 delete_series API를 사용하면 특정 메트릭을 삭제할 수 있습니다.

curl -X POST -g 'http://localhost:9090/api/v1/admin/tsdb/delete_series?match[]={instance="10.10.10.10",type="active"}'

이 명령어를 실행하면 특정 instance 값과 일치하는 시계열 데이터가 삭제됩니다.

* Admin API 활성화 필요

하지만 기본적으로 Prometheus는 admin-api가 비활성화되어 있어, 위 명령어를 실행하면 다음과 같은 오류가 발생할 수 있습니다.

이 경우 Prometheus 설정 파일 또는 실행 인자에 다음 옵션을 추가해야 합니다.

--web.enable-admin-api

필자는 Prometheus를 Docker Compose로 운영 중이므로, docker-compose.yml 파일에서 아래 이미지와 같이 Prometheus 컨테이너에 해당 옵션을 추가한 후 재시작했습니다.

 

설정을 추가한 후 Prometheus를 재시작하고 다시 delete_series API를 실행하면, 중복된 시계열이 삭제되며 정상적으로 Grafana 패널이 표시되는 것을 확인할 수 있습니다.

 

4. 결론

이번 오류는 Prometheus에서 동일한 instance에 대해 두 개의 type 값(active/standby)이 존재하여 중복된 시계열이 생성되면서 발생했습니다. 해결 방법으로는 불필요한 시계열 삭제, PromQL 쿼리 수정 등을 선택할 수 있으며, 필자는 delete_series API를 활용해 메트릭을 삭제하는 방식으로 해결했습니다.

Prometheus와 Grafana를 운영하면서 중복된 시계열 문제를 방지하려면, 라벨 관리에 신경 쓰고 불필요한 중복 데이터를 최소화하는 것이 중요합니다. 이후 동일한 문제가 발생하지 않도록 수집 설정을 주의 깊게 관리하는 것이 필요합니다.

 

 

0. 개요

기존 모니터링인 Nagios에서 시스템 메트릭 모니터링을 하기 위하여 Prometheus+Grafana+NodeExporter로

내부 서버 모니터링을 시작했습니다. 온프레미스 장비들에 node_exporter 설치 후 시스템 메트릭을 수집하는 도중, /var/log/messages에 다음과 같은 ACPI 관련 커널 오류가 발생하는것을 확인 할 수 있었습니다.  

ACPI Error: SMBus or IPMI write requires Buffer of length 66, found length 32 (20090903/exfield-286)

이 오류는 ACPI(Advanced Configuration and Power Interface)와 관련된 SMBus(System Management Bus) 또는 IPMI(Intelligent Platform Management Interface) 요청 중 버퍼 길이 불일치로 인해 발생합니다. 

 

1. 문제원인 

Node Exporter가 /sys/class/hwmon 경로에서 하드웨어 센서 값을 수집할 때 일부 하드웨어 플랫폼 또는 펌웨어가 이러한 쿼리를 완벽하게 지원하지 못하여 커널 오류가 발생합니다. 주로 펌웨어버젼이 낮을경우에 발생합니다.

 

2. 해결방법

이 문제를 해결하려면 Node Exporter에서 하드웨어 모니터링(hwmon) 수집기를 비활성화해야 합니다.

 

1) node_exporter 서비스파일 수정

1. Node_exporter 서비스 파일 오픈

sudo vi /etc/systemd/system/node_exporter.service

 

2. ExecStart 라인을 찾아 다음과 같이 --no-collector.hwmon 옵션을 추가합니다:

ExecStart=/usr/local/bin/node_exporter --no-collector.hwmon

 

만약 Centos6와 같은 구버젼의 Daemon파일은 아래와 같은절차로 진행합니다.

 

1. Node_exporter 데몬파일 오픈

vi /etc/init.d/node_exporter

2. start 함수에  --no-collector.hwmon 설정 추가

start(){
	echo -n"Starting $PROGNAME: "
    cd /usr/shared/node_exporter/
    daemon --user $USER --pidfile="$LOCKFILE" "$PROG --no-collector.hwmon &>$LOGFILE &"
    echo $(pidofproc $PROGNAME) >$LOCKFILE
    echo
}

2) 서비스 리로드 및 재시작

1. systemd 데몬을 리로드하여 변경사항을 적용합니다.

sudo systemctl daemon-reload

2. node_exporter 서비스를 재시작 합니다.

sudo systemctl restart node_exporter

 

위 작업 진행 후 더이상 /var/log/messgaes에 관련 로그가 발생하지 않습니다.

 

3. 결론

Node Exporter에서 hwmon 수집기를 비활성화하여 과도한 로그 발생을 방지했습니다. 하드웨어 센서 모니터링이 비활성화되는 단점이 있지만, ACPI 상호작용이 Node Exporter와 호환되지 않는 시스템에서는 실용적인 해결책입니다.

0. 들어가며

Rocky OS를 운영하며 제목과 같은 에러가 주기적으로 /var/log/messages에 발생하는것을 확인 할 수 있었습니다.

해당 메시지는 crontab에 등록된 작업이 실패 할 경우에 다량으로 발생하는것을 확인하였는데,

이 글은 위에 대한 조치 방안에 대해서 작성하였습니다.

1. 발생 원인

cron은 기본적으로 작업 실행 결과에 대한 내용을 메일로 발송하게끔 구성되어있습니다.

이로 인해  crontab에서 시행한 결과를 발송하려했으나, 서버내에 메일에 대한 설정이 없을 경우에

제목과 같은 에러가 발생합니다.

 

2. 조치 방안

조치 방안은 크게 두가지 방안이 있습니다.

 

2-1) 메일 프로세스 설치

간단한 조치 방법으로 메일서버를 설치해주면 해당 에러는 더이상 발생하지 않습니다.

[root@test ~]# dnf install postfix

==============================================================================================================================================================================================================
 꾸러미                                          구조                                           버전                                                     저장소                                          크기
==============================================================================================================================================================================================================
설치 중:
 postfix                                         x86_64                                         2:3.5.8-7.el8                                            baseos                                         1.5 M

연결 요약
==============================================================================================================================================================================================================

 

2-2) crond mail disable

서버 용도, 환경에 따라 메일 서버 설치가 어려운 경우에 crond 실행 결과 메일 발송을 비활성화 할 수 있습니다. 

man crond 중에 -m 옵션에 대한 내용입니다.

[root@test ~]# man crond
##중략##
-m     This  option allows you to specify a shell command to use for sending Cron mail output instead of using sendmail(8) This command must accept a fully formatted mail message (with headers)
              on standard input and send it as a mail message to the recipients specified in the mail headers.  Specifying the string off (i.e., crond -m off) will disable the sending of mail.

 

뒤에 내용을 보면 -m off를 통해 크론메일전송을 비활성화 할 수 있다는 내용이 있습니다.

아래와 같이 설정 및 재기동 해줍니다.

[root@test ~]# vi /etc/sysconfig/crond

# Settings for the CRON daemon.
# CRONDARGS= :  any extra command-line startup arguments for crond
CRONDARGS="-m off"

[root@test ~]# systemctl restart crond

 

 

 

'System > Linux' 카테고리의 다른 글

SELinux permissive mode logrotate issue  (0) 2024.08.01

+ Recent posts