LabHub
배우기 러닝패스 코스

Tomcat & nginx Operations

Configuring the Thread Pool, Heap and GC Logs With Reasons

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

공유 Executor·acceptCount·Keep-Alive·힙·GC 로그를 직접 설정하고, 리틀의 법칙으로 maxThreads 를 계산해 근거 문서를 남길 수 있게 됩니다.

왜 중요한가

성능 이슈가 생기면 늘 "maxThreads 를 올리자"는 말이 나옵니다. 그런데 그 값이 어느 지점에서 동작하는지 모르면 대부분 아무것도 바뀌지 않거나 더 나빠집니다. maxThreads 는 '동시에 처리 중인 요청', maxConnections 는 '들고 있는 소켓', acceptCount 는 '그마저 넘쳤을 때의 OS 백로그'이고, 백로그까지 넘치면 클라이언트는 503 이 아니라 Connection refused 를 받습니다. 이 차이를 아는 사람이 장애를 빨리 끝냅니다.

단계

  1. Tomcat 을 기동한 뒤 server.xml<Service> 안에 공유 Executor 를 추가합니다. name="tomcatThreadPool", namePrefix="labhub-exec-", maxThreads="150", minSpareThreads="20".
  2. 8080 커넥터가 이 Executor 를 쓰도록 executor="tomcatThreadPool" 속성을 주고 재기동합니다. 요청을 몇 번 보낸 뒤 스레드 덤프를 떠서 /root/tc/threads.txt 에 저장하세요. 덤프에 labhub-exec- 스레드가 보여야 합니다.
  3. 8080 커넥터에 acceptCount="50", maxConnections="2000", connectionTimeout="20000" 을 설정합니다.
  4. 8080 커넥터에 keepAliveTimeout="5000", maxKeepAliveRequests="100" 을 설정하고 재기동한 뒤, 한 번의 curl 로 같은 커넥션에 두 요청을 보내 그 상세 출력(-v)을 /root/tc/keepalive.txt 에 저장합니다. 파일에 Re-using existing connection 또는 Connection #0 to host 재사용 흔적이 있어야 합니다.
  5. /opt/tomcat/bin/setenv.shCATALINA_OPTS-Xms512m -Xmx512m 을 넣고 재기동합니다. 실행 중인 프로세스 인자에 두 값이 모두 보여야 합니다.
  6. 같은 CATALINA_OPTS 에 GC 로그 옵션을 추가하고 재기동합니다. 로그 파일은 /root/tc/gc.log, 파일 회전은 5개 · 각 10MB 로 설정합니다. 재기동 후 /root/tc/gc.log 가 생성되고 비어 있지 않아야 합니다.
  7. /root/tc/load.sh 를 만듭니다. 인자 두 개(URL 동시요청수)를 받아 동시에 요청을 보내고 마지막 줄에 ok=<성공수> fail=<실패수> 를 출력합니다. HTTP 200 이면 성공, 그 외(404 포함)는 실패로 셉니다.
  8. /root/tc/sizing.md 를 작성합니다. 아래 시나리오로 리틀의 법칙을 적용해 maxThreads 값을 계산하고, 계산 과정과 결과를 남깁니다.
    • 목표 처리량: 300 TPS
    • 평균 응답시간: 200ms
    • 피크 여유율: 30% 문서에는 maxThreads=<계산값> 형태의 줄이 정확히 있어야 하고, TPS, ms, 여유율 세 단어가 모두 등장해야 합니다.

참고

공유 Executor 정의

Tomcat 을 기동한 뒤 server.xml<Service> 안에 공유 Executor 를 추가합니다. name="tomcatThreadPool", namePrefix="labhub-exec-", maxThreads="150", minSpareThreads="20".

Executor 는 Service 의 자식으로, Connector 보다 먼저 선언돼야 합니다. namePrefix 를 의미 있게 주면 나중에 스레드 덤프에서 바로 구분됩니다.

커넥터를 Executor 에 연결

8080 커넥터가 이 Executor 를 쓰도록 executor="tomcatThreadPool" 속성을 주고 재기동합니다. 요청을 몇 번 보낸 뒤 스레드 덤프를 떠서 /root/tc/threads.txt 에 저장하세요. 덤프에 labhub-exec- 스레드가 보여야 합니다.

커넥터에 executor 속성을 주면 그 커넥터의 maxThreads 는 무시됩니다. 실제로 적용됐는지는 스레드 덤프에서 스레드 이름을 보면 확인됩니다. jcmd 나 jstack 을 쓰세요.

커넥션 수용 파라미터

8080 커넥터에 acceptCount="50", maxConnections="2000", connectionTimeout="20000" 을 설정합니다.

acceptCount 는 maxConnections 가 소진된 뒤의 OS 백로그입니다. 여기까지 넘치면 클라이언트는 503 이 아니라 연결 거부를 받습니다.

Keep-Alive 설정과 확인

8080 커넥터에 keepAliveTimeout="5000", maxKeepAliveRequests="100" 을 설정하고 재기동한 뒤, 한 번의 curl 로 같은 커넥션에 두 요청을 보내 그 상세 출력(-v)을 /root/tc/keepalive.txt 에 저장합니다. 파일에 Re-using existing connection 또는 Connection #0 to host 재사용 흔적이 있어야 합니다.

curl -v 로 응답 헤더의 Connection 값과 커넥션 재사용 여부를 볼 수 있습니다. 두 요청을 한 번의 curl 로 보내면 재사용이 관찰됩니다.

힙 고정

/opt/tomcat/bin/setenv.shCATALINA_OPTS-Xms512m -Xmx512m 을 넣고 재기동합니다. 실행 중인 프로세스 인자에 두 값이 모두 보여야 합니다.

서버 애플리케이션은 최소 힙과 최대 힙을 같게 잡는 것이 관행입니다. 힙이 늘었다 줄었다 하며 발생하는 GC 를 없애기 위해서입니다.

GC 로그 활성화

같은 CATALINA_OPTS 에 GC 로그 옵션을 추가하고 재기동합니다. 로그 파일은 /root/tc/gc.log, 파일 회전은 5개 · 각 10MB 로 설정합니다. 재기동 후 /root/tc/gc.log 가 생성되고 비어 있지 않아야 합니다.

JDK 9 이후로는 -Xlog 통합 로깅을 씁니다. 파일 회전 옵션(개수와 크기)을 함께 주지 않으면 GC 로그가 디스크를 채웁니다.

동시 부하 스크립트

/root/tc/load.sh 를 만듭니다. 인자 두 개(URL 동시요청수)를 받아 동시에 요청을 보내고 마지막 줄에 ok=<성공수> fail=<실패수> 를 출력합니다. HTTP 200 이면 성공, 그 외(404 포함)는 실패로 셉니다.

백그라운드로 여러 curl 을 띄우고 wait 로 모으는 방식이면 충분합니다. 각 요청의 성공 여부는 HTTP 상태코드로 판정하세요.

스레드풀 산정 근거 작성

/root/tc/sizing.md 를 작성합니다. 아래 시나리오로 리틀의 법칙을 적용해 maxThreads 값을 계산하고, 계산 과정과 결과를 남깁니다.

리틀의 법칙: 동시 처리 수 = 목표 TPS × 평균 응답시간(초). 여유율을 곱한 뒤 올림합니다. 계산 과정을 문서에 남겨야 6개월 뒤 누군가 이 값을 바꿀 때 근거가 됩니다.