Post

애플리케이션 문제 원인 파악하기

애플리케이션 문제 원인에 대해 파악하는 방법을 알아봅니다.

문제 해결 과정

USE 방법론

문제를 정의했으니 원인이 어디에있는지 알아보자. 원인을 찾기 위해서는 USE 방법론을 사요하면 좋다.

  • U(Utilization): 리소스 사용량
  • S(Saturation): 리소스의 포화도
  • E(Error): 에러

여기서 리소스는 크게 하드웨어 리소스와 소프트웨어 리소스로 나뉜다.

  • 하드웨어 리소스
    • CPU
    • Disk
    • Network
    • Memory
  • 소프트웨어 리소스
    • Thread
    • Connection
    • File

문제가 발생하면 E -> U -> S 순서대로 원인을 찾아간다.

  1. E: 시스템이나 애플리케이션의 에러 로그를 확인한다.
  2. U: 리소스의 사용률을 확인한다.
  3. S: 리소스의 포화도를 확인한다.

U(Utilization, 사용률)

사용률이란 단위시간동안 평균적으로 자원을 얼마나 사용했는지를 나타낸다.

  • CPU와 Memory의 경우에는 1분 동안의 사용률 100%와 같이 표현된다.
  • Disk IO, Network IO는 초당 몇바이트를 읽고 쓰는가로 표현된다.
  • 소프트웨어 리소스는 갯수로 셀 수 있다.
    • ex. 최대 쓰레드 X개 중 X개를 사용하고 있다.

      S(Saturation, 포화도)

      사용률 이라는 개념에는 평균의 문제가 존재한다. CPU 사용률이 100%를 찍은적이 있어도 대부분의 시간동안 사용률이 낮았다면 평균적인 사용률은 낮아지기 때문이다. 그래서 사용률에 문제가 있어보인다면 포화도라는 지표를 관찰하게 된다. 포화도란 각각의 리소스가 포화되었을 때 어떤현상이 일어나는지 관찰함으로써 측정할 수 있다.

CPU, DISK의 경우에는 Run Queue, Device Queue에 대기하고 있는 스레드의 수, 메모리는 Swap 발생 여부, 네트워크는 Overrun, Drop 현상 발생 여부로 알 수 있다.

E(Error, 에러)

  • 시스템 로그를 기록하는 /var/log/syslog, /var/log/dmesg에서 error, fail등의 키워드로 에러를 찾을 수 있다.
  • 애플리케이션은 개발자가 직접 에러로그를 수집할 수 있다.
  • 5XX 상태코드를 가지는 HTTP응답이 얼마나 되는지도 체크해볼 수 있다.

원인 찾기

여러 명령어를 사용해서 어떤 프로세스가 하드웨어 리소스를 얼마나 사용하고 있는지 알아낼 수 있다. 스레드 덤프, 힙 덤프, 패킷 덤프를 사용해서 좀 더 자세히 들어가 볼 수 있다.

스레드 덤프 (CPU 이상)

스레드 덤프란 특정 시점의 스레드의 상황에 대해서 기록한 것을 말한다.

  • 덤프 시점의 스레드의 상태(RUNNABLE, BLOCKED)
  • 호출 스택
  • 스레드가 가지고 있는 Lock 등이 그러한 것이다.

리눅스 명령어를 통해서 쉽게 스레드 덤프를 볼 수 있다.

1
2
jps // 현재 실행중인 자바 프로세스의 PID를 얻어온다.
jstack 12275 > thread.dump // 덤프를 가져와서 thread.dump 파일에 쓴다.
  • 스레드 덤프를 살펴보는 핵심은 멈춰있는 스레드를 찾는 것이다.
  • 스레드 덤프를 가져올 때는 5~10초 주기로 최소 5번 가져오는 것이 좋다. (출처)
  • 스레드가 RUNNABLE 상태로 멈춰있는 경우 -> 무한루프에 빠진 경우
  • 스레드가 BLOCKED 상태로 멈춰있는 경우 -> 데드락이나, 하나의 스레드가 Lock을 오래잡고 있는 상황
  • 스레드 덤프는 성능에 영향을 주지않는다. 따라서 문제가 발생한 경우에는 바로 스레드 덤프를 떠보는 것이 좋다.

