プールが枯れるとサービスが止まる
한국어 원문으로 표시합니다.
목표
풀 고갈을 직접 만들고 타임아웃으로 빠른 실패로 바꾼 뒤, Tomcat 의 스레드 풀과 DB 커넥션 풀 설정을 별도 CATALINA_BASE 인스턴스에 넣어 실제로 띄운다.
왜 중요한가
서비스는 가장 작은 풀에서 멈춘다. 풀 크기보다 중요한 것이 대기 상한이다 — 상한이 없으면 느린 하류 하나가 모든 처리 스레드를 삼킨다. 자바 코드(Semaphore)와 Tomcat 설정(커넥터·DataSource) 양쪽에서 같은 원리를 손으로 확인한다. 재료 PoolDemo.java 는 127.0.0.1:8086(처리 스레드 4개, 커넥션 풀 2개, 쿼리 3초)과 관리 포트 8087(/stats, 별도 스레드)로 동작한다. Tomcat 은 $CATALINA_HOME 에 설치돼 있다.
단계
/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=로 시작하는 한 줄을 돌려준다./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 이상이어야 한다.- server.pid 의 프로세스를 kill 하고
-Ddb.pool.timeout.ms=500으로 다시 띄워 server.pid 를 갱신한다. 다시/query6개를 동시에 보내되 그중 하나의 결과를curl -s -m 10 -o /dev/null -w '%{http_code}\n' … >> /root/jvm/pool/after-timeout.txt로 모은다 — 503 이 섞여 있어야 한다. CATALINA_BASE를 만든다:/root/jvm/pool/tc아래conf($CATALINA_HOME/conf를 복사)·logs·temp·webapps·work디렉터리./root/jvm/pool/tc/conf/server.xml의 8080<Connector>에maxThreads="50",acceptCount="20",connectionTimeout="5000"을 둔다(기존 connectionTimeout 값은 바꾼다). XML 이 유효해야 한다./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=""/>를 넣는다.CATALINA_BASE=/root/jvm/pool/tc JAVA_OPTS=-Xmx256m catalina.sh start로 인스턴스를 띄운다. 8080 이 HTTP 상태 줄을 돌려주고, 그 JVM 의 명령줄에-Dcatalina.base=/root/jvm/pool/tc가 있어야 한다.- 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 를 띄우는 것(설정 파일이 없어 죽는다).
PoolDemo 를 띄운다
/root/jvm/pool 에 PoolDemo.java 를 컴파일하고 nohup 으로 띄운 뒤 pid 를 /root/jvm/pool/server.pid 에 적는다. 8086 /health 가 ok, 8087 /stats 가 served= 로 시작한다.
javac -d /root/jvm/pool /opt/lab/fixtures/jvm/PoolDemo.java 뒤 nohup java -cp /root/jvm/pool PoolDemo > server.log 2>&1 & 와 echo $! > server.pid 입니다.
커넥션 풀을 말린다
/query 를 6개 동시에 보내고 1초 뒤 jcmd Thread.print > /root/jvm/pool/dump-pool.txt. parking to wait for 와 Semaphore 가 있어야 하고 /stats 의 max_waiting 이 2 이상이어야 한다.
for i in 1 2 3 4 5 6; do curl -s -m 30 http://127.0.0.1:8086/query & done; sleep 1; jcmd $(cat server.pid) Thread.print > dump-pool.txt. 커넥션 2개에 요청 6개면 4개가 기다립니다.
대기에 상한을 둔다
server.pid 의 프로세스를 kill 하고 -Ddb.pool.timeout.ms=500 으로 다시 띄워 server.pid 를 갱신한다. /query 6개를 동시에 보내며 결과 코드를 /root/jvm/pool/after-timeout.txt 에 모은다(503 이 섞여야 한다).
각 curl 에 -o /dev/null -w '%{http_code}\n' 을 주고 >> after-timeout.txt 로 모으세요. 500ms 안에 커넥션을 못 받은 요청이 503 입니다. 백그라운드 curl 을 기다릴 때 인자 없는 wait 를 쓰면 nohup 으로 띄운 java 까지 기다리므로 curl 의 pid 만 wait 하세요.
CATALINA_BASE 를 만든다
/root/jvm/pool/tc 아래 conf($CATALINA_HOME/conf 복사)·logs·temp·webapps·work 를 만든다.
mkdir -p tc/logs tc/temp tc/webapps tc/work && cp -r $CATALINA_HOME/conf tc/conf. 설치본(HOME)은 건드리지 않습니다.
커넥터의 세 숫자
/root/jvm/pool/tc/conf/server.xml 의 8080 Connector 에 maxThreads="50", acceptCount="20", connectionTimeout="5000" 을 둔다. XML 이 유효해야 한다.
배포판 server.xml 의 8080 커넥터에는 connectionTimeout="20000" 이 이미 있습니다 — 값을 바꾸고 두 속성을 더하세요. 속성은 여는 태그 안 어디에 있어도 됩니다.
커넥션 풀에 대기 상한을
/root/jvm/pool/tc/conf/context.xml 의 안에 name="jdbc/app" DataSource Resource 를 maxTotal="20" maxIdle="5" maxWaitMillis="2000" 으로 넣는다.
바로 앞에 한 줄을 넣으면 됩니다. maxWaitMillis 가 -1 이면 무한 대기 — 사건의 원인입니다.
인스턴스를 띄운다
CATALINA_BASE=/root/jvm/pool/tc JAVA_OPTS=-Xmx256m catalina.sh start 로 띄운다. 8080 이 HTTP 상태 줄을 돌려주고 JVM 명령줄에 -Dcatalina.base=/root/jvm/pool/tc 가 있어야 한다.
catalina.sh 는 $CATALINA_HOME/bin 에 있습니다. 기동에 몇 초 걸리니 curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/ 를 반복해 보세요(404 여도 상태 줄이 온 것입니다).
덤프에서 풀 스레드를 센다
Tomcat JVM 의 덤프를 /root/jvm/pool/dump-tomcat.txt 에 남기고 "http-nio-8080-exec- 로 시작하는 스레드 수와 server.xml 8080 커넥터의 maxThreads 를 /root/jvm/pool/sizing.txt 에 exec_threads= 과 max_threads= 으로 적는다.
pid 는 jcmd -l | awk '/Bootstrap/{print $1}'. 세는 법: grep -c '^"http-nio-8080-exec-' dump-tomcat.txt. minSpareThreads 기본 10 이라 기동 직후에는 10개 안팎입니다. max_threads 는 8080 커넥터의 값입니다 — 배포판 server.xml 의 주석에 든 Executor 예시(maxThreads=150)를 잡지 마세요.