プールが枯れるとサービスが止まる
目標
プール枯渇を自分で再現し、タイムアウトで素早い失敗に変えたあと、TomcatのスレッドプールとDBコネクションプールの設定を別のCATALINA_BASEインスタンスに入れて、実際に起動します。
なぜ重要なのか
サービスは最も小さいプールで止まります。プールサイズよりも重要なのが待機の上限です。上限がなければ、遅い下流1つがすべての処理スレッドを飲み込みます。Javaコード(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=で始まる1行を返します。/queryを6つ同時に(curl -s -m 30 http://127.0.0.1:8086/query &を6回)送り、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を更新してください。再度/queryを6つ同時に送り、そのうち1つの結果を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>の2行で書いてください。
参考
- 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)には触れません。
コネクターの3つの数値
/root/jvm/pool/tc/conf/server.xmlの8080のConnectorに、maxThreads="50"、acceptCount="20"、connectionTimeout="5000"を設定してください。XMLが有効である必要があります。
ディストリビューションのserver.xmlの8080コネクターには、すでにconnectionTimeout="20000"があります。値を変更し、2つの属性を追加してください。属性は開始タグの中のどこにあっても構いません。
コネクションプールに待機の上限を設ける
/root/jvm/pool/tc/conf/context.xmlのの中に、name="jdbc/app"のDataSource Resourceを、maxTotal="20" maxIdle="5" maxWaitMillis="2000"で追加してください。
の直前にを1行入れれば済みます。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)を拾わないでください。