もはやHTTP/2リファレンス実装であるHTTP/2のCライブラリnghttp2の作者であるtatsuhiro_tさんが素晴らしいベンチマーク結果を公開されました。
その中で以下のように、あまりにもH2Oやnghttpdと比べてtrusterdのTLS利用時の性能が遅かったため改善しました。
server | 6 bytes | 4K bytes |
---|---|---|
h2o | 227865 | 78333 |
nghttpd | 226716 | 80673 |
trusterd | 62362 | 44020 |
ref: https://gist.github.com/tatsuhiro-t/5f3b170414582ac58091#tls-with-flow-control
主に原因としては以下が考えられます。
- TLS record sizeが小さすぎる
- それに伴いTCP_NODELAYオプションとも相まってパケットサイズも小さすぎる
- パケットに吐き出すまでに貯めるbufferも小さすぎる
そこで、再度実装を見なおし、SSL_writeするまでのwrite bufferの調整とSSL_writeを含むコールバックが呼ばれる頻度をwrite bufferサイズを元に制御し、約3倍から5倍の性能改善を行いました。
性能改善前(3ea19218)
自身の検証環境では以下の様な結果になりました。
6 bytes | 4096 bytes |
---|---|
36,813 req/s | 24,257 req/s |
同環境で、TLS無しだと20万req/s程度でることを考えると、かなり遅い事がわかります。
※ h2load -v -c 500 -m 100 -n 2000000
パケットの状況
TLSv1.2の右隣のカラムがパケットサイズになります。
479372 10.676007 127.0.0.1 -> 127.0.0.1 TLSv1.2 113 Application Data 479373 10.676058 127.0.0.1 -> 127.0.0.1 TLSv1.2 126 Application Data 479375 10.676120 127.0.0.1 -> 127.0.0.1 TLSv1.2 113 Application Data 479377 10.676171 127.0.0.1 -> 127.0.0.1 TLSv1.2 126 Application Data 479379 10.676191 127.0.0.1 -> 127.0.0.1 TLSv1.2 113 Application Data 479381 10.676242 127.0.0.1 -> 127.0.0.1 TLSv1.2 126 Application Data 479383 10.676262 127.0.0.1 -> 127.0.0.1 TLSv1.2 113 Application Data 479385 10.676312 127.0.0.1 -> 127.0.0.1 TLSv1.2 126 Application Data 479387 10.676421 127.0.0.1 -> 127.0.0.1 TLSv1.2 113 Application Data 479389 10.676474 127.0.0.1 -> 127.0.0.1 TLSv1.2 113 Application Data 479390 10.676484 127.0.0.1 -> 127.0.0.1 TLSv1.2 113 Application Data 479392 10.676516 127.0.0.1 -> 127.0.0.1 TLSv1.2 113 Application Data 479394 10.676547 127.0.0.1 -> 127.0.0.1 TLSv1.2 113 Application Data 479396 10.676578 127.0.0.1 -> 127.0.0.1 TLSv1.2 113 Application Data
100bytes程度と酷い細かさですね...これはダメです。
性能改善後(a266c7a7)
ということで、パケットに吐き出すために貯めるbufferサイズと吐き出す上限サイズに注目して改修を行いました。まずは結果です。
6 bytes | 4096 bytes |
---|---|
152,453 req/s | 62,004 req/s |
かなり早くなりました。
パケットの状況
63591.496181 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63601.496317 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63611.496335 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63621.496350 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63631.496365 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63641.496381 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63651.496395 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63661.496480 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63671.496509 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63681.496538 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63691.496554 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63701.496568 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789 63711.500462 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 63731.502580 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 63751.502676 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 63771.502779 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 63791.502874 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 63811.502956 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 63831.503039 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 63851.503121 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 63871.503201 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 63891.503287 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
パケットサイズも良い感じです。
さらに設定ファイルからチューニング
trusterdには、今回新たに2種類のbufferサイズチューニング設定を追加しています。
- write_packet_buffer_expand_size
- TLSにおいてwrite bufferを貯めこむサイズを拡張
- write_packet_buffer_limit_size
- TLSにおいてwrite bufferからパケットを吐き出すサイズの上限を制限
上記の設定を使って、Optimizing TLS Record Size & Buffering Latency - igvita.comやPerformance and the TLS Record Size « Mike's Lookoutを参考に一旦ざっくり4096bytesと以下のようなチューニングを行います。値について、1400から5000ぐらいを目安にチューニングすると良いんではないでしょうか。ついでにRLIMIT_NOFILEも増やしましょう。ちなみにMacOSXではRLIMIT_NOFILEはなんちゃMAX_SIZE=10240だかの数値以下の値じゃないとinvalid argumentを返しますのでご注意下さい。
:rlimit_nofile => 65535, :write_packet_buffer_expand_size => 4096, :write_packet_buffer_limit_size => 4096,
この設定で再度ベンチマークを行うと、以下の結果となりました。
6 bytes | 4096 bytes |
---|---|
156, 979 req/s | 31,408 req/s |
残念ながら4kの結果は遅くなってしまいましたが、6bytesのベンチマーク結果はさらに5千req/s程早くなりました。
パケット状況
80640.974751 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80650.974775 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80660.974787 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80670.974799 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80680.974809 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80730.980858 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80750.980953 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80760.980977 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80770.980994 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80780.981003 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80790.981011 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80800.981019 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80810.981026 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80820.981033 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80830.981042 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80910.989255 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80920.989334 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80930.989346 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80940.989355 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 80980.989363 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 81040.989372 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162 81050.989380 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
う、美しい...
まとめ
ということで、今回はTLSのrecord sizeおよびパケットサイズが小さすぎる事が原因でおこるTLS接続時の性能問題を解決しました。
というのも、今回この問題を見直しすぐに実装できたのは、tatsuhiro_tさんのベンチマーク情報(+ヒント)とたまたま実家に帰っていて子供を両親が見てくれていたため作業に専念できた事が大きいです。
ありがとうございました。
おまけ
ちなみにH2OとnghttpdのTLSパケットもとても美しいので参考資料まで載せておきます。
H2O TLSパケット
108370 11.008217 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 108371 11.008253 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805 108372 11.008446 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805 108373 11.008449 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 108374 11.008605 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 108375 11.008618 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805 108376 11.008838 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 108377 11.008853 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805 108378 11.009029 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 108379 11.009044 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805 108380 11.009243 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 108381 11.009269 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805 108382 11.009465 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805 108383 11.009642 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805 108384 11.009643 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 108385 11.009831 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 108386 11.009864 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805 108387 11.009913 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 108388 11.010051 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805 108389 11.010058 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
nghttpd TLSパケット
122421.827698 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 122441.828146 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 122451.828359 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 122471.828946 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 122481.829155 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 122501.829585 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 122511.829824 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 122531.830248 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 122541.830455 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 122561.830918 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 122571.831129 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 122591.831537 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 122601.831783 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 122621.832206 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 122631.832412 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 122651.832952 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 122661.833157 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 122681.833575 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095
また、truserdのチューニング無しで同環境でのH2OとnghttpdとのTLSベンチマーク比較は以下になりました。
server | 6 bytes | 4K bytes |
---|---|---|
h2o | 187,598 | 65,747 |
nghttpd | 140,788 | 54,740 |
trusterd | 152,453 | 62,004 |
とりあえず改善と言って良い数値が出たように思います。