힙은 남았는데 서비스가 멈췄다 · 풀이 마르면 서비스가 선다 · 실습
풀이 마르면 서비스가 선다
목표
풀 고갈을 직접 만들고 타임아웃으로 빠른 실패로 바꾼 뒤, Tomcat 의 스레드 풀과 DB 커넥션 풀 설정을 별도 CATALINA_BASE 인스턴스에 넣어 실제로 띄운다.
왜 중요한가
서비스는 가장 작은 풀에서 멈춘다. 풀 크기보다 중요한 것이 대기 상한이다 — 상한이 없으면 느린 하류 하나가 모든 처리 스레드를 삼킨다. 자바 코드(Semaphore)와 Tomcat 설정(커넥터·DataSource) 양쪽에서 같은 원리를 손으로 확인한다. 재료 PoolDemo.java 는 127.0.0.1:8086(처리 스레드 4개, 커넥션 풀 2개, 쿼리 3초)과 관리 포트 8087(/stats, 별도 스레드)로 동작한다. Tomcat 은 $CATALINA_HOME 에 설치돼 있다.
단계
1. /root/jvm/pool 에 PoolDemo.java 를 컴파일하고 nohup java -cp /root/jvm/pool PoolDemo > /root/jvm/pool/server.log 2>&1 & 로 띄운 뒤 pid 를 /root/jvm/pool/server.pid 에 적는다. curl -s http://127.0.0.1:8086/health 가 ok, curl -s http://127.0.0.1:8087/stats 가 served= 로 시작하는 한 줄을 돌려준다.
2. /query 를 6개 동시에(curl -s -m 30 http://127.0.0.1:8086/query & 여섯 번) 보내고 1초 뒤 jcmd <pid> Thread.print > /root/jvm/pool/dump-pool.txt. parking to wait for 와 Semaphore 가 있어야 하고, /stats 의 max_waiting 이 2 이상이어야 한다.
3. server.pid 의 프로세스를 kill 하고 -Ddb.pool.timeout.ms=500 으로 다시 띄워 server.pid 를 갱신한다. 다시 /query 6개를 동시에 보내되 그중 하나의 결과를 curl -s -m 10 -o /dev/null -w '%{http_code}\n' … >> /root/jvm/pool/after-timeout.txt 로 모은다 — 503 이 섞여 있어야 한다.
4. CATALINA_BASE 를 만든다: /root/jvm/pool/tc 아래 conf($CATALINA_HOME/conf 를 복사)·logs·temp·webapps·work 디렉터리.
5. /root/jvm/pool/tc/conf/server.xml 의 8080 <Connector> 에 maxThreads="50", acceptCount="20", connectionTimeout="5000" 을 둔다(기존 connectionTimeout 값은 바꾼다). XML 이 유효해야 한다.
6. /root/jvm/pool/tc/conf/context.xml 의 <Context> 안에 <Resource name="jdbc/app" auth="Container" type="javax.sql.DataSource" maxTotal="20" maxIdle="5" maxWaitMillis="2000" driverClassName="org.h2.Driver" url="jdbc:h2:mem:app" username="sa" password=""/> 를 넣는다.
7. CATALINA_BASE=/root/jvm/pool/tc JAVA_OPTS=-Xmx256m catalina.sh start 로 인스턴스를 띄운다. 8080 이 HTTP 상태 줄을 돌려주고, 그 JVM 의 명령줄에 -Dcatalina.base=/root/jvm/pool/tc 가 있어야 한다.
8. Tomcat JVM 의 덤프를 /root/jvm/pool/dump-tomcat.txt 에 남기고, 덤프에서 "http-nio-8080-exec- 로 시작하는 스레드 수와 server.xml 8080 커넥터의 maxThreads 를 읽어 /root/jvm/pool/sizing.txt 에 exec_threads=<n> 과 max_threads=<n> 두 줄로 적는다.
참고
- Tomcat pid:
jcmd -l | grep Bootstrap. 덤프:jcmd <pid> Thread.print. - 커넥터 속성 문서: https://tomcat.apache.org/tomcat-10.1-doc/config/http.html , DataSource: https://tomcat.apache.org/tomcat-10.1-doc/jndi-datasource-examples-howto.html
- 흔한 실수: 처리 스레드 수만 늘리는 것, 헬스체크가 DB 를 거치게 두는 것,
conf를 복사하지 않고 BASE 를 띄우는 것(설정 파일이 없어 죽는다).
단계 8개
- PoolDemo 를 띄운다
- 커넥션 풀을 말린다
- 대기에 상한을 둔다
- CATALINA_BASE 를 만든다
- 커넥터의 세 숫자
- 커넥션 풀에 대기 상한을
- 인스턴스를 띄운다
- 덤프에서 풀 스레드를 센다