TT Lab
はじめる
学ぶ 学習パス コース

ヒープは余っていたのにサービスが止まった

プールが枯れるとサービスが止まる

TT Labで続きを見る

目標

プール枯渇を自分で再現し、タイムアウトで素早い失敗に変えたあと、TomcatのスレッドプールとDBコネクションプールの設定を別のCATALINA_BASEインスタンスに入れて、実際に起動します。

なぜ重要なのか

サービスは最も小さいプールで止まります。プールサイズよりも重要なのが待機の上限です。上限がなければ、遅い下流1つがすべての処理スレッドを飲み込みます。Javaコード(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=で始まる1行を返します。
  2. /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以上である必要があります。
  3. 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が混ざっている必要があります。
  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>の2行で書いてください。

参考

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)を拾わないでください。