スレッドプール・ヒープ・GCログを根拠を持って設定する
目標
共有Executor・acceptCount・Keep-Alive・ヒープ・GCログを自分で設定し、リトルの法則でmaxThreadsを計算して、根拠の文書を残せるようになります。
なぜ重要なのか
性能の課題が出ると、いつも「maxThreadsを上げよう」という言葉が出ます。ところが、その値がどの地点で動作するかを知らないと、ほとんどの場合、何も変わらないか、さらに悪化します。maxThreadsは「同時に処理中のリクエスト」、maxConnectionsは「保持しているソケット」、acceptCountは「それさえ超えたときのOSのバックログ」で、バックログまであふれると、クライアントは503ではなくConnection refusedを受け取ります。この違いを知っている人が、障害を素早く終わらせます。
ステップ
- Tomcatを起動したあと、
server.xmlの<Service>の中に共有Executorを追加します。name="tomcatThreadPool"、namePrefix="labhub-exec-"、maxThreads="150"、minSpareThreads="20"です。 - ポート8080のコネクターがこのExecutorを使うように、
executor="tomcatThreadPool"属性を指定して再起動します。リクエストを何回か送ったあと、スレッドダンプを取って、/root/tc/threads.txtに保存してください。ダンプにlabhub-exec-のスレッドが見える必要があります。 - ポート8080のコネクターに、
acceptCount="50"、maxConnections="2000"、connectionTimeout="20000"を設定します。 - ポート8080のコネクターに、
keepAliveTimeout="5000"、maxKeepAliveRequests="100"を設定して再起動したあと、1回のcurlで同じコネクションに2つのリクエストを送り、その詳細出力(-v)を/root/tc/keepalive.txtに保存します。ファイルにRe-using existing connectionまたはConnection #0 to hostという、再利用の痕跡がある必要があります。 /opt/tomcat/bin/setenv.shのCATALINA_OPTSに-Xms512m -Xmx512mを入れて、再起動します。実行中のプロセスの引数に、両方の値が見える必要があります。- 同じ
CATALINA_OPTSにGCログのオプションを追加して、再起動します。ログファイルは/root/tc/gc.log、ファイルローテーションは5個・各10MBに設定します。再起動後、/root/tc/gc.logが生成されていて、空でない必要があります。 /root/tc/load.shを作成します。引数を2つ(URL 동시요청수、韓国語の語は「同時リクエスト数」を意味します)受け取って、同時にリクエストを送り、最後の行にok=<성공수> fail=<실패수>を出力します(韓国語の語は、順に「成功数」「失敗数」を意味します)。HTTP 200なら成功、それ以外(404を含む)は失敗として数えます。/root/tc/sizing.mdを作成します。次のシナリオでリトルの法則を適用して、maxThreadsの値を計算し、計算過程と結果を残します。- 目標スループット: 300 TPS
- 平均応答時間: 200ms
- ピークの余裕率: 30%
文書には、
maxThreads=<계산값>の形の行(韓国語の語は「計算値」を意味します)がちょうどある必要があり、TPS、ms、여유율(韓国語の語は「余裕率」を意味します)の3つの単語が、すべて登場する必要があります。
参考
- スレッドダンプ:
jcmd <PID> Thread.printまたはjstack <PID> - 1つのコネクションで2つのリクエスト:
curl -v http://127.0.0.1:8080/ http://127.0.0.1:8080/ - GCログのオプションの形式:
-Xlog:gc*:file=<경로>:time,uptime:filecount=<n>,filesize=<크기>(韓国語の語は、順に「パス」「サイズ」を意味します) - よくあるミス1:
ExecutorをConnectorのあとに宣言するミスです。参照される前に定義されている必要があります。 - よくあるミス2:
executorを指定したコネクターに、maxThreadsも残しておいて、その値が適用されると信じるミスです。Executorを使うと、コネクターのmaxThreadsは無視されます。 - よくあるミス3: リトルの法則で、応答時間をミリ秒のまま掛けるミスです。秒単位に変換する必要があります。
共有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"を設定して再起動したあと、1回のcurlで同じコネクションに2つのリクエストを送り、その詳細出力(-v)を/root/tc/keepalive.txtに保存します。ファイルにRe-using existing connectionまたはConnection #0 to hostという、再利用の痕跡がある必要があります。
curl -vで、レスポンスヘッダーのConnectionの値と、コネクションの再利用の有無を確認できます。2つのリクエストを1回のcurlで送ると、再利用が観察されます。
ヒープの固定
/opt/tomcat/bin/setenv.shのCATALINA_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を作成します。引数を2つ(URL 동시요청수、韓国語の語は「同時リクエスト数」を意味します)受け取って、同時にリクエストを送り、最後の行にok=<성공수> fail=<실패수>を出力します(韓国語の語は、順に「成功数」「失敗数」を意味します)。HTTP 200なら成功、それ以外(404を含む)は失敗として数えます。
バックグラウンドで複数のcurlを起動して、waitでまとめる方式で十分です。各リクエストの成否は、HTTPステータスコードで判定してください。
スレッドプールの算出根拠の作成
/root/tc/sizing.mdを作成します。次のシナリオでリトルの法則を適用して、maxThreadsの値を計算し、計算過程と結果を残します。
- 目標スループット: 300 TPS
- 平均応答時間: 200ms
- ピークの余裕率: 30%
文書には、
maxThreads=<계산값>の形の行(韓国語の語は「計算値」を意味します)がちょうどある必要があり、TPS、ms、여유율(韓国語の語は「余裕率」を意味します)の3つの単語が、すべて登場する必要があります。
リトルの法則: 同時処理数=目標TPS×平均応答時間(秒)です。余裕率を掛けたあと、切り上げます。計算過程を文書に残しておけば、6か月後に誰かがこの値を変えるとき、根拠になります。