IntelliJ Ultimate 나 Arthas라는 툴을 사용하면 좀더 편리하게 분석이 가능하다. 인텔리 제이 스레드 덤프 분석

힙 덤프 (메모리 이상)

힙 덤프란 특정 시점에 각 객체의 메모리 점유 상황을 기록하는 것을 말합니다.

다음의 명령어를 사용해서 힙 덤프를 조회할 수 있다.

1
jmap -dump:[live],format=b, file=<file-path> <java process id>

예를 들어

1
jmap -dump:live,format=b,file=/home/ubuntu/heap.hprof 11541`

옵션의 의미는

  • -dump:live 살아있는 오브젝트 들만
  • format=b 바이너리 형식으로
  • file=/home/ubuntu/heap.hprof 16574 /home/ubuntu 폴더에 heap.hprof 라는 이름으로 PID가 16574인 자바 프로세스에 대한 힙 덤프를 생성하라

라는 의미이다.

Intellij Ultimate를 사용한다면 hprof 확장자로 만든 힙 덤프파일을 분석할 수 있자. 자세한 사항은 Open an external profiling report 를 확인하자. 인

위 이미지가 Intellij Ultimate로 힙 덤프를 분석한 내용이다. 힙 덤프는 가져오는데 시간이 오래걸릴 수 있고, 그 시간동안 서비스가 불가능 하기 때문에 Memory Leak이나 OOM(Out Of Memory)가 발생했을 때만 힙 덤프를 떠봐야한다.

패킷 덤프(네크워크 이상)

  • 패킷 덤프를 사용하면 일정시간 오고가는 패킷에 대한 상세한 정보를 알 수 있다.
  • 요청 IP, 포트번호로 필터링해서 조회할 수도 있다.
  • 평소의 패킷 흐름과 문제가 발생했을 때 패킷흐름을 비교해보면서 이상현상을 찾을 수 있다.

다음과 같은 리눅스 명령어로 패킷 덤프를 떠볼 수 있다.

1
tcpdump -i eth0 tcp port 8080 -w dump.pcap

eth0 네트워크 인터페이스, TCP 프로토콜, 8080 포트를 사용한 패킷 캡쳐본을 dump.pcap 파일로 생성한다.

내 네트워크 인터페이스 정보는 ifconfig 명령으로 확인할 수 있다.

이렇게 생성된 패킷 덤프는 로컬 컴퓨터로 가져와서 Wireshark를 사용해 분석할 수 있다.

이러한 것들을 이용해서…

스레드 덤프, 힙 덤프, 패킷 덤프와 같은 여러가지 데이터를 통해서 이상현상과 관련있어보이는 여러가지 단서를 찾을 수 있다. 단서들을 찾았다면 가설을 세우고 검증하는 과정을 통해서 문제의 원인을 파악하게 된다.

지금까지의 방법론을 정의하면 다음과 같다.

  1. 문제를 정의하고, 정말로 문제가 맞는지 고민한다.
  2. USE 방법론을 사용해 큰 틀에서 원인이 있을만한 곳을 고민한다.
  3. 각종 툴을 사용해 세세하게 단서를 수집한다.
  4. 가설을 세우고 검증하는 과정을 통해 원인을 파악한다.
  5. 왜 문제가 발생하였는지 근본적인 원인을 파악한다.

애플리케이션 응답이 없을 때 포인트

무한루프

애플리케이션 로직이 무한루프에 빠지게 되면 쓰레드가 CPU하나를 점유하게 되면서 여유 CPU 리소스의 부족으로 처리량이 떨어지고 응답도 느려지게 된다. 문제가 된 무한루프 로직에 다른 CPU자원이 계속해서 들어올 경우에는 무한루프 로직에 CPU자원이 모두 점유당해 응답을 할 수 없게되는 문제로 발전할 수 있다.

이러한 문제가 발생할 경우 CPU사용량이 점차적으로 올라가고, 내려오지 않는다. CPU 코어 하나만 100% 사용되고 있는 것도 무한루프 문제일 가능성이 크다. USE 방법론을 사용해서 CPU 점유율에 문제가 있다는 것을 파악했다면 스레드 덤프를 사용해서 어떤 스레드가 무한루프에 빠진것인지 분석할 수 있다.

데드락

데드락이란 락을 잡고있는 스레드들이 서로의 락을 기다리는 상황을 말한다. 데드락이 발생하면 데드락에 걸려있는 락을 중심으로 스레드들이 BLOCKED 상태로 빠지게 된다. BLOCKED된 스레드들은 스레드 풀로 반환되지 않고 스레드 풀의 스레드 들은 시간이 지날 수록 수가 증가하게 된다. 그러다 스레드 풀이 허용할 수 있는 스레드의 수를 넘어서게 되면 더이상 요청에 응답할 수 없게 된다.

데드락이 발생하게 되면 스레드들이 BLOCKED된 상태이기 때문에 CPU사용량은 낮지만 요청에 응답할 수 없는 상태가 된다. 스레드덤프를 이용해서 데드락이 발생했는지 알아낼 수 있다. 스레드 덤프를 분석하면 데드락이 있을 경우 데드락이 있다고 표시된다.

메모리 릭

메모리 릭이란 사용하지 않는 객체들로 메모리가 가득차 부족해지는 현상을 말합니다.

메모리 릭이 발생하면 사용가능한 메모리의 영역이 점차 줄어들면서 잦은 Full GC가 일어나 애플리케이션이 자주 멈추고 느려지는 현상이 발생합니다. 메모리 릭으로 인해 메모리가 가득차면 GC관련 스레드만 실행되면서 CPU 코어 하나를 100% 점유하게 되고, 다른 스레드들은 아무런 작업도 하지 못하게 됩니다. 그러다 GC조차도 사용할 수 있는 메모리가 없어지면 OOM(Out Of Memory)Error 가 발생하여 애플리케이션이 다운될 수 있습니다.

메모리 릭은 일반적으로 Full GC가 자주 발생하면서 메모리 사용률이 80~90%를 유지할 때 의심해볼 수 있습니다. 또 메모리 릭이 발생하게 되면 힙 덤프를 이용해서 메모리를 많이 잡고있는 객체를 파악할 수 있습니다.

OOM이 발생하면 힙 덤프를 뜨지 못한다. JVM의 시작 옵션으로 -XX:+HeapDumpOnOutOfMemoryError 를 주면 OOM발생 시 자동으로 힙덤프를 떠준다.

설정문제

  • 스레드 풀의 사이즈
  • DB 커넥션 풀 사이즈
  • DB 커넥션 타임아웃

등의 여러 설정정보를 조정해서 문제를 해결할 수도 있습니다. 이 부분은 성능 테스트를 진행하며 최적의 값을 찾아 나가야 합니다.

디스크 용량 포화

  • 디스크 용량이 가득차면 시스템이 아무런 출력을 하지 못해서 멈추어 버리는 hang 현상이 발생할 수 있다.
  • 보통 로그파일로 인해 디스크의 용량이 가득 차는 경우가 많다.
  • 주기적으로 백업, 압축, 이동등을 통해 로그파일을 관리해주는 것이 좋다.
    • 애플리케이션의 설정으로 특정 용량이상이 되면 로그파일을 분리하거나 삭제하는 방법도 있다.

Reference

This post is licensed under CC BY 4.0 by the author.